# Building CCIP Messages from EVM to TON
Source: https://docs.chain.link/ccip/v1/ton/tutorials/destination/build-messages
Last Updated: 2026-03-29

> 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 Sepolia, Arbitrum Sepolia) to the TON blockchain. It covers the message structure, required parameters, and implementation details for sending arbitrary data payloads to a Tolk smart contract on TON.

> **NOTE: Note**
>
> The code snippets below use the [ethers.js](https://www.npmjs.com/package/ethers) and
> [`@ton/core`](https://www.npmjs.com/package/@ton/core) packages for encoding and address parsing. Ensure both are
> 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 filename="Client.sol"
struct EVM2AnyMessage {
    bytes receiver;
    bytes data;
    EVMTokenAmount[] tokenAmounts;
    address feeToken;
    bytes extraArgs;
}
```

### receiver

- **Definition**: The encoded TON address of the smart contract on TON that will receive and process the CCIP message.
- **Encoding**: TON addresses must be encoded into a 36-byte [user-friendly format](https://docs.ton.org/foundations/addresses/formats#user-friendly-format): 1 byte for flags, 1 byte for `workchain_id`, 32 bytes for the `account_id` and 2 bytes for the [CRC16-CCITT](https://github.com/ton-blockchain/ton-kotlin/blob/main/crypto/src/crc16.kt) checksum of the preceding 34 bytes.

> **NOTE: Encoding a TON Address**
>
> TON user-friendly addresses (e.g., `EQ...`) are base64-encoded 36-byte sequences. Decode the address string
> directly to get the bytes expected by the EVM CCIP Router:
>
> ```typescript filename="scripts/evm2ton/sendMessage.ts"
> const tonContractAddr = "EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj"
> const receiver = new Uint8Array(Buffer.from(tonContractAddr, "base64"))
> ```

Warning: If the `receiver` address is not correctly encoded and the checksum doesn't match, the message will be directed to TON [Zero Account](https://docs.ton.org/foundations/whitepapers/tblkch#4-1-2-zero-account),

### data

- **Definition**: The raw bytes payload delivered to the `ccipReceive` entry point on the TON destination contract.
- **For arbitrary messaging**: Contains the custom data payload your receiver will process.
- **Encoding**: Pass raw bytes directly — do **not** use `hexlify`. Use `ethers.toUtf8Bytes()` for string payloads.

> **TIP: Data Encoding**
>
> ```typescript filename="scripts/evm2ton/sendMessage.ts"
> import { ethers } from "ethers"
>
> // Pass raw bytes — do NOT call hexlify on the result.
> const data = ethers.toUtf8Bytes("Hello TON!")
> ```

### tokenAmounts

- **Definition**: An array of token addresses and amounts to transfer.
- **Current support**: EVM-to-TON currently supports arbitrary messaging only. Set `tokenAmounts` to an empty array (`[]`).

### feeToken

- **Definition**: Specifies which token to use for paying CCIP fees.
- **Native gas token**: Use `ethers.ZeroAddress` (`address(0)`) to pay with the source chain's native token (e.g., ETH on Ethereum Sepolia).
- **LINK**: Specify the LINK token address on your source chain. See the [CCIP Directory](/ccip/directory/testnet) for token addresses.

## extraArgs

For TON-bound messages, the `extraArgs` parameter is a byte string composed of the 4-byte `GenericExtraArgsV2` tag (`0x181dcf10`) prepended to the ABI-encoded values `(uint256 gasLimit, bool allowOutOfOrderExecution)`. This format matches the [`GenericExtraArgsV2`](/ccip/v1/evm/api-reference/v1.6.1/client#genericextraargsv2) specification.

### gasLimit

- **Definition**: The amount of nanoGRAM reserved for execution on the TON destination chain.
- **Units**: This value is denominated in **nanoGRAM**, not EVM gas units. 1 GRAM = 1,000,000,000 nanoGRAM. A starting value of `100_000_000n` (0.1 GRAM)) covers most receive operations. Any unused nanoGRAM is returned to the contract.
- **Usage**: Increase this value if your receiver performs heavy computation. Determine the right amount through testing.

### allowOutOfOrderExecution

- **Definition**: A boolean required by the TON lane.
- **Usage**: Must be set to `true` when TON is the destination chain.

> **NOTE: Encoding extraArgs for TON**
>
> ABI-encode `gasLimit` (in nanoGRAM) and `allowOutOfOrderExecution`, then prepend the `0x181dcf10` tag:
>
> ```typescript filename="scripts/utils/utils.ts"
> import { ethers } from "ethers"
>
> function buildExtraArgsForTON(gasLimitNanoGRAM: bigint, allowOutOfOrderExecution: boolean): Uint8Array {
>   const encoded = ethers.AbiCoder.defaultAbiCoder().encode(
>     ["uint256", "bool"],
>     [gasLimitNanoGRAM, allowOutOfOrderExecution]
>   )
>   return ethers.getBytes(ethers.concat(["0x181dcf10", encoded]))
> }
>
> // 100_000_000n nanoGRAM = 0.1 GRAM reserved for execution
> const extraArgs = buildExtraArgsForTON(100_000_000n, true)
> ```

## Implementation by Message Type

### Arbitrary Messaging

Use this configuration when sending a data payload to a custom smart contract on TON.

### Configuration

```
{
  destinationChainSelector: TON_TESTNET_CHAIN_SELECTOR,
  receiver: encodedTONAddress,    // 36-byte encoded TON contract address
  tokenAmounts: [],               // Empty — token transfers not yet supported
  feeToken: feeTokenAddress,      // address(0) for native, LINK address for LINK
  data: rawBytesPayload,          // ethers.toUtf8Bytes(...) — do NOT hexlify
  extraArgs: {
    gasLimit: 100_000_000n,       // 0.1 GRAM in nanoGRAM
    allowOutOfOrderExecution: true
  }
}
```

### Pay with Native Token

```typescript filename="scripts/evm2ton/sendMessage.ts"
import { ethers } from "ethers"

const TON_TESTNET_CHAIN_SELECTOR = 1399300952838017768n

const tonContractAddr = "EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj"
const receiver = new Uint8Array(Buffer.from(tonContractAddr, 'base64'))
const data = ethers.toUtf8Bytes("Hello TON!")

const message = {
  receiver,
  data,
  tokenAmounts: [],
  feeToken: ethers.ZeroAddress,   // pay fee in native ETH
  extraArgs: buildExtraArgsForTON(100_000_000n, true)
}

const fee = await router.getFee(TON_TESTNET_CHAIN_SELECTOR, message)
const feeWithBuffer = (fee * 110n) / 100n  // add 10% buffer

const tx = await router.ccipSend(TON_TESTNET_CHAIN_SELECTOR, message, {
  value: feeWithBuffer
})
```

### Pay with LINK

> Get test LINK from the [Chainlink Faucet](https://faucets.chain.link).

```typescript filename="scripts/evm2ton/sendMessage.ts"
import { ethers } from "ethers"

const TON_TESTNET_CHAIN_SELECTOR = 1399300952838017768n
const LINK_ADDRESS = "0x779877A7B0D9E8603169DdbD7836e478b4624789"  // LINK on Sepolia

const tonContractAddr = "EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj"
const receiver = new Uint8Array(Buffer.from(tonContractAddr, 'base64'))
const data = ethers.toUtf8Bytes("Hello TON!")

const message = {
  receiver,
  data,
  tokenAmounts: [],
  feeToken: LINK_ADDRESS,         // pay fee in LINK
  extraArgs: buildExtraArgsForTON(100_000_000n, true)
}

const fee = await router.getFee(TON_TESTNET_CHAIN_SELECTOR, message)
const feeWithBuffer = (fee * 110n) / 100n  // add 10% buffer

await linkToken.approve(await router.getAddress(), feeWithBuffer)
const tx = await router.ccipSend(TON_TESTNET_CHAIN_SELECTOR, message)
```

> **NOTE: Key Requirements**
>
> - `receiver` must be the 36-byte encoded TON address of your destination smart contract — not a raw TON address string.
> - `tokenAmounts` **MUST** be an empty array.
> - `data` must be raw bytes. Use `ethers.toUtf8Bytes()` — do **not** call `hexlify`.
> - `gasLimit` is in **nanoGRAM** (`100_000_000n` = 0.1 GRAM is a recommended starting value).
> - `allowOutOfOrderExecution` **MUST** be `true`.

## Related Tutorials

To see these concepts in action with a step-by-step implementation guide, check out the following tutorial:

- [Arbitrary Messaging: EVM to TON](/ccip/v1/ton/tutorials/destination/arbitrary-messaging) — Learn how to send data messages from an EVM chain to a TON smart contract.

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