MessageTransmitterV2
and Iris (Circle’s attestation API)
to move arbitrary ERC-20 tokens across blockchains. Four additional contracts
handle token registration, balance changes, and the developer-facing API.
Components
CrossChainTokenService: The singleton entry point on each domain. Registers tokens, deploys remote token contracts, and routes crosschain transfers.TokenManager: Deployed per token on each domain. Moves tokens (mint, burn, lock, or unlock) and enforces per-token controls including rate limits, max transfer amount, and pause.CrossChainToken: An ERC-20 withERC20Permitdeployed for tokens that are natively crosschain. Holders callcrossChainTransferdirectly on the token.CrossChainTokenExecutable: Optional abstract helper for destination receivers. Integrators can implementexecuteWithCrossChainTokendirectly and returnEXECUTE_SUCCESS. See Hooks.
Transfer flow
The following sequence shows a transfer from a source domain to a destination domain: The exact behavior at the source and destinationTokenManager depends on the
token’s manager type.
Component map
TheCrossChainTokenService is one contract per domain. It fronts every
TokenManager on that domain. A TokenManager is created for each registered
token and is identified across all domains by its tokenId.
Token manager types
EveryTokenManager has a type that determines how it moves tokens on the
source and destination blockchains during a transfer. The type is set when the
token is registered.
Solidity
Most tokens use the same type on all blockchains. Exception: the ownerless model
pairs
LOCK_UNLOCK on the home blockchain with NATIVE_CROSSCHAIN_TOKEN on
remote blockchains. See
Ownerless versus custom tokens.
How tokens are identified
Each token is identified by itstokenId, a bytes32 value derived from the
address that registered the token and a caller-chosen salt. The same tokenId
identifies the same token across every blockchain where it is registered.
You do not construct a tokenId manually. The CrossChainTokenService returns
it when you register or deploy a token, and exposes read helpers to rederive it
later:
getOwnerlessTokenId(tokenAddress)for ownerless tokenscrossChainTokenId(deployer, salt)for nativeCrossChainTokendeployments (deployCrossChainToken)crossChainTokenId(deployer, customTokenDeploySalt(deployer, salt))for custom tokens. CallingcrossChainTokenId(deployer, salt)alone yields a different id.customTokenDeploySaltis also the storage salt used to derive the custom token’sTokenManager.
(msg.sender, salt), using the same registrar
address and the same salt on every blockchain produces the same tokenId
everywhere. This is what lets a single value identify a token on every supported
blockchain without an offchain registry.
Once you have a tokenId, look up the local contracts:
resolveTokenManager(tokenId)returns theTokenManageraddress.resolveTokenAddress(tokenId)returns the underlying ERC-20 address.
TokenNotRegistered(tokenId) if no token manager has been
deployed for the supplied tokenId on the local domain. The predicted address
is deterministic through CREATE3, so callers that need a non-throwing existence
check can wrap the call in try/catch or perform their own extcodesize
check.
Decimal consistency
The protocol does not scale token amounts when delivering transfers. A token must use the same number of decimals on every blockchain where it is deployed.Transfer finality and fast burn allowance
Two transfer speeds are available, set by theminFinalityThreshold parameter
on crossChainTransfer:
- Standard transfer (
minFinalityThreshold = 2000) waits for source blockchain finality before attestation. - Fast transfer (
minFinalityThreshold = 1000) attests on soft finality and consumes a per-token fast burn allowance.
GET /v2/cctpx/allowances to confirm
remaining capacity and that the token has FX pricing configured. If allowance is
insufficient or pricing data is unavailable (for example
FX_SYMBOL_NOT_CONFIGURED), Iris rejects the fast quote; there is no automatic
fallback. Request a standard quote (omit PRE_FINALITY) and use
minFinalityThreshold = 2000 instead.
USDC and non-USDC fast transfers share the same MessageTransmitterV2 and
finality thresholds, but each tracks its own allowance pool. USDC transfers
consume the USDC allowance; non-USDC transfers consume a per-token allowance.
Forwarding service
An optional forwarding service delivers completed transfers to the recipient on the destination blockchain. When forwarding is used, Circle relays the Iris attestation and callsreceiveMessage on the destination MessageTransmitterV2
on behalf of the sender, so neither the sender nor the recipient needs to submit
the attestation manually.
Forwarding is opt-in and must be requested when you obtain the fee quote from
the Iris API. The fee quote response includes a signed claim that you pass as
the claim parameter on crossChainTransfer. Transfers that omit the claim
proceed without automatic relay, and you or the recipient are responsible for
submitting the attestation to MessageTransmitterV2.
You might choose not to use forwarding if you manage delivery yourself, if you
are performing complex hook-based transactions that require manual relay
sequencing, or if you have your own relayer infrastructure.
Reuse of CCTP infrastructure
No separate attestation service runs for non-USDC transfers. Every message flows through the sameMessageTransmitterV2 and Iris (Circle’s attestation API) that
CCTP uses for USDC transfers, including the same set of attesters, the same
finality thresholds, and the same message verification logic. USDC and non-USDC
transfers share the attestation pipeline but are otherwise independent. USDC
transfer behavior is unchanged.