Building CCIP Messages from EVM to Aptos

Introduction

This guide explains how to construct CCIP Messages from Ethereum Virtual Machine (EVM) chains (e.g., Ethereum, Arbitrum, Base, etc.) to the Aptos blockchain. We'll cover the message structure, required parameters, and implementation details for different message types including token transfers, arbitrary data messaging, and programmatic token transfers (data and tokens).

CCIP Message Structure

CCIP messages from EVM are built using the EVM2AnyMessage struct from the Client.sol library. The EVM2AnyMessage struct is defined as follows:

struct EVM2AnyMessage {
    bytes receiver;
    bytes data;
    EVMTokenAmount[] tokenAmounts;
    address feeToken;
    bytes extraArgs;
}

receiver

  • Definition: The 32-byte account address of the receiver on Aptos.
  • For token-only transfers: This is the end user's wallet address on Aptos.
  • For arbitrary messaging or programmatic token transfers: This must be the account address of your custom Aptos module that is intended to receive and process the message.

data

  • Definition: Contains the payload to be delivered to the destination chain.
  • For token-only transfers: Must be empty (0x).
  • For arbitrary messaging or programmatic token transfers: Contains the custom data the receiver module will process.
  • Encoding requirement: Must be encoded as a hex string with a 0x prefix.

tokenAmounts

  • Definition: An array of token addresses and amounts to transfer.
  • For data-only messages: Must be an empty array ([]).
  • For token transfers or programmatic token transfers: Each entry specifies a token address and amount. Note: Check the CCIP Directory for the list of supported tokens on each lane.

feeToken

  • Definition: Specifies which token to use for paying CCIP fees.
  • For native gas token: Use address(0) (ethers.ZeroAddress) to specify the source chain's native gas token (e.g., ETH on Ethereum).
  • For ERC-20 tokens: Specify the ERC-20 token address for fee payment. Note: Check the CCIP Directory for the list of supported fee tokens on your source chain.

extraArgs

For Aptos-bound messages, the extraArgs parameter is a byte string composed of a 4-byte tag (0x181dcf10) prepended to the ABI-encoded tuple (uint256 gasLimit, bool allowOutOfOrderExecution). This format is specified in the GenericExtraArgsV2 reference.

gasLimit

  • Definition: Specifies the amount of gas units to allocate for execution on Aptos.
  • Usage: For simple token and data transfers as shown in the examples, 0 is a sufficient value. For complex programmatic token transfers involving significant computation in your receiving module, this value must be determined through testing.

allowOutOfOrderExecution

  • Definition: A boolean flag required for the message.
  • Usage: The provided example scripts consistently use true for this value. It must be set to true when Aptos is the destination chain.

Implementation by Message Type

Token Transfer

Use this configuration when sending only tokens from an EVM chain to a user's wallet on Aptos.

{
  destinationChainSelector: APTOS_CHAIN_SELECTOR,
  receiver: userAptosAddress, // The end user's 32-byte Aptos address
  tokenAmounts: [{ token: tokenAddress, amount: tokenAmount }],
  feeToken: feeTokenAddress, // or address(0) for native
  data: "0x", // Data must be empty
  extraArgs: {
    gasLimit: 0, // Gas limit is 0 for token-only transfers
    allowOutOfOrderExecution: true
  }
}

Arbitrary Messaging

Use this configuration when sending only a data payload to a custom module on Aptos.

{
  destinationChainSelector: APTOS_CHAIN_SELECTOR,
  receiver: yourAptosModuleAddress,
  tokenAmounts: [], // Empty for data-only
  feeToken: feeTokenAddress, // or address(0) for native
  data: customHexData,
  extraArgs: {
    gasLimit: determinedGasLimit, // Must be tested
    allowOutOfOrderExecution: true
  }
}

Programmatic Token Transfer (Data and Tokens)

Use this configuration when sending both tokens and a data payload to a custom module on Aptos.

{
  destinationChainSelector: APTOS_CHAIN_SELECTOR,
  receiver: yourAptosModuleAddress,
  tokenAmounts: [{ token: tokenAddress, amount: tokenAmount }],
  feeToken: feeTokenAddress,
  data: customHexData,
  extraArgs: {
    gasLimit: determinedGasLimit, // Must be tested
    allowOutOfOrderExecution: true
  }
}

To see these concepts in action with step-by-step implementation guides, check out the following tutorials:

These tutorials provide complete, working examples using the concepts covered in this guide.

Further Resources

Get the latest Chainlink content straight to your inbox.