> ## 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.

# CCTP Bridge

> Bridge USDC across chains using Circle's CCTP v2 — Fast or Standard finality, both auto-relayed by the Forwarding Service

export const date_0 = "2026-05-13"

This workflow demonstrates bridging USDC across chains using Circle's [Cross-Chain Transfer Protocol (CCTP) v2](https://developers.circle.com/cctp). 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](https://developers.circle.com/cctp/concepts/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.

```mermaid theme={null}
flowchart LR
    subgraph base ["🔵 Base"]
        USDC_BASE((USDC)) --> A1(( ))
    end

    A1 -.->|cctp<br/>burn + forward| B1

    subgraph optimism ["🔴 Optimism"]
        B1(( )) -->|mint| USDC_OP((USDC))
    end
```

**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.

| Fee              | When it applies          | Source                                                              | Notes                                                                          |
| ---------------- | ------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Protocol fee** | Fast transfers only      | Iris `minimumFee` (basis points, dynamic per route)                 | Buffered by 20% per Circle's recommendation. `0` on Standard.                  |
| **Forward fee**  | Always (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"`.

<Note>
  **Which chains support Fast Transfer?** See Circle's [supported blockchains &
  transfer
  types](https://developers.circle.com/cctp/concepts/supported-chains-and-domains#supported-blockchains)
  for the canonical matrix. Fast vs Standard availability per source chain comes
  straight from Circle's Iris API at request time — no hardcoded list.
</Note>

<Note>
  Use the [GET
  `/api/v1/cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status)
  endpoint to fetch the `TokenMessengerV2` address for the source chain and pass
  it as `primaryAddress`.
</Note>

## 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.

```typescript cctpFastBridge.ts theme={null}
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:

| Level           | When to use                                     |
| --------------- | ----------------------------------------------- |
| `low`           | Cheap destination chain, no gas spikes expected |
| `med` (default) | Normal operating conditions                     |
| `high`          | Destination chain is expensive or congested     |

```typescript cctpStandardTransfer.ts theme={null}
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:

```typescript cctpFallback.ts theme={null}
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

| Parameter            | Type                              | Required | Description                                                                                                                                       |
| -------------------- | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `primaryAddress`     | `string`                          | Yes      | `TokenMessengerV2` contract for the source chain — fetch via [`GET /api/v1/cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status) |
| `destinationChainId` | `number`                          | Yes      | Target chain ID (must be CCTP-supported)                                                                                                          |
| `tokenIn`            | `string`                          | Yes      | Source-chain USDC address (CCTP only supports USDC)                                                                                               |
| `amountIn`           | `string \| { useOutputOfCallAt }` | Yes      | Amount in USDC subunits (6 decimals)                                                                                                              |
| `receiver`           | `string`                          | Yes      | Recipient on the destination chain                                                                                                                |
| `cctpTransferType`   | `"fast" \| "standard"`            | No       | Defaults to `fast` when supported on the source; otherwise falls back to `standard`                                                               |
| `cctpForwardFee`     | `"low" \| "med" \| "high"`        | No       | Forwarding 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](/pages/use-cases/bridges/ccip-bridge-callback), [Stargate](/pages/use-cases/bridges/stargate-double-bridge), or [Relay](/pages/use-cases/bridges/relay-bridge-callback).
* **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.

<Warning>
  **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`](/pages/build/get-started/bridge-status) on your tx
  hash — the `cctpTransferType` field reflects the **executed** finality (read
  from `finalityThresholdExecuted`), which may differ from what you requested.
</Warning>

## 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](https://developers.circle.com/cctp/concepts/supported-chains-and-domains#supported-blockchains).

## 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:

```bash theme={null}
curl "https://api.enso.finance/api/v1/cctp/bridge/claim?chainId=8453&txHash=0x..." \
  -H "Authorization: Bearer $ENSO_API_KEY"
```

Response when claimable:

```json theme={null}
{
  "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)" }`.

<Warning>
  The claim endpoint is rate-limited to **1 request per 10 seconds** per API key
  (HTTP 429 on exceed).
</Warning>

## Resources

* [Bridge Transaction Status](/pages/build/get-started/bridge-status) — Polling `/api/v1/cctp/bridge/check`
* [Crosschain Routing Guide](/pages/build/get-started/crosschain-routing) — Complete guide to crosschain operations
* [Circle CCTP v2 Docs](https://developers.circle.com/cctp) — Protocol reference
* [Circle Forwarding Service](https://developers.circle.com/cctp/concepts/forwarding-service) — How the destination mint is auto-relayed
* [Supported blockchains & transfer types](https://developers.circle.com/cctp/concepts/supported-chains-and-domains#supported-blockchains) — Per-chain Fast/Standard support matrix

**Upstream Circle Iris API endpoints used by Enso:**

* [`GET /v2/burn/USDC/fees/{srcDomain}/{dstDomain}`](https://developers.circle.com/api-reference/cctp/all/get-burn-usdc-fees) — protocol fee + forwarding fee brackets (`?forward=true`)
* [`GET /v2/fastBurn/USDC/allowance`](https://developers.circle.com/api-reference/cctp/all/get-fast-burn-usdc-allowance) — global rolling Fast Transfer allowance
* [`GET /v2/messages/{srcDomain}`](https://developers.circle.com/api-reference/cctp/all/get-messages-v2) — attestation + decoded message; backs `/api/v1/cctp/bridge/check` and `/claim`

<div className="text-right text-xs gray-200 font-semibold w-full" style={{marginTop: '0'}}>
  <p style={{
        color: "#b2b2b2"  
    }}>Updated {date_0}</p>
</div>
