CCTP Lane (Cross-Chain Transfer Protocol)
What is CCTP?
Circle’s Cross-Chain Transfer Protocol. A protocol for moving USDC from one blockchain to another. The core mechanism is simple: burn → attest → mint.The bank check analogy
Think of a bank check:- Burn: Go to Bank A and say “issue me a 100 from your account and issues the check
- Attestation: The check-issuing authority (Circle) stamps it with “this check is legitimate” (notarization)
- Mint: Take the notarized check to Bank B → Bank B creates $100 and deposits it into your account
- Receipt: Read your Bank B statement and confirm the deposit is really there, for the amount you expected
- Finality: Confirmation that “the deposit is finalized and cannot be reversed”
Why the “burn and mint” approach: tokens cannot be transferred directly between chains — each chain is an independent ledger. So the protocol destroys tokens on one side (burn) and creates an equal amount on the other side (mint). Total supply stays unchanged. Circle acts as the trust anchor for this process.
The CCTP path in focus right now
You can read this doc in general terms, but the path currently being validated is the EVM staging CCTP lane. The primary combination is:- Source chain: Ethereum Sepolia (
11155111) - Destination chain: Base Sepolia (
84532) - Source/Destination domain:
0 -> 6 - Signer: a single local AWS KMS EVM signer
- Key principle: both burn and mint use the same signer address as the
destination_caller
CCTP Lane in Detail
Where the CEX Lane had 3 nodes, the seeded CCTP Lane has 5:burn → attestation → mint → mint_receive_observe → mint_finality. Attestation is an
extra step CEX transfers do not have, and mint_receive_observe confirms the destination
address actually received the USDC before finality is waited on.
The receipt observation is what makes destination_finalized mean what it says. Finality
alone confirms that the mint transaction is durable; it never reads the token or the
amount. Only mint_receive_observe asserts that the expected amount of the expected token
arrived at the expected address. For the full node config tables, see
CCTP lane reference.
Data propagation between nodes
Operations perspective: preflight before live E2E
The current CCTP flow treats “check first” as more important than “run immediately.” The typical sequence looks like this:- The preflight CLI checks signer health, chain ID, RPC capability, contract code, USDC balance, allowance, native gas, pending nonce, and Circle fast allowance.
- Only when ready does the operator proceed to a live staging E2E that creates an actual movement.
- The live E2E runs the lane —
burn -> attestation -> mint -> mint_receive_observe -> mint_finality— end to end, and verifies that artifacts and provider refs are recorded as expected.
Practical checklist
Node 1: Burn
The node that burns USDC on the source chain. Registered under the keyexec.cctp.burn.
depositForBurn() contract call
The smart contract function actually called:Preflight — 6 steps
destination_caller and security
Thedestination_caller is read from node config, must be present, and is validated as a canonical EVM address before the burn can proceed. A missing or invalid value fails the node immediately.
bytes32 conversion
EVM addresses are 20 bytes (40 hex characters), but the CCTP contract requires 32 bytes (64 hex characters). So 12 bytes (24 zeros) are prepended as padding:Prepare & Submit
prepare builds the EVM transaction payload and returns it with signing_required=true. As described in the Executor Protocol, an external Signer signs it, and then submit broadcasts the result via eth_sendRawTransaction.
If submit times out, it returns UNKNOWN. In recover, the presence of a burn_tx_hash leads to COMPLETED; its absence leads to FAILED.
Node 2: Attestation
The step in which Circle’s IRIS API verifies the burn transaction and issues a certificate saying “this burn is legitimate.”What the IRIS API does
Returned data:attestation: the certificate containing Circle’s signature. Must be submitted to the destination chain contract to trigger the mintmessage(= message_bytes): the original burn event message. Submitted together with the attestation when mintingeventNonce: unique message identifier (prevents duplicate mints)
Fast vs Standard
Determined by themin_finality_threshold value:
For fast transfers (threshold = 1000), preflight runs one extra check: it queries Circle’s remaining fast-burn allowance and fails the node with
FAST_ALLOWANCE_EXHAUSTED if the requested amount exceeds what’s available. Standard transfers (threshold = 2000) skip this check.
Attestation probe
Attestation checking is implemented as a dedicated CCTP probe behind a generic observe step:Why the probe pattern: attestation checking is not CCTP-specific. Other bridge protocols also need a similar “proof verification” step. That is why the generic observe step calls through a protocol-probe interface, with the CCTP attestation probe looked up by key.
Artifact generation
When attestation completes, an audit artifact is recorded with:artifact_type:attestationcontent_format:hex- the attestation hex itself as the inline content
- metadata:
attestation_hash,message_nonce,source_domain_id - the attestation hash as the artifact digest
Node 3: Mint
The node that mints new USDC on the destination chain. Registered under the keyexec.cctp.mint.
receiveMessage() contract call
Preflight: destination_caller consistency check
Mint’s preflight performs the most critical security validation:Recover: idempotency detection
Mint’srecover step is particularly interesting. It handles the scenario of “what if mint is attempted again after already succeeding?”:
- If a
mint_tx_hashis present, recover fetches the receipt. No receipt yet →UNKNOWN; a reverted receipt →FAILED(TX_REVERTED); a successful receipt →COMPLETED. - If no
mint_tx_hashis present, recover simulatesreceiveMessageto probe whether the mint already happened:- If the simulation succeeds, the mint has not happened yet →
FAILED(MINT_NOT_SUBMITTED). - If the simulation reverts with an “already processed” error, the mint already succeeded →
COMPLETED. - Any other revert →
FAILED.
- If the simulation succeeds, the mint has not happened yet →
Node 4: Destination Receipt
The step that turns “the mint transaction succeeded” into “the recipient actually holds the USDC.” It re-reads the mint receipt from the destination chain and matches the ERC-20Transfer it contains against the token, the address, and the amount the movement asked for.
Why the mint result is not enough on its own
Node 3 reportsCOMPLETED when eth_sendRawTransaction returns a hash and the transaction
is mined. That is a statement about the transaction, not about the money. The receipt node
is what reads the destination-side effect back:
Every one of those failures is fatal, not a retry. A mint that lands the wrong amount at
the wrong address does not sit in
OBSERVING waiting for a better answer — the node fails
and the movement stops.
The node’s timeout_policy.observe_seconds is not a receipt deadline by default: the
observer only enforces it when MG_OBSERVE_TIMEOUT_ENFORCEMENT_ENABLED is on (it ships
off), and enforcement moves the node to UNKNOWN with OBSERVATION_TIMEOUT for recovery
to classify — it does not decide that the money did not arrive.
The amount is a floor, not an equality
Fast CCTP (min_finality_threshold=1000) mints amount - fee_executed, and the only bound
on fee_executed is the maxFee the burn itself carried — Node 1 encodes max_fee_raw
into the depositForBurn calldata, so the ceiling is set on-chain at burn time. An
exact-equality check would therefore reject every fast transfer that paid any fee at all.
The seeded lane sets amount_match: "at_least" with
amount_floor_deduction_raw = max_fee_raw, so the assertion is:
observed_amount as proof with proof_source: destination_chain
— evidence read from the destination chain rather than from the transaction QTG submitted.
Node 5: Finality Observe
The final step that confirms the mint transaction has received enough confirmations.Why you still need to wait after mint succeeds
Confirmation check logic
The finality probe reads the mint transaction receipt and the current chain head, then computes:0 (not finalized). As long as confirmations stay below the required threshold the node remains OBSERVING; once they reach it, the node resolves COMPLETED.
The required number of confirmations varies by chain:
confirmations_required is set in node_config, so it can be adjusted to match chain characteristics when configuring a route.
Security summary
Signed-recipient invariant (validated == signed)
On top of thedestination_caller checks, CCTP burn/mint are subject to QTG’s cross-lane signed-recipient invariant: the recipient and source addresses re-derived from the actual signed transaction must equal the allowlist-validated intent — checked both at prepare time (intent-only resolvers) and at submit/broadcast time (the raw signed legacy tx is RLP-decoded and re-anchored). CCTP is a strict source + destination action (cctp_burn / cctp_mint), so both ends are validated.
A mismatch is fail-closed — a fatal movement error drives the node to FAILED instead of broadcasting:
SIGNED_RECIPIENT_MISMATCH— signed recipient ≠ validated destination authority.SIGNED_SOURCE_MISMATCH— signed source ≠ validated source authority.
Scenario 1: destination_caller mismatch
Prevention:- Preflight triple-validates: signer_address == destination_caller == caller specified at burn
- Signer retirement refuses while a committed nonterminal node still references the signer — matched by signer key, and by signer-address snapshot where that is recoverable — so an in-flight CCTP movement normally blocks retirement of the signer it is using. The scan reads committed rows, so treat it as a gate against ordinary operator sequencing, not as a serialization guarantee against a movement being created concurrently. Rotation is not this gate at all: it removes the old signer from binding resolution for newly created movements, while nodes already created keep the binding stamped on them. Both operations are part of the QTG Pro signer lifecycle surface
Scenario 2: Mint attempted without attestation
Minting without an attestation is impossible. The MessageTransmitter contract verifies Circle’s signature, so this is guaranteed at the protocol level.Scenario 3: Burn succeeds + submit timeout
If the burn TX was sent butsubmit times out, it returns UNKNOWN rather than failing. Recover then sees that burn_tx_hash is present and resolves to COMPLETED. Since the burn TX is already on-chain, the attestation → mint process can continue.
Scenario 4: Duplicate mint attempt
Attempting to mint with the same attestation twice? → The contract reverts with “already processed.” Recover detects this error and resolves to COMPLETED (applying the Executor Protocol’s idempotency principle).CCTP Lane architecture summary
Related Docs
- Bridge Lane Overview — shared bridge family pattern and trust-anchor comparison
- CCIP Lane — Chainlink CCIP cross-chain transfer (DON + Risk Management Network)
- LayerZero Lane — Stargate / LayerZero v2 OFT transfer
- Executor Protocol — Executor common interface and the prepare/submit separation design
- CEX Lane — CEX exchange-to-exchange transfer (3 nodes, simpler)
- State Machine — UNKNOWN state handling and the recover flow