Prerequisites
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
stellarchain 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
ethereumchain type works. - A Tempo API key with the
routes:readandroutes:writescopes. 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-sdkfor Node.js orstellar-sdkfor Python
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
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 withGET 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.
quote.quote.expiresAt passes, request a new quote. A quote prices the amount; it doesn’t mean funds arrived.
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.
GET https://api.tempo.xyz/v2/routes/deposit-addresses/{id} instead of creating it again.
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 throughraw_sign. Attach the signature to the transaction, then submit it to Horizon.
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.
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.destinationAmountis the OUSD delivered, anddestinationTransactionHasheslists the Tempo transactions.refundingthenrefunded: Tempo returns the funds to the refund address on Stellar.refundAmountandrefundTransactionHashesdescribe the refund.action-required: The deposit needs attention.statusReason.codeandstatusReason.messageexplain why.
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
- Accept deposits in the Tempo Routes documentation
- Deposit addresses API reference
- Using Tempo with Privy
- Tier 2 wallet integration

