cctp-rs
A production-ready Rust implementation of Circle's Cross-Chain Transfer Protocol (CCTP), enabling seamless USDC transfers across blockchain networks.
Features
- 🚀 Type-safe contract interactions using Alloy
- 🔄 Bridge SDK for 11 v2-capable EVM mainnet chains plus 6 USDC testnets; protocol parser recognizes all 21 CCTP v2 domain IDs (including non-EVM domains such as Solana and Starknet Testnet)
- 📦 Builder pattern for intuitive API usage
- ⚡ CCTP v2 support with fast transfers (<30s settlement)
- 🤝 Relayer-aware APIs for permissionless v2 relay handling
- 🎯 Programmable hooks for advanced use cases
- 🔍 Comprehensive observability with OpenTelemetry integration
- 🤖 Agent/tooling-friendly message inspection with serializable v2 parsers
Supported Chains
cctp-rs has two layers with different coverage. Read both before choosing an integration path.
- Bridge SDK —
CctpV2Bridge,Cctp, and theCctpV1/CctpV2traits onalloy_chains::NamedChaincan burn USDC and relay attestations end-to-end for the chains listed below. These are the chains whereNamedChain::supports_cctp_v2()returnstrue. - Protocol parser —
ParsedV2Message,ParsedV2MessageSummary, and theDomainIdenum recognize every CCTP v2 domain ID Circle has announced (21 at time of writing), including non-EVM domains. Parsing a domain is independent of whether the bridge SDK can route to or from it.
Bridge SDK — supported chains
Chains returning true from NamedChain::supports_cctp_v2():
Mainnet
- Ethereum, Arbitrum, Base, Optimism, Avalanche, Polygon, Unichain
- Linea, Sonic, Sei, HyperEVM (v2-only chains)
Testnet
- Sepolia, Arbitrum Sepolia, Base Sepolia, Optimism Sepolia
- Avalanche Fuji, Polygon Amoy
Protocol parser — additional domains
DomainId and ParsedV2MessageSummary recognize the following CCTP
v2 domains, but the bridge SDK does not currently accept them as
source or destination — NamedChain::supports_cctp_v2() returns
false and the bridge builder will reject them:
- Solana (5, non-EVM), Codex (12), World Chain (14), Monad (15), BNB Smart Chain (17), XDC (18), Ink (21), Plume (22), Starknet Testnet (25, non-EVM), Arc Testnet (26)
Use this list when you need to inspect messages crossing these domains (for indexing, analytics, or wallet UIs) without routing through them. To extend bridge SDK support to one of these domains, follow AGENTS.md → Adding chain support.
CCTP v1 (Legacy)
The original v1 chain families (Ethereum, Arbitrum, Base, Optimism,
Avalanche, Polygon, Unichain) and the six USDC testnets above remain
supported through Cctp and the CctpV1 trait for backwards
compatibility. v1 has no Unichain testnet entry.
Quick Start
Add to your Cargo.toml:
[]
= "6"
Basic Example
use ;
use NamedChain;
use ;
use ;
async
Bridging USDC (V1)
use ;
use NamedChain;
use ;
use Provider;
async
Bridging USDC (V2 - Recommended)
use ;
use NamedChain;
use ;
use Provider;
async
Fast Transfer Fees (V2)
Fast transfers require a maxFee cap in USDC atomic units. Fees are dynamic and
route-aware, so fetch the live route fee from Circle Iris before constructing
your fast-transfer mode. Do not use
NamedChain::fast_transfer_fee_bps() for production quotes; that helper is
chain-level static metadata and currently reports FastTransferFee::Unknown
until static fee tables are deliberately sourced.
use ;
use U256;
use Provider;
async
The live lookup methods are no-wallet, no-RPC HTTP calls against Iris:
get_transfer_fees() returns every route entry,
get_fast_transfer_fee() and get_standard_transfer_fee() select a finality
threshold, and calculate_fast_transfer_max_fee() converts the Fast Transfer
fee into an amount-denominated cap with your selected buffer. Funded transfer
execution remains separate: after computing maxFee, pass it into
TransferMode::Fast { max_fee } before calling burn/transfer helpers.
Agent Tooling: Inspect a Canonical V2 Message
Tooling layers usually need structured JSON instead of raw message bytes. ParsedV2Message
and ParsedV2MessageSummary decode the canonical message returned by Circle's v2 API
into serializable Rust types.
Parsing failures return ParseMessageError, so this inspection path does not expand the
existing CctpError surface used by bridge operations.
DomainId values in serialized summaries use snake_case strings. Future releases may
add new domain variants, so older tooling should treat unknown domain strings as a
forward-compatibility case.
use ;
async
Architecture
The library is organized into several key modules:
bridge- Core CCTP bridge implementationchain- Chain-specific configurations and supportattestation- Attestation response types from Circle's Iris APIprotocol- Serializable protocol types, live fee response types, and canonical v2 message parsingerror- Comprehensive error types for proper error handlingcontracts- Type-safe bindings for TokenMessenger and MessageTransmitter
Error Handling
cctp-rs provides detailed error types for different failure scenarios:
use ;
// V1 example
match bridge.get_attestation.await
// V2 example (returns both message and attestation)
match v2_bridge.get_attestation.await
Advanced Usage
Custom Polling Configuration
use PollingConfig;
// V1: Wait up to 10 minutes with 30-second intervals
let attestation = bridge.get_attestation.await?;
// V2: Use preset for fast transfers (5 second intervals)
let = v2_bridge.get_attestation.await?;
// V2: Or customize for your needs
let = v2_bridge.get_attestation.await?;
// Check total timeout
let config = default;
println!;
Chain Configuration
use ;
use NamedChain;
// Get v1 chain-specific information
let chain = Arbitrum;
let confirmation_time = chain.confirmation_average_time_seconds?; // Standard: 19 minutes
let domain_id = chain.cctp_domain_id?;
let token_messenger = chain.token_messenger_address?;
println!;
// Get v2 attestation times (choose based on transfer mode)
let fast_time = chain.fast_transfer_confirmation_time_seconds?; // ~8 seconds
let standard_time = chain.standard_transfer_confirmation_time_seconds?; // ~19 minutes
println!;
println!;
Relayer-Aware Patterns (V2)
CCTP v2 is permissionless - anyone can relay a message once Circle's attestation is available. Third-party relayers (Synapse, LI.FI, etc.) actively monitor for burns and may complete transfers before your application does. This is a feature, not a bug!
Option A: Wait for Completion (Recommended)
If you don't need to self-relay, just wait for the transfer to complete:
use ;
async
Option B: Self-Relay with Graceful Handling
If you want to try minting yourself but handle relayer races:
use ;
async
Option C: Check Status Manually
let is_complete = bridge.is_message_received.await?;
if is_complete
Examples
Check out the examples/ directory for complete working examples:
CCTP v2 Examples
v2_integration_validation.rs- Comprehensive v2 validation (no network required)v2_standard_transfer.rs- Standard transfer with finalityv2_fast_transfer.rs- Fast transfer (<30s settlement)
CCTP v1 Examples (Legacy)
basic_bridge.rs- Simple USDC bridge exampleattestation_monitoring.rs- Monitor attestation statusmulti_chain.rs- Bridge across multiple chains
Run examples with:
# Recommended: Run v2 integration validation
# Or run specific examples
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Testing
Unit Tests
Run the full test suite with:
The default, network-free tests validate:
- Contract method selection logic
- Domain ID resolution and mapping
- Configuration validation
- URL construction for Circle's Iris API
- Error handling and edge cases
- Cross-chain compatibility
- Fast transfer support
- Hooks integration
Integration Validation
We provide comprehensive runnable examples that validate the complete v2 API without requiring network access:
# Validate all v2 configurations (no network required)
# Educational examples showing complete flows
The v2_integration_validation example validates:
- Chain support matrix (11 v2-capable mainnet chain families plus 6 testnets)
- Domain ID mappings against Circle's official values
- Contract address consistency (unified v2 addresses)
- Bridge configuration variations (standard, fast, hooks)
- API endpoint construction (mainnet vs testnet)
- Fast transfer support and fee structures
- Error handling for unsupported chains
- Cross-chain compatibility
Live Fee Smoke Testing
The Live CCTP Fee Smoke GitHub Actions workflow runs weekly and can be
started manually before a release. It calls only no-wallet Iris fee endpoints,
not funded transfer flows, and stays out of default PR/push CI.
For a cheap local pre-release drift check against Circle's Iris fee endpoints that does not require wallets, RPC endpoints, or funded accounts:
These opt-in tests query both the Sepolia -> Base Sepolia sandbox route and the
Ethereum -> Base mainnet route (/v2/burn/USDC/fees/0/6 on each Iris host),
verify the responses decode as CCTP v2 transfer fees, and check that Iris
returns a Fast Transfer threshold.
Live Testnet Testing
For pre-release validation on testnet:
- Get testnet tokens from Circle's faucet
- Update examples with your addresses and RPC endpoints
- Set environment variables for private keys
- Execute and monitor the full flow
Note: Integration tests requiring Circle's Iris API and live blockchains are not run in CI due to:
- Cost (gas fees on every test run)
- Time (10-15 minutes per transfer for attestation)
- Flakiness (network dependencies and rate limits)
- Complexity (requires funded wallets with private keys)
Instead, we validate via extensive unit tests and runnable examples. This approach ensures reliability while maintaining fast CI/CD pipelines.
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Acknowledgments
- Circle for creating CCTP
- Alloy for the excellent Ethereum libraries
- The Rust community for amazing tools and support