Mavna
Rails

Circle CCTP

Circle burns USDC on the source chain and mints it on Stellar. How the adapter builds the burn, verifies its chains and gets the mint delivered.

@mavna/adapter-cctp, adapter id cctp, trust model native-mint. Circle's Cross-Chain Transfer Protocol burns USDC on the source chain and Circle mints the same amount on Stellar once it attests the burn. Nobody holds the money in between, and what arrives is Circle's own USDC.

const cctp = new CctpAdapter({ network, rpc, baseReserveStroops });

Chains

The adapter moves USDC from 26 chains on each network: 24 EVM chains, Solana and Starknet. Each chain's entry carries its Circle domain number, its TokenMessengerV2 address and its USDC contract. Rails and networks lists them.

The EVM list comes from scripts/verify-cctp-chains.mjs, which records no address it has not checked on the chain itself: that there is code at the TokenMessengerV2 address, which minter that contract names, and which token the minter calls local USDC, and that the token reports itself as USDC with six decimals. Run it with node scripts/verify-cctp-chains.mjs.

The burn

The burn's mintRecipient and destinationCaller are both Circle's Stellar forwarder contract, not the sender's address, and the sender's address travels inside the message as hook data. Circle mints to the forwarder, which passes the USDC on to the recipient. The forwarder delivers to an account, a muxed address or a contract. For a muxed address it pays the account the address is built on, and the token's transfer event carries the muxed id as to_muxed_id. The hook data is validated before it is encoded, and any other address is refused.

Before the burn, the sender approves the exact amount for TokenMessengerV2, never an unlimited allowance. The approval is skipped when the wallet's allowance already covers the amount.

Fees

The fee is read from Circle's fee service on every quote, for the standard path.

Time

The attestation waits for the source chain's finality, so the estimate depends on the chain. The bridge step takes Circle's published standard attestation time for the source chain, at the slow end of its range, plus a minute for Circle's polling and the mint on Stellar. Every other step counts 30 seconds, and the route's estimatedSeconds is the sum. The bridge step is 20 minutes from Ethereum, OP Mainnet, Arbitrum or Base, 4 hours and 1 minute from Starknet, 32 hours and 1 minute from Linea, and 61 seconds from Arc. standardAttestationSeconds(domain) and expectedBridgeSeconds(domain) export the figures.

Delivery on Stellar

Circle's own forwarding service does not deliver to Stellar, so someone on Stellar has to submit the mint once Circle attests the burn. With a sponsor that covers costs, the sponsor submits it and pays its fee. With MAVNA_RESERVE_FUNDING=self, the receiving account signs and pays for it: for a muxed address, the account it is built on. A contract cannot sign it, so with that setting a Circle route to a contract is declined at the quote. Either way, the recipient is fixed inside the attested message, so whoever submits it cannot redirect anything.

Delivery explains how arrival is proven and why a delivery cannot happen twice.

On this page