> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/circlefin/evm-cctp-contracts/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-Chain USDC Transfer

> Transfer USDC between chains using CCTP

## Overview

This guide demonstrates how to transfer USDC from Ethereum Sepolia testnet to Avalanche Fuji testnet using Circle's Cross-Chain Transfer Protocol (CCTP).

## Prerequisites

* Node.js installed
* Web3.js library
* Private keys for source and destination addresses
* USDC on the source chain (Sepolia testnet)

## Testnet Contract Addresses

### Ethereum Sepolia

```javascript theme={null}
const ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS = '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5';
const USDC_ETH_CONTRACT_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const ETH_MESSAGE_CONTRACT_ADDRESS = '0x80537e4e8bab73d21096baa3a8c813b45ca0b7c9';
```

### Avalanche Fuji

```javascript theme={null}
const AVAX_MESSAGE_TRANSMITTER_CONTRACT_ADDRESS = '0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79';
const AVAX_DESTINATION_DOMAIN = 1;
```

## Setup

<Steps>
  <Step title="Install Dependencies">
    ```bash theme={null}
    npm install web3 dotenv
    ```
  </Step>

  <Step title="Configure Environment">
    Create a `.env` file with the following variables:

    ```bash theme={null}
    ETH_TESTNET_RPC=<ETH_TESTNET_RPC_URL>
    AVAX_TESTNET_RPC=<AVAX_TESTNET_RPC_URL>
    ETH_PRIVATE_KEY=<ORIGINATING_ADDRESS_PRIVATE_KEY>
    AVAX_PRIVATE_KEY=<RECIPIENT_ADDRESS_PRIVATE_KEY>
    RECIPIENT_ADDRESS=<RECIPIENT_ADDRESS_FOR_AVAX>
    AMOUNT=<AMOUNT_TO_TRANSFER>
    ```
  </Step>
</Steps>

## Transfer Process

The cross-chain transfer involves 5 main steps:

### Step 1: Approve Token Messenger

Approve the TokenMessenger contract to withdraw USDC from your Ethereum address:

```javascript theme={null}
const { Web3 } = require('web3');
const web3 = new Web3(process.env.ETH_TESTNET_RPC);

// Initialize signer
const ethSigner = web3.eth.accounts.privateKeyToAccount(process.env.ETH_PRIVATE_KEY);
web3.eth.accounts.wallet.add(ethSigner);

// Initialize USDC contract
const usdcEthContract = new web3.eth.Contract(
  usdcAbi, 
  USDC_ETH_CONTRACT_ADDRESS, 
  { from: ethSigner.address }
);

// Approve
const approveTxGas = await usdcEthContract.methods
  .approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount)
  .estimateGas();

const approveTx = await usdcEthContract.methods
  .approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount)
  .send({ gas: approveTxGas });
```

### Step 2: Burn USDC on Source Chain

Call `depositForBurn` on the TokenMessenger contract:

```javascript theme={null}
// Initialize TokenMessenger contract
const ethTokenMessengerContract = new web3.eth.Contract(
  tokenMessengerAbi,
  ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS,
  { from: ethSigner.address }
);

// Convert recipient address to bytes32
const ethMessageContract = new web3.eth.Contract(
  messageAbi,
  ETH_MESSAGE_CONTRACT_ADDRESS,
  { from: ethSigner.address }
);

const destinationAddressInBytes32 = await ethMessageContract.methods
  .addressToBytes32(mintRecipient)
  .call();

// Burn USDC
const burnTxGas = await ethTokenMessengerContract.methods
  .depositForBurn(
    amount,
    AVAX_DESTINATION_DOMAIN,
    destinationAddressInBytes32,
    USDC_ETH_CONTRACT_ADDRESS
  )
  .estimateGas();

const burnTx = await ethTokenMessengerContract.methods
  .depositForBurn(
    amount,
    AVAX_DESTINATION_DOMAIN,
    destinationAddressInBytes32,
    USDC_ETH_CONTRACT_ADDRESS
  )
  .send({ gas: burnTxGas });
```

### Step 3: Retrieve Message Bytes

Extract the `messageBytes` from the `MessageSent` event:

```javascript theme={null}
const transactionReceipt = await web3.eth.getTransactionReceipt(
  burnTx.transactionHash
);

// Find MessageSent event
const eventTopic = web3.utils.keccak256('MessageSent(bytes)');
const log = transactionReceipt.logs.find((l) => l.topics[0] === eventTopic);

// Decode message bytes
const messageBytes = web3.eth.abi.decodeParameters(['bytes'], log.data)[0];
const messageHash = web3.utils.keccak256(messageBytes);

console.log(`MessageHash: ${messageHash}`);
```

### Step 4: Poll for Attestation

Request attestation from Circle's attestation service:

```javascript theme={null}
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 attestationSignature = attestationResponse.attestation;
console.log(`Attestation: ${attestationSignature}`);
```

<Warning>
  The attestation service is rate-limited. Please limit your requests to less than 1 per second.
