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.