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

# Message Lifecycle

> Complete flow of a cross-chain USDC transfer from source to destination chain

## Overview

A cross-chain USDC transfer via CCTP involves three main phases:

1. **Source Chain**: User initiates burn and message emission
2. **Attestation**: Off-chain service validates and signs the message
3. **Destination Chain**: User or relayer completes the transfer with minting

<Info>
  The examples below show a transfer from Ethereum (Domain 0) to Avalanche (Domain 1), but the flow is identical for any supported chain pair.
</Info>

## Phase 1: Source Chain - depositForBurn

### Step 1: User Approves USDC

Before calling `depositForBurn()`, users must approve the TokenMessenger contract to spend their USDC:

```javascript theme={null}
const usdcContract = new web3.eth.Contract(usdcAbi, USDC_ETH_ADDRESS);
const amount = web3.utils.toWei('100', 'mwei'); // 100 USDC (6 decimals)

await usdcContract.methods
  .approve(TOKEN_MESSENGER_ETH_ADDRESS, amount)
  .send({ from: userAddress });
```

### Step 2: Call depositForBurn

User initiates the cross-chain transfer:

```javascript theme={null}
const tokenMessengerContract = new web3.eth.Contract(
  tokenMessengerAbi,
  TOKEN_MESSENGER_ETH_ADDRESS
);

const AVAX_DESTINATION_DOMAIN = 1; // Avalanche domain ID
const mintRecipient = '0x...';
const mintRecipientBytes32 = web3.utils.padLeft(mintRecipient, 64);

const txReceipt = await tokenMessengerContract.methods
  .depositForBurn(
    amount,
    AVAX_DESTINATION_DOMAIN,
    mintRecipientBytes32,
    USDC_ETH_ADDRESS
  )
  .send({ from: userAddress });
```

### Step 3: Contract Execution Flow

Inside `TokenMessenger.depositForBurn()`, the following occurs:

```solidity theme={null}
function _depositForBurn(
    uint256 _amount,
    uint32 _destinationDomain,
    bytes32 _mintRecipient,
    address _burnToken,
    bytes32 _destinationCaller
) internal returns (uint64 nonce) {
    // 1. Validation
    require(_amount > 0, "Amount must be nonzero");
    require(_mintRecipient != bytes32(0), "Mint recipient must be nonzero");
    
    // 2. Get destination TokenMessenger
    bytes32 _destinationTokenMessenger = _getRemoteTokenMessenger(_destinationDomain);
    
    // 3. Transfer USDC from user to TokenMinter
    ITokenMinter _localMinter = _getLocalMinter();
    IMintBurnToken(_burnToken).transferFrom(
        msg.sender,
        address(_localMinter),
        _amount
    );
    
    // 4. Burn the tokens
    _localMinter.burn(_burnToken, _amount);
    
    // 5. Format burn message
    bytes memory _burnMessage = BurnMessage._formatMessage(
        messageBodyVersion,
        Message.addressToBytes32(_burnToken),
        _mintRecipient,
        _amount,
        Message.addressToBytes32(msg.sender)
    );
    
    // 6. Send message via MessageTransmitter
    uint64 _nonce = localMessageTransmitter.sendMessage(
        _destinationDomain,
        _destinationTokenMessenger,
        _burnMessage
    );
    
    // 7. Emit event
    emit DepositForBurn(
        _nonce,
        _burnToken,
        _amount,
        msg.sender,
        _mintRecipient,
        _destinationDomain,
        _destinationTokenMessenger,
        _destinationCaller
    );
    
    return _nonce;
}
```

### Step 4: MessageTransmitter Sends Message

`MessageTransmitter.sendMessage()` formats and emits the cross-chain message:

