Skip to main content

General Questions

Cross-Chain Transfer Protocol (CCTP) is Circle’s permissionless on-chain utility that enables native USDC to be transferred securely between blockchain networks.Instead of traditional lock-and-mint bridge mechanisms, CCTP uses a burn-and-mint approach:
  1. USDC is burned on the source chain
  2. Circle’s attestation service signs the burn message
  3. USDC is minted on the destination chain
This ensures that USDC remains native and fungible across all supported chains.
CCTP supports multiple EVM-compatible chains. Supported chains are identified by domain IDs configured in the contracts.Common domains include:
  • Ethereum mainnet and testnets
  • Avalanche C-Chain
  • Arbitrum
  • Optimism
  • Base
  • Polygon PoS
Check Circle’s official documentation for the complete and up-to-date list of supported chains and their domain IDs.
Transfer time varies by chain due to different finality requirements:
  • Fast chains (e.g., Avalanche): 1-2 minutes
  • Medium chains (e.g., Polygon): 5-10 minutes
  • Slower chains (e.g., Ethereum): 15-20 minutes
The process includes:
  1. Source chain transaction confirmation
  2. Block finality waiting period
  3. Attestation service signing (usually quick)
  4. Destination chain transaction submission
V2 introduces finality thresholds that can reduce wait times for unfinalized messages with appropriate fee incentives.
Attesters are Circle-operated nodes that validate and sign burn messages. They provide the cryptographic proof needed to mint USDC on the destination chain.Key points:
  • Multiple attesters create decentralization
  • Threshold signatures ensure security (e.g., 2-of-3)
  • Attesters verify the burn transaction occurred
  • Attestation service coordinates attester signatures
The attester manager role can enable/disable attesters and set signature thresholds.

Version Comparison

V2 introduces significant enhancements over V1:V2 New Features:
  • Fee support: Protocol fees for transfers, with fee recipient role
  • Hook execution: Custom logic execution on destination chain via depositForBurnWithHook
  • Finality thresholds: Configurable finality levels (finalized, confirmed, unfinalized)
  • Denylist: Address denylist functionality for compliance
  • CREATE2 deployment: Deterministic addresses across chains for easier integration
  • Min fee controller: Separate role for managing minimum fee percentages
  • Enhanced message format: Additional fields for new functionality
V1 Features:
  • Basic burn-and-mint functionality
  • Simple depositForBurn and depositForBurnWithCaller
  • Role-based access control
  • Pausability and rescuability
Migration considerations:
  • V1 and V2 use different message versions (0 vs 1)
  • V2 contracts are not backward compatible with V1 messages
  • Both versions can coexist on the same chain
Use V2 if you need:
  • Fee-based transfers
  • Hook functionality for custom destination logic
  • Different finality guarantees
  • Address denylist support
  • Future-proof integration
Use V1 if:
  • You need a simpler, battle-tested implementation
  • You don’t need advanced V2 features
  • You’re integrating with existing V1 deployments
Recommendation: New integrations should generally use V2 for enhanced functionality and future compatibility.

Function Selection

V1 Functions:depositForBurn(amount, destinationDomain, mintRecipient, burnToken)
  • Anyone can call receiveMessage on the destination chain
  • Simpler, more permissionless
  • Use when you don’t need to control who submits the destination transaction
depositForBurnWithCaller(amount, destinationDomain, mintRecipient, burnToken, destinationCaller)
  • Only the specified destinationCaller can call receiveMessage
  • More control over execution
  • Use when you need to restrict destination execution (e.g., specific relayer)
V2 Functions:V2 unifies these into depositForBurn with a destinationCaller parameter:
  • Set destinationCaller to bytes32(0) for permissionless execution
  • Set destinationCaller to specific address for restricted execution
Use depositForBurnWithHook when you need to execute custom logic on the destination chain after minting.Use cases:
  • Automatic swaps after receiving USDC
  • Depositing into DeFi protocols
  • Forwarding funds to another contract
  • Complex multi-step operations
Requirements:
  • hookData must be non-empty
  • Destination contract must implement hook handler interface
  • Additional gas required on destination for hook execution
Example:
bytes memory hookData = abi.encode(
    targetContract,
    targetFunction,
    targetParams
);

tokenMessenger.depositForBurnWithHook(
    amount,
    destinationDomain,
    mintRecipient,
    burnToken,
    destinationCaller,
    maxFee,
    minFinalityThreshold,
    hookData
);
The minFinalityThreshold parameter in V2 specifies the minimum level of finality required before attesters will sign the message.Threshold levels:
  • 2000+: Finalized (fully confirmed, highest security)
  • 1000-1999: Confirmed (strong but not final)
  • 500-999: Unfinalized (faster but requires fees)
