Send tokens from a user-controlled wallet to any blockchain address, or execute a smart contract from the wallet. The transaction is broadcast onchain and costs gas. To have the user sign an offchain message instead, use Request a Signature.Documentation Index
Fetch the complete documentation index at: https://developers.circle.com/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
Before you begin, ensure that you’ve:- Obtained a Circle Developer API key from the Circle Console.
- Completed the
Build a Wallet App tutorial,
which sets up a user-controlled wallet and stores the user’s
userId. - Funded the user’s wallet with testnet USDC from the Circle Faucet, or otherwise have a non-zero token balance to transfer.
- Integrated a user-controlled wallet client-side SDK in your app to walk the user through authorizing the transaction challenge: Web SDK, iOS SDK, Android SDK, or React Native SDK.
- Installed the user-controlled wallet server-side SDK in your backend to create the transaction challenge: Node.js or Python.
Steps
Acquire a session token
Request a 60-minute session token for the user. The token authorizes the
transaction challenge later in the flow.
Identify the wallet and token
Get the user’s wallet and confirm it has a balance of the token you want to
transfer.
Estimate transaction cost (optional)
Estimate gas fees before initiating the transfer to surface them to the user or
block low-balance transfers early.The response returns fee estimates at low, medium, and high priorities.
Initiate the transaction
Create a transaction challenge that the user authorizes in the next step. Pick
the tab for the operation you’re performing.Include an
- Transfer tokens
- Execute a contract
idempotencyKey (a UUID) on every transaction. If the request fails
or times out, retrying with the same key prevents duplicate transactions. See
Idempotent requests for details on
idempotency key usage.Have the user authorize the transaction
Pass the
userToken, encryptionKey, and challengeId to your client-side
SDK. The SDK presents the transaction details and the appropriate authorization
UI for the user’s authentication method:- Social login or email OTP: Circle displays a confirmation UI by default. See Confirmation UIs to customize or replace it.
- PIN: The user enters their PIN (or uses biometrics) to authorize.
Check transaction status
Once the user authorizes the challenge, Circle submits the transaction onchain.
Use webhooks (push) or polling (pull) to detect when the transaction reaches a
terminal state: For a full list of transaction states and timing expectations, see
Asynchronous States and Statuses.
COMPLETE, FAILED, or CANCELLED.- Webhook
- Polling
Subscribe to outbound transaction notifications. Circle sends a notification
when the transaction state changes.For webhook setup, see Webhook Notifications.
Webhook notification
Confirm tokens were received
To confirm tokens were credited to a destination wallet (for testing or to
trigger downstream logic), check the destination wallet’s balance the same way
you did in Step 2. If you control both wallets in your app, you can also rely on
the inbound
transactions.inbound webhook to confirm receipt.Error handling
Handle these common failure cases when integrating token transfers and contract execution:- Expired session token (error code
155104): TheuserTokenfrom Step 1 expires after 60 minutes. If you get this error, request a new session token and retry. - Insufficient balance: The wallet must hold at least the transfer amount plus gas. Validate balance and estimated fees (Step 3) before initiating the challenge.
- Insufficient gas: EOA wallets must hold native tokens for gas. SCA wallets can use Gas Station or a paymaster to sponsor gas. See Gas fees for details.
- Invalid destination address: Verify the address format matches the wallet’s blockchain (hex with checksum for EVM, base58 for Solana, and so on).
- User declines or fails to authorize: If the user cancels the challenge or enters an incorrect PIN, the transaction never broadcasts. Surface the cancellation in your UI and let them retry.
- Idempotency conflicts: Reusing an
idempotencyKeyfrom a successful request returns the original transaction without creating a new one. Reusing a key from a failed request also returns the original failure. Generate a fresh key when retrying after a permanent error.