CCTP

Migrate from CCTP V1 (legacy) to the latest version

Complete migration guide for developers upgrading CCTP integrations

This guide provides a summary of the breaking changes when migrating from Cross-Chain Transfer Protocol (CCTP) V1 to V2. CCTP V2 introduces enhancements including Fast Transfer, Hooks features, and improved API endpoints, but requires updating your integration due to breaking changes.

Bridge Kit can help simplify your migration to CCTP V2. See the Migrating with Bridge Kit section and the Bridge Kit docs for more information.

Circle is deprecating CCTP V1 to focus on the newer version, which is upgradable and provides a faster, more secure, and more robust crosschain experience across a wider network of blockchains.

CCTP V2 is now referred to as CCTP (except in this document). The V1 version of CCTP is now CCTP V1 (legacy).

CCTP V1 will be phased out over the course of 10 months beginning in July 2026. CCTP V2 contracts are available on all CCTP V1 chains except for Aptos, Noble, and Sui. Aptos and Sui will be supported by V2 before the phase out begins. Circle is working with Noble and Cosmos ecosystem teams on an intermediate solution to route USDC flows to and from Noble.

You will not lose access to funds during the V1 phase out. All pending redemptions will remain available as CCTP V1 (legacy) begins its phase out. Circle will maintain minter allowances greater than the total of pending attestations, ensuring every redemption can be processed before V1 contracts are fully paused.

The deprecation process is designed to wind down activity gradually, message limits will tighten over time until no new burns can be initiated, bringing transfer volume to zero before contracts are fully paused.

In addition to this guide and Bridge Kit, you can contact the Circle team on the BuildOnCircle Discord for questions and migration support.

The latest version of CCTP introduces architectural changes that make it incompatible with V1 integrations. You must update your implementation to use the new contracts, APIs, and transfer speeds. Additionally, the overall flow of the protocol has been streamlined, which means you need to update your integration to use the new functions.

  • Contracts are deployed at different addresses than V1 contracts. You should update your integration to point to the new contract addresses.
  • Contract interfaces have changed. Importantly, the depositForBurn function now takes additional parameters. You should update your integration to use the new ABIs and contract calls.
  • CCTP now allows you to specify a transfer speed. The finalityThreshold parameter specifies whether the transfer should be a Fast Transfer or a Standard Transfer.
  • You no longer need to extract the message from the onchain transaction to fetch an attestation. Instead, you can call the new /v2/messages/{sourceDomainId} endpoint with the transaction hash to get the message and attestation in a single call.
  • API endpoints have changed. The new /v2/ endpoints have different functions than the old /v1/ endpoints. You should update your integration to use the new endpoints. Review the CCTP API reference for details on the changes to the CCTP offchain API.
  • Fees have been introduced. Fast Transfer has a variable fee based on the source chain. You should update your integration to account for the new fees.

Bridge Kit provides a simplified migration path by abstracting routine setup steps and standardizing bridging flows. This enables you to integrate bridging operations with minimal code.

  • No contract management: Bridge Kit handles contract addresses, ABIs, and function calls for you.
  • No attestation polling: Automatically retrieves attestations without manual API calls.
  • Built-in CCTP features: Access Fast Transfer and other capabilities through simple configuration.
  • Type-safe interface: Compatible with viem and ethers for safer development.
  • Fee collection: Optionally collect fees from transfers to monetize your application.

Replace manual contract calls and API polling with a single Bridge Kit method:

Typescript
import { BridgeKit } from "@circle-fin/bridge-kit";
import { createAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";

// Initialize Bridge Kit
const kit = new BridgeKit();

// Create adapter for your wallet
const adapter = createAdapterFromPrivateKey({
  privateKey: process.env.PRIVATE_KEY as string,
});

// Transfer USDC with Fast Transfer
const result = await kit.bridge({
  from: { adapter, chain: "Ethereum" },
  to: { adapter, chain: "Base" },
  amount: "100",
  config: {
    transferSpeed: "FAST", // Use Fast Transfer
    maxFee: "5000000", // Max 5 USDC fee (optional)
  },
});

// Result includes transaction details and explorer URLs
console.log("Transfer complete:", result.steps);

For more information, see the Bridge Kit quickstart guides.

CCTP uses new smart contracts with different names, addresses, and interfaces. You must update your integration to use the new contracts and their new function signatures.

All legacy contracts have V2 equivalents deployed at new addresses:

Legacy contractV2 contractDocumentation
TokenMessengerTokenMessengerV2V2 Interface
MessageTransmitterMessageTransmitterV2V2 Interface
TokenMinterTokenMinterV2V2 Addresses
MessageMessageV2V2 Addresses

Modified functions:

  • depositForBurn() now requires three additional parameters:
    • destinationCaller (bytes32) - Address that can call receiveMessage on destination
    • maxFee (uint256) - Maximum fee for Fast Transfer in units of burn token
    • minFinalityThreshold (uint32) - Minimum finality level (1000 for Fast, 2000 for Standard)

New functions:

  • depositForBurnWithHook() - Enables custom logic execution on destination chain via hook data
  • getMinFeeAmount() - Calculates minimum fee for Standard Transfer (on supported chains only)

Removed functions:

  • depositForBurnWithCaller() - Use destinationCaller parameter in depositForBurn() instead
  • replaceDepositForBurn() - No V2 equivalent available

Full contract source code is available on GitHub:

CCTP streamlines the API workflow by combining message retrieval and attestation into single calls, while introducing new endpoints for features like Fast Transfer monitoring and re-attestation.

The API eliminates the need to extract the message emitted by the onchain transaction:

Legacy workflow:

  1. Get the transaction receipt from the onchain transaction
  2. Find the MessageSent event in the transaction receipt
  3. Hash the message bytes emitted by the MessageSent event
  4. Call /v1/attestations/{messageHash} to get an attestation

V2 workflow:

  1. Call /v2/messages/{sourceDomainId} with transaction hash or nonce to get message, attestation, and decoded data
JavaScript
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";

// V1 requires multiple steps to extract message and get attestation
const burnTxHash = "0x1234..."; // Transaction hash from depositForBurn

// Step 1: Get the transaction receipt from the onchain transaction
const client = createPublicClient({
  chain: sepolia,
  transport: http(),
});
const transactionReceipt = await client.getTransactionReceipt({
  hash: burnTxHash,
});

// Step 2: Find the MessageSent event in the transaction receipt
const eventTopic = keccak256(toBytes("MessageSent(bytes)"));
const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic);
const messageBytes = decodeAbiParameters([{ type: "bytes" }], log.data)[0];