Trade-offs:
  • Higher threshold: Slower but more secure, no fees required
  • Lower threshold: Faster but requires higher fees, small reorg risk
Minimum for TokenMessenger: 500 (TOKEN_MESSENGER_MIN_FINALITY_THRESHOLD)Use cases:
  • Time-sensitive transfers: Use 500-999 with appropriate fees
  • High-value transfers: Use 2000+ for maximum security
  • Standard transfers: Use 1000 for balanced speed and security

Features and Mechanisms

CCTP contracts include an emergency pause mechanism:Pause functionality:
  • Pauser role can call pause() to stop operations
  • Pauser role can call unpause() to resume operations
  • Only affects state-changing functions (marked with whenNotPaused)
  • View functions continue to work during pause
What gets paused:
  • depositForBurn / depositForBurnWithCaller (V1)
  • depositForBurn / depositForBurnWithHook (V2)
  • sendMessage (MessageTransmitter)
  • receiveMessage (MessageTransmitter)
What still works:
  • Reading contract state
  • Querying balances and allowances
  • Checking pause status
Checking pause status:
bool isPaused = messageTransmitter.paused();
V2 introduces a flexible fee system:Fee structure:
  • Fees are paid in the same token being transferred (USDC)
  • Fees are deducted from the transfer amount on destination
  • Fee recipient receives minted fees separately
Fee parameters:
  • maxFee: Maximum fee sender is willing to pay
  • minFee: Minimum fee percentage set by protocol (basis points)
  • feeExecuted: Actual fee charged (recorded in message)
Calculating minimum fee:
const minFeeAmount = await tokenMessenger.methods
    .getMinFeeAmount(amount)
    .call();
Fee validation:
require(maxFee < amount, "Max fee must be less than amount");
require(maxFee >= minFeeAmount, "Insufficient max fee");
Fee distribution:
  • Recipient receives: amount - feeExecuted
  • Fee recipient receives: feeExecuted
V2 includes a denylist mechanism for regulatory compliance:How it works:
  • Denylister role can add/remove addresses
  • Checks both msg.sender and tx.origin
  • Denylisted addresses cannot call depositForBurn functions
  • Reverts with “Denylistable: account is on denylist”
Checking denylist status:
const isDenylisted = await tokenMessenger.methods
    .isDenylisted(address)
    .call();
Impact:
  • Prevents initiating new transfers
  • Does not affect receiving USDC from other addresses
  • Does not freeze existing USDC balances
V2 uses CREATE2 for deterministic contract addresses across chains:Benefits:
  • Same contract addresses on all chains
  • Easier integration (one address to remember)
  • Simplified remote resource configuration
  • Predictable deployment planning
Components:
  • Create2Factory: Deploys contracts deterministically
  • Salt-based addressing: Same salt = same address
  • Cross-chain coordination: Deploy in same order everywhere
Predicting addresses:
forge script scripts/v2/PredictCreate2Deployments.s.sol \
    --sig "tokenMessengerV2Proxy(address)" \
    <create2FactoryAddress>

Development and Testing

Use Foundry’s testing framework:Unit tests:
forge test
Integration tests with Anvil:
make anvil-test
Debug tests:
forge test -vv  # Show console.log output
forge test -vvv  # Show stack traces
Test examples are available in the test/ directory, including V1 and V2 test suites.
Gas costs vary by chain and operation:Typical costs:
  • depositForBurn: ~100-200k gas
  • depositForBurnWithHook (V2): ~150-300k gas (depends on hook)
  • receiveMessage: ~100-200k gas
  • Token approval: ~50k gas
Factors affecting gas:
  • Chain gas price
  • Message body size
  • Hook complexity (V2)
  • First-time operations (e.g., first approval)
Always estimate gas before transactions and add a buffer for safety.
Yes! CCTP is available on various testnets:Common testnets:
  • Ethereum Sepolia
  • Avalanche Fuji
  • Arbitrum Sepolia
  • Optimism Sepolia
  • Base Sepolia
Testnet attestation service:
https://iris-api-sandbox.circle.com/attestations/{messageHash}
Getting testnet USDC:
  • Use Circle’s testnet faucet
  • Check chain-specific faucets
  • Bridge from other testnets using CCTP

Need More Help?

Troubleshooting

Common issues and solutions

Security

Security best practices and reporting

Build docs developers (and LLMs) love