```solidity theme={null}
function _sendMessage(
    uint32 _destinationDomain,
    bytes32 _recipient,
    bytes32 _destinationCaller,
    bytes32 _sender,
    uint64 _nonce,
    bytes calldata _messageBody
) internal {
    // Validate message size
    require(
        _messageBody.length <= maxMessageBodySize,
        "Message body exceeds max size"
    );
    require(_recipient != bytes32(0), "Recipient must be nonzero");
    
    // Format complete message
    bytes memory _message = Message._formatMessage(
        version,              // Message format version
        localDomain,         // Source domain (e.g., 0 for Ethereum)
        _destinationDomain,  // Destination domain (e.g., 1 for Avalanche)
        _nonce,              // Unique nonce
        _sender,             // TokenMessenger address on source chain
        _recipient,          // TokenMessenger address on destination chain
        _destinationCaller,  // Optional: specific caller on destination
        _messageBody         // Burn message with amount/recipient details
    );
    
    // Emit event for attestation service to observe
    emit MessageSent(_message);
}
```

### Step 5: Extract Message from Event

The emitted `MessageSent` event contains the complete message bytes needed for attestation:

```javascript theme={null}
const transactionReceipt = await web3.eth.getTransactionReceipt(
  txReceipt.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];

// Hash the message for attestation lookup
const messageHash = web3.utils.keccak256(messageBytes);

console.log('Message Hash:', messageHash);
```

**Message Hash**: This hash uniquely identifies the transfer and is used to retrieve the attestation.

## Phase 2: Attestation Service

### Step 6: Attestation Service Observes Event

Circle's attestation service continuously monitors all supported chains for `MessageSent` events:

1. **Event Detection**: Service detects new `MessageSent` event
2. **Message Validation**:
   * Verifies transaction is confirmed
   * Checks burn amount is within limits
   * Validates destination domain is supported
   * Confirms source contract is legitimate
3. **Signature Generation**:
   * Hashes the complete message bytes
   * Signs the hash with authorized attester private keys
   * Combines signatures into attestation format

### Step 7: Poll for Attestation

Users fetch the attestation via Circle's API:

```javascript theme={null}
let attestationResponse = { status: 'pending' };

while (attestationResponse.status !== 'complete') {
  const response = await fetch(
    `https://iris-api.circle.com/attestations/${messageHash}`
  );
  attestationResponse = await response.json();
  
  if (attestationResponse.status === 'pending') {
    console.log('Waiting for attestation...');
    await new Promise(r => setTimeout(r, 2000)); // Wait 2 seconds
  }
}

const attestation = attestationResponse.attestation;
console.log('Attestation received:', attestation);
```

<Warning>
  **Rate Limiting**: The attestation service is rate-limited. Limit requests to less than 1 per second to avoid being blocked.
</Warning>

**Attestation Format**:

* Concatenated 65-byte ECDSA signatures (v + r + s)
* Number of signatures equals `signatureThreshold`
* Signatures must be in increasing order of attester address

**Response Format**:

```json theme={null}
{
  "status": "complete",
  "attestation": "0x1234567890abcdef..."
}
```

## Phase 3: Destination Chain - receiveMessage

### Step 8: Call receiveMessage

With the attestation, anyone can complete the transfer on the destination chain:

```javascript theme={null}
// Connect to Avalanche
const avaxWeb3 = new Web3(AVAX_RPC_URL);

const messageTransmitterContract = new avaxWeb3.eth.Contract(
  messageTransmitterAbi,
  MESSAGE_TRANSMITTER_AVAX_ADDRESS
);

// Submit message with attestation
const receiveTx = await messageTransmitterContract.methods
  .receiveMessage(messageBytes, attestation)
  .send({ 
    from: relayerAddress, // Can be any address
    gas: 500000 
  });

