A starter kit for working with Chainlink CCIP cross-chain messaging between TON and EVM chains.
Before you begin, ensure you have:
- Node.js 18+ and npm installed
- TON wallet with testnet funds and 24-word mnemonic (see TON Wallet Setup and Getting Test Funds)
- EVM wallet with Sepolia ETH (see Getting Test Funds)
- Basic understanding of CCIP messaging concepts
To interact with TON testnet, you need a wallet and its 24-word recovery phrase (mnemonic).
- Download TON Keeper on iOS or Android
- On the first run, you'll have to create a new wallet
- Copy the 24-word recovery phrase by selecting the Gear icon then Backup
- Select "Back Up Manually" and Continue to save the recovery phrase to a safe location. This is your
TON_MNEMONIC - Go back to the main Wallet screen, select the wallet drop-down, and choose "Add Wallet"
- Scroll to the bottom and select "Testnet Account"
- Type in the 24-word recovery phrase you saved earlier and select "Continue"
- Name the wallet "Testnet Wallet" and select Continue
- Select the Gear icon and select V4R2 as the wallet version (this starter kit uses V4R2)
- Save the
TON_MNEMONICto your.envfile in quotes (e.g.TON_MNEMONIC="...")
Why V4R2? V4R2 and W5 are different wallet contract versions on TON. The same mnemonic generates different addresses for each version. CCIP TON infrastructure is built for V4R2. You can add additional versions later via Settings > Active Address.
If you switch TON_RPC_URL to a toncenter endpoint (e.g. https://testnet.toncenter.com/api/v2/jsonRPC), you will need a free API key to avoid rate limits:
- Visit the TON Center API Bot: @tonapibot on Telegram
- Send
/startto the bot - Follow the instructions to get your free testnet API key
- Add it to your
.envfile:TON_CENTER_API_KEY="your_api_key_here"
The default RPC (https://ton-testnet.api.onfinality.io/public) does not require a key.
- Telegram Bot: @testgiver_ton_bot - Primary faucet for TON testnet
- Chainstack TON Faucet
git clone https://github.com/smartcontractkit/ton-starter-kit.git
cd ton-starter-kit
git submodule update --init --recursive
npm installcp .env.example .envEdit .env and fill in the required values:
EVM_PRIVATE_KEY- Your EVM wallet private keyTON_MNEMONIC- Your 24-word TON wallet mnemonic
All scripts load .env automatically — no need to source it.
This starter kit includes three receiver contracts for EVM → TON messaging:
| Contract | Script | When to use |
|---|---|---|
chainlink-ton/.../ccip/test/receiver/contract.tolk |
deploy:ton:receiver |
Full-featured Chainlink test receiver — includes ownership, upgradeability, and configurable behavior (accept/reject/ignore). Good for testing the CCIP lane end-to-end |
contracts/minimal_receiver.tolk |
deploy:ton:receiver:minimal |
Recommended starting point for your own receiver — protocol steps are written inline so each check is visible and easy to modify |
contracts/receiver_with_validateAndConfirm.tolk |
deploy:ton:receiver:validate-and-confirm |
Uses the Receiver library helper to handle all three protocol steps in a single call |
The three mandatory steps every TON CCIP receiver must implement:
- Accept
CCIPReceivemessages only from the authorized CCIP Router - Verify the attached value (gas limit) is sufficient — the Router needs at least 0.02 TON to process the confirmation, so
MIN_VALUEshould be set above that and account for your own execution costs - Send
Router_CCIPReceiveConfirmback to the Router so the protocol marks the message as delivered
Note:
receiver_with_validateAndConfirm.tolkuses the Receiver library helper which is still in early development. For complex receivers, preferminimal_receiver.tolkand implement the steps inline.
contracts/minimal_sender.tolk is a reusable on-chain relay for TON → EVM messaging. Deploy it once, then trigger CCIP sends by sending it a CCIPSender_RelayCCIPSend message. This pattern is useful when your application logic lives on-chain and needs to initiate cross-chain sends from within a smart contract rather than directly from a wallet.
This tutorial will guide you through deploying receiver contracts and sending cross-chain messages in both directions.
Before sending messages, you need to deploy receiver contracts on both chains.
npm run deploy:evm:receiver -- --evmChain sepoliaAfter deployment, copy the printed contract address — you'll pass it as --evmReceiver when sending messages.
Choose one of the receiver contracts described in TON Receiver Contracts:
# Full-featured Chainlink test receiver (ownership, upgradeability, configurable behavior)
npm run deploy:ton:receiver
# Minimal receiver (inline protocol steps — recommended starting point for custom receivers)
npm run deploy:ton:receiver:minimal
# Receiver using the validateAndConfirm library helper
npm run deploy:ton:receiver:validate-and-confirmRate limited? Follow the instructions for Get TON Center API Key
After deployment, copy the printed contract address — you'll pass it as --tonReceiver when sending messages.
Verify on TON Explorer
https://testnet.tonviewer.com/<TON_RECEIVER_ADDRESS>
npm run evm2ton:send -- --sourceChain sepolia --tonReceiver <TON_RECEIVER_ADDRESS> --msg "Hello TON from EVM" --feeToken nativeUse the source EVM transaction hash to track delivery on CCIP Explorer:
https://ccip.chain.link/
NOTE: It may take up to 15 minutes for the message to be finalized.
npm run utils:checkTON -- --sourceChain sepolia --tonReceiver <TON_RECEIVER_ADDRESS> --msg "<message>"Output includes:
- Message ID (in 0x-prefixed hex format)
- CCIP Explorer link for tracking the cross-chain message
- Verification status and timestamp
npm run ton2evm:send -- --destChain sepolia --evmReceiver <EVM_RECEIVER_ADDRESS> --msg "Hello EVM from TON" --feeToken nativeDeploy the sender contract once:
npm run deploy:ton:senderThen send through it:
npm run ton2evm:send -- --destChain sepolia --evmReceiver <EVM_RECEIVER_ADDRESS> --tonSender <TON_SENDER_ADDRESS> --msg "Hello EVM from TON" --feeToken nativeThe sender contract receives a CCIPSender_RelayCCIPSend message, forwards the pre-built Router_CCIPSend cell to the Router, and handles the ACK/NACK response. This is the pattern to use when your application logic lives on-chain.
Use the destination EVM transaction hash to track delivery on CCIP Explorer:
https://ccip.chain.link/
NOTE: It may take up to 15 minutes for the message to be finalized.
npm run utils:checkEVM -- --destChain sepolia --evmReceiver <EVM_RECEIVER_ADDRESS> --msg "<message>"Output includes:
- Message ID (in 0x-prefixed hex format)
- CCIP Explorer link for tracking the cross-chain message
- Event details including source chain verification
Why this matters: Unlike other chains, TON explorer shows transactions as successful even when CCIP messages fail due to insufficient fees. You must check the Router's response to know if your message actually succeeded.
When you send a message from TON → EVM, the Router responds with either:
- ACK (
0x78d0f21e) - Message successfully processed - NACK (
0x5a45d434) - Message failed (e.g., insufficient fee)
Common NACK reasons:
error: 1002 (0x3ea)- Insufficient CCIP fee. Increase fee amount insendMessage.ts- Other error codes indicate different router validation failures
For detailed information and error code meanings, see the documentation in scripts/ton-utils/routerResponses.ts header comments.
View recent CCIP send transactions with filtering and status indicators:
# Show last 20 transactions for your wallet
npm run utils:checkLastTxs
# Show only CCIP_SEND transactions
npm run utils:checkLastTxs -- --ccipSendOnly true
# Check transactions for a specific address (e.g. a sender contract)
npm run utils:checkLastTxs -- --address <TON_ADDRESS> --ccipSendOnly true
# Filter by a specific queryID
npm run utils:checkLastTxs -- --address <TON_ADDRESS> --queryId <QUERY_ID>
# Show last 50 transactions
npm run utils:checkLastTxs -- --limit 50Features:
- Scan any TON address via
--address(defaults to your wallet) - Filter by CCIP send transactions only
- Shows queryID, message ID, and Router response status
- Color-coded status (🟢 ACK for success, 🔴 NACK for failure)
- Displays CCIP Explorer URL for successful messages
- Options:
--address,--ccipSendOnly,--queryId,--limit