Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.injective.network/llms.txt

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

In this tutorial you will:
  • Learn how the CCTP burn-attest-mint protocol works
  • Burn USDC on a source chain to initiate a cross-chain transfer
  • Fetch the Circle attestation that authorises minting on the destination chain
  • Mint USDC on the destination chain to complete the transfer
What is CCTP on Injective? Cross-Chain Transfer Protocol (CCTP) allows you to transfer USDC 1:1 between Injective and other networks. USDC is available on Injective as a native stablecoin. It is available through CCTP. CCTP transfers USDC 1:1 between supported networks. The process is to:
  1. Burn USDC on the source chain.
  2. Receive an attestation.
  3. Mint an equivalent amount of USDC on the destination chain.
No wrapping or bridging is required.

Prerequisites

Before starting this tutorial, ensure you have the following installed:
ToolMinimum versionCheckInstall
Node.js22+node --versionnodejs.org
npm11+npm --versionIncluded with Node.js
You also need:

Get started

The accompanying code for this tutorial is available in the USDC CCTP demo repository. Clone the repository and install its dependencies:
git clone https://github.com/injective-dev/usdc-cctp-injective-demo
cd usdc-cctp-injective-demo
npm install
Start the development server:
npm run dev
Open http://localhost:5173 in your browser to view the dApp.

How does CCTP work?

CCTP transfers USDC across chains by burning it on the source chain, obtaining a signed attestation from Circle’s API, and minting an equivalent amount on the destination chain. CCTP transfers USDC across chains in three steps:
  1. Burn: The source chain’s TokenMessengerV2 contract burns the specified USDC amount. It then emits a MessageSent event containing the transfer details.
  2. Attest: Circle’s Iris API monitors on-chain events, and signs a message. It then produces an attestation once the burn is confirmed.
  3. Mint: The destination chain’s MessageTransmitterV2 contract verifies the attestation and mints an equivalent amount of USDC to the recipient address.
The demo application accompanying this tutorial automates steps two and three, polling Circle’s API until the attestation is ready, then unlocking the mint action.
The dApp implements each step with the following calls:
// Step 1 — src/components/CCTPTransfer.tsx
writeBurn({ functionName: 'depositForBurn', args: [amount, destDomain, mintRecipient, burnToken, ...] })

// Step 2 — src/hooks/useAttestation.ts
const { messages } = await fetch(`${CIRCLE_ATTESTATION_API}/${sourceDomain}?transactionHash=${txHash}`).then(r => r.json())

// Step 3 — src/components/CCTPTransfer.tsx
writeMint({ functionName: 'receiveMessage', args: [messages[0].message, messages[0].attestation] })
For the contract addresses used on Injective, refer to USDC on Injective.

Burning USDC on the source chain

With the dApp open at http://localhost:5173:
  1. Select Connect Wallet and approve the MetaMask connection prompt.
  2. Select the source chain (for example, Ethereum Sepolia) and the destination chain (Injective EVM Testnet), then enter the USDC amount to transfer.
  3. Select Approve USDC and confirm the approval transaction in MetaMask.
  4. Select Burn USDC and confirm the burn transaction.
The dApp will display the transaction hash and advance to the attestation step automatically once the burn transaction is confirmed on-chain.
When you select Burn USDC, the dApp calls depositForBurn on the TokenMessengerV2 contract:
// src/components/CCTPTransfer.tsx
writeBurn({
  address: messengerAddr,
  abi: tokenMessengerAbi,
  functionName: 'depositForBurn',
  args: [
    amountParsed,                                       // USDC amount (6 decimals)
    destDomain,                                         // 0 = Sepolia, 29 = Injective
    padHex(destAddress as `0x${string}`, { size: 32 }), // 32-byte-padded recipient
    usdcContract,                                       // USDC token address
    '0x0000000000000000000000000000000000000000000000000000000000000000',
    maxFee,                                             // relay fee (fast mode on Sepolia)
    minFinalityThreshold,                               // 1 = Sepolia, 2000 = Injective
  ],
})
The value of minFinalityThreshold should be set to:
  • A value ≤ 1000 produces a “fast” message, and
  • a value > 1000 produces a “standard” message.
On Sepolia, both standard and fast messages are supported, with standard messages taking approximately 15 to 19 minutes, and fast messages taking approximately 20 seconds.On Injective, only standard messages are supported, with standard messages taking approximately 0.65 seconds. Fast message are not supported as there is no need, thanks to Injective’s fast block time and instant finality.See Circle documentation on Block confirmation requirements and attestation timing for CCTP as the canonical reference on this.

What is a Circle attestation?

A Circle attestation is a cryptographic signature from Circle’s Iris API that proves a USDC burn occurred on the source chain. Attestations are used to authorise the corresponding mint on the destination chain. After a burn, Circle’s Iris API watches for the MessageSent event, on the source chain. Once the required number of block confirmations is reached, Circle signs the message and issues an attestation. Attestation times vary by source chain:
  • Ethereum Sepolia (standard mode): 15–20 minutes
  • Injective EVM Testnet: typically a few minutes
Transfers from Sepolia support a fast mode that reduces wait times significantly, but require a sufficient maxFee to be set. If the fee is too low, the transfer falls back to standard mode.

Waiting for attestation

This dApp polls the Iris API automatically. You do not need to leave the page. The UI will notify you when the attestation is ready.
The dApp fetches the attestation using the transaction hash from the burn step:
// src/hooks/useAttestation.ts
const url = `${CIRCLE_ATTESTATION_API}/${sourceDomain}?transactionHash=${txHash}`
const { messages } = await fetch(url).then(r => r.json())

if (messages[0]?.status === 'complete') {
  setResult({ message: messages[0].message, attestation: messages[0].attestation })
} else {
  setResult({ status: 'pending_confirmations' }) // poll again in 15 s
}

Minting USDC on the destination chain

Once the dApp shows “Attestation received”:
  1. When prompted, switch MetaMask to the destination chain.
  2. Select Mint USDC and confirm the transaction in MetaMask.
  3. Verify your USDC balance on the destination chain using the balance shown in the dApp, or by viewing your address in Injective Testnet Blockscout or Sepolia Etherscan.
When you select Mint USDC, the dApp calls receiveMessage on the MessageTransmitterV2 contract:
// src/components/CCTPTransfer.tsx
writeMint({
  address: transmitterAddr,
  abi: messageTransmitterAbi,
  functionName: 'receiveMessage',
  args: [attMessage, attestation], // message bytes + Circle's ECDSA signature
})

Next steps

Congratulations on completing your first cross-chain USDC transfer using CCTP on Injective! Here is what you learnt in this tutorial:
  • How the CCTP burn-attest-mint protocol transfers USDC 1:1 across chains, without wrapping or bridging
  • How to burn USDC on a source chain to initiate a cross-chain transfer
  • How to obtain a Circle attestation and use it to mint USDC on the destination chain
Now that you can transfer USDC across chains, you may want to explore:
Last modified on May 7, 2026