console.log('Transfer completed!', receiveTx.transactionHash);
```

### Step 9: MessageTransmitter Validates Message

`MessageTransmitter.receiveMessage()` performs extensive validation:

```solidity theme={null}
function receiveMessage(
    bytes calldata message,
    bytes calldata attestation
) external override whenNotPaused returns (bool) {
    // 1. Verify attestation signatures (m-of-n multisig)
    _verifyAttestationSignatures(message, attestation);
    
    bytes29 _msg = message.ref(0);
    
    // 2. Validate message format
    _msg._validateMessageFormat();
    
    // 3. Validate destination domain matches this chain
    require(
        _msg._destinationDomain() == localDomain,
        "Invalid destination domain"
    );
    
    // 4. Validate destination caller (if specified)
    if (_msg._destinationCaller() != bytes32(0)) {
        require(
            _msg._destinationCaller() == Message.addressToBytes32(msg.sender),
            "Invalid caller for message"
        );
    }
    
    // 5. Validate message version
    require(_msg._version() == version, "Invalid message version");
    
    // 6. Check nonce hasn't been used (replay protection)
    uint32 _sourceDomain = _msg._sourceDomain();
    uint64 _nonce = _msg._nonce();
    bytes32 _sourceAndNonce = _hashSourceAndNonce(_sourceDomain, _nonce);
    require(usedNonces[_sourceAndNonce] == 0, "Nonce already used");
    
    // 7. Mark nonce as used
    usedNonces[_sourceAndNonce] = 1;
    
    // 8. Forward to message handler (TokenMessenger)
    bytes32 _sender = _msg._sender();
    bytes memory _messageBody = _msg._messageBody().clone();
    require(
        IMessageHandler(Message.bytes32ToAddress(_msg._recipient()))
            .handleReceiveMessage(_sourceDomain, _sender, _messageBody),
        "handleReceiveMessage() failed"
    );
    
    // 9. Emit success event
    emit MessageReceived(
        msg.sender,
        _sourceDomain,
        _nonce,
        _sender,
        _messageBody
    );
    
    return true;
}
```

### Step 10: TokenMessenger Handles Message

`TokenMessenger.handleReceiveMessage()` processes the burn message:

```solidity theme={null}
function handleReceiveMessage(
    uint32 remoteDomain,
    bytes32 sender,
    bytes calldata messageBody
)
    external
    override
    onlyLocalMessageTransmitter           // Must be called by MessageTransmitter
    onlyRemoteTokenMessenger(remoteDomain, sender) // Sender must be registered
    returns (bool)
{
    // 1. Parse burn message
    bytes29 _msg = messageBody.ref(0);
    _msg._validateBurnMessageFormat();
    require(
        _msg._getVersion() == messageBodyVersion,
        "Invalid message body version"
    );
    
    // 2. Extract burn details
    bytes32 _mintRecipient = _msg._getMintRecipient();
    bytes32 _burnToken = _msg._getBurnToken();
    uint256 _amount = _msg._getAmount();
    
    // 3. Get local minter
    ITokenMinter _localMinter = _getLocalMinter();
    
    // 4. Mint tokens to recipient
    _mintAndWithdraw(
        address(_localMinter),
        remoteDomain,
        _burnToken,
        Message.bytes32ToAddress(_mintRecipient),
        _amount
    );
    
    return true;
}
```

### Step 11: TokenMinter Mints USDC

Finally, `TokenMinter.mint()` creates new USDC on the destination chain:

```solidity theme={null}
function mint(
    uint32 sourceDomain,
    bytes32 burnToken,
    address to,
    uint256 amount
)
    external
    override
    whenNotPaused
    onlyLocalTokenMessenger  // Only TokenMessenger can call
    returns (address mintToken)
{
    // 1. Lookup local token from remote token mapping
    address _mintToken = _getLocalToken(sourceDomain, burnToken);
    require(_mintToken != address(0), "Mint token not supported");
    
    // 2. Mint USDC to recipient
    IMintBurnToken _token = IMintBurnToken(_mintToken);
    require(_token.mint(to, amount), "Mint operation failed");
    
    return _mintToken;
}
```

**Result**: The recipient now has USDC on Avalanche, completing the cross-chain transfer!

## Complete Example

Here's a full working example combining all steps:

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

// Configure chains
const ethWeb3 = new Web3(process.env.ETH_RPC);
const avaxWeb3 = new Web3(process.env.AVAX_RPC);

async function transferUSDC() {
  // Step 1-2: Approve and deposit
  const amount = ethWeb3.utils.toWei('100', 'mwei');
  const usdcEth = new ethWeb3.eth.Contract(usdcAbi, USDC_ETH_ADDRESS);
  
  await usdcEth.methods
    .approve(TOKEN_MESSENGER_ETH, amount)
    .send({ from: userAddress });
  
  const tmEth = new ethWeb3.eth.Contract(tmAbi, TOKEN_MESSENGER_ETH);
  const burnTx = await tmEth.methods
    .depositForBurn(amount, 1, recipientBytes32, USDC_ETH_ADDRESS)
    .send({ from: userAddress });
  
  // Step 3-5: Extract message
  const receipt = await ethWeb3.eth.getTransactionReceipt(burnTx.transactionHash);
  const eventTopic = ethWeb3.utils.keccak256('MessageSent(bytes)');
  const log = receipt.logs.find(l => l.topics[0] === eventTopic);
  const messageBytes = ethWeb3.eth.abi.decodeParameters(['bytes'], log.data)[0];
  const messageHash = ethWeb3.utils.keccak256(messageBytes);
  
  console.log('Message hash:', messageHash);
  
  // Step 6-7: Get attestation
  let attestationResponse = { status: 'pending' };
  while (attestationResponse.status !== 'complete') {
    const response = await fetch(
      `https://iris-api.circle.com/attestations/${messageHash}`
    );
    attestationResponse = await response.json();
    if (attestationResponse.status === 'pending') {
      await new Promise(r => setTimeout(r, 2000));
    }
  }
  
  const attestation = attestationResponse.attestation;
  console.log('Attestation received');
  
  // Step 8-11: Complete on destination
  const mtAvax = new avaxWeb3.eth.Contract(mtAbi, MESSAGE_TRANSMITTER_AVAX);
  const receiveTx = await mtAvax.methods
    .receiveMessage(messageBytes, attestation)
    .send({ from: relayerAddress, gas: 500000 });
  
  console.log('Transfer complete!', receiveTx.transactionHash);
}