// Step 3: Hash the message bytes emitted by the MessageSent event
const messageHash = keccak256(messageBytes);

// Step 4: Call attestation API with the message hash
let attestationResponse = { status: "pending" };
while (attestationResponse.status !== "complete") {
  const response = await fetch(
    `https://iris-api-sandbox.circle.com/attestations/${messageHash}`,
  );
  attestationResponse = await response.json();
  await new Promise((r) => setTimeout(r, 2000));
}

const attestation = attestationResponse.attestation;

// Now you can use messageBytes and attestation to call receiveMessage
JavaScript
// V2 gets message and attestation in a single call
const sourceDomainId = 0; // Ethereum mainnet
const transactionHash = "0x1234...";

// Single step: Get message, attestation, and decoded data
const response = await fetch(
  `https://iris-api.circle.com/v2/messages/${sourceDomainId}?transactionHash=${transactionHash}`,
);
const data = await response.json();

// All data available in single response
const message = data.messages[0].message;
const attestation = data.messages[0].attestation;
const decodedMessage = data.messages[0].decodedMessage;

// Now you can use message and attestation to call receiveMessage
// You can also access decoded fields without manual parsing
console.log(`Amount: ${decodedMessage.decodedMessageBody.amount}`);
console.log(`Recipient: ${decodedMessage.decodedMessageBody.mintRecipient}`);
Legacy endpointV2 replacementMigration notes
GET /v1/attestations/{messageHash}GET /v2/messages/{sourceDomainId}?transactionHash={hash}Combined into messages endpoint with enhanced response
GET /v1/messages/{sourceDomainId}/{transactionHash}GET /v2/messages/{sourceDomainId}?transactionHash={hash}Enhanced with decoded data and attestation
GET /v1/publicKeysGET /v2/publicKeysMulti-version support, backward compatible

V2 introduces additional endpoints for advanced features:

EndpointPurposeUse case
POST /v2/reattest/{nonce}Re-attest messages for edge casesHandle expired Fast Transfer burns or finality changes
GET /v2/fastBurn/USDC/allowanceMonitor Fast Transfer allowanceCheck remaining Fast Transfer capacity in real-time
GET /v2/burn/USDC/fees/{sourceDomainId}/{destDomainId}Get current transfer feesCalculate fees before initiating transfers

V2 message responses now include the decoded message data and attestation:

JSON
{
  "messages": [
    {
      "attestation": "0xdc485fb2f9a8f68c871f4ca7386dee9086ff9d4387756990c9c4b9280338325252866861f9495dce3128cd524d525c44e8e7b731dedd3098a618dcc19c45be1e1c",
      "message": "0x00000000000000050000000300000000000194c2...",
      "eventNonce": "9682"
    }
  ]
}
JSON
{
  "messages": [
    {
      "message": "0x00000000000000050000000300000000000194c2...",
      "eventNonce": "9682",
      "attestation": "0x6edd90f4a0ad0212fd9fbbd5058a25aa8ee10ce77e4fc143567bbe73fb6e164f384a3e14d350c8a4fc50b781177297e03c16b304e8d7656391df0f59a75a271f1b",
      "decodedMessage": {
        "sourceDomain": "7",
        "destinationDomain": "5",
        "nonce": "569",
        "sender": "0xca9142d0b9804ef5e239d3bc1c7aa0d1c74e7350",
        "recipient": "0xb7317b4EFEa194a22bEB42506065D3772C2E95EF",
        "destinationCaller": "0xf2Edb1Ad445C6abb1260049AcDDCA9E84D7D8aaA",
        "messageBody": "0x00000000000000050000000300000000000194c2...",
        "decodedMessageBody": {
          "burnToken": "0x4Bc078D75390C0f5CCc3e7f59Ae2159557C5eb85",
          "mintRecipient": "0xb7317b4EFEa194a22bEB42506065D3772C2E95EF",
          "amount": "5000",
          "messageSender": "0xca9142d0b9804ef5e239d3bc1c7aa0d1c74e7350"
        }
      },
      "cctpVersion": "2",
      "status": "complete"
    }
  ]
}
Did this page help you?
© 2023-2025 Circle Technology Services, LLC. All rights reserved.