Skip to main content

Overview

This guide will walk you through executing a complete cross-chain USDC transfer using the CCTP Go SDK. You’ll learn how to:
  • Poll for attestations from Circle’s Iris service
  • Query available chains and their configurations
  • Execute a full transfer workflow programmatically
Make sure you’ve installed the SDK before proceeding.

Basic attestation polling

The simplest use case is polling for an attestation after a burn transaction:
package main

import (
    "context"
    "fmt"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/circlefin/cctp-go"
)

func main() {
    // Create an Iris client for attestations
    irisClient := cctp.NewIrisClient("https://iris-api.circle.com")

    // Poll for an attestation
    ctx := context.Background()
    msg, err := irisClient.PollForAttestation(
        ctx,
        0, // source domain (Ethereum)
        "0x...", // burn transaction hash
        nil, // optional progress callback
    )
    if err != nil {
        panic(err)
    }

    fmt.Printf("Attestation received: %s\n", msg.Attestation)
}
The Iris API returns attestations from Circle’s attestation service. The polling will continue until the attestation is available or the context is cancelled.

Working with chains

Get information about supported chains:
package main

import (
    "fmt"
    "github.com/circlefin/cctp-go"
)

func main() {
    // Get available chains
    chains := cctp.GetChains(false) // false = mainnet, true = testnet

    // Print all chains
    for _, chain := range chains {
        fmt.Printf("Chain: %s, Domain: %d, ChainID: %s\n",
            chain.Name, chain.Domain, chain.ChainID)
    }

    // Get a specific chain by domain
    ethereum, err := cctp.GetChainByDomain(0, false)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Ethereum RPC: %s\n", ethereum.RPC)

    // Get a chain by name using constants
    arbitrum, err := cctp.GetChainByName(cctp.Arbitrum, false)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Arbitrum RPC: %s\n", arbitrum.RPC)
}
Use the chain name constants (e.g., cctp.Ethereum, cctp.Arbitrum) for IDE autocomplete support and to avoid typos.

Overriding RPC endpoints

The library provides default RPC endpoints, but you can override them with custom endpoints:
package main

import (
    "fmt"
    "github.com/circlefin/cctp-go"
)

func main() {
    // Get chains with default RPC endpoints
    chains := cctp.GetChains(false) // false = mainnet

    // Define custom RPC endpoints using the chain name constants
    customRPCs := map[string]string{
        cctp.Ethereum: "https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
        cctp.Arbitrum: "https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
        cctp.Base:     "https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
    }

    // Apply RPC overrides
    chains = cctp.ApplyRPCOverrides(chains, customRPCs)

    // Now use chains with your custom RPC endpoints
    for _, chain := range chains {
        if chain.Name == cctp.Ethereum {
            fmt.Printf("Ethereum RPC: %s\n", chain.RPC)
        }
    }
}
Available chain constants:Mainnet: cctp.Ethereum, cctp.Avalanche, cctp.OPMainnet, cctp.Arbitrum, cctp.Base, cctp.PolygonPoS, cctp.Unichain, cctp.Linea, cctp.Codex, cctp.Sonic, cctp.WorldChain, cctp.Sei, cctp.XDC, cctp.HyperEVM, cctp.Ink, cctp.PlumeTestnet: cctp.EthereumSepolia, cctp.AvalancheFuji, cctp.OPSepolia, cctp.ArbitrumSepolia, cctp.BaseSepolia, cctp.PolygonPoSAmoy, cctp.LineaSepolia, cctp.ArcTestnet, cctp.UnichainSepolia, cctp.CodexTestnet, cctp.SonicTestnet, cctp.WorldChainSepolia, cctp.SeiTestnet, cctp.XDCApothem, cctp.HyperEVMTestnet, cctp.InkTestnet, cctp.PlumeTestnet

Contract interactions

Interact with USDC and CCTP contracts using V2 bindings:
package main

import (
    "context"
    "fmt"
    "math/big"

    "github.com/ethereum/go-ethereum/accounts/abi/bind/v2"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/circlefin/cctp-go/tokenmessenger"
)

func main() {
    // Connect to a chain
    client, err := ethclient.Dial("https://ethereum-rpc.publicnode.com")
    if err != nil {
        panic(err)
    }

    // Create V2 contract binding
    ctx := context.Background()
    ierc20 := tokenmessenger.NewIERC20()
    usdcAddress := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
    usdcInstance := ierc20.Instance(client, usdcAddress)

    // Check USDC balance using V2 bindings
    walletAddress := common.HexToAddress("0x...")
    balance, err := bind.Call(usdcInstance, &bind.CallOpts{Context: ctx},
        ierc20.PackBalanceOf(walletAddress), ierc20.UnpackBalanceOf)
    if err != nil {
        panic(err)
    }

    fmt.Printf("USDC Balance: %s\n", balance.String())
}
The SDK uses Go Ethereum V2 Contract Bindings for type-safe contract interactions.

Full transfer example

For a complete example of orchestrating a full CCTP transfer with approval, burn, attestation polling, and mint, see the transfer orchestrator in your application. The TransferOrchestrator handles the entire workflow:
1

Check balance

Verify the wallet has sufficient USDC for the transfer
2

Check allowance

Verify USDC allowance for the TokenMessengerV2 contract
3

Approve (if needed)

Approve USDC spending if allowance is insufficient
4

Burn USDC

Call depositForBurn on the source chain
5

Poll for attestation

Wait for Circle’s attestation service to sign the message
6

Mint USDC

Call receiveMessage on the destination chain
See the CLI implementation for a complete working example of the full transfer workflow.

Transfer types

The SDK supports two transfer types:
params := &cctp.TransferParams{
    SourceChain:      sourceChain,
    DestChain:        destChain,
    Amount:           amount,
    RecipientAddress: recipient,
    TransferType:     cctp.TransferTypeFast, // ~8-20 seconds, with fee
}
Use TransferTypeAuto to automatically select the best transfer type based on chain capabilities. The SDK will use Fast Transfer when available, or Standard Transfer for instant finality chains.

Next steps

SDK overview

Explore all SDK features and APIs

CLI usage

Learn how to use the CLI tool

Transfer orchestration

Deep dive into the transfer workflow

Chain configuration

Learn about chain configurations and constants

Build docs developers (and LLMs) love