Skip to main content
Tempo Routes moves stablecoins between Tempo and other networks. A deposit address is a Routes address on another network. Funds sent to it are delivered to a chosen recipient on Tempo. Developers building on Stellar can use this recipe to move USDC on Stellar to OUSD on Tempo. This recipe creates a deposit address on Stellar that delivers USDC to Tempo as OUSD. It then funds the address with USDC from a Privy Stellar wallet and tracks the deposit until it reaches Tempo. As an option, Tempo Routes can subsidize the route cost so that 1 USDC on Stellar delivers 1 OUSD on Tempo. The subsidy requires approval for your organization. See Quote the deposit.

Prerequisites

This recipe runs on Stellar mainnet and Tempo mainnet only, because Tempo Routes lists no Stellar testnet route. Every step moves real USDC and XLM.
Before starting, set up the following:
  • A Privy app and its app secret. The signing step shows the Node.js SDK, the Python SDK, and the REST API.
  • A Privy wallet with the stellar chain type that sends the USDC. It also receives refunds if a deposit can’t be delivered. See Create a wallet to create one.
  • A Tempo address that receives the OUSD. Tempo is EVM-compatible, so a Privy wallet with the ethereum chain type works.
  • A Tempo API key with the routes:read and routes:write scopes. To get a Tempo API key for your app, contact Privy support.
  • A Stellar SDK to build and submit Stellar transactions, such as @stellar/stellar-sdk for Node.js or stellar-sdk for Python
Keep the Privy app secret and the Tempo API key on the server. Show users only the deposit address and its funding instructions. Steps 1, 2, and 4 call the Tempo Routes API at https://api.tempo.xyz with the Tempo API key. These are plain HTTP requests, so any language works. Step 3 calls the Privy API to sign the Stellar transaction.

The route

This recipe uses the following Stellar to Tempo route: Tempo Routes identifies the source token as USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN. The Tempo Routes public directory lists the currently supported Stellar routes at https://api.tempo.xyz/v1/routes?sourceChain=stellar:pubnet. Other destination tokens on Tempo, such as USDC.e, use the same flow with a different destinationToken. Before it can send USDC, the Stellar wallet needs:
  • XLM for the account’s minimum balance and network fees
  • A trustline to USDC from the issuer in the route
  • A USDC balance
A Stellar account must hold a minimum balance of XLM. The minimum is currently 1 XLM for the account, plus 0.5 XLM for each trustline. Without a USDC trustline, the wallet can’t hold USDC, and a USDC payment fails with op_src_no_trust. Keep the trustline in place after the deposit: the wallet needs it to receive a USDC refund. Add the trustline once per wallet. Build a transaction with a change-trust operation for USDC, then sign and submit it the same way as the payment in step 3.
This recipe uses wallets owned by the app, so the app secret alone authorizes signing. For a wallet with an owner, the signing request also needs an authorization signature. See Authorization context for each SDK, and Create a wallet for wallet ownership.

1. Quote the deposit

Request a quote from the Tempo Routes API with GET https://api.tempo.xyz/v1/routes/deposit-addresses/quote. Set amount in USDC base units. Stellar USDC has 7 decimals, so 10000000 is 1 USDC. Pass the recipient and the refund address that the deposit address will use.
Show users the destination amount and fees from the quote. When quote.quote.expiresAt passes, request a new quote. A quote prices the amount; it doesn’t mean funds arrived.
Set subsidize to true for normalized 1:1 delivery, where Tempo funds the route cost. Tempo returns a 403 error when the organization is not eligible for a Routes subsidy. See Quotes and fees for details. To enable subsidies for an app, contact Privy support.

2. Create the deposit address

Create the address with the Tempo Routes API: POST https://api.tempo.xyz/v2/routes/deposit-addresses. Set refundAddresses.stellar to the Stellar wallet that sends the funds. Send an idempotency-key header. Create one key for the user’s address and store it with the user record before the request. The key protects retries of the same request:
  • A retry with the same key and the same body returns the same address instead of creating another. Tempo replays the successful response for 24 hours.
  • A request with the same key and a changed body, such as a new amount, returns a conflict.
  • A failed request releases the key, so a retry with the same key can succeed.
After the address is created, store its ID. Read the address back later with GET https://api.tempo.xyz/v2/routes/deposit-addresses/{id} instead of creating it again.
The response lists one entry in addresses for each chain family. Save the top-level id, the Stellar entry’s address, and its id (rda_…) with the user record. Invite deposits only to the entry whose status is active. On Stellar, the deposit address is a muxed account address that starts with M: a Tempo account plus an ID that identifies this deposit address, so the payment needs no memo. Pay it as it is; Stellar SDKs accept a muxed address as a payment destination.

3. Send USDC to the deposit address

Build a Stellar payment with a Stellar SDK. Privy signs the transaction hash with the Stellar wallet’s Ed25519 key through raw_sign. Attach the signature to the transaction, then submit it to Horizon.
Send the amount from the quote. Stellar payments take a decimal amount, so the quoted 10000000 base units are 1.0000000 USDC. The deposit address accepts each new deposit on its route, so users can fund it more than once.

4. Track the deposit

List the deposits the address received with the Tempo Routes API: GET https://api.tempo.xyz/v1/routes/deposits. Find the deposit that matches the Stellar transaction, then poll it until it completes, is refunded, or needs action. Set depositAddress to the Stellar address, not the rda_… ID. To filter by ID, use depositAddressId instead.
The list returns deposits in data, newest first, and nextCursor points to the next page. findDeposit follows nextCursor until it finds the transaction, so it also works for an address with many deposits. It returns undefined until Tempo detects the Stellar transaction, and waitForDeposit keeps searching until then. waitForDeposit throws after POLL_TIMEOUT_MS, for example when the payment never reached the deposit address. After the deposit is found, waitForDeposit reads only that deposit with GET https://api.tempo.xyz/v1/routes/deposits/{id} and its rdp_… ID. A deposit moves through detected, bridging, and settling to completed. It can also move to action-required, refunding, or refunded.

Reconcile delivery and refunds

Save the user, the deposit address ID, and each deposit ID together. One address can receive many deposits, so reconcile each deposit on its own:
  • completed: The deposit reached Tempo. destinationAmount is the OUSD delivered, and destinationTransactionHashes lists the Tempo transactions.
  • refunding then refunded: Tempo returns the funds to the refund address on Stellar. refundAmount and refundTransactionHashes describe the refund.
  • action-required: The deposit needs attention. statusReason.code and statusReason.message explain why.
Only a completed deposit confirms delivery to Tempo. An active address only means the address can receive funds. Before crediting a balance or treating a refund as complete, follow Track delivery.

Learn more