Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.enso.build/llms.txt

Use this file to discover all available pages before exploring further.

This workflow demonstrates bridging USDC across chains using Circle’s Cross-Chain Transfer Protocol (CCTP) v2. CCTP burns USDC on the source chain and mints native USDC on the destination chain — no wrapped or synthetic tokens involved. Every CCTP bundle action is submitted through Circle’s Forwarding Service, which auto-relays receiveMessage on the destination chain so the recipient doesn’t need destination-chain gas. Two finality modes are available:
  • Fast Transfer — sub-15s delivery, available only on a subset of source chains (Circle’s Iris API decides). Pays a small protocol fee on top of the forwarding fee.
  • Standard Transfer — waits for full source-chain finality. No protocol fee, only the forwarding fee.
Route Mechanics:
  • Burn USDC on the source chain via Circle’s TokenMessengerV2.depositForBurnWithHook (with the cctp-forward hook)
  • Circle attests the burn message off-chain via the Iris API
  • Circle’s Forwarding Service calls MessageTransmitterV2.receiveMessage on the destination, minting native USDC to the receiver. The forwarding fee covers destination gas.

User-Paid Fees

Both fees are deducted from the minted USDC amount on the destination — the user does not pay gas in any native token at any point.
FeeWhen it appliesSourceNotes
Protocol feeFast transfers onlyIris minimumFee (basis points, dynamic per route)Buffered by 20% per Circle’s recommendation. 0 on Standard.
Forward feeAlways (fast + standard)Iris forwardFee.{low,med,high} (USDC subunits, dynamic per route)Pays the Forwarding Service to relay receiveMessage. Default bracket: med.
To skip the protocol fee on a fast-supporting source chain, force Standard with cctpTransferType: "standard".
Which chains support Fast Transfer? See Circle’s supported blockchains & transfer types for the canonical matrix. Fast vs Standard availability per source chain comes straight from Circle’s Iris API at request time — no hardcoded list.
Use the GET /api/v1/cctp/bridge/tokenmessengerv2 endpoint to fetch the TokenMessengerV2 address for the source chain and pass it as primaryAddress.

Defaults (Fast Transfer, Base → Optimism)

When the source chain supports Fast Transfer (Iris reports minimumFee > 0 on the fast finality row), no extra args are needed — Fast is picked automatically and both fees are taken from the minted amount.
cctpFastBridge.ts
import { EnsoClient } from "@ensofinance/sdk";

// Chain IDs
const BASE_CHAIN_ID = 8453;
const OPTIMISM_CHAIN_ID = 10;

// Common addresses
const WALLET_ADDRESS = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";

// USDC on Base (native)
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const client = new EnsoClient({
  apiKey: process.env.ENSO_API_KEY!,
});

// Fetch TokenMessengerV2 for the source chain
const { address: cctpTokenMessenger } = await fetch(
  `https://api.enso.finance/api/v1/cctp/bridge/tokenmessengerv2?chainId=${BASE_CHAIN_ID}`,
  { headers: { Authorization: `Bearer ${process.env.ENSO_API_KEY}` } },
).then((r) => r.json());

const bundle = await client.getBundleData(
  {
    chainId: BASE_CHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
    receiver: WALLET_ADDRESS,
  },
  [
    {
      protocol: "cctp",
      action: "bridge",
      args: {
        primaryAddress: cctpTokenMessenger,
        destinationChainId: OPTIMISM_CHAIN_ID,
        tokenIn: USDC_BASE,
        amountIn: "5000000", // 5 USDC
        receiver: WALLET_ADDRESS,
        // cctpTransferType defaults to "fast" when available on source
        // cctpForwardFee defaults to "med" (always applied)
      },
    },
  ],
);

return bundle;

Standard Transfer (Skip the Protocol Fee)

Force Standard finality with cctpTransferType: "standard". This drops the protocol fee to 0 — only the forwarding fee is charged. Pick cctpForwardFee based on destination-chain conditions:
LevelWhen to use
lowCheap destination chain, no gas spikes expected
med (default)Normal operating conditions
highDestination chain is expensive or congested
cctpStandardTransfer.ts
const bundle = await client.getBundleData(
  {
    chainId: BASE_CHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
    receiver: WALLET_ADDRESS,
  },
  [
    {
      protocol: "cctp",
      action: "bridge",
      args: {
        primaryAddress: cctpTokenMessenger,
        destinationChainId: OPTIMISM_CHAIN_ID,
        tokenIn: USDC_BASE,
        amountIn: "5000000",
        receiver: WALLET_ADDRESS,
        cctpTransferType: "standard", // protocol fee = 0
        cctpForwardFee: "med",
      },
    },
  ],
);

