# Building CCIP Messages from EVM to TON
Source: https://docs.chain.link/ccip/tutorials/ton/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/api-reference/evm/v1.6.1/client#evm2anymessage) struct from the [`Client.sol`](/ccip/api-reference/evm/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/api-reference/evm/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/tutorials/ton/destination/arbitrary-messaging) — Learn how to send data messages from an EVM chain to a TON smart contract.

> **CAUTION: Educational Example Disclaimer**
>
> This page includes an educational example to use a Chainlink system, product, or service and is provided to
> demonstrate how to interact with Chainlink's systems, products, and services to integrate them into your own. This
> template is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, it has not been audited, and it may be
> missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the
> code in this example in a production environment without completing your own audits and application of best practices.
> Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs
> that are generated due to errors in code.