Skip to main content

Overview

A cross-chain USDC transfer via CCTP involves three main phases:
  1. Source Chain: User initiates burn and message emission
  2. Attestation: Off-chain service validates and signs the message
  3. Destination Chain: User or relayer completes the transfer with minting
The examples below show a transfer from Ethereum (Domain 0) to Avalanche (Domain 1), but the flow is identical for any supported chain pair.

Phase 1: Source Chain - depositForBurn

Step 1: User Approves USDC

Before calling depositForBurn(), users must approve the TokenMessenger contract to spend their USDC:

Step 2: Call depositForBurn

User initiates the cross-chain transfer:

Step 3: Contract Execution Flow

Inside TokenMessenger.depositForBurn(), the following occurs:

Step 4: MessageTransmitter Sends Message

MessageTransmitter.sendMessage() formats and emits the cross-chain message:

Step 5: Extract Message from Event

The emitted MessageSent event contains the complete message bytes needed for attestation:
Message Hash: This hash uniquely identifies the transfer and is used to retrieve the attestation.

Phase 2: Attestation Service

Step 6: Attestation Service Observes Event

Circle’s attestation service continuously monitors all supported chains for MessageSent events:
  1. Event Detection: Service detects new MessageSent event
  2. Message Validation:
    • Verifies transaction is confirmed
    • Checks burn amount is within limits
    • Validates destination domain is supported
    • Confirms source contract is legitimate
  3. Signature Generation:
    • Hashes the complete message bytes
    • Signs the hash with authorized attester private keys
    • Combines signatures into attestation format

Step 7: Poll for Attestation

Users fetch the attestation via Circle’s API:
Rate Limiting: The attestation service is rate-limited. Limit requests to less than 1 per second to avoid being blocked.
Attestation Format:
  • Concatenated 65-byte ECDSA signatures (v + r + s)
  • Number of signatures equals signatureThreshold
  • Signatures must be in increasing order of attester address
Response Format:

Phase 3: Destination Chain - receiveMessage

Step 8: Call receiveMessage

With the attestation, anyone can complete the transfer on the destination chain:

Step 9: MessageTransmitter Validates Message

MessageTransmitter.receiveMessage() performs extensive validation:

Step 10: TokenMessenger Handles Message

TokenMessenger.handleReceiveMessage() processes the burn message:

Step 11: TokenMinter Mints USDC

Finally, TokenMinter.mint() creates new USDC on the destination chain:
Result: The recipient now has USDC on Avalanche, completing the cross-chain transfer!

Complete Example

Here’s a full working example combining all steps:

Advanced: depositForBurnWithCaller

For permissioned receiving, use depositForBurnWithCaller():
Use Cases:
  • Relayer-exclusive completion (prevent front-running)
  • Smart contract-only receivers
  • Conditional minting logic
If destinationCaller is invalid or unable to call receiveMessage(), the funds will be permanently stuck. Only use this for advanced scenarios.

Timing and Finality

Attestation Delay:
  • Typical: 10-20 minutes after source chain confirmation
  • Depends on: Chain finality requirements, attestation service processing
Destination Confirmation:
  • Immediate once receiveMessage() transaction confirms
  • Subject to destination chain’s block time
Total Time: Usually 15-30 minutes for complete cross-chain transfer

Error Handling

Common failure scenarios:

Next Steps

Attestation

Understand signature verification in detail

Quickstart

Try the example integration yourself