</Warning>

### Step 5: Receive Message on Destination Chain

Call `receiveMessage` on the destination chain's MessageTransmitter:

```javascript theme={null}
// Switch to Avalanche network
web3.setProvider(process.env.AVAX_TESTNET_RPC);

// Initialize AVAX signer
const avaxSigner = web3.eth.accounts.privateKeyToAccount(
  process.env.AVAX_PRIVATE_KEY
);
web3.eth.accounts.wallet.add(avaxSigner);

// Initialize MessageTransmitter contract
const avaxMessageTransmitterContract = new web3.eth.Contract(
  messageTransmitterAbi,
  AVAX_MESSAGE_TRANSMITTER_CONTRACT_ADDRESS,
  { from: avaxSigner.address }
);

// Receive message
const receiveTxGas = await avaxMessageTransmitterContract.methods
  .receiveMessage(messageBytes, attestationSignature)
  .estimateGas();

const receiveTx = await avaxMessageTransmitterContract.methods
  .receiveMessage(messageBytes, attestationSignature)
  .send({ gas: receiveTxGas });

const receiveTxReceipt = await waitForTransaction(web3, receiveTx.transactionHash);
console.log('Transfer complete!');
```

## Complete Example

Here's the complete transfer script:

```javascript theme={null}
require('dotenv').config();
const { Web3 } = require('web3');

const main = async () => {
  const web3 = new Web3(process.env.ETH_TESTNET_RPC);
  
  // Setup signers
  const ethSigner = web3.eth.accounts.privateKeyToAccount(
    process.env.ETH_PRIVATE_KEY
  );
  web3.eth.accounts.wallet.add(ethSigner);
  
  const avaxSigner = web3.eth.accounts.privateKeyToAccount(
    process.env.AVAX_PRIVATE_KEY
  );
  web3.eth.accounts.wallet.add(avaxSigner);
  
  // Contract addresses and configuration
  const ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS = 
    '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5';
  const USDC_ETH_CONTRACT_ADDRESS = 
    '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
  const AVAX_MESSAGE_TRANSMITTER_CONTRACT_ADDRESS = 
    '0xa9fb1b3009dcb79e2fe346c16a604b8fa8ae0a79';
  const AVAX_DESTINATION_DOMAIN = 1;
  
  // Initialize contracts
  const ethTokenMessengerContract = new web3.eth.Contract(
    tokenMessengerAbi,
    ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS,
    { from: ethSigner.address }
  );
  
  const usdcEthContract = new web3.eth.Contract(
    usdcAbi,
    USDC_ETH_CONTRACT_ADDRESS,
    { from: ethSigner.address }
  );
  
  // Transfer amount
  const amount = process.env.AMOUNT;
  
  // Step 1: Approve
  console.log('Step 1: Approving USDC...');
  const approveTx = await usdcEthContract.methods
    .approve(ETH_TOKEN_MESSENGER_CONTRACT_ADDRESS, amount)
    .send();
  
  // Step 2: Burn
  console.log('Step 2: Burning USDC...');
  const burnTx = await ethTokenMessengerContract.methods
    .depositForBurn(
      amount,
      AVAX_DESTINATION_DOMAIN,
      destinationAddressInBytes32,
      USDC_ETH_CONTRACT_ADDRESS
    )
    .send();
  
  // Step 3: Get message hash
  console.log('Step 3: Retrieving message hash...');
  const messageHash = getMessageHashFromTx(burnTx);
  
  // Step 4: Fetch attestation
  console.log('Step 4: Polling for attestation...');
  const attestation = await pollForAttestation(messageHash);
  
  // Step 5: Receive on destination
  console.log('Step 5: Receiving USDC on destination chain...');
  web3.setProvider(process.env.AVAX_TESTNET_RPC);
  const receiveTx = await avaxMessageTransmitterContract.methods
    .receiveMessage(messageBytes, attestation)
    .send();
  
  console.log('Transfer complete!');
};

main();
```

## Running the Script

```bash theme={null}
node index.js
```

## Domain IDs

| Chain     | Domain ID |
| --------- | --------- |
| Ethereum  | 0         |
| Avalanche | 1         |
| Arbitrum  | 3         |
| Optimism  | 2         |
| Base      | 6         |
| Polygon   | 7         |

## Troubleshooting

### Approval Fails

* Check that you have sufficient USDC balance
* Verify the token messenger contract address
* Ensure your wallet has enough ETH for gas

### Burn Transaction Fails

* Verify approval was successful
* Check that amount is within burn limits
* Ensure destination domain is valid

### Attestation Not Available

* Wait longer (attestations typically take 10-20 seconds)
* Verify the message hash is correct
* Check that the burn transaction was successful

### Receive Message Fails

* Ensure attestation is complete
* Verify message hasn't already been received (check nonce)
* Check that destination contract address is correct

## Next Steps

* [Integrate CCTP into your application](/guides/integration)
* [Set up automated relaying](/guides/integration#automated-relaying)
* [Test your integration](/guides/testing)
