# Building CCIP Messages from EVM to Aptos
Source: https://docs.chain.link/ccip/v1/aptos/tutorials/destination/build-messages
Last Updated: 2025-09-03

> For the complete documentation index, see [llms.txt](/llms.txt).

## 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 programmable token transfers (data and tokens).

> **NOTE: Note**
>
> The code snippets below use the [ethers.js](https://www.npmjs.com/package/ethers) package for formatting and encoding
> data. Ensure it is installed in your project if you are following along with the code examples.

## CCIP Message Structure

CCIP messages from EVM are built using the [`EVM2AnyMessage`](/ccip/v1/evm/api-reference/v1.6.1/client#evm2anymessage) struct from the [`Client.sol`](/ccip/v1/evm/api-reference/v1.6.1/client) library. The `EVM2AnyMessage` struct is defined as follows:

```solidity
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 **programmable token transfers**: This must be the account address of your custom Aptos module that is intended to receive and process the message.

> **NOTE: Formatting Aptos Addresses**
>
> When providing an Aptos account address as the `receiver`, it must be represented as a 32-byte hex string (`bytes32`). Standard Aptos addresses are already 32 bytes and represented in hex, so you just need to ensure it includes the `0x` prefix.
>
> ```javascript
> // An Aptos address is already in the correct format.
> const aptosReceiverAddress = "0xca843279e3427144cead5e4d5999a3d0abf820035b3b4104284b792e2a936c86"
>
> // Use the hex string directly in the message.
> const message = {
>   // ...
>   receiver: aptosReceiverAddress,
>   // ...
> }
> ```

### data

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

> **TIP: Data Encoding**
>
> ```javascript
> import { toUtf8Bytes, hexlify } from "ethers"
>
> // For token-only transfers, data is empty.
> const tokenTransferData = "0x"
>
> // For arbitrary data, convert your payload to a hex string.
> const arbitraryMessageData = hexlify(toUtf8Bytes("Hello Aptos"))
> ```

### tokenAmounts

- **Definition**: An array of token addresses and amounts to transfer.
- **For data-only messages**: Must be an empty array (`[]`).
- **For token transfers** or **programmable token transfers**: Each entry specifies a token address and amount. **Note**: Check the [CCIP Directory](/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](/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`](/ccip/v1/evm/api-reference/v1.6.1/client#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 programmable 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.

> **NOTE: Encoding extraArgs for Aptos**
>
> You must ABI-encode a tuple containing the `gasLimit` and `allowOutOfOrderExecution` parameters, and then prepend the `0x181dcf10` tag. You can use the [`Client.encodeExtraArgsV2`](/ccip/v1/aptos/api-reference/v1.6.0/client#encode_generic_extra_args_v2) helper function for this.
>
> ```javascript
> import { ethers } from "ethers";
>
> function encodeExtraArgsV2(gasLimit: bigint, allowOutOfOrderExecution: boolean): string {
>     const abiCoder = ethers.AbiCoder.defaultAbiCoder();
>
>     // Encode the arguments as a tuple
>     const encodedArgs = abiCoder.encode(
>         ["tuple(uint256 gasLimit, bool allowOutOfOrderExecution)"],
>         [[gasLimit, allowOutOfOrderExecution]]
>     );
>
>     const GENERIC_EXTRA_ARGS_V2_TAG = "0x181dcf10";
>
>     // Concatenate the tag and the encoded arguments
>     return ethers.concat([GENERIC_EXTRA_ARGS_V2_TAG, encodedArgs]);
> }
>
> // Example for a simple transfer
> const extraArgs = encodeExtraArgsV2(0n, true);
> ```

## Implementation by Message Type

### Token Transfer

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

### Configuration

```
{
  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
  }
}
```

### Example

```javascript
const aptosRecipient = "0xabf820035b3b4104284b792e2a936c86ca843279e3427144cead5e4d5999a3d0";
const tokenAmount = ethers.parseUnits("0.001", 18); // 0.001 CCIP-BnM

const message = {
  receiver: aptosRecipient,
  data: "0x", // No data for a simple token transfer
  tokenAmounts: [{
    token: "0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05", // CCIP-BnM on Sepolia
    amount: tokenAmount
  }],
  feeToken: ethers.ZeroAddress, // Pay fee in native ETH
  extraArgs: encodeExtraArgsV2(0n, true)
};
```

> **NOTE: Key Requirements**
>
> - `receiver` is the final user's wallet address on Aptos.
> - `data` **MUST** be `0x`.
> - `extraArgs` should use `gasLimit: 0n` and `allowOutOfOrderExecution: true`.

### Arbitrary Messaging

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

### Configuration

```
{
  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
  }
}
```

### Example

```javascript
const yourAptosModule = "0xca843279e3427144cead5e4d5999a3d0abf820035b3b4104284b792e2a936c86";
const messageData = ethers.hexlify(ethers.toUtf8Bytes("Hello Aptos!"));

const message = {
  receiver: yourAptosModule,
  data: messageData,
  tokenAmounts: [],  // No tokens
  feeToken: "0x779877A7B0D9E8603169DdbD7836e478b4624789", // Pay fee in LINK
  extraArgs: encodeExtraArgsV2(0n, true)
};
```

> **NOTE: Key Requirements**
>
> - `receiver` must be the address of your custom Aptos module.
> - `tokenAmounts` **MUST** be an empty array.
> - `gasLimit` must be sufficient for all operations within your `ccip_receive` function and requires testing

### Programmable Token Transfer (Data and Tokens)

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

### Configuration

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

### Example

```javascript
const yourAptosModule = "0xca843279e3427144cead5e4d5999a3d0abf820035b3b4104284b792e2a936c86";
const messageData = ethers.hexlify(ethers.toUtf8Bytes("Deposit for user X"));

const message = {
  receiver: yourAptosModule,
  data: messageData,
  tokenAmounts: [{
    token: "0xFd57b4ddBf88a4e07fF4e34C487b99af2Fe82a05", // CCIP-BnM on Sepolia
    amount: ethers.parseUnits("1.5", 18)
  }],
  feeToken: "0x779877A7B0D9E8603169DdbD7836e478b4624789", // Pay fee in LINK
  extraArgs: encodeExtraArgsV2(200000n, true) // Gas determined from testing
};
```

> **NOTE: Key Requirements**
>
> - `receiver` must be the address of your custom Aptos module.
> - Your receiver module must implement logic to handle both the data payload and the incoming tokens.
> - `gasLimit` must be sufficient for all operations within your `ccip_receive` function and requires testing.

## Related Tutorials

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

- [Token Transfers: EVM to Aptos](/ccip/v1/aptos/tutorials/destination/token-transfers) - Learn how to implement token-only transfers from EVM chains to Aptos wallets.
- [Arbitrary Messaging: EVM to Aptos](/ccip/v1/aptos/tutorials/destination/arbitrary-messaging) - Learn how to send data messages from EVM chains to Aptos modules.
- [Programmable Token Transfers: EVM to Aptos](/ccip/v1/aptos/tutorials/destination/programmable-token-transfers) - Learn how to send both tokens and data in a single message to trigger module execution with token transfers on Aptos.

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

## Further Resources

- [**CCIP EVM Client API Reference**](/ccip/v1/evm/api-reference/v1.6.1/client): Complete technical details about the `EVM2AnyMessage` struct, helper functions, and message construction on EVM chains.
- **Aptos TS-SDK Docs**: For more information on building the receiving Aptos module, refer to the [official Aptos TS-SDK docs](https://aptos.dev/en/build/sdks/ts-sdk).

> **CAUTION: Educational Example Disclaimer**
>
> Please note, this page contains community examples only — these are not Chainlink products or services and are not
> supported or maintained by Chainlink. This code represents an example of using a Chainlink product or service, and is
> intended for demonstration and educational purposes only. It is provided "AS IS" and "AS AVAILABLE" without warranties
> of any kind, may not have been audited, and may omit checks or error handling. Each party intending to use this
> example code does so entirely at their own risk and must perform its own audits, security and code review, key
> management, and testing before any production deployment and ensure the operation and performance of such code matches
> expectations. Neither Chainlink Labs nor the Chainlink Foundation deploys, operates, monitors, maintains or endorses
> any deployment of this code. Note that this is not a Chainlink product, feature or service, and there are no
> commitments made with respect to the code, including compatibility with future Chainlink releases. You should not rely
> on this code without first conducting your own technical, engineering, and security review. This code is also outside
> the scope of any Chainlink bug bounty programs. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node
> operators are responsible for outcomes due to errors in this example or how it is deployed or operated, or liable for
> any resulting claims or damages. Use of the Chainlink Network is subject to the Chainlink Foundation [Terms of
> Service](https://chain.link/terms), which provides important information and disclosures. By using this code, you
> acknowledge and agree to these terms.