Skip to main content

Overview

The TransferOrchestrator coordinates the entire CCTP transfer workflow, handling balance checks, approvals, burning, attestation retrieval, and minting. It provides progress updates throughout the process and manages all blockchain interactions.

TransferOrchestrator Type

transfer.go:83
type TransferOrchestrator struct {
    wallet       *wallet.Wallet
    irisClient   *IrisClient
    sourceClient *ethclient.Client
    destClient   *ethclient.Client
    params       *TransferParams
}

Creating an Orchestrator

transfer.go:92
func NewTransferOrchestrator(
    w *wallet.Wallet,
    params *TransferParams,
    irisBaseURL string,
) (*TransferOrchestrator, error)
w
*wallet.Wallet
required
Wallet instance for signing transactions
params
*TransferParams
required
Transfer parameters (chains, amount, recipient)
irisBaseURL
string
required
Iris API base URL for attestations
Example
import (
    "github.com/circlefin/cctp-go"
    "github.com/circlefin/cctp-go/internal/wallet"
)

// Get chain configurations
sourceChain, _ := cctp.GetChainByName(cctp.Ethereum, false)
destChain, _ := cctp.GetChainByName(cctp.Avalanche, false)

// Create wallet
wallet, _ := wallet.NewWallet(privateKey)

// Configure transfer
params := &cctp.TransferParams{
    SourceChain:      sourceChain,
    DestChain:        destChain,
    Amount:           big.NewInt(1000000), // 1 USDC (6 decimals)
    RecipientAddress: common.HexToAddress("0x..."),
    TransferType:     cctp.TransferTypeAuto,
}

// Create orchestrator
orchestrator, err := cctp.NewTransferOrchestrator(
    wallet,
    params,
    "https://iris-api.circle.com",
)
if err != nil {
    log.Fatal(err)
}
defer orchestrator.Close()

Transfer Parameters

transfer.go:72
type TransferParams struct {
    SourceChain      *Chain
    DestChain        *Chain
    Amount           *big.Int
    RecipientAddress common.Address
    Testnet          bool
    TransferType     TransferType
    CachedBalance    *big.Int  // Optional: skip balance check
}
SourceChain
*Chain
required
Source chain configuration
DestChain
*Chain
required
Destination chain configuration
Amount
*big.Int
required
Transfer amount in USDC’s smallest unit (6 decimals)
RecipientAddress
common.Address
required
Recipient address on destination chain
TransferType
TransferType
Transfer mode: TransferTypeAuto, TransferTypeFast, or TransferTypeStandard
CachedBalance
*big.Int
Pre-fetched balance to skip redundant RPC call

Transfer Types

transfer.go:54
type TransferType string

const (
    TransferTypeFast     TransferType = "fast"     // ~8-20 seconds, with fee
    TransferTypeStandard TransferType = "standard" // ~13-19 minutes, no fee
    TransferTypeAuto     TransferType = "auto"     // Auto-select based on chain
)
TransferTypeAuto automatically selects Standard Transfer for instant finality chains (Avalanche, Polygon, etc.) and Fast Transfer for others.

Executing a Transfer

transfer.go:121
func (t *TransferOrchestrator) Execute(
    ctx context.Context,
    updates chan<- TransferUpdate,
) error
ctx
context.Context
required
Context for cancellation and timeout
updates
chan<- TransferUpdate
required
Channel for receiving progress updates
Example
ctx := context.Background()
updates := make(chan cctp.TransferUpdate)

// Execute transfer in goroutine
go func() {
    err := orchestrator.Execute(ctx, updates)
    if err != nil {
        log.Printf("Transfer failed: %v", err)
    }
}()

// Monitor progress
for update := range updates {
    switch update.Step {
    case cctp.StepCheckBalance:
        fmt.Printf("[Balance] %s\n", update.Message)
    case cctp.StepApproving:
        fmt.Printf("[Approve] %s (tx: %s)\n", update.Message, update.TxHash)
    case cctp.StepBurning:
        fmt.Printf("[Burn] %s (tx: %s)\n", update.Message, update.TxHash)
    case cctp.StepPollingAttestation:
        fmt.Printf("[Attestation] %s\n", update.Message)
    case cctp.StepMinting:
        fmt.Printf("[Mint] %s (tx: %s)\n", update.Message, update.TxHash)
    case cctp.StepComplete:
        fmt.Printf("[Success] %s\n", update.Message)
    case cctp.StepError:
        fmt.Printf("[Error] %v\n", update.Error)
    }
}

Transfer Steps

The orchestrator progresses through these steps:
transfer.go:36
const (
    StepCheckBalance       TransferStep = "checking_balance"
    StepCheckAllowance     TransferStep = "checking_allowance"
    StepApproving          TransferStep = "approving"
    StepApprovingWait      TransferStep = "approving_wait"
    StepBurning            TransferStep = "burning"
    StepBurningWait        TransferStep = "burning_wait"
    StepPollingAttestation TransferStep = "polling_attestation"
    StepMinting            TransferStep = "minting"
    StepMintingWait        TransferStep = "minting_wait"
    StepComplete           TransferStep = "complete"
    StepError              TransferStep = "error"
)
1

Balance Check

