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
CctpForwarder guide.
Transfer flow from Stellar
A transfer from Stellar to another chain has three steps:- Burn. Call
deposit_for_burnordeposit_for_burn_with_hookon theTokenMessengerMintercontract. The contract burns your USDC and emits a CCTP message. - Attest. Circle’s attestation service signs the message. Fetch the message and attestation with
GET /v2/messages/27?transactionHash=<hash>. Stellar’s CCTP domain is27. Use the Stellar transaction hash as it is, with no0xprefix. - Mint. Someone calls
receiveMessageon the destination chain’sMessageTransmitterV2contract with the message and attestation. That can be you or a relayer. See relayer options.
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 callsapprove 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:
approve_and_deposit_with_hook, which passes hook_data on to deposit_for_burn_with_hook.
Option 2: an existing allowance
Approve theTokenMessengerMinter 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
TokenMessengerMintercontract 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 callreceiveMessage. 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
- Check that Circle’s supported blockchains table lists the destination chain under “Forwarding Service”.
- Set
hook_datato the 32-byte version 0 forwarding hook:0x636374702d666f72776172640000000000000000000000000000000000000000. This iscctp-forwardfollowed by zero bytes. See Circle’s hook format. - Quote the fee with
GET /v2/burn/USDC/fees/27/<destination domain>?forward=true. Use the entry withfinalityThreshold2000.minimumFeeis the protocol fee in basis points, andforwardFeeis in 6-decimal USDC units. - Set
max_feeto cover the protocol fee plus the forwarding fee, in Stellar’s 7-decimal units. MultiplyforwardFeeby 10 to convert it.
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.