Non-Fast Source Chain (Monad → Avalanche)

Some chains don’t support Fast Transfer as a source (Iris reports minimumFee: 0 on the fast finality row). For these, the bundle action silently falls back to Standard even if cctpTransferType: "fast" was requested — a warn is logged but the call succeeds:
cctpFallback.ts
const MONAD_CHAIN_ID = 143;
const AVALANCHE_CHAIN_ID = 43114;
const USDC_MONAD = "0x754704Bc059F8C67012fEd69BC8A327a5aafb603";

const bundle = await client.getBundleData(
  {
    chainId: MONAD_CHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
    receiver: WALLET_ADDRESS,
  },
  [
    {
      protocol: "cctp",
      action: "bridge",
      args: {
        primaryAddress: cctpTokenMessenger, // fetched for MONAD_CHAIN_ID
        destinationChainId: AVALANCHE_CHAIN_ID,
        tokenIn: USDC_MONAD,
        amountIn: "5000000",
        receiver: WALLET_ADDRESS,
        cctpTransferType: "fast", // → silently downgraded to "standard" + forwardFee "med"
      },
    },
  ],
);
To make the fallback explicit (and skip the warn log), pass cctpTransferType: "standard" directly.

Bridge Parameters

ParameterTypeRequiredDescription
primaryAddressstringYesTokenMessengerV2 contract for the source chain — fetch via GET /api/v1/cctp/bridge/tokenmessengerv2
destinationChainIdnumberYesTarget chain ID (must be CCTP-supported)
tokenInstringYesSource-chain USDC address (CCTP only supports USDC)
amountInstring | { useOutputOfCallAt }YesAmount in USDC subunits (6 decimals)
receiverstringYesRecipient on the destination chain
cctpTransferType"fast" | "standard"NoDefaults to fast when supported on the source; otherwise falls back to standard
cctpForwardFee"low" | "med" | "high"NoForwarding fee bracket. Defaults to med. Always applied (both fast and standard use the Forwarding Service)

Important Considerations

  • USDC only — CCTP supports exactly one token. Bridging anything else returns 400 Bad Request: CCTP only supports USDC transfers. The destination-chain USDC is the native Circle-issued token, not a wrapped variant.
  • No callbacks — CCTP bundle actions do not accept a callback array. Passing one returns 400 Bad Request: CCTP does not support callback/post-bridge actions. If you need a post-bridge swap, use CCIP, Stargate, or Relay.
  • Minimum amount — if the amount is too small to cover protocol fee + forwarding fee, the request fails with CCTP: amountIn too small. To reduce the floor: pass cctpForwardFee: "low", or pass cctpTransferType: "standard" (zeroes the protocol fee), or increase amountIn.
  • maxFee is consumed, not refunded — both fast and standard go through depositForBurnWithHook, so any unused maxFee headroom is taken by the Forwarding Service as destination priority fee. Don’t over-buffer.
  • Source-chain finality — Standard transfers wait for full source-chain finality (sub-second on Avalanche, up to 15-20 minutes on Ethereum) before Circle attests the burn. Fast transfers attest after a few block confirmations.
Fast Transfer Allowance: Circle enforces a global rolling allowance on Fast Transfers. When the allowance is exhausted, new Fast requests get downgraded to Standard until the budget refills. Use GET /api/v1/cctp/bridge/check on your tx hash — the cctpTransferType field reflects the executed finality (read from finalityThresholdExecuted), which may differ from what you requested.

Supported Chains

For the current canonical list of supported networks, domains, USDC addresses, and per-chain Fast/Standard support, see Circle’s supported blockchains & domains.

Manual Claim (Recovery Flow)

The Forwarding Service automatically calls receiveMessage on the destination for every transfer (fast and standard). Manual claiming is rarely needed — but if a transfer gets stuck (Forwarding Service delay, exhausted allowance, etc.) you can fetch a ready-to-submit claim transaction:
curl "https://api.enso.finance/api/v1/cctp/bridge/claim?chainId=8453&txHash=0x..." \
  -H "Authorization: Bearer $ENSO_API_KEY"
Response when claimable:
{
  "claimable": true,
  "destinationChainId": 10,
  "tx": {
    "to": "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
    "data": "0x57ecfd28...",
    "value": "0",
    "chainId": 10
  }
}
Submit the returned tx from any wallet on the destination chain to mint the USDC. If the nonce was already consumed (e.g., the Forwarding Service got there first), the response is { claimable: false, reason: "Already claimed (nonce used)" }.
The claim endpoint is rate-limited to 1 request per 10 seconds per API key (HTTP 429 on exceed).

Resources

Upstream Circle Iris API endpoints used by Enso:

Updated