Verifies wallet has sufficient USDC balance on source chain.
transfer.go:133
balance, err := util.GetUSDCBalance(ctx, sourceClient, usdcAddress, wallet.Address)
2

Allowance Check

Checks if TokenMessengerV2 has approval to spend USDC.
transfer.go:163
allowance, err := bind.Call(usdcInstance, &bind.CallOpts{Context: ctx},
    ierc20.PackAllowance(wallet.Address, tokenMessengerAddr),
    ierc20.UnpackAllowance)
3

Approval (if needed)

If allowance is insufficient, approve TokenMessengerV2 to spend USDC.
transfer.go:182
approveTx, err := bind.Transact(usdcInstance, auth,
    ierc20.PackApprove(tokenMessengerAddr, amount))
4

Burn USDC

Call depositForBurn on TokenMessengerV2 to burn USDC on source chain.
transfer.go:311
burnData := tokenMessengerV2.PackDepositForBurn(
    amount,
    destDomain,
    recipientBytes32,
    burnToken,
    destinationCaller,
    maxFee,
    minFinalityThreshold,
)
5

Poll for Attestation

Wait for Circle’s attestation service to sign the message.
transfer.go:393
msg, err := irisClient.PollForAttestation(
    ctx,
    sourceDomain,
    burnTxHash,
    progressCallback,
)
6

Mint USDC

Call receiveMessage on MessageTransmitterV2 to mint USDC on destination chain.
transfer.go:434
mintData := messageTransmitterV2.PackReceiveMessage(
    messageBytes,
    attestationBytes,
)

Transfer Updates

transfer.go:62
type TransferUpdate struct {
    Step        TransferStep
    Message     string
    TxHash      string
    Error       error
    SourceChain *Chain
    DestChain   *Chain
}
Step
TransferStep
Current step in the transfer process
Message
string
Human-readable status message
TxHash
string
Transaction hash (when applicable)
Error
error
Error details if step failed

Fee Calculation

The orchestrator automatically queries and applies appropriate fees:
transfer.go:250
// Fetch fee information from Iris API
feesResp, err := irisClient.GetTransferFees(ctx, sourceDomain, destDomain)

// Find fee for selected finality threshold
var feeBps uint32 = 0
for _, feeInfo := range feesResp.Data {
    if feeInfo.FinalityThreshold == minFinalityThreshold {
        feeBps = feeInfo.MinimumFee
        break
    }
}

// Calculate maxFee with 1 bps safety buffer
maxFee = (amount * (feeBps + 1)) / 10000
The SDK adds a 1 basis point buffer to protect against fee fluctuations between query and execution.

Error Handling

The orchestrator performs validation before submitting transactions:
if balance.Cmp(amount) < 0 {
    err := fmt.Errorf("insufficient USDC balance: have %s, need %s",
        util.FormatUSDCBalance(balance),
        util.FormatUSDCBalance(amount))
    updates <- TransferUpdate{Step: StepError, Error: err}
    return err
}

Resource Management

Always close the orchestrator to release RPC connections:
transfer.go:485
func (t *TransferOrchestrator) Close()
orchestrator, err := cctp.NewTransferOrchestrator(wallet, params, irisURL)
if err != nil {
    log.Fatal(err)
}
defer orchestrator.Close()

// Execute transfer
err = orchestrator.Execute(ctx, updates)

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "os"
    
    "github.com/circlefin/cctp-go"
    "github.com/circlefin/cctp-go/internal/wallet"
    "github.com/ethereum/go-ethereum/common"
)

func main() {
    // Setup
    privateKey := os.Getenv("PRIVATE_KEY")
    wallet, _ := wallet.NewWallet(privateKey)
    
    sourceChain, _ := cctp.GetChainByName(cctp.Ethereum, false)
    destChain, _ := cctp.GetChainByName(cctp.Base, false)
    
    // Configure transfer
    params := &cctp.TransferParams{
        SourceChain:      sourceChain,
        DestChain:        destChain,
        Amount:           big.NewInt(10_000000), // 10 USDC
        RecipientAddress: common.HexToAddress("0x..."),
        TransferType:     cctp.TransferTypeFast,
    }
    
    // Create orchestrator
    orchestrator, err := cctp.NewTransferOrchestrator(
        wallet,
        params,
        "https://iris-api.circle.com",
    )
    if err != nil {
        log.Fatal(err)
    }
    defer orchestrator.Close()
    
    // Execute transfer
    ctx := context.Background()
    updates := make(chan cctp.TransferUpdate)
    
    go func() {
        err := orchestrator.Execute(ctx, updates)
        if err != nil {
            log.Printf("Transfer failed: %v", err)
        }
    }()
    
    // Monitor progress
    for update := range updates {
        if update.Step == cctp.StepComplete {
            fmt.Println("Transfer complete!")
            fmt.Printf("Mint tx: %s\n", update.TxHash)
            break
        }
        if update.Step == cctp.StepError {
            log.Fatal(update.Error)
        }
        fmt.Printf("%s: %s\n", update.Step, update.Message)
    }
}

Next Steps

Iris Client

Learn about attestation polling

Contract Bindings

Understand V2 contract interfaces

Build docs developers (and LLMs) love