Skip to main content
Circle’s Cross-Chain Transfer Protocol (CCTP) supports native USDC transfers between Stellar and other CCTP-enabled chains — no wrapped assets, no third-party bridges.
Full documentationCCTP on Stellar is documented and maintained by Circle. For contract addresses, supported chains, SDKs, code samples, and the attestation API:CCTP on Stellar — Circle developer docs
This page covers the Stellar-specific parts of a transfer that starts on Stellar: the allowance the burn needs, how to get the destination mint done, and a worked example. For transfers into Stellar, follow Circle’s CctpForwarder guide.

Transfer flow from Stellar

A transfer from Stellar to another chain has three steps:
  1. Burn. Call deposit_for_burn or deposit_for_burn_with_hook on the TokenMessengerMinter contract. The contract burns your USDC and emits a CCTP message.
  2. Attest. Circle’s attestation service signs the message. Fetch the message and attestation with GET /v2/messages/27?transactionHash=<hash>. Stellar’s CCTP domain is 27. Use the Stellar transaction hash as it is, with no 0x prefix.
  3. Mint. Someone calls receiveMessage on the destination chain’s MessageTransmitterV2 contract with the message and attestation. That can be you or a relayer. See relayer options.
Circle lists Fast Transfer as not applicable for Stellar, because a standard attestation is already fast. A burn from Stellar attests at the finalized threshold (2000), even when you request 1000.

Burn arguments

deposit_for_burn takes these arguments. deposit_for_burn_with_hook takes the same arguments plus hook_data. Stellar USDC has 7 decimals, while CCTP messages use 6. The burn takes only the first 6 decimals of amount, and the 7th decimal stays in your account. See USDC precision in Circle’s docs. The contract addresses are in Circle’s Stellar contracts list. The USDC SAC addresses are:

The allowance requirement

TokenMessengerMinter does not take USDC with a transfer that you authorize. It pulls USDC from caller with transfer_from on the USDC SAC. So before the burn, caller must approve the TokenMessengerMinter contract as a spender for at least amount. See the token interface for approve and transfer_from. A transaction can hold only one InvokeHostFunctionOp (see Stellar transactions). So by default, a transfer from Stellar takes two transactions and two signatures: approve, then deposit_for_burn. To get to one signature, use one of these options.

Option 1: a wrapper contract

Deploy a contract that calls approve and then deposit_for_burn in one invocation. One user signature covers the wrapper call and every authorization that the inner approve and deposit_for_burn calls need. The stellar-cctp-demo repository has an example wrapper in contracts/stellar/cctp-wrapper:
The same contract has approve_and_deposit_with_hook, which passes hook_data on to deposit_for_burn_with_hook.
The demo wrapper is example code, and it is deployed on testnet only. Review and audit any wrapper contract before you use it on mainnet.

Option 2: an existing allowance

Approve the TokenMessengerMinter once, for a larger amount and a later expiration ledger. Each later burn then needs only one signature, until the allowance is spent or expires. Know the tradeoffs:
  • The TokenMessengerMinter contract can pull USDC up to the allowance until it expires.
  • An allowance is temporary storage with an expiration ledger. When it expires, the user must approve again.
  • A burn fails when the remaining allowance is less than amount.

Relayer options

After the burn and the attestation, a transaction on the destination chain must call receiveMessage. You have these options: To restrict the mint to one relayer, set destination_caller to that relayer’s address. The Forwarding Service does not work when destination_caller is set.

Use the Circle Forwarding Service

  1. Check that Circle’s supported blockchains table lists the destination chain under “Forwarding Service”.
  2. Set hook_data to the 32-byte version 0 forwarding hook: 0x636374702d666f72776172640000000000000000000000000000000000000000. This is cctp-forward followed by zero bytes. See Circle’s hook format.
  3. Quote the fee with GET /v2/burn/USDC/fees/27/<destination domain>?forward=true. Use the entry with finalityThreshold 2000. minimumFee is the protocol fee in basis points, and forwardFee is in 6-decimal USDC units.
  4. Set max_fee to cover the protocol fee plus the forwarding fee, in Stellar’s 7-decimal units. Multiply forwardFee by 10 to convert it.
Circle does not support upfront fees for transfers from Stellar, so the fee comes out of the minted amount. Circle does not refund a max_fee that is higher than the gas used, so do not add a large buffer. When the forward is done, the attestation response shows forwardState and forwardTxHash.

Worked example

This example sends USDC from Stellar testnet to Base Sepolia with the Circle Forwarding Service. It uses two transactions, approve and then deposit_for_burn_with_hook, signed by a secret key. It uses @stellar/stellar-sdk v16.
TypeScript
forwardState can stay empty for a short time after the attestation completes. Check the same endpoint again to see the destination transaction in forwardTxHash. To mint it yourself instead, call deposit_for_burn without hook_data, set max_fee from the quote without forward=true, and call receiveMessage(message, attestation) on the destination chain’s MessageTransmitterV2. Do not call deposit_for_burn_with_hook with empty hook_data, because the contract rejects it. The stellar-cctp-demo app runs these flows in a browser with Varial (formerly Freighter), in both directions. It shows the two-transaction flow, the wrapper flow, and the Forwarding Service, and its src/lib/stellar/cctp.ts file has the Stellar calls.