transferUSDC();
```

## Advanced: depositForBurnWithCaller

For permissioned receiving, use `depositForBurnWithCaller()`:

```solidity theme={null}
function depositForBurnWithCaller(
    uint256 amount,
    uint32 destinationDomain,
    bytes32 mintRecipient,
    address burnToken,
    bytes32 destinationCaller  // Only this address can call receiveMessage
) external returns (uint64 nonce)
```

**Use Cases**:

* Relayer-exclusive completion (prevent front-running)
* Smart contract-only receivers
* Conditional minting logic

<Warning>
  If `destinationCaller` is invalid or unable to call `receiveMessage()`, the funds will be **permanently stuck**. Only use this for advanced scenarios.
</Warning>

## Timing and Finality

**Attestation Delay**:

* Typical: 10-20 minutes after source chain confirmation
* Depends on: Chain finality requirements, attestation service processing

**Destination Confirmation**:

* Immediate once `receiveMessage()` transaction confirms
* Subject to destination chain's block time

**Total Time**: Usually 15-30 minutes for complete cross-chain transfer

## Error Handling

Common failure scenarios:

| Error                             | Cause                               | Solution                     |
| --------------------------------- | ----------------------------------- | ---------------------------- |
| "Transfer operation failed"       | Insufficient approval or balance    | Increase approval amount     |
| "No TokenMessenger for domain"    | Destination domain not supported    | Check supported domains      |
| "Nonce already used"              | Message already processed           | Transfer already completed   |
| "Invalid attestation length"      | Wrong number of signatures          | Fetch attestation again      |
| "Invalid signature order or dupe" | Malformed attestation               | Use official attestation API |
| "Mint token not supported"        | Token not configured on destination | Contact Circle support       |

## Next Steps

<CardGroup cols={2}>
  <Card title="Attestation" icon="signature" href="/concepts/attestation">
    Understand signature verification in detail
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Try the example integration yourself
  </Card>
</CardGroup>
