diff --git a/package-lock.json b/package-lock.json index 404e487f8a9..cfa462e044f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4826,21 +4826,22 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", + "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "license": "MIT", "optional": true, "dependencies": { @@ -4848,12 +4849,13 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -8427,20 +8429,22 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@next/env": { @@ -11885,9 +11889,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/assets-registry": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.1.tgz", - "integrity": "sha512-AT7/T6UwQqO39bt/4UL5EXvidmrddXrt0yJa7ENXndAv+8yBzMsZn6fyiax6+ERMt9GLzAECikv3lj22cn2wJA==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.84.1.tgz", + "integrity": "sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==", "license": "MIT", "optional": true, "peer": true, @@ -11896,19 +11900,19 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/codegen": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.1.tgz", - "integrity": "sha512-FpRxenonwH+c2a5X5DZMKUD7sCudHxB3eSQPgV9R+uxd28QWslyAWrpnJM/Az96AEksHnymDzEmzq2HLX5nb+g==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", + "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", - "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", "yargs": "^17.6.2" }, "engines": { @@ -11919,14 +11923,14 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/community-cli-plugin": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.1.tgz", - "integrity": "sha512-FqR1ftydr08PYlRbrDF06eRiiiGOK/hNmz5husv19sK6iN5nHj1SMaCIVjkH/a5vryxEddyFhU6PzO/uf4kOHg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.84.1.tgz", + "integrity": "sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==", "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@react-native/dev-middleware": "0.83.1", + "@react-native/dev-middleware": "0.84.1", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.3", @@ -11951,9 +11955,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/debugger-frontend": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.1.tgz", - "integrity": "sha512-01Rn3goubFvPjHXONooLmsW0FLxJDKIUJNOlOS0cPtmmTIx9YIjxhe/DxwHXGk7OnULd7yl3aYy7WlBsEd5Xmg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.84.1.tgz", + "integrity": "sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==", "license": "BSD-3-Clause", "optional": true, "peer": true, @@ -11962,14 +11966,15 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/debugger-shell": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.1.tgz", - "integrity": "sha512-d+0w446Hxth5OP/cBHSSxOEpbj13p2zToUy6e5e3tTERNJ8ueGlW7iGwGTrSymNDgXXFjErX+dY4P4/3WokPIQ==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.84.1.tgz", + "integrity": "sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==", "license": "MIT", "optional": true, "peer": true, "dependencies": { "cross-spawn": "^7.0.6", + "debug": "^4.4.0", "fb-dotslash": "0.5.8" }, "engines": { @@ -11977,16 +11982,16 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/dev-middleware": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.1.tgz", - "integrity": "sha512-QJaSfNRzj3Lp7MmlCRgSBlt1XZ38xaBNXypXAp/3H3OdFifnTZOeYOpFmcpjcXYnDqkxetuwZg8VL65SQhB8dg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.84.1.tgz", + "integrity": "sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==", "license": "MIT", "optional": true, "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.83.1", - "@react-native/debugger-shell": "0.83.1", + "@react-native/debugger-frontend": "0.84.1", + "@react-native/debugger-shell": "0.84.1", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -12002,9 +12007,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/gradle-plugin": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.1.tgz", - "integrity": "sha512-6ESDnwevp1CdvvxHNgXluil5OkqbjkJAkVy7SlpFsMGmVhrSxNAgD09SSRxMNdKsnLtzIvMsFCzyHLsU/S4PtQ==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.84.1.tgz", + "integrity": "sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==", "license": "MIT", "optional": true, "peer": true, @@ -12013,9 +12018,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/js-polyfills": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.1.tgz", - "integrity": "sha512-qgPpdWn/c5laA+3WoJ6Fak8uOm7CG50nBsLlPsF8kbT7rUHIVB9WaP6+GPsoKV/H15koW7jKuLRoNVT7c3Ht3w==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", + "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", "license": "MIT", "optional": true, "peer": true, @@ -12024,17 +12029,17 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/normalize-colors": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.1.tgz", - "integrity": "sha512-84feABbmeWo1kg81726UOlMKAhcQyFXYz2SjRKYkS78QmfhVDhJ2o/ps1VjhFfBz0i/scDwT1XNv9GwmRIghkg==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.1.tgz", + "integrity": "sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==", "license": "MIT", "optional": true, "peer": true }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native/virtualized-lists": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.1.tgz", - "integrity": "sha512-MdmoAbQUTOdicCocm5XAFDJWsswxk7hxa6ALnm6Y88p01HFML0W593hAn6qOt9q6IM1KbAcebtH6oOd4gcQy8w==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.84.1.tgz", + "integrity": "sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==", "license": "MIT", "optional": true, "peer": true, @@ -12143,33 +12148,10 @@ "node": ">=18" } }, - "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/hermes-compiler": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.0.tgz", - "integrity": "sha512-clxa193o+GYYwykWVFfpHduCATz8fR5jvU7ngXpfKHj+E9hr9vjLNtdLSEe8MUbObvVexV3wcyxQ00xTPIrB1Q==", + "version": "250829098.0.9", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.9.tgz", + "integrity": "sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==", "license": "MIT", "optional": true, "peer": true @@ -12278,9 +12260,9 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "optional": true, "peer": true, @@ -12289,21 +12271,21 @@ } }, "node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/react-native": { - "version": "0.83.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.1.tgz", - "integrity": "sha512-mL1q5HPq5cWseVhWRLl+Fwvi5z1UO+3vGOpjr+sHFwcUletPRZ5Kv+d0tUfqHmvi73/53NjlQqX1Pyn4GguUfA==", + "version": "0.84.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", + "integrity": "sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==", "license": "MIT", "optional": true, "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.83.1", - "@react-native/codegen": "0.83.1", - "@react-native/community-cli-plugin": "0.83.1", - "@react-native/gradle-plugin": "0.83.1", - "@react-native/js-polyfills": "0.83.1", - "@react-native/normalize-colors": "0.83.1", - "@react-native/virtualized-lists": "0.83.1", + "@react-native/assets-registry": "0.84.1", + "@react-native/codegen": "0.84.1", + "@react-native/community-cli-plugin": "0.84.1", + "@react-native/gradle-plugin": "0.84.1", + "@react-native/js-polyfills": "0.84.1", + "@react-native/normalize-colors": "0.84.1", + "@react-native/virtualized-lists": "0.84.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -12312,8 +12294,7 @@ "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", - "glob": "^7.1.1", - "hermes-compiler": "0.14.0", + "hermes-compiler": "250829098.0.9", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", @@ -12328,6 +12309,7 @@ "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" @@ -12340,7 +12322,7 @@ }, "peerDependencies": { "@types/react": "^19.1.1", - "react": "^19.2.0" + "react": "^19.2.3" }, "peerDependenciesMeta": { "@types/react": { @@ -18548,20 +18530,6 @@ "node": ">=4.5" } }, - "node_modules/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/bufio": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/bufio/-/bufio-1.2.3.tgz", @@ -37279,20 +37247,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/public/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg b/public/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg new file mode 100644 index 00000000000..2c4ef9df8c4 Binary files /dev/null and b/public/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg differ diff --git a/public/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg b/public/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg new file mode 100644 index 00000000000..b3d5aaa930c Binary files /dev/null and b/public/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg differ diff --git a/public/images/language-icons/tolk.svg b/public/images/language-icons/tolk.svg new file mode 100644 index 00000000000..e0e4cda3b74 --- /dev/null +++ b/public/images/language-icons/tolk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/github-card/GitHubCard.module.css b/src/components/github-card/GitHubCard.module.css new file mode 100644 index 00000000000..bbbbadf2d71 --- /dev/null +++ b/src/components/github-card/GitHubCard.module.css @@ -0,0 +1,94 @@ +.card { + display: flex; + align-items: center; + gap: var(--space-3x); + padding: var(--space-4x); + min-height: 96px; + border-radius: 10px; + border: 1px solid var(--color-border-primary); + background: var(--color-background-primary); + color: var(--color-text-primary); + text-decoration: none; + transition: + background-color 0.2s ease, + border-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.2s ease; +} + +.card:hover { + background: var(--theme-bg-hover); + border-color: var(--gray-300); + box-shadow: + 0 2px 6px rgba(0, 0, 0, 0.06), + 0 6px 20px rgba(0, 0, 0, 0.04); + transform: translateY(-1px); +} + +.card:focus-visible { + outline: 2px solid var(--theme-accent); + outline-offset: 2px; +} + +.icon { + flex-shrink: 0; + width: 44px; + height: 44px; + border-radius: 999px; + border: 1px solid var(--gray-200); + background: var(--gray-50); + display: flex; + align-items: center; + justify-content: center; +} + +.icon > :global(svg), +.icon > :global(img) { + width: 22px; + height: 22px; + display: block; +} + +.icon > :global(img) { + border-radius: 999px; +} + +.content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: var(--space-1x); +} + +.title { + font-size: 16px; + font-weight: var(--font-weight-medium); + color: var(--color-text-heading); + line-height: 1.4; + padding-bottom: 1px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.description { + font-size: 14px; + color: var(--color-text-secondary); + line-height: 1.4; + padding-bottom: 1px; +} + +.arrow { + flex-shrink: 0; + color: var(--color-text-secondary); + transition: + transform 0.15s ease, + color 0.15s ease; +} + +.card:hover .arrow, +.card:focus-visible .arrow { + transform: translateX(3px); + color: var(--color-text-primary); +} diff --git a/src/components/github-card/GitHubCard.tsx b/src/components/github-card/GitHubCard.tsx new file mode 100644 index 00000000000..a3d56a0b92b --- /dev/null +++ b/src/components/github-card/GitHubCard.tsx @@ -0,0 +1,33 @@ +import type { ReactNode } from "react" +import styles from "./GitHubCard.module.css" +import { clsx } from "~/lib/clsx/clsx.ts" +import { cardIcons, type CardIconName } from "./icons/index.js" + +type GitHubCardProps = { + title: string + href: string + icon?: ReactNode + iconName?: CardIconName | string + children?: ReactNode + className?: string +} & Omit, "href" | "className"> + +export function GitHubCard({ title, href, icon, iconName, children, className, ...props }: GitHubCardProps) { + const iconFromName = iconName ? cardIcons[iconName as CardIconName] : undefined + const resolvedIcon = icon ?? (iconFromName ? : null) + + return ( + + {resolvedIcon ? {resolvedIcon} : null} + + + {title} + + {children ? {children} : null} + + + + ) +} diff --git a/src/components/github-card/icons/github-svg-card.svg b/src/components/github-card/icons/github-svg-card.svg new file mode 100644 index 00000000000..a11334011a7 --- /dev/null +++ b/src/components/github-card/icons/github-svg-card.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/components/github-card/icons/index.ts b/src/components/github-card/icons/index.ts new file mode 100644 index 00000000000..60b24b836e5 --- /dev/null +++ b/src/components/github-card/icons/index.ts @@ -0,0 +1,7 @@ +import githubSvgCard from "./github-svg-card.svg" + +export const cardIcons = { + "github-svg-card": githubSvgCard, +} as const + +export type CardIconName = keyof typeof cardIcons diff --git a/src/components/github-card/index.ts b/src/components/github-card/index.ts new file mode 100644 index 00000000000..239899d7003 --- /dev/null +++ b/src/components/github-card/index.ts @@ -0,0 +1,3 @@ +export { GitHubCard } from "./GitHubCard.tsx" +export { cardIcons } from "./icons/index.js" +export type { CardIconName } from "./icons/index.js" diff --git a/src/components/index.ts b/src/components/index.ts index 64617eaf7b6..0738a698246 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -22,3 +22,4 @@ export { default as SideBySideCode } from "./SideBySideCode/SideBySideCode.astro export { default as CodeHighlightBlock } from "./CodeHighlightBlock/CodeHighlightBlock.astro" export { default as CodeHighlightBlockMulti } from "./CodeHighlightBlockMulti/CodeHighlightBlockMulti.astro" export { Callout } from "./Callout/Callout.tsx" +export { GitHubCard } from "./github-card/index.ts" diff --git a/src/config/chainTypes.ts b/src/config/chainTypes.ts index 02f4ce4ddfc..a4af3e010b1 100644 --- a/src/config/chainTypes.ts +++ b/src/config/chainTypes.ts @@ -84,9 +84,9 @@ export const CHAIN_TYPE_CONFIGS: Record = { /** * Chain types supported in CCIP - * Currently: EVM, Solana, Aptos + * Currently: EVM, Solana, Aptos, TON */ -export const CCIP_SUPPORTED_CHAINS: ChainType[] = ["evm", "solana", "aptos"] +export const CCIP_SUPPORTED_CHAINS: ChainType[] = ["evm", "solana", "aptos", "ton"] /** * Sections that support chain type filtering (OPT-IN) diff --git a/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap b/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap index ea4955575db..8c14bf25a76 100644 --- a/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap +++ b/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap @@ -183,13 +183,53 @@ exports[`CCIP Sidebar Configuration Snapshot should match the expected sidebar s "title": "Onchain Architecture", "url": "ccip/concepts/architecture/onchain/aptos", }, + { + "chainTypes": [ + "ton", + ], + "children": [ + { + "chainTypes": [ + "ton", + ], + "title": "Overview", + "url": "ccip/concepts/architecture/onchain/ton/overview", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Components", + "url": "ccip/concepts/architecture/onchain/ton/components", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Upgradability", + "url": "ccip/concepts/architecture/onchain/ton/upgradability", + }, + ], + "title": "Onchain Architecture", + "url": "ccip/concepts/architecture/onchain/ton", + }, { "title": "Offchain Architecture", "url": "ccip/concepts/architecture/offchain/overview", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "children": [ { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Overview", "url": "ccip/concepts/cross-chain-token/overview", }, @@ -279,40 +319,90 @@ exports[`CCIP Sidebar Configuration Snapshot should match the expected sidebar s "url": "ccip/concepts/manual-execution", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "children": [ { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Overview", "url": "ccip/concepts/rate-limit-management/overview", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "How Rate Limits Work", "url": "ccip/concepts/rate-limit-management/how-rate-limits-work", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Prerequisites and Permissions", "url": "ccip/concepts/rate-limit-management/prerequisites-and-permissions", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Inspect Current Rate Limits", "url": "ccip/concepts/rate-limit-management/inspect-current-rate-limits", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Token Units and Decimals", "url": "ccip/concepts/rate-limit-management/token-units-and-decimals", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Update Rate Limits", "url": "ccip/concepts/rate-limit-management/update-rate-limits", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Emergency Actions", "url": "ccip/concepts/rate-limit-management/emergency-actions", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Common Scenarios", "url": "ccip/concepts/rate-limit-management/common-scenarios", }, { + "chainTypes": [ + "evm", + "solana", + "aptos", + ], "title": "Executing with a Multisig", "url": "ccip/concepts/rate-limit-management/executing-with-a-multisig", }, @@ -340,6 +430,13 @@ exports[`CCIP Sidebar Configuration Snapshot should match the expected sidebar s "title": "Best Practices", "url": "ccip/concepts/best-practices/aptos", }, + { + "chainTypes": [ + "ton", + ], + "title": "Best Practices", + "url": "ccip/concepts/best-practices/ton", + }, ], "section": "Concepts", }, @@ -350,7 +447,7 @@ exports[`CCIP Sidebar Configuration Snapshot should match the expected sidebar s "url": "ccip/test-tokens", }, { - "title": "CCIP API, SDK & CLI", + "title": "Offchain", "url": "ccip/tutorials/offchain", }, { @@ -694,6 +791,73 @@ exports[`CCIP Sidebar Configuration Snapshot should match the expected sidebar s "title": "Cross-Chain Token (CCT)", "url": "ccip/tutorials/aptos/cross-chain-tokens", }, + { + "chainTypes": [ + "ton", + ], + "title": "Implement CCIP Receiver", + "url": "ccip/tutorials/ton/receivers", + }, + { + "chainTypes": [ + "ton", + ], + "children": [ + { + "chainTypes": [ + "ton", + ], + "title": "Build CCIP Messages", + "url": "ccip/tutorials/ton/source/build-messages", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Prerequisites", + "url": "ccip/tutorials/ton/source/prerequisites", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Arbitrary Messaging", + "url": "ccip/tutorials/ton/source/arbitrary-messaging", + }, + ], + "title": "Source", + "url": "ccip/tutorials/ton/source", + }, + { + "chainTypes": [ + "ton", + ], + "children": [ + { + "chainTypes": [ + "ton", + ], + "title": "Build CCIP Messages", + "url": "ccip/tutorials/ton/destination/build-messages", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Prerequisites", + "url": "ccip/tutorials/ton/destination/prerequisites", + }, + { + "chainTypes": [ + "ton", + ], + "title": "Arbitrary Messaging", + "url": "ccip/tutorials/ton/destination/arbitrary-messaging", + }, + ], + "title": "Destination", + "url": "ccip/tutorials/ton/destination", + }, ], "section": "Tutorials", }, diff --git a/src/config/sidebar/__tests__/ccip-dynamic.test.ts b/src/config/sidebar/__tests__/ccip-dynamic.test.ts index b0f73d2694d..b40469cbf03 100644 --- a/src/config/sidebar/__tests__/ccip-dynamic.test.ts +++ b/src/config/sidebar/__tests__/ccip-dynamic.test.ts @@ -43,7 +43,7 @@ describe("CCIP Sidebar Configuration", () => { if (item.chainTypes !== undefined) { expect(Array.isArray(item.chainTypes)).toBe(true) item.chainTypes.forEach((chainType: ChainType) => { - expect(["evm", "solana", "aptos"]).toContain(chainType) + expect(["evm", "solana", "aptos", "ton"]).toContain(chainType) }) } } @@ -56,7 +56,7 @@ describe("CCIP Sidebar Configuration", () => { }) it("should only contain valid chainTypes", () => { - const validChainTypes = ["evm", "solana", "aptos"] + const validChainTypes = ["evm", "solana", "aptos", "ton"] const checkChainTypes = (item: SectionContent) => { if (item.chainTypes) { diff --git a/src/config/sidebar/ccip-dynamic.ts b/src/config/sidebar/ccip-dynamic.ts index a140aae0347..ea07800d712 100644 --- a/src/config/sidebar/ccip-dynamic.ts +++ b/src/config/sidebar/ccip-dynamic.ts @@ -21,6 +21,7 @@ import evmCcipV162Contents from "./ccip/api-reference/evm/v1_6_2.json" with { ty import evmCcipV163Contents from "./ccip/api-reference/evm/v1_6_3.json" with { type: "json" } import aptosCcipV160Contents from "./ccip/api-reference/aptos/v1_6_0.json" with { type: "json" } import svmCcipV160Contents from "./ccip/api-reference/svm/v1_6_0.json" with { type: "json" } +import tonCcipV160Contents from "./ccip/api-reference/ton/v1_6_0.json" with { type: "json" } /** * CCIP Sidebar Content with Chain Type Annotations @@ -80,6 +81,11 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ url: "ccip/service-limits/aptos", chainTypes: ["aptos"], }, + // { + // title: "Service Limits", + // url: "ccip/service-limits/ton", + // chainTypes: ["ton"], + // }, { title: "Service Responsibility", url: "ccip/service-responsibility", @@ -177,6 +183,28 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ }, ], }, + { + title: "Onchain Architecture", + url: "ccip/concepts/architecture/onchain/ton", + chainTypes: ["ton"], + children: [ + { + title: "Overview", + url: "ccip/concepts/architecture/onchain/ton/overview", + chainTypes: ["ton"], + }, + { + title: "Components", + url: "ccip/concepts/architecture/onchain/ton/components", + chainTypes: ["ton"], + }, + { + title: "Upgradability", + url: "ccip/concepts/architecture/onchain/ton/upgradability", + chainTypes: ["ton"], + }, + ], + }, { title: "Offchain Architecture", url: "ccip/concepts/architecture/offchain/overview", @@ -185,11 +213,12 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ { title: "Cross-Chain Token (CCT)", url: "ccip/concepts/cross-chain-token", + chainTypes: ["evm", "solana", "aptos"], children: [ { title: "Overview", url: "ccip/concepts/cross-chain-token/overview", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Tokens", @@ -253,54 +282,55 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ url: "ccip/concepts/manual-execution", // Universal }, - // NEW: Rate Limit Management folder + children (Universal) + // Rate Limit Management folder + children (Universal except TON) { title: "Rate Limit Management", + chainTypes: ["evm", "solana", "aptos"], children: [ { title: "Overview", url: "ccip/concepts/rate-limit-management/overview", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "How Rate Limits Work", url: "ccip/concepts/rate-limit-management/how-rate-limits-work", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Prerequisites and Permissions", url: "ccip/concepts/rate-limit-management/prerequisites-and-permissions", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Inspect Current Rate Limits", url: "ccip/concepts/rate-limit-management/inspect-current-rate-limits", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Token Units and Decimals", url: "ccip/concepts/rate-limit-management/token-units-and-decimals", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Update Rate Limits", url: "ccip/concepts/rate-limit-management/update-rate-limits", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Emergency Actions", url: "ccip/concepts/rate-limit-management/emergency-actions", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Common Scenarios", url: "ccip/concepts/rate-limit-management/common-scenarios", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, { title: "Executing with a Multisig", url: "ccip/concepts/rate-limit-management/executing-with-a-multisig", - // Universal + chainTypes: ["evm", "solana", "aptos"], }, ], }, @@ -319,6 +349,11 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ url: "ccip/concepts/best-practices/aptos", chainTypes: ["aptos"], }, + { + title: "Best Practices", + url: "ccip/concepts/best-practices/ton", + chainTypes: ["ton"], + }, ], }, { @@ -330,7 +365,7 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ // Universal }, { - title: "CCIP API, SDK & CLI", + title: "Offchain", url: "ccip/tutorials/offchain", // Universal - supports all chain families }, @@ -577,6 +612,75 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ url: "ccip/tutorials/aptos/cross-chain-tokens", chainTypes: ["aptos"], }, + { + title: "Implement CCIP Receiver", + url: "ccip/tutorials/ton/receivers", + chainTypes: ["ton"], + }, + { + title: "Source", + url: "ccip/tutorials/ton/source", + chainTypes: ["ton"], + children: [ + { + title: "Build CCIP Messages", + url: "ccip/tutorials/ton/source/build-messages", + chainTypes: ["ton"], + }, + { + title: "Prerequisites", + url: "ccip/tutorials/ton/source/prerequisites", + chainTypes: ["ton"], + }, + // { + // title: "Token Transfers", + // url: "ccip/tutorials/ton/source/token-transfers", + // chainTypes: ["ton"], + // }, + { + title: "Arbitrary Messaging", + url: "ccip/tutorials/ton/source/arbitrary-messaging", + chainTypes: ["ton"], + }, + ], + }, + { + title: "Destination", + url: "ccip/tutorials/ton/destination", + chainTypes: ["ton"], + children: [ + { + title: "Build CCIP Messages", + url: "ccip/tutorials/ton/destination/build-messages", + chainTypes: ["ton"], + }, + { + title: "Prerequisites", + url: "ccip/tutorials/ton/destination/prerequisites", + chainTypes: ["ton"], + }, + // { + // title: "Token Transfers", + // url: "ccip/tutorials/ton/destination/token-transfers", + // chainTypes: ["ton"], + // }, + { + title: "Arbitrary Messaging", + url: "ccip/tutorials/ton/destination/arbitrary-messaging", + chainTypes: ["ton"], + }, + // { + // title: "Programmable Token Transfers", + // url: "ccip/tutorials/ton/destination/programmable-token-transfers", + // chainTypes: ["ton"], + // }, + ], + }, + // { + // title: "Cross-Chain Token (CCT)", + // url: "ccip/tutorials/ton/cross-chain-tokens", + // chainTypes: ["ton"], + // }, ], }, { @@ -689,6 +793,20 @@ export const CCIP_SIDEBAR_CONTENT: SectionEntry[] = [ }, ], }, + // { + // title: "TON Modules Interface", + // url: "ccip/api-reference/ton", + // chainTypes: ["ton"], + // children: [ + // { + // title: "v1.6.0", + // url: "ccip/api-reference/ton/v1.6.0", + // isCollapsible: true, + // chainTypes: ["ton"], + // children: tonCcipV160Contents, + // }, + // ], + // }, { title: "CCIP API, SDK & CLI", url: "https://docs.chain.link/ccip/tools", diff --git a/src/config/sidebar/ccip/api-reference/ton/v1_6_0.json b/src/config/sidebar/ccip/api-reference/ton/v1_6_0.json new file mode 100644 index 00000000000..82c7fc21d3b --- /dev/null +++ b/src/config/sidebar/ccip/api-reference/ton/v1_6_0.json @@ -0,0 +1,34 @@ +[ + { + "title": "Messages", + "url": "ccip/api-reference/ton/v1.6.0/messages" + }, + { + "title": "Router", + "url": "ccip/api-reference/ton/v1.6.0/router" + }, + { + "title": "OnRamp", + "url": "ccip/api-reference/ton/v1.6.0/onramp" + }, + { + "title": "OnRamp State Helper", + "url": "ccip/api-reference/ton/v1.6.0/onramp-state-helper" + }, + { + "title": "Receiver", + "url": "ccip/api-reference/ton/v1.6.0/receiver" + }, + { + "title": "Client", + "url": "ccip/api-reference/ton/v1.6.0/client" + }, + { + "title": "Errors", + "url": "ccip/api-reference/ton/v1.6.0/errors" + }, + { + "title": "Events", + "url": "ccip/api-reference/ton/v1.6.0/events" + } +] diff --git a/src/content/ccip/concepts/architecture/onchain/ton/components.mdx b/src/content/ccip/concepts/architecture/onchain/ton/components.mdx new file mode 100644 index 00000000000..924a8fb3f8d --- /dev/null +++ b/src/content/ccip/concepts/architecture/onchain/ton/components.mdx @@ -0,0 +1,192 @@ +--- +section: ccip +date: Last Modified +title: "Onchain Architecture - Components (TON)" +metadata: + description: "Explore the onchain components of CCIP architecture on TON: Sender/Receiver, Router, OnRamp, OffRamp, FeeQuoter, SendExecutor, MerkleRoot, and ReceiveExecutor contract details." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "ccip ton components, architecture, contracts, router contract, onramp contract, offramp contract, fee quoter, send executor, receive executor, merkle root, arbitrary message, rmn" + datePublished: "2026-04-07" + lastModified: "2026-04-07" + difficulty: "advanced" + estimatedTime: "35 minutes" +--- + +import { Aside } from "@components" + +This section provides more detail on the onchain components for TON. + + + +## Sender/Receiver + +**CCIP supports the following as senders and receivers on TON:** + +- A user-controlled wallet — A wallet account controlled by a private key. +- A smart contract — Onchain executable logic written in Tolk or FunC. + +**For arbitrary message passing, the supported sender/receiver combinations are:** + +- **Wallet → Smart Contract** + - **Supported Message Type**: Arbitrary data. + - **Reason**: The receiving contract implements a handler for `CCIPReceive` internal messages and can process the data payload. + +- **Smart Contract → Smart Contract** + - **Supported Message Type**: Arbitrary data. + - **Reason**: Both the sender and receiver are programmable contracts that can initiate and handle CCIP messages with arbitrary data. + +**A CCIP Message on TON can include:** + +- An arbitrary bytes data payload. + +**Sender Responsibilities:** + +- Prepare the `CCIPSend` message, including the encoded receiver address, data payload, destination chain selector, and extra arguments (e.g., gas limit for EVM destination chains). +- Retrieve a fee estimate using one of two methods: + - **Onchain**: Send a `Router_GetValidatedFee` message to the `Router`. The `Router` relays the request to the `OnRamp`, which forwards it to the `FeeQuoter`. The result is returned via `Router_MessageValidated` (or `Router_MessageValidationFailed`). + - **Offchain**: Call the `validatedFee` getter directly on the `FeeQuoter` contract. +- Send `CCIPSend` to the `Router` with sufficient TON attached to cover both the CCIP fee and internal message forwarding gas costs. The router checks that the attached value meets the minimum required (`Router_Costs.CCIPSend()`). + +**Receiver Responsibilities:** + +- **Security validation**: The receiver contract must verify that the sender of any `CCIPReceive` internal message is the registered CCIP `Router` contract. +- **Confirmation**: The receiver must reply with a `CCIPReceiveConfirm{execId}` message back to the `Router` after processing the message. This confirmation must carry sufficient TON to cover the confirmation trace costs (`Router_Costs.receiveConfirm()`). Without this confirmation the execution is treated as failed. +- **Failure handling**: If the receiver cannot process the message, it may allow `CCIPReceive` to bounce. The `Router` detects the bounce and forwards `CCIPReceiveBounced` to the `OffRamp`, initiating the failure path. + +## Router + +The `Router` contract is the single user-facing entry point for both sending and receiving CCIP messages on TON. + +**On the source chain (sending)**, the `Router`: + +- Accepts `CCIPSend` messages from senders. Requires a minimum TON value (`Router_Costs.CCIPSend()`); messages with insufficient value are rejected before any further processing. +- Checks the destination chain is not cursed (using its locally stored `CursedSubjects`) before forwarding to the `OnRamp`. +- Resolves the `OnRamp` address for the given destination chain selector from its `onRamps` map and forwards the `OnRamp_Send` message, carrying all remaining value. +- Receives `Router_MessageSent` from the `OnRamp` on success and delivers `CCIPSendACK` back to the original sender with unused TON. +- Receives `Router_MessageRejected` from the `OnRamp` on failure and delivers `CCIPSendNACK` to the sender. +- Accepts `Router_GetValidatedFee` from any caller, relays to the `OnRamp`, and returns the result (`Router_MessageValidated` or `Router_MessageValidationFailed`). + +**On the destination chain (receiving)**, the `Router`: + +- Accepts `Router_RouteMessage` from the registered `OffRamp` for the source chain and forwards `CCIPReceive` to the Receiver contract. +- Accepts `Router_CCIPReceiveConfirm` from any caller (permissionless at the Router level; authorization is enforced by the `ReceiveExecutor`) and forwards `OffRamp_CCIPReceiveConfirm` to the `OffRamp`. +- Detects a bounced `CCIPReceive` from the Receiver and forwards `OffRamp_CCIPReceiveBounced` to the `OffRamp`. + +**As the RMN Remote**, the `Router`: + +- Stores the set of cursed subjects (`CursedSubjects`) in its own storage, acting as the RMN Remote for the TON chain family. There is no separate RMN Remote contract. +- Accepts `Router_RMNRemoteCurse` and `Router_RMNRemoteUncurse` messages exclusively from the authorized RMN admin (`Ownable2Step`-gated). +- On each curse or un-curse operation, emits a `Cursed` or `Uncursed` chain log and updates the `forwardUpdates` set, then propagates `OffRamp_UpdateCursedSubjects` to every registered `OffRamp` address (one message per unique OffRamp). The `forwardUpdates` set is rebuilt automatically whenever the `onRamps`/`offRamps` maps change. +- Accepts permissionless `Router_RMNRemoteVerifyNotCursed` queries and synchronously replies with whether the given subject is cursed. + +## OnRamp + +The `OnRamp` contract processes all outbound CCIP messages on the source chain. It is not called directly by users; the `Router` is the only authorized caller of `OnRamp_Send`. + +When the `Router` forwards `OnRamp_Send`, the `OnRamp`: + +- **Allowlist check**: If `allowlistEnabled` is set for the destination chain configuration, verifies the sender address is in the `allowedSenders` map for that lane. Rejects the message if not. +- **SendExecutor deployment**: Generates a random `executorID` (`uint224` derived from a 256-bit random value), computes a deterministic address using `autoDeployAddress(executorID)`, and sends `Deployable_InitializeAndSend` to deploy and initialize the ephemeral `SendExecutor{id}` contract in a single message. All remaining value is carried to the `SendExecutor`. +- **ExecutorFinishedSuccessfully handling**: When the `SendExecutor` reports success, the `OnRamp` assigns the next sequence number for the destination chain, generates the `messageId` (as a hash over a metadata preimage, the sender address, sequence number, nonce, and message body), emits a `CCIPMessageSent` chain log containing the complete `TVM2AnyRampMessage`, and sends `Router_MessageSent` back to the `Router`. The collected CCIP fee is retained in the `OnRamp`'s balance. +- **ExecutorFinishedWithError handling**: Forwards a `Router_MessageRejected` notification to the `Router` with the error code. + +The `OnRamp` also handles fee withdrawal (`OnRamp_WithdrawFeeTokens`, permissionless), dynamic config updates (owner-only), destination chain config updates (owner-only), and allowlist updates (allowlist admin or owner). + +## SendExecutor\{id\} + +The `SendExecutor{id}` is an ephemeral contract deployed by the `OnRamp` once per outgoing message. Its address is deterministically derived from the `OnRamp`'s address and a randomly generated `id`, so the `OnRamp` can authenticate replies without storing any state. + +The `SendExecutor{id}`: + +- Is deployed and immediately receives `CCIPSendExecutor_Execute` (a self-message sent as part of `Deployable_InitializeAndSend`), which carries the full `OnRamp_Send` payload and the `FeeQuoter` address. +- Sends `FeeQuoter_GetValidatedFee` to the `FeeQuoter` and transitions to state `OnGoingFeeValidation`. +- On `FeeQuoter_MessageValidated`: verifies the sender attached enough value to cover the CCIP fee, then reports `OnRamp_ExecutorFinishedSuccessfully` to the `OnRamp` carrying the fee amount and returning any remaining balance. +- On `FeeQuoter_MessageValidationFailed` or a bounce of `FeeQuoter_GetValidatedFee`: reports `OnRamp_ExecutorFinishedWithError` to the `OnRamp`. +- Is destroyed after reporting (state `Finalized` is terminal; the contract returns its remaining balance to the `OnRamp`). + +## FeeQuoter + +The `FeeQuoter` contract is responsible for fee validation and price data storage. + +**On the source chain**, the `FeeQuoter`: + +- Accepts `FeeQuoter_GetValidatedFee` from the `SendExecutor{id}` (routed via the `OnRamp`). Validates the message payload (receiver encoding, gas limit, supported destination chain, fee token), computes the total fee from execution cost, premium multiplier, and data availability cost, and replies with either `FeeQuoter_MessageValidated` (including the fee amount) or `FeeQuoter_MessageValidationFailed`. +- Stores fee token premium multipliers, destination-chain fee configuration, and token prices with staleness enforcement. +- Accepts `FeeQuoter_UpdatePrices` from the `OffRamp` (forwarded from OCR `Commit` reports) to update token and gas prices onchain. + +The `FeeQuoter` is not directly accessible to end users for fee computation. The intended path is `Router_GetValidatedFee` → `OnRamp` → `FeeQuoter_GetValidatedFee`, or direct getter calls offchain. + +## OffRamp + +The `OffRamp` contract operates on the destination chain and is the primary contract that the offchain Committing and Executing DONs interact with. + +### Commit Phase + +1. **Report submission**: The Committing DON calls `OffRamp_Commit` with an OCR3 report containing a Merkle root (covering a batch of messages from a source chain) and optional token/gas price updates. + +1. **Fee check**: The `OffRamp` verifies the attached TON covers the minimum commit cost. Price-update-only reports require less TON than reports with Merkle roots. + +1. **Curse and source chain check**: The `OffRamp` checks its locally cached `CursedSubjects` and verifies the source chain is enabled in `sourceChainConfigs`. If either check fails, the report is rejected. + +1. **Sequence number validation**: Verifies the root's `minSeqNr` matches the next expected sequence number for the source chain and that the range covers at most 64 messages. If valid, advances `minSeqNr` to `maxSeqNr + 1`. + +1. **MerkleRoot deployment**: Deploys a `MerkleRoot{id}` contract by sending `Deployable_Initialize` to its deterministic address. The `MerkleRoot{id}` is initialized with the Merkle root value, the `OffRamp`'s address as owner, the current timestamp, and the sequence number range. + +1. **Price updates**: If the report includes price updates and their OCR sequence number is newer than `latestPriceSequenceNumber`, forwards `FeeQuoter_UpdatePrices` to the `FeeQuoter`. + +1. **OCR3 signature verification and event emission**: Calls `ocr3Base.transmit()` to verify the Committing DON's signatures and emit `OCR3Base_Transmitted`. Then emits `CommitReportAccepted`. + + + +### Execution Phase + +1. **Report submission**: The Executing DON calls `OffRamp_Execute` with an OCR3 execute report. The OCR3 transmit call for the Execute plugin uses an empty signatures cell because signature verification is disabled for the Execute plugin by configuration. + +1. **Curse check**: The `OffRamp` checks `CursedSubjects` immediately. If the source chain is cursed, the report is rejected before any further processing. + +1. **Source chain and message validation**: Verifies the source chain is enabled, computes the metadata hash from the source chain selector, destination chain selector, and the known `OnRamp` cross-chain address. Verifies the message's `destChainSelector` and `sourceChainSelector` match the stored configuration. Reconstructs the Merkle root from the message and the provided proof, then resolves the `MerkleRoot{id}` contract address from this root. + +1. **MerkleRoot validation**: Sends `MerkleRoot_Validate` to the `MerkleRoot{id}` contract. The `MerkleRoot{id}` checks the per-message execution state (must be `Untouched` for DON execution, or `Failure` for retries after the permissionless threshold), marks it `InProgress`, and replies with `OffRamp_ExecuteValidated`. If this was the last message in the root, the `MerkleRoot{id}` destroys itself and returns its balance. + +1. **ExecutionStateChanged emission**: Emits `ExecutionStateChanged` with state `InProgress`. + +1. **ReceiveExecutor deployment or reuse**: Deploys (or, on retry, reuses) a `ReceiveExecutor{id}` contract at a deterministic address derived from the `messageId` and `sourceChainSelector`. Sends `Deployable_Initialize` followed immediately by `ReceiveExecutor_InitExecute`. + +1. **Dispatch**: The `ReceiveExecutor{id}` replies with `OffRamp_DispatchValidated`. The `OffRamp` sends `Router_RouteMessage` to the `Router`, which forwards `CCIPReceive` to the Receiver contract. + +1. **Confirmation**: If the Receiver confirms (`Router_CCIPReceiveConfirm` → `OffRamp_CCIPReceiveConfirm`), the `OffRamp` sends `ReceiveExecutor_Confirm` to the `ReceiveExecutor{id}`. The `ReceiveExecutor{id}` verifies the confirm came from the `OffRamp` and that the confirming address matches the intended Receiver, transitions to `Success`, destroys itself, and sends `OffRamp_NotifySuccess`. The `OffRamp` calls `MerkleRoot_MarkState(Success)` and emits `ExecutionStateChanged` with state `Success`. + +1. **Failure**: If the Receiver bounces `CCIPReceive`, the `Router` sends `OffRamp_CCIPReceiveBounced`. The `OffRamp` sends `ReceiveExecutor_Bounced` to the `ReceiveExecutor{id}`, which transitions to `ExecuteFailed` and sends `OffRamp_NotifyFailure`. The `OffRamp` calls `MerkleRoot_MarkState(Failure)` and emits `ExecutionStateChanged` with state `Failure`. + +### Permissionless Manual Execution (Fallback) + +The `OffRamp` includes an `OffRamp_ManuallyExecute` handler that follows the same path as `OffRamp_Execute` but accepts a `gasOverride` field. Manual execution is permissionless and becomes available after `permissionlessExecutionThresholdSeconds` have elapsed since the `MerkleRoot{id}` was deployed. It can retry messages in `Failure` state or, after the threshold, messages still in `Untouched` state. For more information, read the [manual execution](/ccip/concepts/manual-execution) page. + +### Curse Propagation + +The `OffRamp` accepts `OffRamp_UpdateCursedSubjects` exclusively from the address stored as `rmnRouter` in its `deployables` storage (which is the `Router` address). When received, it overwrites the local `CursedSubjects` cache. This is the only mechanism by which the curse state is updated in the `OffRamp`. + +## MerkleRoot\{id\} + +The `MerkleRoot{id}` is an ephemeral contract deployed once per committed Merkle root by the `OffRamp`. Its address is deterministic, derived from the `OffRamp`'s address and the Merkle root value itself. + +- Tracks the execution state of every message in the committed root using a packed two-bit bitmap keyed by sequence number offset. Supports up to 64 messages per root. +- Per-message states are: `Untouched` (0), `InProgress` (1), `Success` (2), `Failure` (3). +- Accepts `MerkleRoot_Validate` from the `OffRamp`. Verifies the sender is the owning `OffRamp`, checks the message's current state (must be `Untouched` for normal DON execution; `Failure` or stale `Untouched` for manual retry), marks the message as `InProgress`, and replies `OffRamp_ExecuteValidated`. +- Accepts `MerkleRoot_MarkState` from the `OffRamp` to transition a message to `Success` or `Failure`. Increments a delivered count on `Success`. When delivered count equals the total expected messages, the `MerkleRoot{id}` freezes and returns its balance to the `OffRamp`. +- Is never directly redeployed; if a root's `MerkleRoot{id}` was destroyed (all messages succeeded) and a retry attempt arrives, the address will not exist and the message will bounce. + +## ReceiveExecutor\{id\} + +The `ReceiveExecutor{id}` is an ephemeral contract deployed (or reused on retry) by the `OffRamp` per incoming message. Its address is deterministic, derived from the `OffRamp`'s address, the `messageId`, and the `sourceChainSelector`. + +- Stores the message content, the owning `MerkleRoot{id}` address (`root`), the execution ID, the current per-message delivery state, and the last execution timestamp. +- Per-message delivery states are: `Untouched`, `Execute`, `ExecuteFailed`, `Success`. +- Accepts `ReceiveExecutor_InitExecute` from the `OffRamp`. Records the current timestamp, transitions to `Execute`, and replies `OffRamp_DispatchValidated` carrying all remaining value. +- Accepts `ReceiveExecutor_Confirm` from the `OffRamp`. Verifies the `OffRamp` is the sender and that the provided receiver address matches the intended receiver. Transitions to `Success`, destroys itself, and replies `OffRamp_NotifySuccess`. +- Accepts `ReceiveExecutor_Bounced` from the `OffRamp`. Verifies the `OffRamp` is the sender and that the bounced receiver matches the intended receiver. Transitions to `ExecuteFailed` and replies `OffRamp_NotifyFailure`. +- On retry (`ExecuteFailed` → `Execute`): the same `ReceiveExecutor{id}` contract at the same deterministic address is reused. The `OffRamp` sends `ReceiveExecutor_InitExecute` again, which updates the timestamp and re-triggers the dispatch. diff --git a/src/content/ccip/concepts/architecture/onchain/ton/index.mdx b/src/content/ccip/concepts/architecture/onchain/ton/index.mdx new file mode 100644 index 00000000000..29f31bb8e71 --- /dev/null +++ b/src/content/ccip/concepts/architecture/onchain/ton/index.mdx @@ -0,0 +1,20 @@ +--- +section: ccip +date: Last Modified +title: "CCIP Onchain Architecture (TON)" +metadata: + description: "Explore Chainlink CCIP's onchain architecture for TON. Learn about Tolk contracts, components, message lifecycle, and upgradability strategies." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "ccip ton architecture, router contract, onramp contract, offramp contract, fee quoter, rmn remote, send executor, merkle root, receive executor, arbitrary messaging" + datePublished: "2026-04-07" + lastModified: "2026-04-07" +isIndex: true +--- + +Chainlink CCIP's TON onchain architecture consists of specialized Tolk contracts deployed on both source and destination chains. These contracts work together with CCIP's offchain infrastructure to provide end-to-end cross-chain interoperability. + +- **[Overview](/ccip/concepts/architecture/onchain/ton/overview)**: Provides a high-level introduction to CCIP's TON-based onchain architecture, including a component diagram, key roles, and a detailed walk-through of a message's lifecycle from source to destination chain. + +- **[Components](/ccip/concepts/architecture/onchain/ton/components)**: Describes each architectural component in detail. + +- **[Upgradability](/ccip/concepts/architecture/onchain/ton/upgradability)**: Explains CCIP's approach to secure system evolution. diff --git a/src/content/ccip/concepts/architecture/onchain/ton/overview.mdx b/src/content/ccip/concepts/architecture/onchain/ton/overview.mdx new file mode 100644 index 00000000000..4912de6f0f7 --- /dev/null +++ b/src/content/ccip/concepts/architecture/onchain/ton/overview.mdx @@ -0,0 +1,115 @@ +--- +section: ccip +date: Last Modified +title: "Onchain Architecture - Overview (TON)" +metadata: + description: "Get an overview of Chainlink CCIP's onchain architecture for TON. Learn key contracts, roles, and the typical lifecycle of a cross-chain arbitrary message on TON." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "ccip ton overview, architecture, contracts, router contract, onramp contract, offramp contract, fee quoter, send executor, receive executor, merkle root, arbitrary message" + datePublished: "2026-04-07" + lastModified: "2026-04-07" + difficulty: "intermediate" + estimatedTime: "20 minutes" +--- + +import { Aside, ClickToZoom } from "@components" + +## TON as Source Chain + +On the TON source blockchain, a user or another contract initiates a cross-chain message by sending a `CCIPSend` internal message to the CCIP `Router` contract. The `Router` validates that sufficient TON is attached for gas and forwards the message to the appropriate `OnRamp` contract. The `OnRamp` assigns a unique message ID, deploys an ephemeral `SendExecutor{id}` contract to isolate fee validation, and instructs it to call the `FeeQuoter` for a price check. Once the `FeeQuoter` confirms the fee, the `SendExecutor` reports back to the `OnRamp`, which then assigns a sequence number and emits a chain log (`ExtOutLogMessage`) containing the full `CCIPSend` payload. This external log is observed offchain by the Committing DON, which relays the message to the destination blockchain. + +## TON as Destination Chain + +On the TON destination blockchain, the `OffRamp` contract receives two types of OCR3 reports from the offchain DONs. The Committing DON submits a `Commit` report containing a Merkle root of batched messages. The `OffRamp` verifies the OCR3 signatures, deploys an ephemeral `MerkleRoot{id}` contract to track per-message execution state for that root, and emits a `CommitReportAccepted` log. The Executing DON observes this log along with the original source chain messages, and for each message submits an `Execute` report. The `OffRamp` sends a `Validate` message to the relevant `MerkleRoot{id}` contract to verify the Merkle proof and mark the message as in-progress. The `MerkleRoot{id}` replies with `ExecuteValidated`, after which the `OffRamp` deploys (or reuses) an ephemeral `ReceiveExecutor{id}` to track the delivery state. The `ReceiveExecutor{id}` dispatches the message back to the `OffRamp`, which routes it via the `Router` to the Receiver contract. The Receiver processes the data and sends a `CCIPReceiveConfirm` back through the `Router` and `OffRamp`, which forwards a `Confirm` to the `ReceiveExecutor{id}`. On success, the `ReceiveExecutor{id}` reports `NotifySuccess` to the `OffRamp`, which emits an `ExecutionStateChanged` log marking the message as `Success`. + + + +## Key Components + + + +**Source Chain**: + + + +**Destination Chain**: + + + +| Component | Ownership | Role | +| --------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Sender | External (User/Contract) | A user wallet or a custom contract that initiates a cross-chain message on the source chain by sending `CCIPSend` to the `Router`. | +| Receiver | External (User/Contract) | A custom contract on the destination chain that receives `CCIPReceive` from the `Router`, sends back a `CCIPReceiveConfirm`, and processes the message data. | +| Router | CCIP | The primary entry point for both sending and receiving. On the source chain it validates the attached TON, checks for sufficient gas, and forwards to the `OnRamp`. On the destination chain it routes executed messages from the `OffRamp` to the Receiver and relays the confirmation back. The `Router` also acts as the RMN Remote: it stores the set of cursed subjects, accepts `Router_RMNRemoteCurse` and `Router_RMNRemoteUncurse` messages from the authorized RMN admin, and propagates the updated curse state to all registered `OffRamp` contracts via `OffRamp_UpdateCursedSubjects`. | +| OnRamp | CCIP | Processes outbound messages. Assigns a message ID, deploys a `SendExecutor{id}` for fee validation, assigns a sequence number on success, and emits a chain log (`CCIPSend`) observed by the offchain DONs. | +| `SendExecutor{id}` | CCIP | An ephemeral contract deployed by the `OnRamp` per outgoing message. It calls `FeeQuoter` for fee validation, verifies the sender attached enough TON to cover the CCIP fee, and reports the result back to the `OnRamp`. It is destroyed after reporting. | +| FeeQuoter | CCIP | Stores destination-chain fee configuration, fee token prices, and premium multipliers. Validates the `CCIPSend` request and returns the computed fee or a validation error to the `SendExecutor{id}`. | +| OffRamp | CCIP | Receives OCR3 `Commit` and `Execute` reports from the DONs. On commit, deploys a `MerkleRoot{id}` and emits `CommitReportAccepted`. On execute, validates each message via the `MerkleRoot{id}`, orchestrates delivery through a `ReceiveExecutor{id}` and the `Router`, and emits `ExecutionStateChanged`. Maintains a local cache of cursed subjects, updated by the `Router` whenever the RMN admin curses or un-curses a subject. This cache is checked synchronously during every `Commit` and `Execute` call. | +| `MerkleRoot{id}` | CCIP | An ephemeral contract deployed once per committed Merkle root. Stores the two-bit execution state for every message in the root, verifies Merkle proofs, and gates retries. Destroyed when all messages in the root reach `Success`. | +| `ReceiveExecutor{id}` | CCIP | An ephemeral contract deployed (or reused on retry) per incoming message. Holds the message content and its delivery state (`Untouched`, `Execute`, `ExecuteFailed`, `Success`). Dispatches delivery back to the `OffRamp` and tracks the confirmation or bounce result. | + +## Typical Lifecycle of a Message + +### Source Blockchain (TON) + +This outlines the process when initiating a CCIP transaction from the TON blockchain. + +1. **Preparation** + - The Sender prepares the CCIP message, including: + - **Receiver**: An encoded destination address (e.g., a 20-byte EVM address). + - **Data payload**: Arbitrary bytes to be delivered to the receiver contract. + - **Destination chain selector**: Identifies the target blockchain. + - **Extra arguments**: Destination-specific parameters such as a gas limit for EVM chains. + - The fee can be queried in two ways: + - **Onchain**: Send a `Router_GetValidatedFee` message to the `Router`, which relays to the `FeeQuoter` via the `OnRamp`. The `Router` responds with `Router_MessageValidated` (or `Router_MessageValidationFailed`) containing the computed fee. + - **Offchain**: Call the `validatedFee` getter directly on the `FeeQuoter` contract. + - The fee is paid in native TON. The Sender must attach the CCIP fee plus additional TON to cover internal message forwarding gas costs. + +1. **Sending** + - The Sender sends a `CCIPSend` internal message to the `Router`, attaching the required TON. + - The `Router` validates that sufficient TON is attached and forwards the message to the `OnRamp`. + - The `OnRamp` assigns a message ID, then deploys a `SendExecutor{id}` contract with a deterministic address derived from the `OnRamp` address and a randomized `id`. It sends an `Execute` message to the `SendExecutor{id}` containing the full `CCIPSend` payload. + - The `SendExecutor{id}` sends `GetValidatedFee` to the `FeeQuoter`. If the fee is valid and the sender attached sufficient TON, the `FeeQuoter` replies with `MessageValidated`. The `SendExecutor{id}` then destroys itself and reports `ExecutorFinishedSuccessfully` to the `OnRamp`, returning any remaining balance. + - If the fee is insufficient or validation fails, the `FeeQuoter` replies with `MessageValidationFailed` and the `SendExecutor{id}` reports `ExecutorFinishedWithError`, triggering a rejection path back through the `OnRamp` and `Router` to the Sender (`CCIPSendNACK`). + - On success, the `OnRamp` assigns a sequence number and emits a chain log (`ExtOutLogMessage`) containing the sequenced `CCIPSend` message. The Sender receives a `CCIPSendACK` with the unused TON. + +1. **Initial Offchain Processing** + - The Committing DON observes the `CCIPSend` chain log emitted by the `OnRamp` and begins batching messages offchain to prepare a Merkle root for commitment on the destination chain. + +### Destination Blockchain (TON) + +This outlines the process when TON is the receiving chain for a CCIP message. + +1. **Commit Phase** + - The Committing DON submits a `Commit` OCR3 report to the `OffRamp`, containing a Merkle root covering a batch of sequenced messages and any price updates. + - The `OffRamp` verifies the source chain is enabled and checks its locally cached cursed subjects. If the source chain is cursed, the report is rejected. + - The `OffRamp` deploys a `MerkleRoot{id}` contract initialized with the Merkle root and the expected message sequence range, and advances the next expected sequence number. + - If the report includes price updates that are newer than the latest recorded OCR sequence number, the `OffRamp` forwards them to the `FeeQuoter`. + - The `OffRamp` verifies the Committing DON's OCR3 signatures and emits an `OCR3Base_Transmitted` log. Signature verification is enabled for the Commit plugin and required. RMN BLS signature verification over blessed Merkle roots is not performed on TON; curse protection is provided exclusively through the `Router`'s curse mechanism. + - The `OffRamp` emits a `CommitReportAccepted` log, signaling that the root is stored and messages are ready for execution. + +1. **Secondary Offchain Processing** + - The Executing DON observes the `CommitReportAccepted` log on the destination chain and the original source chain logs. For each message in the committed batch, it computes the Merkle proof and submits a separate `Execute` OCR3 report. + +1. **Execution Phase** + - The `OffRamp` receives the `Execute` report and immediately checks its locally cached cursed subjects. If the source chain is cursed, execution is rejected before any further processing. + - The `OffRamp` verifies the source chain is enabled and computes a metadata hash from the source chain selector, destination chain selector, and the known OnRamp address. This hash is used together with the Merkle proof to reconstruct and verify the committed root, ensuring the message has not been tampered with. The Execute plugin does not perform OCR3 signature verification; signature verification is disabled for the Execute plugin by configuration. + - The `OffRamp` sends a `Validate` message to the `MerkleRoot{id}` contract corresponding to this root. + - The `MerkleRoot{id}` verifies the Merkle proof, checks that the message has not already been executed (state must be `Untouched`), marks it as `InProgress`, and replies with `ExecuteValidated`. If this is the last message in the root, the `MerkleRoot{id}` destroys itself. + - The `OffRamp` emits `ExecutionStateChanged` with state `InProgress`, then deploys (or reuses on retry) a `ReceiveExecutor{id}` for the message. It sends `InitExecute` to the `ReceiveExecutor{id}`. + - The `ReceiveExecutor{id}` sends `DispatchValidated` back to the `OffRamp` and sets its state to `Execute`. + - The `OffRamp` sends `RouteMessage` to the `Router`, which forwards a `CCIPReceive` internal message to the Receiver contract. The `CCIPReceive` payload includes the `execId`, `messageId`, source chain selector, sender address, and data. + - The Receiver verifies that the sender is the `Router`, sends back `CCIPReceiveConfirm{execId}` to the `Router`, and processes the message data. + - The `Router` forwards `CCIPReceiveConfirm` to the `OffRamp`, which sends `Confirm` to the `ReceiveExecutor{id}`. + - The `ReceiveExecutor{id}` verifies the confirm came from the `OffRamp` and that the original sender matches the Receiver. It sets its state to `Success`, destroys itself, and replies `NotifySuccess` to the `OffRamp`. + - The `OffRamp` emits `ExecutionStateChanged` with state `Success`. + +1. **Failure and Retry** + - If the Receiver bounces the `CCIPReceive` message (fails to process it), the `Router` sends `CCIPReceiveBounced` to the `OffRamp`. The `OffRamp` forwards `Bounced` to the `ReceiveExecutor{id}`, which sets its state to `ExecuteFailed` and reports `NotifyFailure`. The `OffRamp` emits `ExecutionStateChanged` with state `Failure` and calls `MarkState(Failure)` on the `MerkleRoot{id}`. + - Failed messages can be retried. The `MerkleRoot{id}` allows a transition from `Failure` back to `InProgress` on a subsequent `Validate` call, and the existing `ReceiveExecutor{id}` is reused for the retry attempt. Manual execution is also supported permissionlessly after a configured time delay. For more information, read the [manual execution](/ccip/concepts/manual-execution) page. diff --git a/src/content/ccip/concepts/architecture/onchain/ton/upgradability.mdx b/src/content/ccip/concepts/architecture/onchain/ton/upgradability.mdx new file mode 100644 index 00000000000..1fcc8189f4e --- /dev/null +++ b/src/content/ccip/concepts/architecture/onchain/ton/upgradability.mdx @@ -0,0 +1,59 @@ +--- +section: ccip +date: Last Modified +title: "Onchain Architecture - Upgradability (TON)" +metadata: + description: "Learn about CCIP's upgradability strategy on TON, covering onchain configuration, in-place contract code upgrades via the Upgradeable library, storage migration, and the cross-chain MCMS-based governance process with RBACTimelock." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "ccip ton upgradability tvm code upgrade upgradeable library migrate storage manychainmultisig timelock governance configuration changes onchain upgrade authority backward compatibility secure evolution expedited bypasser" + datePublished: "2026-04-07" + lastModified: "2026-04-07" + difficulty: "advanced" + estimatedTime: "10 minutes" +--- + +Chainlink's Cross-Chain Interoperability Protocol (CCIP) is designed to evolve in response to new feature requests, security considerations, and the need to support additional blockchains over time. This requires a secure upgrade process that preserves CCIP's robust security while allowing for iterative improvements. + +## What Can Be Upgraded + +On the TON blockchain, upgradability primarily happens in two ways: + +1. **Onchain Configuration** + + The core CCIP contracts (`Router`, `OnRamp`, `OffRamp`, `FeeQuoter`) expose owner-gated message handlers that allow authorized accounts to adjust operational parameters without requiring a new deployment. + + Examples include: + - Enabling support for a new destination chain on the OnRamp. + - Updating fee parameters on the FeeQuoter. + - Updating OCR3 configuration on the OffRamp. + +1. **Contract Code Upgrades** + + Each persistent CCIP contract (`Router`, `OnRamp`, `OffRamp`, `FeeQuoter`) implements the `Upgradeable` library. This library handles the `Upgradeable_Upgrade` message (opcode `0x0aa811ed`), which carries a `code` cell containing the new compiled [Tolk](https://docs.ton.org/tolk/overview) bytecode. + + On TON, the [TVM](https://docs.ton.org/tvm/overview) supports in-place code replacement within a single compute phase. When an upgrade message is processed: + 1. `contract.setCodePostponed(msg.code)` schedules the new bytecode to take effect at the end of the compute phase. + 1. The [TVM `c3` register](https://docs.ton.org/tvm/registers#c3--function-selector) (the continuation dispatch table) is immediately redirected to the new code, so the remainder of the upgrade handler runs under the new version. + 1. The `migrate` function — called from the new code with the current storage cell and the previous version string — transforms the old storage layout into the new one. + 1. The migrated storage is written back via `contract.setData`. + 1. An `Upgradeable_UpgradedEvent` is emitted containing the new code cell, its SHA256 hash, and the new version string. + + Because the TVM account (and its address) persists across code changes, no external client or off-chain system needs to update a stored address after an upgrade. The `migrate` and `version` functions are assigned stable method IDs (`@method_id(1000)` and `@method_id(1001)` respectively) that are preserved in every version, ensuring the new code can always locate and call the correct function from the previous version during the upgrade. + + Authorization is enforced before the upgrade executes: every contract checks `ownable.requireOwner(senderAddress)`, so only the `Ownable2Step` owner of the contract — in production, the RBACTimelock — can initiate an upgrade. + +## Implementation Process + +All critical changes to CCIP on TON are governed by a secure, cross-chain process using the **ManyChainMultiSig (MCMS)** system and the **RBACTimelock** contract, which function consistently across all CCIP-supported chains. + +The MCMS deployment on TON consists of three independent MCMS instances — **Proposer**, **Canceller**, and **Bypasser** — each holding its own Merkle root of authorized operations and its own signer quorum configuration. Signers are identified by EVM addresses and arranged in a hierarchical group tree, where each group has its own quorum threshold; a proposal is authorized only when the root group of the tree is satisfied. The MCMS contract stores a `seenSignedHashes` map, so a signed root cannot be replayed once it has been submitted. + +The RBACTimelock is the `Ownable2Step` owner of all core CCIP contracts. The Proposer, Canceller, and Bypasser MCMS instances hold the corresponding roles within the Timelock. Any change to a CCIP contract — whether a configuration update or a code upgrade — requires a message to be sent from the Timelock to the target contract. + +Any proposal must follow one of two paths: + +1. **Time-locked Review**: An authorized quorum of signers approves an operation off-chain and the Proposer MCMS submits the resulting Merkle root onchain via `MCMS_SetRoot`. Anyone who holds a valid Merkle inclusion proof can then call `MCMS_Execute` to deliver the operation to the Timelock as a `Timelock_ScheduleBatch` call, which places the batch into a pending queue with a mandatory `minDelay`. During this review window, the Canceller MCMS can cancel a pending batch via `Timelock_Cancel`. If no cancellation occurs, once the delay expires anyone can deliver a `Timelock_ExecuteBatch` message directly to the Timelock. There is no separate Executor role or Executor MCMS instance on TON: the Timelock is deployed with the executor role check disabled, because TON has no CallProxy equivalent. The Timelock then forwards the contained operation (for example, an `Upgradeable_Upgrade` message or a configuration update) to the target CCIP contract. + +1. **Expedited Approval**: For time-sensitive situations such as emergency responses, the Bypasser MCMS can authorize an operation that is delivered to the Timelock as `Timelock_BypasserExecuteBatch`. This path bypasses the mandatory review delay and immediately forwards the operation to the target contract. + +The entire process is publicly verifiable on-chain. All MCMS roots and executed operations, the Timelock batch schedule and execution history, and the `Upgradeable_UpgradedEvent` emissions from upgraded contracts are recorded as on-chain events. diff --git a/src/content/ccip/concepts/best-practices/ton.mdx b/src/content/ccip/concepts/best-practices/ton.mdx new file mode 100644 index 00000000000..e475b53b756 --- /dev/null +++ b/src/content/ccip/concepts/best-practices/ton.mdx @@ -0,0 +1,308 @@ +--- +section: ccip +date: Last Modified +title: "CCIP Best Practices (TON)" +metadata: + description: "Best practices for building secure and reliable cross-chain dApps using CCIP on TON. Covers receiver protocol steps, source and sender validation, MIN_VALUE configuration, extraArgs, fee estimation, deduplication, monitoring, and multisig guidance." + image: "/images/ccip/ccip-hl-v1.6.gif" + excerpt: "ccip ton best practices, receiver validation, MIN_VALUE, Router_CCIPReceiveConfirm, allowOutOfOrderExecution, extraArgs, fee estimation, QueryID, ACK NACK, monitoring, multisig, Tolk smart contract" + datePublished: "2026-04-07" + lastModified: "2026-04-07" + difficulty: "intermediate" + estimatedTime: "35 minutes" +--- + +import { Aside } from "@components" +import CcipCommon from "@features/ccip/CcipCommon.astro" + + + + + +Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet. + +## Implement all three mandatory receiver protocol steps + +Every TON CCIP receiver must implement three mandatory protocol steps in order. Omitting or reordering any step puts your contract into a broken state — either allowing unauthorized execution or stranding messages that cannot be retried. + + + +**Step 1 — Authorize the Router.** Accept `Receiver_CCIPReceive` only from the configured CCIP Router address. Reject any other sender unconditionally. + +```tolk +assert(in.senderAddress == st.router) throw ERROR_UNAUTHORIZED; +``` + +**Step 2 — Check attached value.** The Router attaches TON to cover the cost of routing `Router_CCIPReceiveConfirm` back through the protocol chain. Verify the attached value meets your `MIN_VALUE` constant. The Router needs at least **0.02 TON** to process the confirmation. Use **0.03 TON** as your baseline and increase it to account for your own execution costs. + +```tolk +assert(in.valueCoins >= MIN_VALUE) throw ERROR_LOW_VALUE; +``` + +**Step 3 — Send `Router_CCIPReceiveConfirm`.** Return the confirmation message to the Router along with enough TON to cover the remaining protocol chain. Using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` is the simplest correct choice — it forwards all remaining value automatically. + +```tolk +val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, +}); +receiveConfirm.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +``` + +The `MinimalReceiver` contract in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton) implements all three steps inline and serves as the reference starting point. For a detailed explanation and multiple implementation options, see [Implementing CCIP Receivers](/ccip/tutorials/ton/receivers). + +## Set `MIN_VALUE` correctly + +`MIN_VALUE` is the minimum TON your receiver requires attached to each `Receiver_CCIPReceive` message. Setting it too low is a critical misconfiguration: + +- If too little TON reaches your contract, the `Router_CCIPReceiveConfirm` may not propagate through the protocol chain. +- The message is left in an undelivered state and **cannot be retried**. Used gas is not refunded. + +Start at **0.03 TON** and benchmark your contract under realistic load to determine the right value. The 0.03 TON baseline includes 0.02 TON for the Router's confirmation cost plus 0.01 TON margin. + +```tolk +// In your receiver contract +const MIN_VALUE: int = ton("0.03"); // increase if your logic is more expensive +``` + +The EVM-side `gasLimit` (set in `extraArgs`) controls how much nanoTON the protocol reserves for delivery. If your receiver requires more than the default 0.1 TON allocation, increase the `gasLimit` in `buildExtraArgsForTON` accordingly: + +```typescript +// In your EVM sender script — increase gasLimit if your TON receiver is expensive +const extraArgs = buildExtraArgsForTON(200_000_000n, true) // 0.2 TON in nanoTON +``` + +## Verify destination chain + +Before sending a CCIP message from TON, verify that the destination chain is supported. The CCIP Router on TON exposes its supported destination chains through the `OnRamp`. Sending a message to an unsupported chain selector will be rejected at the Router level, wasting the TON attached to the transaction. + +Check the [CCIP Directory](/ccip/directory/testnet/chain/ton-testnet) for the list of supported TON → EVM lanes and their chain selectors before hardcoding a destination. Always use the chain selector constant from `helper-config.ts` in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/helper-config.ts) rather than deriving it manually. + +## Verify source chain + +The CCIP protocol delivers messages to your receiver with a `sourceChainSelector` field in `Any2TVMMessage`. **Source chain validation is not enforced at the protocol level on TON** — it is your responsibility to check this field. + +Inside your `Receiver_CCIPReceive` handler, validate `message.sourceChainSelector` against an allowlist of chains your application trusts: + +```tolk +// After the three mandatory protocol steps, validate the source chain +assert(isAllowedSourceChain(msg.message.sourceChainSelector)) throw ERROR_UNTRUSTED_SOURCE; +``` + +Without this check, a valid CCIP message sent from any supported source chain will be accepted and processed by your receiver. + +## Verify sender + +The `sender` field in `Any2TVMMessage` is a `CrossChainAddress` (a `slice` containing the encoded source-chain address). For EVM-to-TON messages, it contains the 20-byte EVM address of the sender contract. + +**Sender validation is also not enforced at the protocol level.** Your contract is responsible for checking this field if your application depends on messages arriving from specific addresses: + +```tolk +// After source chain validation +// For EVM senders, msg.message.sender is a slice containing the 20-byte EVM address +assert(isTrustedSender(msg.message.sender)) throw ERROR_UNTRUSTED_SENDER; +``` + + + +## Use `messageId` for deduplication + +The `messageId` field in `Any2TVMMessage` is a `uint256` that uniquely identifies each CCIP message in the protocol. Store received message IDs and reject any message whose ID has already been processed: + +```tolk +// Check deduplication before processing +assert(!hasBeenProcessed(msg.message.messageId)) throw ERROR_ALREADY_PROCESSED; +// Mark as processed before executing business logic +markProcessed(msg.message.messageId); +``` + +This prevents replay attacks in the event of any re-delivery edge case. The `messageId` should be stored in your contract's persistent storage. + +## Configure `extraArgs` correctly + +The `extraArgs` field controls execution parameters for the destination chain. Its content and meaning differ depending on which chain is the source and which is the destination. + + + +### TON sending to EVM + +When sending from TON to an EVM destination, use `buildExtraArgsForEVM` from the Starter Kit. Two fields must be set correctly: + +**`gasLimit`** — EVM gas units allocated for `_ccipReceive` execution on the destination contract. `100_000` units covers simple message storage (for example, `MessageReceiver.sol`). Increase this for contracts with heavier logic. If the gas limit is too low, execution fails on EVM. For EVM destinations, failed messages can be retried with a higher gas limit via the CCIP manual execution path, but unused gas is not refunded. + +**`allowOutOfOrderExecution`** — Must always be `true` for TON-to-EVM messages. The TON CCIP Router rejects any message where this flag is `false`. + +```typescript filename="scripts/utils/utils.ts" +// gasLimit: EVM gas units for ccipReceive. allowOutOfOrderExecution must be true. +const extraArgs = buildExtraArgsForEVM(100_000, true) +``` + +### EVM sending to TON + +When sending from EVM to a TON destination, use `buildExtraArgsForTON` from the Starter Kit. The parameter meanings differ from the EVM case: + +**`gasLimit`** — Denominated in **nanoTON**, not EVM gas units. This is the amount of nanoTON reserved for execution on the TON destination chain. A starting value of `100_000_000n` (0.1 TON) covers most receive operations. Any unused nanoTON is returned to the contract after execution. + +**`allowOutOfOrderExecution`** — Must be `true` for TON-bound lanes. + +```typescript filename="scripts/utils/utils.ts" +// gasLimit: nanoTON reserved for TON execution (1 TON = 1_000_000_000 nanoTON) +const extraArgs = buildExtraArgsForTON(100_000_000n, true) // 0.1 TON +``` + + + +## Estimate fees accurately and apply a buffer + +The CCIP protocol fee for TON-to-EVM messages is denominated in nanoTON and computed by the FeeQuoter contract, reachable through a chain of on-chain getter calls: + +``` +Router.onRamp(destChainSelector) → OnRamp address +OnRamp.feeQuoter(destChainSelector) → FeeQuoter address +FeeQuoter.validatedFeeCell(ccipSendCell) → fee in nanoTON +``` + +Use the `getCCIPFeeForEVM` helper in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/scripts/utils/utils.ts) to perform this lookup. The CCIP message Cell passed to it must be fully populated — all fields must match the values used in the final send. + +Apply two buffers on top of the quoted fee: + +- **10% fee buffer**: Accounts for small fluctuations between quote time and execution time. +- **0.5 TON gas reserve**: Covers the wallet-level transaction cost and source-chain execution overhead. Any surplus above actual costs is returned via the ACK message. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +const feeWithBuffer = (fee * 110n) / 100n // +10% +const gasReserve = 500_000_000n // 0.5 TON in nanoTON +const valueToAttach = feeWithBuffer + gasReserve // total value sent to Router +``` + +For EVM-to-TON messages, apply the same 10% buffer to the fee returned by `router.getFee()`: + +```typescript filename="scripts/evm2ton/sendMessage.ts" +const fee = await router.getFee(TON_TESTNET_CHAIN_SELECTOR, message) +const feeWithBuffer = (fee * 110n) / 100n +``` + +## Decouple CCIP message reception from business logic + +Your `Receiver_CCIPReceive` handler should perform the three mandatory protocol steps and then delegate to a separate internal function for application logic. Keep the handler itself minimal: + +1. Perform the three mandatory protocol steps (Router check, value check, send confirm). +2. Optionally validate source chain and sender. +3. Check deduplication using `messageId`. +4. Store the incoming message or its processed result in contract storage. +5. Emit an event (if needed for monitoring). +6. Call your application logic from a separate function. + +Keeping reception and business logic separate provides a natural "escape hatch": if your business logic encounters a critical issue, you can upgrade or replace that function without touching the protocol-facing entry point. This pattern also simplifies testing — you can unit-test the business logic independently of the Receiver protocol. + +## Monitor your dApps + +### TON → EVM: track ACK and NACK responses + +After sending a message from TON, the CCIP Router sends back a `Router_CCIPSendACK` (accepted) or `Router_CCIPSendNACK` (rejected). Use the `queryID` field to correlate responses to their originating send. The recommended value for `queryID` is the wallet's current sequence number (`seqno`), which is monotonically increasing and collision-free. + +Use the Starter Kit verification scripts to check the status of your sends: + +```bash filename="Terminal" +# Check ACK/NACK for recent transactions +npm run utils:checkLastTxs -- --ccipSendOnly true + +# Filter to a specific send by QueryID +npm run utils:checkLastTxs -- --queryId +``` + +An ACK contains the CCIP Message ID and a CCIP Explorer URL. A NACK means the Router rejected the message — both the fee and any TON surplus are returned to the sender. + +### EVM → TON: track delivery on TON + +After sending from EVM, track delivery on TON using the message ID from the `ccipSend` transaction: + +```bash filename="Terminal" +# Check delivery of an EVM-to-TON message +npm run utils:checkTON -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "your message" +``` + +EVM-to-TON delivery typically takes 5–15 minutes. You can also track the full message lifecycle on the [CCIP Explorer](https://ccip.chain.link) using the message ID printed by the send script. + +### Set up monitoring alerts + +Build monitoring on top of the verification scripts or TON Testnet block explorer data. Create alerts for: + +- NACKs on TON → EVM sends (rejected messages, insufficient fees). +- Messages that remain undelivered on TON beyond the expected delivery window. +- Sudden changes in CCIP fee levels that may indicate network conditions affecting your buffer assumptions. + +## Evaluate the security and reliability of the networks that you use + +Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your contracts, and the network conditions on those blockchains. + +## Review and audit your code + +Before securing value with contracts that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet. + +## Soak test your dApps + +Be aware of the [Service Limits for Supported Networks](/ccip/directory). Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions. + +## Monitor your dApps + +When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages. + +Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them. + +## Multi-Signature Authorities + +Multi-signature authorities enhance security by requiring multiple signatures to authorize transactions. + +### Threshold configuration + +Set an optimal threshold for signers based on the trust level of participants and the required security. + +### Role-based access control + +Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals. + +### Hardware wallet integration + +Use hardware wallets for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated. + +### Regular audits and updates + +Conduct periodic audits of signer access and authority settings. Update the multisig setup as necessary, especially when personnel changes occur. + +### Emergency recovery plans + +Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders. + +### Transaction review process + +Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks. + +### Documentation and training + +Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols. diff --git a/src/content/ccip/llms-full.txt b/src/content/ccip/llms-full.txt index 072c188f740..a888d91075f 100644 --- a/src/content/ccip/llms-full.txt +++ b/src/content/ccip/llms-full.txt @@ -2295,6 +2295,356 @@ Once a proposal is approved through either path, it can be executed, and the `mc --- +# CCIP Onchain Architecture (TON) +Source: https://docs.chain.link/ccip/concepts/architecture/onchain/ton +Last Updated: 2026-04-07 + +Chainlink CCIP's TON onchain architecture consists of specialized Tolk contracts deployed on both source and destination chains. These contracts work together with CCIP's offchain infrastructure to provide end-to-end cross-chain interoperability. + +- **[Overview](/ccip/concepts/architecture/onchain/ton/overview)**: Provides a high-level introduction to CCIP's TON-based onchain architecture, including a component diagram, key roles, and a detailed walk-through of a message's lifecycle from source to destination chain. + +- **[Components](/ccip/concepts/architecture/onchain/ton/components)**: Describes each architectural component in detail. + +- **[Upgradability](/ccip/concepts/architecture/onchain/ton/upgradability)**: Explains CCIP's approach to secure system evolution. + +--- + +# Onchain Architecture - Overview (TON) +Source: https://docs.chain.link/ccip/concepts/architecture/onchain/ton/overview +Last Updated: 2026-04-07 + +## TON as Source Chain + +On the TON source blockchain, a user or another contract initiates a cross-chain message by sending a `CCIPSend` internal message to the CCIP `Router` contract. The `Router` validates that sufficient TON is attached for gas and forwards the message to the appropriate `OnRamp` contract. The `OnRamp` assigns a unique message ID, deploys an ephemeral `SendExecutor{id}` contract to isolate fee validation, and instructs it to call the `FeeQuoter` for a price check. Once the `FeeQuoter` confirms the fee, the `SendExecutor` reports back to the `OnRamp`, which then assigns a sequence number and emits a chain log (`ExtOutLogMessage`) containing the full `CCIPSend` payload. This external log is observed offchain by the Committing DON, which relays the message to the destination blockchain. + +## TON as Destination Chain + +On the TON destination blockchain, the `OffRamp` contract receives two types of OCR3 reports from the offchain DONs. The Committing DON submits a `Commit` report containing a Merkle root of batched messages. The `OffRamp` verifies the OCR3 signatures, deploys an ephemeral `MerkleRoot{id}` contract to track per-message execution state for that root, and emits a `CommitReportAccepted` log. The Executing DON observes this log along with the original source chain messages, and for each message submits an `Execute` report. The `OffRamp` sends a `Validate` message to the relevant `MerkleRoot{id}` contract to verify the Merkle proof and mark the message as in-progress. The `MerkleRoot{id}` replies with `ExecuteValidated`, after which the `OffRamp` deploys (or reuses) an ephemeral `ReceiveExecutor{id}` to track the delivery state. The `ReceiveExecutor{id}` dispatches the message back to the `OffRamp`, which routes it via the `Router` to the Receiver contract. The Receiver processes the data and sends a `CCIPReceiveConfirm` back through the `Router` and `OffRamp`, which forwards a `Confirm` to the `ReceiveExecutor{id}`. On success, the `ReceiveExecutor{id}` reports `NotifySuccess` to the `OffRamp`, which emits an `ExecutionStateChanged` log marking the message as `Success`. + + + +## Key Components + + + +**Source Chain**: + +**Destination Chain**: + +| Component | Ownership | Role | +| --------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Sender | External (User/Contract) | A user wallet or a custom contract that initiates a cross-chain message on the source chain by sending `CCIPSend` to the `Router`. | +| Receiver | External (User/Contract) | A custom contract on the destination chain that receives `CCIPReceive` from the `Router`, sends back a `CCIPReceiveConfirm`, and processes the message data. | +| Router | CCIP | The primary entry point for both sending and receiving. On the source chain it validates the attached TON, checks for sufficient gas, and forwards to the `OnRamp`. On the destination chain it routes executed messages from the `OffRamp` to the Receiver and relays the confirmation back. The `Router` also acts as the RMN Remote: it stores the set of cursed subjects, accepts `Router_RMNRemoteCurse` and `Router_RMNRemoteUncurse` messages from the authorized RMN admin, and propagates the updated curse state to all registered `OffRamp` contracts via `OffRamp_UpdateCursedSubjects`. | +| OnRamp | CCIP | Processes outbound messages. Assigns a message ID, deploys a `SendExecutor{id}` for fee validation, assigns a sequence number on success, and emits a chain log (`CCIPSend`) observed by the offchain DONs. | +| `SendExecutor{id}` | CCIP | An ephemeral contract deployed by the `OnRamp` per outgoing message. It calls `FeeQuoter` for fee validation, verifies the sender attached enough TON to cover the CCIP fee, and reports the result back to the `OnRamp`. It is destroyed after reporting. | +| FeeQuoter | CCIP | Stores destination-chain fee configuration, fee token prices, and premium multipliers. Validates the `CCIPSend` request and returns the computed fee or a validation error to the `SendExecutor{id}`. | +| OffRamp | CCIP | Receives OCR3 `Commit` and `Execute` reports from the DONs. On commit, deploys a `MerkleRoot{id}` and emits `CommitReportAccepted`. On execute, validates each message via the `MerkleRoot{id}`, orchestrates delivery through a `ReceiveExecutor{id}` and the `Router`, and emits `ExecutionStateChanged`. Maintains a local cache of cursed subjects, updated by the `Router` whenever the RMN admin curses or un-curses a subject. This cache is checked synchronously during every `Commit` and `Execute` call. | +| `MerkleRoot{id}` | CCIP | An ephemeral contract deployed once per committed Merkle root. Stores the two-bit execution state for every message in the root, verifies Merkle proofs, and gates retries. Destroyed when all messages in the root reach `Success`. | +| `ReceiveExecutor{id}` | CCIP | An ephemeral contract deployed (or reused on retry) per incoming message. Holds the message content and its delivery state (`Untouched`, `Execute`, `ExecuteFailed`, `Success`). Dispatches delivery back to the `OffRamp` and tracks the confirmation or bounce result. | + +## Typical Lifecycle of a Message + +### Source Blockchain (TON) + +This outlines the process when initiating a CCIP transaction from the TON blockchain. + +1. **Preparation** + - The Sender prepares the CCIP message, including: + - **Receiver**: An encoded destination address (e.g., a 20-byte EVM address). + - **Data payload**: Arbitrary bytes to be delivered to the receiver contract. + - **Destination chain selector**: Identifies the target blockchain. + - **Extra arguments**: Destination-specific parameters such as a gas limit for EVM chains. + - The fee can be queried in two ways: + - **Onchain**: Send a `Router_GetValidatedFee` message to the `Router`, which relays to the `FeeQuoter` via the `OnRamp`. The `Router` responds with `Router_MessageValidated` (or `Router_MessageValidationFailed`) containing the computed fee. + - **Offchain**: Call the `validatedFee` getter directly on the `FeeQuoter` contract. + - The fee is paid in native TON. The Sender must attach the CCIP fee plus additional TON to cover internal message forwarding gas costs. + +2. **Sending** + - The Sender sends a `CCIPSend` internal message to the `Router`, attaching the required TON. + - The `Router` validates that sufficient TON is attached and forwards the message to the `OnRamp`. + - The `OnRamp` assigns a message ID, then deploys a `SendExecutor{id}` contract with a deterministic address derived from the `OnRamp` address and a randomized `id`. It sends an `Execute` message to the `SendExecutor{id}` containing the full `CCIPSend` payload. + - The `SendExecutor{id}` sends `GetValidatedFee` to the `FeeQuoter`. If the fee is valid and the sender attached sufficient TON, the `FeeQuoter` replies with `MessageValidated`. The `SendExecutor{id}` then destroys itself and reports `ExecutorFinishedSuccessfully` to the `OnRamp`, returning any remaining balance. + - If the fee is insufficient or validation fails, the `FeeQuoter` replies with `MessageValidationFailed` and the `SendExecutor{id}` reports `ExecutorFinishedWithError`, triggering a rejection path back through the `OnRamp` and `Router` to the Sender (`CCIPSendNACK`). + - On success, the `OnRamp` assigns a sequence number and emits a chain log (`ExtOutLogMessage`) containing the sequenced `CCIPSend` message. The Sender receives a `CCIPSendACK` with the unused TON. + +3. **Initial Offchain Processing** + - The Committing DON observes the `CCIPSend` chain log emitted by the `OnRamp` and begins batching messages offchain to prepare a Merkle root for commitment on the destination chain. + +### Destination Blockchain (TON) + +This outlines the process when TON is the receiving chain for a CCIP message. + +1. **Commit Phase** + - The Committing DON submits a `Commit` OCR3 report to the `OffRamp`, containing a Merkle root covering a batch of sequenced messages and any price updates. + - The `OffRamp` verifies the source chain is enabled and checks its locally cached cursed subjects. If the source chain is cursed, the report is rejected. + - The `OffRamp` deploys a `MerkleRoot{id}` contract initialized with the Merkle root and the expected message sequence range, and advances the next expected sequence number. + - If the report includes price updates that are newer than the latest recorded OCR sequence number, the `OffRamp` forwards them to the `FeeQuoter`. + - The `OffRamp` verifies the Committing DON's OCR3 signatures and emits an `OCR3Base_Transmitted` log. Signature verification is enabled for the Commit plugin and required. RMN BLS signature verification over blessed Merkle roots is not performed on TON; curse protection is provided exclusively through the `Router`'s curse mechanism. + - The `OffRamp` emits a `CommitReportAccepted` log, signaling that the root is stored and messages are ready for execution. + +2. **Secondary Offchain Processing** + - The Executing DON observes the `CommitReportAccepted` log on the destination chain and the original source chain logs. For each message in the committed batch, it computes the Merkle proof and submits a separate `Execute` OCR3 report. + +3. **Execution Phase** + - The `OffRamp` receives the `Execute` report and immediately checks its locally cached cursed subjects. If the source chain is cursed, execution is rejected before any further processing. + - The `OffRamp` verifies the source chain is enabled and computes a metadata hash from the source chain selector, destination chain selector, and the known OnRamp address. This hash is used together with the Merkle proof to reconstruct and verify the committed root, ensuring the message has not been tampered with. The Execute plugin does not perform OCR3 signature verification; signature verification is disabled for the Execute plugin by configuration. + - The `OffRamp` sends a `Validate` message to the `MerkleRoot{id}` contract corresponding to this root. + - The `MerkleRoot{id}` verifies the Merkle proof, checks that the message has not already been executed (state must be `Untouched`), marks it as `InProgress`, and replies with `ExecuteValidated`. If this is the last message in the root, the `MerkleRoot{id}` destroys itself. + - The `OffRamp` emits `ExecutionStateChanged` with state `InProgress`, then deploys (or reuses on retry) a `ReceiveExecutor{id}` for the message. It sends `InitExecute` to the `ReceiveExecutor{id}`. + - The `ReceiveExecutor{id}` sends `DispatchValidated` back to the `OffRamp` and sets its state to `Execute`. + - The `OffRamp` sends `RouteMessage` to the `Router`, which forwards a `CCIPReceive` internal message to the Receiver contract. The `CCIPReceive` payload includes the `execId`, `messageId`, source chain selector, sender address, and data. + - The Receiver verifies that the sender is the `Router`, sends back `CCIPReceiveConfirm{execId}` to the `Router`, and processes the message data. + - The `Router` forwards `CCIPReceiveConfirm` to the `OffRamp`, which sends `Confirm` to the `ReceiveExecutor{id}`. + - The `ReceiveExecutor{id}` verifies the confirm came from the `OffRamp` and that the original sender matches the Receiver. It sets its state to `Success`, destroys itself, and replies `NotifySuccess` to the `OffRamp`. + - The `OffRamp` emits `ExecutionStateChanged` with state `Success`. + +4. **Failure and Retry** + - If the Receiver bounces the `CCIPReceive` message (fails to process it), the `Router` sends `CCIPReceiveBounced` to the `OffRamp`. The `OffRamp` forwards `Bounced` to the `ReceiveExecutor{id}`, which sets its state to `ExecuteFailed` and reports `NotifyFailure`. The `OffRamp` emits `ExecutionStateChanged` with state `Failure` and calls `MarkState(Failure)` on the `MerkleRoot{id}`. + - Failed messages can be retried. The `MerkleRoot{id}` allows a transition from `Failure` back to `InProgress` on a subsequent `Validate` call, and the existing `ReceiveExecutor{id}` is reused for the retry attempt. Manual execution is also supported permissionlessly after a configured time delay. For more information, read the [manual execution](/ccip/concepts/manual-execution) page. + +--- + +# Onchain Architecture - Components (TON) +Source: https://docs.chain.link/ccip/concepts/architecture/onchain/ton/components +Last Updated: 2026-04-07 + +This section provides more detail on the onchain components for TON. + + + +## Sender/Receiver + +**CCIP supports the following as senders and receivers on TON:** + +- A user-controlled wallet — A wallet account controlled by a private key. +- A smart contract — Onchain executable logic written in Tolk or FunC. + +**For arbitrary message passing, the supported sender/receiver combinations are:** + +- **Wallet → Smart Contract** + - **Supported Message Type**: Arbitrary data. + - **Reason**: The receiving contract implements a handler for `CCIPReceive` internal messages and can process the data payload. + +- **Smart Contract → Smart Contract** + - **Supported Message Type**: Arbitrary data. + - **Reason**: Both the sender and receiver are programmable contracts that can initiate and handle CCIP messages with arbitrary data. + +**A CCIP Message on TON can include:** + +- An arbitrary bytes data payload. + +**Sender Responsibilities:** + +- Prepare the `CCIPSend` message, including the encoded receiver address, data payload, destination chain selector, and extra arguments (e.g., gas limit for EVM destination chains). +- Retrieve a fee estimate using one of two methods: + - **Onchain**: Send a `Router_GetValidatedFee` message to the `Router`. The `Router` relays the request to the `OnRamp`, which forwards it to the `FeeQuoter`. The result is returned via `Router_MessageValidated` (or `Router_MessageValidationFailed`). + - **Offchain**: Call the `validatedFee` getter directly on the `FeeQuoter` contract. +- Send `CCIPSend` to the `Router` with sufficient TON attached to cover both the CCIP fee and internal message forwarding gas costs. The router checks that the attached value meets the minimum required (`Router_Costs.CCIPSend()`). + +**Receiver Responsibilities:** + +- **Security validation**: The receiver contract must verify that the sender of any `CCIPReceive` internal message is the registered CCIP `Router` contract. +- **Confirmation**: The receiver must reply with a `CCIPReceiveConfirm{execId}` message back to the `Router` after processing the message. This confirmation must carry sufficient TON to cover the confirmation trace costs (`Router_Costs.receiveConfirm()`). Without this confirmation the execution is treated as failed. +- **Failure handling**: If the receiver cannot process the message, it may allow `CCIPReceive` to bounce. The `Router` detects the bounce and forwards `CCIPReceiveBounced` to the `OffRamp`, initiating the failure path. + +## Router + +The `Router` contract is the single user-facing entry point for both sending and receiving CCIP messages on TON. + +**On the source chain (sending)**, the `Router`: + +- Accepts `CCIPSend` messages from senders. Requires a minimum TON value (`Router_Costs.CCIPSend()`); messages with insufficient value are rejected before any further processing. +- Checks the destination chain is not cursed (using its locally stored `CursedSubjects`) before forwarding to the `OnRamp`. +- Resolves the `OnRamp` address for the given destination chain selector from its `onRamps` map and forwards the `OnRamp_Send` message, carrying all remaining value. +- Receives `Router_MessageSent` from the `OnRamp` on success and delivers `CCIPSendACK` back to the original sender with unused TON. +- Receives `Router_MessageRejected` from the `OnRamp` on failure and delivers `CCIPSendNACK` to the sender. +- Accepts `Router_GetValidatedFee` from any caller, relays to the `OnRamp`, and returns the result (`Router_MessageValidated` or `Router_MessageValidationFailed`). + +**On the destination chain (receiving)**, the `Router`: + +- Accepts `Router_RouteMessage` from the registered `OffRamp` for the source chain and forwards `CCIPReceive` to the Receiver contract. +- Accepts `Router_CCIPReceiveConfirm` from any caller (permissionless at the Router level; authorization is enforced by the `ReceiveExecutor`) and forwards `OffRamp_CCIPReceiveConfirm` to the `OffRamp`. +- Detects a bounced `CCIPReceive` from the Receiver and forwards `OffRamp_CCIPReceiveBounced` to the `OffRamp`. + +**As the RMN Remote**, the `Router`: + +- Stores the set of cursed subjects (`CursedSubjects`) in its own storage, acting as the RMN Remote for the TON chain family. There is no separate RMN Remote contract. +- Accepts `Router_RMNRemoteCurse` and `Router_RMNRemoteUncurse` messages exclusively from the authorized RMN admin (`Ownable2Step`-gated). +- On each curse or un-curse operation, emits a `Cursed` or `Uncursed` chain log and updates the `forwardUpdates` set, then propagates `OffRamp_UpdateCursedSubjects` to every registered `OffRamp` address (one message per unique OffRamp). The `forwardUpdates` set is rebuilt automatically whenever the `onRamps`/`offRamps` maps change. +- Accepts permissionless `Router_RMNRemoteVerifyNotCursed` queries and synchronously replies with whether the given subject is cursed. + +## OnRamp + +The `OnRamp` contract processes all outbound CCIP messages on the source chain. It is not called directly by users; the `Router` is the only authorized caller of `OnRamp_Send`. + +When the `Router` forwards `OnRamp_Send`, the `OnRamp`: + +- **Allowlist check**: If `allowlistEnabled` is set for the destination chain configuration, verifies the sender address is in the `allowedSenders` map for that lane. Rejects the message if not. +- **SendExecutor deployment**: Generates a random `executorID` (`uint224` derived from a 256-bit random value), computes a deterministic address using `autoDeployAddress(executorID)`, and sends `Deployable_InitializeAndSend` to deploy and initialize the ephemeral `SendExecutor{id}` contract in a single message. All remaining value is carried to the `SendExecutor`. +- **ExecutorFinishedSuccessfully handling**: When the `SendExecutor` reports success, the `OnRamp` assigns the next sequence number for the destination chain, generates the `messageId` (as a hash over a metadata preimage, the sender address, sequence number, nonce, and message body), emits a `CCIPMessageSent` chain log containing the complete `TVM2AnyRampMessage`, and sends `Router_MessageSent` back to the `Router`. The collected CCIP fee is retained in the `OnRamp`'s balance. +- **ExecutorFinishedWithError handling**: Forwards a `Router_MessageRejected` notification to the `Router` with the error code. + +The `OnRamp` also handles fee withdrawal (`OnRamp_WithdrawFeeTokens`, permissionless), dynamic config updates (owner-only), destination chain config updates (owner-only), and allowlist updates (allowlist admin or owner). + +## SendExecutor\{id} + +The `SendExecutor{id}` is an ephemeral contract deployed by the `OnRamp` once per outgoing message. Its address is deterministically derived from the `OnRamp`'s address and a randomly generated `id`, so the `OnRamp` can authenticate replies without storing any state. + +The `SendExecutor{id}`: + +- Is deployed and immediately receives `CCIPSendExecutor_Execute` (a self-message sent as part of `Deployable_InitializeAndSend`), which carries the full `OnRamp_Send` payload and the `FeeQuoter` address. +- Sends `FeeQuoter_GetValidatedFee` to the `FeeQuoter` and transitions to state `OnGoingFeeValidation`. +- On `FeeQuoter_MessageValidated`: verifies the sender attached enough value to cover the CCIP fee, then reports `OnRamp_ExecutorFinishedSuccessfully` to the `OnRamp` carrying the fee amount and returning any remaining balance. +- On `FeeQuoter_MessageValidationFailed` or a bounce of `FeeQuoter_GetValidatedFee`: reports `OnRamp_ExecutorFinishedWithError` to the `OnRamp`. +- Is destroyed after reporting (state `Finalized` is terminal; the contract returns its remaining balance to the `OnRamp`). + +## FeeQuoter + +The `FeeQuoter` contract is responsible for fee validation and price data storage. + +**On the source chain**, the `FeeQuoter`: + +- Accepts `FeeQuoter_GetValidatedFee` from the `SendExecutor{id}` (routed via the `OnRamp`). Validates the message payload (receiver encoding, gas limit, supported destination chain, fee token), computes the total fee from execution cost, premium multiplier, and data availability cost, and replies with either `FeeQuoter_MessageValidated` (including the fee amount) or `FeeQuoter_MessageValidationFailed`. +- Stores fee token premium multipliers, destination-chain fee configuration, and token prices with staleness enforcement. +- Accepts `FeeQuoter_UpdatePrices` from the `OffRamp` (forwarded from OCR `Commit` reports) to update token and gas prices onchain. + +The `FeeQuoter` is not directly accessible to end users for fee computation. The intended path is `Router_GetValidatedFee` → `OnRamp` → `FeeQuoter_GetValidatedFee`, or direct getter calls offchain. + +## OffRamp + +The `OffRamp` contract operates on the destination chain and is the primary contract that the offchain Committing and Executing DONs interact with. + +### Commit Phase + +1. **Report submission**: The Committing DON calls `OffRamp_Commit` with an OCR3 report containing a Merkle root (covering a batch of messages from a source chain) and optional token/gas price updates. + +2. **Fee check**: The `OffRamp` verifies the attached TON covers the minimum commit cost. Price-update-only reports require less TON than reports with Merkle roots. + +3. **Curse and source chain check**: The `OffRamp` checks its locally cached `CursedSubjects` and verifies the source chain is enabled in `sourceChainConfigs`. If either check fails, the report is rejected. + +4. **Sequence number validation**: Verifies the root's `minSeqNr` matches the next expected sequence number for the source chain and that the range covers at most 64 messages. If valid, advances `minSeqNr` to `maxSeqNr + 1`. + +5. **MerkleRoot deployment**: Deploys a `MerkleRoot{id}` contract by sending `Deployable_Initialize` to its deterministic address. The `MerkleRoot{id}` is initialized with the Merkle root value, the `OffRamp`'s address as owner, the current timestamp, and the sequence number range. + +6. **Price updates**: If the report includes price updates and their OCR sequence number is newer than `latestPriceSequenceNumber`, forwards `FeeQuoter_UpdatePrices` to the `FeeQuoter`. + +7. **OCR3 signature verification and event emission**: Calls `ocr3Base.transmit()` to verify the Committing DON's signatures and emit `OCR3Base_Transmitted`. Then emits `CommitReportAccepted`. + + + +### Execution Phase + +1. **Report submission**: The Executing DON calls `OffRamp_Execute` with an OCR3 execute report. The OCR3 transmit call for the Execute plugin uses an empty signatures cell because signature verification is disabled for the Execute plugin by configuration. + +2. **Curse check**: The `OffRamp` checks `CursedSubjects` immediately. If the source chain is cursed, the report is rejected before any further processing. + +3. **Source chain and message validation**: Verifies the source chain is enabled, computes the metadata hash from the source chain selector, destination chain selector, and the known `OnRamp` cross-chain address. Verifies the message's `destChainSelector` and `sourceChainSelector` match the stored configuration. Reconstructs the Merkle root from the message and the provided proof, then resolves the `MerkleRoot{id}` contract address from this root. + +4. **MerkleRoot validation**: Sends `MerkleRoot_Validate` to the `MerkleRoot{id}` contract. The `MerkleRoot{id}` checks the per-message execution state (must be `Untouched` for DON execution, or `Failure` for retries after the permissionless threshold), marks it `InProgress`, and replies with `OffRamp_ExecuteValidated`. If this was the last message in the root, the `MerkleRoot{id}` destroys itself and returns its balance. + +5. **ExecutionStateChanged emission**: Emits `ExecutionStateChanged` with state `InProgress`. + +6. **ReceiveExecutor deployment or reuse**: Deploys (or, on retry, reuses) a `ReceiveExecutor{id}` contract at a deterministic address derived from the `messageId` and `sourceChainSelector`. Sends `Deployable_Initialize` followed immediately by `ReceiveExecutor_InitExecute`. + +7. **Dispatch**: The `ReceiveExecutor{id}` replies with `OffRamp_DispatchValidated`. The `OffRamp` sends `Router_RouteMessage` to the `Router`, which forwards `CCIPReceive` to the Receiver contract. + +8. **Confirmation**: If the Receiver confirms (`Router_CCIPReceiveConfirm` → `OffRamp_CCIPReceiveConfirm`), the `OffRamp` sends `ReceiveExecutor_Confirm` to the `ReceiveExecutor{id}`. The `ReceiveExecutor{id}` verifies the confirm came from the `OffRamp` and that the confirming address matches the intended Receiver, transitions to `Success`, destroys itself, and sends `OffRamp_NotifySuccess`. The `OffRamp` calls `MerkleRoot_MarkState(Success)` and emits `ExecutionStateChanged` with state `Success`. + +9. **Failure**: If the Receiver bounces `CCIPReceive`, the `Router` sends `OffRamp_CCIPReceiveBounced`. The `OffRamp` sends `ReceiveExecutor_Bounced` to the `ReceiveExecutor{id}`, which transitions to `ExecuteFailed` and sends `OffRamp_NotifyFailure`. The `OffRamp` calls `MerkleRoot_MarkState(Failure)` and emits `ExecutionStateChanged` with state `Failure`. + +### Permissionless Manual Execution (Fallback) + +The `OffRamp` includes an `OffRamp_ManuallyExecute` handler that follows the same path as `OffRamp_Execute` but accepts a `gasOverride` field. Manual execution is permissionless and becomes available after `permissionlessExecutionThresholdSeconds` have elapsed since the `MerkleRoot{id}` was deployed. It can retry messages in `Failure` state or, after the threshold, messages still in `Untouched` state. For more information, read the [manual execution](/ccip/concepts/manual-execution) page. + +### Curse Propagation + +The `OffRamp` accepts `OffRamp_UpdateCursedSubjects` exclusively from the address stored as `rmnRouter` in its `deployables` storage (which is the `Router` address). When received, it overwrites the local `CursedSubjects` cache. This is the only mechanism by which the curse state is updated in the `OffRamp`. + +## MerkleRoot\{id} + +The `MerkleRoot{id}` is an ephemeral contract deployed once per committed Merkle root by the `OffRamp`. Its address is deterministic, derived from the `OffRamp`'s address and the Merkle root value itself. + +- Tracks the execution state of every message in the committed root using a packed two-bit bitmap keyed by sequence number offset. Supports up to 64 messages per root. +- Per-message states are: `Untouched` (0), `InProgress` (1), `Success` (2), `Failure` (3). +- Accepts `MerkleRoot_Validate` from the `OffRamp`. Verifies the sender is the owning `OffRamp`, checks the message's current state (must be `Untouched` for normal DON execution; `Failure` or stale `Untouched` for manual retry), marks the message as `InProgress`, and replies `OffRamp_ExecuteValidated`. +- Accepts `MerkleRoot_MarkState` from the `OffRamp` to transition a message to `Success` or `Failure`. Increments a delivered count on `Success`. When delivered count equals the total expected messages, the `MerkleRoot{id}` freezes and returns its balance to the `OffRamp`. +- Is never directly redeployed; if a root's `MerkleRoot{id}` was destroyed (all messages succeeded) and a retry attempt arrives, the address will not exist and the message will bounce. + +## ReceiveExecutor\{id} + +The `ReceiveExecutor{id}` is an ephemeral contract deployed (or reused on retry) by the `OffRamp` per incoming message. Its address is deterministic, derived from the `OffRamp`'s address, the `messageId`, and the `sourceChainSelector`. + +- Stores the message content, the owning `MerkleRoot{id}` address (`root`), the execution ID, the current per-message delivery state, and the last execution timestamp. +- Per-message delivery states are: `Untouched`, `Execute`, `ExecuteFailed`, `Success`. +- Accepts `ReceiveExecutor_InitExecute` from the `OffRamp`. Records the current timestamp, transitions to `Execute`, and replies `OffRamp_DispatchValidated` carrying all remaining value. +- Accepts `ReceiveExecutor_Confirm` from the `OffRamp`. Verifies the `OffRamp` is the sender and that the provided receiver address matches the intended receiver. Transitions to `Success`, destroys itself, and replies `OffRamp_NotifySuccess`. +- Accepts `ReceiveExecutor_Bounced` from the `OffRamp`. Verifies the `OffRamp` is the sender and that the bounced receiver matches the intended receiver. Transitions to `ExecuteFailed` and replies `OffRamp_NotifyFailure`. +- On retry (`ExecuteFailed` → `Execute`): the same `ReceiveExecutor{id}` contract at the same deterministic address is reused. The `OffRamp` sends `ReceiveExecutor_InitExecute` again, which updates the timestamp and re-triggers the dispatch. + +--- + +# Onchain Architecture - Upgradability (TON) +Source: https://docs.chain.link/ccip/concepts/architecture/onchain/ton/upgradability +Last Updated: 2026-04-07 + +Chainlink's Cross-Chain Interoperability Protocol (CCIP) is designed to evolve in response to new feature requests, security considerations, and the need to support additional blockchains over time. This requires a secure upgrade process that preserves CCIP's robust security while allowing for iterative improvements. + +## What Can Be Upgraded + +On the TON blockchain, upgradability primarily happens in two ways: + +1. **Onchain Configuration** + + The core CCIP contracts (`Router`, `OnRamp`, `OffRamp`, `FeeQuoter`) expose owner-gated message handlers that allow authorized accounts to adjust operational parameters without requiring a new deployment. + + Examples include: + + - Enabling support for a new destination chain on the OnRamp. + - Updating fee parameters on the FeeQuoter. + - Updating OCR3 configuration on the OffRamp. + +2. **Contract Code Upgrades** + + Each persistent CCIP contract (`Router`, `OnRamp`, `OffRamp`, `FeeQuoter`) implements the `Upgradeable` library. This library handles the `Upgradeable_Upgrade` message (opcode `0x0aa811ed`), which carries a `code` cell containing the new compiled [Tolk](https://docs.ton.org/tolk/overview) bytecode. + + On TON, the [TVM](https://docs.ton.org/tvm/overview) supports in-place code replacement within a single compute phase. When an upgrade message is processed: + + 1. `contract.setCodePostponed(msg.code)` schedules the new bytecode to take effect at the end of the compute phase. + 2. The [TVM `c3` register](https://docs.ton.org/tvm/registers#c3--function-selector) (the continuation dispatch table) is immediately redirected to the new code, so the remainder of the upgrade handler runs under the new version. + 3. The `migrate` function — called from the new code with the current storage cell and the previous version string — transforms the old storage layout into the new one. + 4. The migrated storage is written back via `contract.setData`. + 5. An `Upgradeable_UpgradedEvent` is emitted containing the new code cell, its SHA256 hash, and the new version string. + + Because the TVM account (and its address) persists across code changes, no external client or off-chain system needs to update a stored address after an upgrade. The `migrate` and `version` functions are assigned stable method IDs (`@method_id(1000)` and `@method_id(1001)` respectively) that are preserved in every version, ensuring the new code can always locate and call the correct function from the previous version during the upgrade. + + Authorization is enforced before the upgrade executes: every contract checks `ownable.requireOwner(senderAddress)`, so only the `Ownable2Step` owner of the contract — in production, the RBACTimelock — can initiate an upgrade. + +## Implementation Process + +All critical changes to CCIP on TON are governed by a secure, cross-chain process using the **ManyChainMultiSig (MCMS)** system and the **RBACTimelock** contract, which function consistently across all CCIP-supported chains. + +The MCMS deployment on TON consists of three independent MCMS instances — **Proposer**, **Canceller**, and **Bypasser** — each holding its own Merkle root of authorized operations and its own signer quorum configuration. Signers are identified by EVM addresses and arranged in a hierarchical group tree, where each group has its own quorum threshold; a proposal is authorized only when the root group of the tree is satisfied. The MCMS contract stores a `seenSignedHashes` map, so a signed root cannot be replayed once it has been submitted. + +The RBACTimelock is the `Ownable2Step` owner of all core CCIP contracts. The Proposer, Canceller, and Bypasser MCMS instances hold the corresponding roles within the Timelock. Any change to a CCIP contract — whether a configuration update or a code upgrade — requires a message to be sent from the Timelock to the target contract. + +Any proposal must follow one of two paths: + +1. **Time-locked Review**: An authorized quorum of signers approves an operation off-chain and the Proposer MCMS submits the resulting Merkle root onchain via `MCMS_SetRoot`. Anyone who holds a valid Merkle inclusion proof can then call `MCMS_Execute` to deliver the operation to the Timelock as a `Timelock_ScheduleBatch` call, which places the batch into a pending queue with a mandatory `minDelay`. During this review window, the Canceller MCMS can cancel a pending batch via `Timelock_Cancel`. If no cancellation occurs, once the delay expires anyone can deliver a `Timelock_ExecuteBatch` message directly to the Timelock. There is no separate Executor role or Executor MCMS instance on TON: the Timelock is deployed with the executor role check disabled, because TON has no CallProxy equivalent. The Timelock then forwards the contained operation (for example, an `Upgradeable_Upgrade` message or a configuration update) to the target CCIP contract. + +2. **Expedited Approval**: For time-sensitive situations such as emergency responses, the Bypasser MCMS can authorize an operation that is delivered to the Timelock as `Timelock_BypasserExecuteBatch`. This path bypasses the mandatory review delay and immediately forwards the operation to the target contract. + +The entire process is publicly verifiable on-chain. All MCMS roots and executed operations, the Timelock batch schedule and execution history, and the `Upgradeable_UpgradedEvent` emissions from upgraded contracts are recorded as on-chain events. + +--- + # Offchain Architecture - Overview Source: https://docs.chain.link/ccip/concepts/architecture/offchain/overview Last Updated: 2025-05-19 @@ -6139,6 +6489,308 @@ Maintain thorough documentation of multisig operations and provide training for --- +# CCIP Best Practices (TON) +Source: https://docs.chain.link/ccip/concepts/best-practices/ton +Last Updated: 2026-04-07 + + + + + +Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet. + +## Implement all three mandatory receiver protocol steps + +Every TON CCIP receiver must implement three mandatory protocol steps in order. Omitting or reordering any step puts your contract into a broken state — either allowing unauthorized execution or stranding messages that cannot be retried. + + + +**Step 1 — Authorize the Router.** Accept `Receiver_CCIPReceive` only from the configured CCIP Router address. Reject any other sender unconditionally. + +```tolk +assert(in.senderAddress == st.router) throw ERROR_UNAUTHORIZED; +``` + +**Step 2 — Check attached value.** The Router attaches TON to cover the cost of routing `Router_CCIPReceiveConfirm` back through the protocol chain. Verify the attached value meets your `MIN_VALUE` constant. The Router needs at least **0.02 TON** to process the confirmation. Use **0.03 TON** as your baseline and increase it to account for your own execution costs. + +```tolk +assert(in.valueCoins >= MIN_VALUE) throw ERROR_LOW_VALUE; +``` + +**Step 3 — Send `Router_CCIPReceiveConfirm`.** Return the confirmation message to the Router along with enough TON to cover the remaining protocol chain. Using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` is the simplest correct choice — it forwards all remaining value automatically. + +```tolk +val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, +}); +receiveConfirm.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +``` + +The `MinimalReceiver` contract in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton) implements all three steps inline and serves as the reference starting point. For a detailed explanation and multiple implementation options, see [Implementing CCIP Receivers](/ccip/tutorials/ton/receivers). + +## Set `MIN_VALUE` correctly + +`MIN_VALUE` is the minimum TON your receiver requires attached to each `Receiver_CCIPReceive` message. Setting it too low is a critical misconfiguration: + +- If too little TON reaches your contract, the `Router_CCIPReceiveConfirm` may not propagate through the protocol chain. +- The message is left in an undelivered state and **cannot be retried**. Used gas is not refunded. + +Start at **0.03 TON** and benchmark your contract under realistic load to determine the right value. The 0.03 TON baseline includes 0.02 TON for the Router's confirmation cost plus 0.01 TON margin. + +```tolk +// In your receiver contract +const MIN_VALUE: int = ton("0.03"); // increase if your logic is more expensive +``` + +The EVM-side `gasLimit` (set in `extraArgs`) controls how much nanoTON the protocol reserves for delivery. If your receiver requires more than the default 0.1 TON allocation, increase the `gasLimit` in `buildExtraArgsForTON` accordingly: + +```typescript +// In your EVM sender script — increase gasLimit if your TON receiver is expensive +const extraArgs = buildExtraArgsForTON(200_000_000n, true) // 0.2 TON in nanoTON +``` + +## Verify destination chain + +Before sending a CCIP message from TON, verify that the destination chain is supported. The CCIP Router on TON exposes its supported destination chains through the `OnRamp`. Sending a message to an unsupported chain selector will be rejected at the Router level, wasting the TON attached to the transaction. + +Check the [CCIP Directory](/ccip/directory/testnet/chain/ton-testnet) for the list of supported TON → EVM lanes and their chain selectors before hardcoding a destination. Always use the chain selector constant from `helper-config.ts` in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/helper-config.ts) rather than deriving it manually. + +## Verify source chain + +The CCIP protocol delivers messages to your receiver with a `sourceChainSelector` field in `Any2TVMMessage`. **Source chain validation is not enforced at the protocol level on TON** — it is your responsibility to check this field. + +Inside your `Receiver_CCIPReceive` handler, validate `message.sourceChainSelector` against an allowlist of chains your application trusts: + +```tolk +// After the three mandatory protocol steps, validate the source chain +assert(isAllowedSourceChain(msg.message.sourceChainSelector)) throw ERROR_UNTRUSTED_SOURCE; +``` + +Without this check, a valid CCIP message sent from any supported source chain will be accepted and processed by your receiver. + +## Verify sender + +The `sender` field in `Any2TVMMessage` is a `CrossChainAddress` (a `slice` containing the encoded source-chain address). For EVM-to-TON messages, it contains the 20-byte EVM address of the sender contract. + +**Sender validation is also not enforced at the protocol level.** Your contract is responsible for checking this field if your application depends on messages arriving from specific addresses: + +```tolk +// After source chain validation +// For EVM senders, msg.message.sender is a slice containing the 20-byte EVM address +assert(isTrustedSender(msg.message.sender)) throw ERROR_UNTRUSTED_SENDER; +``` + + + +## Use `messageId` for deduplication + +The `messageId` field in `Any2TVMMessage` is a `uint256` that uniquely identifies each CCIP message in the protocol. Store received message IDs and reject any message whose ID has already been processed: + +```tolk +// Check deduplication before processing +assert(!hasBeenProcessed(msg.message.messageId)) throw ERROR_ALREADY_PROCESSED; +// Mark as processed before executing business logic +markProcessed(msg.message.messageId); +``` + +This prevents replay attacks in the event of any re-delivery edge case. The `messageId` should be stored in your contract's persistent storage. + +## Configure `extraArgs` correctly + +The `extraArgs` field controls execution parameters for the destination chain. Its content and meaning differ depending on which chain is the source and which is the destination. + + + +### TON sending to EVM + +When sending from TON to an EVM destination, use `buildExtraArgsForEVM` from the Starter Kit. Two fields must be set correctly: + +**`gasLimit`** — EVM gas units allocated for `_ccipReceive` execution on the destination contract. `100_000` units covers simple message storage (for example, `MessageReceiver.sol`). Increase this for contracts with heavier logic. If the gas limit is too low, execution fails on EVM. For EVM destinations, failed messages can be retried with a higher gas limit via the CCIP manual execution path, but unused gas is not refunded. + +**`allowOutOfOrderExecution`** — Must always be `true` for TON-to-EVM messages. The TON CCIP Router rejects any message where this flag is `false`. + +```typescript filename="scripts/utils/utils.ts" +// gasLimit: EVM gas units for ccipReceive. allowOutOfOrderExecution must be true. +const extraArgs = buildExtraArgsForEVM(100_000, true) +``` + +### EVM sending to TON + +When sending from EVM to a TON destination, use `buildExtraArgsForTON` from the Starter Kit. The parameter meanings differ from the EVM case: + +**`gasLimit`** — Denominated in **nanoTON**, not EVM gas units. This is the amount of nanoTON reserved for execution on the TON destination chain. A starting value of `100_000_000n` (0.1 TON) covers most receive operations. Any unused nanoTON is returned to the contract after execution. + +**`allowOutOfOrderExecution`** — Must be `true` for TON-bound lanes. + +```typescript filename="scripts/utils/utils.ts" +// gasLimit: nanoTON reserved for TON execution (1 TON = 1_000_000_000 nanoTON) +const extraArgs = buildExtraArgsForTON(100_000_000n, true) // 0.1 TON +``` + + + +## Estimate fees accurately and apply a buffer + +The CCIP protocol fee for TON-to-EVM messages is denominated in nanoTON and computed by the FeeQuoter contract, reachable through a chain of on-chain getter calls: + +``` +Router.onRamp(destChainSelector) → OnRamp address +OnRamp.feeQuoter(destChainSelector) → FeeQuoter address +FeeQuoter.validatedFeeCell(ccipSendCell) → fee in nanoTON +``` + +Use the `getCCIPFeeForEVM` helper in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/scripts/utils/utils.ts) to perform this lookup. The CCIP message Cell passed to it must be fully populated — all fields must match the values used in the final send. + +Apply two buffers on top of the quoted fee: + +- **10% fee buffer**: Accounts for small fluctuations between quote time and execution time. +- **0.5 TON gas reserve**: Covers the wallet-level transaction cost and source-chain execution overhead. Any surplus above actual costs is returned via the ACK message. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +const feeWithBuffer = (fee * 110n) / 100n // +10% +const gasReserve = 500_000_000n // 0.5 TON in nanoTON +const valueToAttach = feeWithBuffer + gasReserve // total value sent to Router +``` + +For EVM-to-TON messages, apply the same 10% buffer to the fee returned by `router.getFee()`: + +```typescript filename="scripts/evm2ton/sendMessage.ts" +const fee = await router.getFee(TON_TESTNET_CHAIN_SELECTOR, message) +const feeWithBuffer = (fee * 110n) / 100n +``` + +## Decouple CCIP message reception from business logic + +Your `Receiver_CCIPReceive` handler should perform the three mandatory protocol steps and then delegate to a separate internal function for application logic. Keep the handler itself minimal: + +1. Perform the three mandatory protocol steps (Router check, value check, send confirm). +2. Optionally validate source chain and sender. +3. Check deduplication using `messageId`. +4. Store the incoming message or its processed result in contract storage. +5. Emit an event (if needed for monitoring). +6. Call your application logic from a separate function. + +Keeping reception and business logic separate provides a natural "escape hatch": if your business logic encounters a critical issue, you can upgrade or replace that function without touching the protocol-facing entry point. This pattern also simplifies testing — you can unit-test the business logic independently of the Receiver protocol. + +## Monitor your dApps + +### TON → EVM: track ACK and NACK responses + +After sending a message from TON, the CCIP Router sends back a `Router_CCIPSendACK` (accepted) or `Router_CCIPSendNACK` (rejected). Use the `queryID` field to correlate responses to their originating send. The recommended value for `queryID` is the wallet's current sequence number (`seqno`), which is monotonically increasing and collision-free. + +Use the Starter Kit verification scripts to check the status of your sends: + +```bash filename="Terminal" +# Check ACK/NACK for recent transactions +npm run utils:checkLastTxs -- --ccipSendOnly true + +# Filter to a specific send by QueryID +npm run utils:checkLastTxs -- --queryId +``` + +An ACK contains the CCIP Message ID and a CCIP Explorer URL. A NACK means the Router rejected the message — both the fee and any TON surplus are returned to the sender. + +### EVM → TON: track delivery on TON + +After sending from EVM, track delivery on TON using the message ID from the `ccipSend` transaction: + +```bash filename="Terminal" +# Check delivery of an EVM-to-TON message +npm run utils:checkTON -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "your message" +``` + +EVM-to-TON delivery typically takes 5–15 minutes. You can also track the full message lifecycle on the [CCIP Explorer](https://ccip.chain.link) using the message ID printed by the send script. + +### Set up monitoring alerts + +Build monitoring on top of the verification scripts or TON Testnet block explorer data. Create alerts for: + +- NACKs on TON → EVM sends (rejected messages, insufficient fees). +- Messages that remain undelivered on TON beyond the expected delivery window. +- Sudden changes in CCIP fee levels that may indicate network conditions affecting your buffer assumptions. + +## Evaluate the security and reliability of the networks that you use + +Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your contracts, and the network conditions on those blockchains. + +## Review and audit your code + +Before securing value with contracts that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, [contact the Chainlink Labs Team](https://chain.link/ccip-contact) before deploying your application to mainnet. + +## Soak test your dApps + +Be aware of the [Service Limits for Supported Networks](/ccip/directory). Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions. + +## Monitor your dApps + +When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages. + +Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them. + +## Multi-Signature Authorities + +Multi-signature authorities enhance security by requiring multiple signatures to authorize transactions. + +### Threshold configuration + +Set an optimal threshold for signers based on the trust level of participants and the required security. + +### Role-based access control + +Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals. + +### Hardware wallet integration + +Use hardware wallets for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated. + +### Regular audits and updates + +Conduct periodic audits of signer access and authority settings. Update the multisig setup as necessary, especially when personnel changes occur. + +### Emergency recovery plans + +Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders. + +### Transaction review process + +Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks. + +### Documentation and training + +Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols. + +--- + # CCIP Test Tokens - Faucets for EVM and Solana Source: https://docs.chain.link/ccip/test-tokens Last Updated: 2025-08-19 @@ -23503,6 +24155,1440 @@ Last Updated: 2025-09-03 --- +# Implementing CCIP Receivers +Source: https://docs.chain.link/ccip/tutorials/ton/receivers +Last Updated: 2026-03-29 + + + +# Implementing CCIP Receivers + +A **CCIP Receiver** is a TON smart contract (written in Tolk) that accepts incoming cross-chain messages delivered by the CCIP protocol. When a message is sent from an EVM chain to TON via CCIP, the CCIP Router on TON forwards it to your receiver contract as an internal message. Your contract must validate the delivery, acknowledge it to the protocol, and process the payload. + +## How Message Delivery Works + +When a cross-chain message arrives on TON: + +1. The CCIP off-ramp verifies the message against a Merkle root and routes it through the CCIP Router on TON. +2. The Router sends a `Receiver_CCIPReceive` internal message to your receiver contract, with enough TON attached to cover the confirmation transaction. +3. Your contract performs three mandatory protocol steps, then executes your application logic. +4. Your contract sends `Router_CCIPReceiveConfirm` back to the Router, which marks the message as successfully delivered on-chain. + +## Security Architecture + +### Three Mandatory Protocol Steps + +Every TON CCIP receiver **must** implement all three steps in order: + +**Step 1 — Authorize the Router.** Accept `Receiver_CCIPReceive` messages only from the configured CCIP Router address. Any other sender must be rejected. + +```tolk +assert(in.senderAddress == st.router) throw ERROR_UNAUTHORIZED; +``` + +**Step 2 — Check attached value.** The Router forwards TON with the message to cover the confirmation transaction. Verify the attached value meets `MIN_VALUE`. The Router needs at least **0.02 TON** to send `Router_CCIPReceiveConfirm` back through the protocol chain. Use **0.03 TON** as a baseline and increase it to cover your own execution costs. + +```tolk +assert(in.valueCoins >= MIN_VALUE) throw ERROR_LOW_VALUE; +``` + +**Step 3 — Acknowledge delivery.** Send `Router_CCIPReceiveConfirm` back to the Router with enough TON attached to cover the protocol's confirmation chain. Using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` is the simplest approach — it forwards all remaining value automatically — but any send mode is valid as long as at least **0.02 TON** reaches the Router. + +```tolk +val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, +}); +receiveConfirm.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +``` + + + +### Developer Responsibilities + +Unlike EVM receivers, **source chain and sender validation are not enforced at the protocol level on TON** — only the Router address check (step 1) is a protocol requirement. Your contract is responsible for any application-layer checks: + +- **Source chain validation**: Check `message.sourceChainSelector` against an allowlist of trusted chains. +- **Sender validation**: Check `message.sender` against trusted source-side addresses. + +Without these checks, any address on any chain can send a CCIP message to your receiver and have it processed. + +## Message Structure + +The CCIP Router delivers an `Any2TVMMessage` struct inside each `Receiver_CCIPReceive` message: + +```tolk filename="chainlink-ton/contracts/contracts/lib/receiver/types.tolk" +struct Any2TVMMessage { + messageId: uint256; // Unique message identifier + sourceChainSelector: uint64; // CCIP chain selector of the originating chain + sender: CrossChainAddress; // Encoded sender address from the source chain + data: cell; // Arbitrary payload (your application data) + tokenAmounts: cell?; // Reserved for future token support; currently unused +} +``` + +| Field | Type | Description | +| --------------------- | ------------------- | ---------------------------------------------------------------------------------------- | +| `messageId` | `uint256` | Unique identifier — use for deduplication to prevent replay | +| `sourceChainSelector` | `uint64` | CCIP selector of the originating chain | +| `sender` | `CrossChainAddress` | Encoded source-chain sender address; for EVM sources, these are the 20 EVM address bytes | +| `data` | `cell` | Application payload encoded as a TON Cell | +| `tokenAmounts` | `cell?` | Reserved for future token-transfer support; currently `null` | + +`CrossChainAddress` is a `slice` type. For EVM-to-TON messages, it contains the 20-byte EVM address of the sender. + + + +## Receiver Implementations + +The starter kit provides three receiver contracts at different complexity levels. Choose the one that fits your use case, or use one as a starting template. + +## After Deployment + +After deploying, send a test message from an EVM chain to verify delivery: + +```bash filename="Terminal" +npm run evm2ton:send -- \ + --sourceChain \ + --tonReceiver \ + --msg "Hello TON from EVM" \ + --feeToken native +``` + +Then confirm the message was received on TON: + +```bash filename="Terminal" +npm run utils:checkTON -- \ + --sourceChain \ + --tonReceiver \ + --msg "Hello TON from EVM" +``` + + + +--- + +# CCIP Tutorials: TON to EVM +Source: https://docs.chain.link/ccip/tutorials/ton/source +Last Updated: 2026-03-29 + +This section provides comprehensive guides and tutorials for implementing cross-chain communication from the TON blockchain to Ethereum Virtual Machine (EVM) chains using Chainlink's Cross-Chain Interoperability Protocol (CCIP). + + + +## Getting Started + +Before implementing specific use cases, it's important to set up your environment and understand the fundamental concepts: + +- [Prerequisites for TON to EVM Tutorials](/ccip/tutorials/ton/source/prerequisites) - Set up your development environment with Node.js, a TON wallet, and testnet tokens. +- [Building CCIP Messages from TON to EVM](/ccip/tutorials/ton/source/build-messages) - Learn the TL-B Cell layout, required fields, fee estimation, and sending options. + +## Tutorials by Use Case + +Depending on your specific needs, choose the appropriate tutorial: + +- [Arbitrary Messaging](/ccip/tutorials/ton/source/arbitrary-messaging) - Send a data payload from TON Testnet to a receiver contract on Ethereum Sepolia. + +--- + +# Building CCIP Messages from TON to EVM +Source: https://docs.chain.link/ccip/tutorials/ton/source/build-messages +Last Updated: 2026-03-29 + +## Introduction + +This guide explains how to construct CCIP messages from the TON blockchain to EVM chains (e.g., Ethereum Sepolia, Arbitrum Sepolia). TON's CCIP integration currently supports **arbitrary messaging only** — token transfers are not supported on TON lanes. + +You send a CCIP message from TON by constructing a [Cell](https://docs.ton.org/foundations/serialization/cells) in the specific [TL-B layout](https://docs.ton.org/languages/tl-b/overview) expected by the CCIP Router contract, then sending that Cell as the body of an internal TON message to the Router address with enough TON attached to cover both the CCIP protocol fee and source-chain execution costs. + + + +## CCIP Message Cell Layout on TON + +CCIP messages from TON are sent by constructing a `Router_CCIPSend` Cell and delivering it as the body of an [internal message](https://docs.ton.org/foundations/messages/internal) to the CCIP Router address on TON Testnet (`EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj`). + +The `buildCCIPMessageForEVM` helper in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/scripts/utils/utils.ts) assembles this Cell: + +```typescript filename="scripts/utils/utils.ts" +import { Address, beginCell, Cell } from "@ton/core" + +const CCIP_SEND_OPCODE = 0x31768d95 + +export function buildCCIPMessageForEVM( + queryID: bigint | number, + destChainSelector: bigint | number, + receiverBytes: Buffer, // 32 bytes: 12 zero-bytes + 20-byte EVM address + data: Cell, // message payload + feeToken: Address, // native TON address + extraArgs: Cell // GenericExtraArgsV2 cell +): Cell { + return beginCell() + .storeUint(CCIP_SEND_OPCODE, 32) // Router opcode + .storeUint(queryID, 64) // unique message identifier (wallet seqno) + .storeUint(destChainSelector, 64) // destination chain selector + .storeUint(receiverBytes.length, 8) // receiver byte-length prefix (always 32) + .storeBuffer(receiverBytes) // encoded EVM receiver address + .storeRef(data) // message payload cell + .storeRef(Cell.EMPTY) // tokenAmounts — always empty for TON + .storeAddress(feeToken) // fee token (native TON only) + .storeRef(extraArgs) // GenericExtraArgsV2 cell + .endCell() +} +``` + +The following sections describe each field in detail. + +*** + +### queryID + +- **Type**: `uint64` +- **Purpose**: A unique identifier that lets the TON CCIP Router correlate `Router_CCIPSendACK` and `Router_CCIPSendNACK` responses back to the originating send. +- **Recommended value**: Use the sending wallet's current sequence number (`seqno`). Wallet seqnos are monotonically increasing and unique per wallet, making them collision-free. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const seqno = await walletContract.getSeqno() +// pass BigInt(seqno) as queryID +``` + + + +*** + +### destChainSelector + +- **Type**: `uint64` +- **Purpose**: Identifies the destination EVM chain where the message will be delivered. +- **Supported chains**: See the [CCIP Directory](/ccip/directory/testnet/chain/ton-testnet) for the complete list of supported TON → EVM lanes and their chain selectors. + +```typescript filename="helper-config.ts" +// From helper-config.ts in the Starter Kit +const destChainSelector = BigInt(networkConfig["sepolia"].chainSelector) +// => 16015286601757825753n (Ethereum Sepolia) +``` + +*** + +### receiver + +- **Definition**: The address of the contract on the destination EVM chain that will receive the CCIP message. +- **Encoding**: EVM addresses are 20 bytes, but the TON CCIP Router expects a 32-byte buffer. Left-pad the 20-byte address with 12 zero-bytes. + +```typescript filename="scripts/utils/utils.ts" +export function encodeEVMAddress(evmAddr: string): Buffer { + const addrBytes = Buffer.from(evmAddr.slice(2), "hex") // strip '0x' + return Buffer.concat([Buffer.alloc(12, 0), addrBytes]) // 12 zero-bytes + 20-byte address +} +``` + +**Usage:** + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const receiverBytes = encodeEVMAddress("0xYourEVMReceiverAddress") +// receiverBytes.length === 32 +``` + + + +*** + +### data + +- **Definition**: The raw bytes delivered to the `_ccipReceive` function on the destination EVM contract via `Client.Any2EVMMessage.data`. +- **Format**: A TL-B `Cell` containing your message payload. + +For sending a plain text string: + +```typescript filename="scripts/ton2evm/sendMessage.ts" +import { beginCell } from "@ton/core" + +const data = beginCell().storeStringTail("Hello EVM from TON").endCell() +``` + +The EVM receiver contract receives these bytes as `message.data` and is responsible for interpreting them. The `MessageReceiver.sol` contract in the Starter Kit emits the raw bytes in a `MessageFromTON` event, which can be decoded with `ethers.toUtf8String(message.data)`. + + + +*** + +### tokenAmounts + +- **Value**: Always `Cell.EMPTY`. +- **Reason**: Token transfers are not supported on TON CCIP lanes. All messages from TON carry data only. + +```typescript +.storeRef(Cell.EMPTY) // tokenAmounts — must always be an empty cell +``` + +*** + +### feeToken + +- **Definition**: The token used to pay the CCIP protocol fee on TON. +- **Supported value**: Only native TON is supported. Paying fees in LINK is not available for TON-to-EVM messages. +- **Address**: `EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99` + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const feeToken = Address.parse(networkConfig.tonTestnet.nativeTokenAddress) +// "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99" +``` + +The CCIP protocol fee is deducted from the TON value attached to the Router message, not from a separate token transfer. + +*** + +### extraArgs + +The `extraArgs` field is a `Cell` encoding [`GenericExtraArgsV2`](/ccip/api-reference/evm/v1.6.1/client#genericextraargsv2) parameters required by the destination EVM chain. The tag `0x181dcf10` is the [`GENERIC_EXTRA_ARGS_V2_TAG`](/ccip/api-reference/evm/v1.6.1/client#generic_extra_args_v2_tag). The Cell layout is: + +| Field | Type | Description | +| -------------------------- | --------- | --------------------------------------------------- | +| `tag` | `uint32` | `0x181dcf10` — identifies GenericExtraArgsV2 format | +| `hasGasLimit` | `bit` | Must be `1` (gas limit is always present) | +| `gasLimit` | `uint256` | EVM gas units allocated for receiver execution | +| `allowOutOfOrderExecution` | `bit` | Must be `1` for TON-to-EVM messages | + +```typescript filename="scripts/utils/utils.ts" +export function buildExtraArgsForEVM(gasLimitEVMUnits: number, allowOutOfOrderExecution: boolean): Cell { + return beginCell() + .storeUint(0x181dcf10, 32) // GenericExtraArgsV2 tag + .storeBit(true) // gasLimit IS present + .storeUint(gasLimitEVMUnits, 256) // gasLimit in EVM gas units + .storeBit(allowOutOfOrderExecution) // must be true + .endCell() +} +``` + + + +*** + +## Estimating the CCIP Fee + +Before sending, query the protocol fee. The fee is returned in [nanoTON](https://docs.ton.org/foundations/fees) and is computed by the FeeQuoter contract, reachable through a chain of on-chain getter calls: + +``` +Router.onRamp(destChainSelector) → OnRamp address +OnRamp.feeQuoter(destChainSelector) → FeeQuoter address +FeeQuoter.validatedFeeCell(ccipSendCell) → fee in nanoTON +``` + +The `getCCIPFeeForEVM` helper in the Starter Kit performs this lookup. The CCIP message Cell passed to it must be fully populated — `queryID`, `destChainSelector`, `receiver`, `data`, `feeToken`, and `extraArgs` must all match the values used in the final send. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +import { TonClient } from "@ton/ton" +import { fromNano } from "@ton/core" + +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +console.log(`CCIP fee: ${fromNano(fee)} TON`) +``` + +### Applying a buffer and gas reserve + +Add a buffer on top of the quoted fee to account for minor variations between quote time and execution: + +- **10% fee buffer**: Covers small fluctuations in the protocol fee. +- **0.5 TON gas reserve**: Covers the wallet-level transaction fee and source-chain execution. This is sent to the Router along with the fee and any surplus is returned via the ACK message. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +const feeWithBuffer = (fee * 110n) / 100n // +10% +const gasReserve = 500_000_000n // 0.5 TON in nanoTON + +const valueToAttach = feeWithBuffer + gasReserve // total value sent to Router +``` + +*** + +## Sending the Message + +After building the Cell and calculating the total value to attach, you have two options. + +*** + +## Reference: Full Message Construction + +The complete flow from wallet setup to sending is available in the TON Starter Kit: + +## Related Tutorials + +To see these concepts in action with a step-by-step implementation guide, check out the following tutorial: + +- [Arbitrary Messaging: TON to EVM](/ccip/tutorials/ton/source/arbitrary-messaging) — Learn how to send data messages from a TON wallet to an EVM receiver contract. + + + +--- + +# Prerequisites for TON to EVM Tutorials +Source: https://docs.chain.link/ccip/tutorials/ton/source/prerequisites +Last Updated: 2026-03-29 + +Before starting the TON to EVM tutorials, ensure you have the following: + +## Development Environment + +- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with: + + ```bash filename="Terminal" + node -v + ``` + + Example output: + + ```text + $ node -v + v22.15.0 + ``` + +- **npm**: For installing and managing dependencies. + +- **Git**: For cloning the repository. + +## Starter Kit Repository + +1. Clone the CCIP TON Starter Kit: + + ```bash filename="Terminal" + git clone https://github.com/smartcontractkit/ccip-starter-kit-ton.git + ``` + +2. Navigate to the directory: + + ```bash filename="Terminal" + cd ccip-starter-kit-ton + ``` + +3. Install dependencies: + + ```bash filename="Terminal" + npm install + ``` + +This installs the required TON SDK packages, including [`@ton/core`](https://github.com/ton-org/ton-core), [`@ton/ton`](https://github.com/ton-org/ton), and [`@ton/crypto`](https://github.com/ton-org/ton-crypto), which are used to interact with the TON blockchain. + +## Understanding TON Network Configuration + +The TON Starter Kit uses a `helper-config.ts` file to manage network settings and contract addresses for TON Testnet. This configuration includes: + +- **CCIP Router Address**: The on-chain address of the CCIP Router contract on TON +- **Chain Selectors**: Unique identifiers for destination EVM chains +- **RPC Endpoints**: URLs for connecting to TON blockchain nodes + +**Example configuration snippet:** + +```typescript filename="helper-config.ts" +tonTestnet: { + chainSelector: '1399300952838017768', + router: 'EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj', + // ... + destChains: { + sepolia: 'sepolia', + arbitrumSepolia: 'arbitrumSepolia' + }, + // ... +}, +sepolia: { + chainSelector: '16015286601757825753', + router: '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59', + // ... +}, +arbitrumSepolia: { + chainSelector: '3478487238524512106', + router: '0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165', + // ... +}, +``` + +## Wallets + +- **TON Wallet**: You'll need a TON wallet to send transactions. The starter kit uses the **[V4R2 wallet version](https://docs.ton.org/standard/wallets/v4)**. You can create a TON wallet using [Tonkeeper](https://tonkeeper.com/) (available on iOS, Android, desktop, and as a browser extension): + + 1. Download and install Tonkeeper + 2. Create a new wallet and save the [24-word recovery phrase (mnemonic)](https://docs.ton.org/standard/wallets/mnemonics) + 3. Add a Testnet Account using the same mnemonic + 4. In wallet settings, select **V4R2** as the wallet version + + + + The wallet's private key is derived from the 24-word mnemonic phrase and stored in your environment configuration. + +- **EVM Wallet Address**: You'll need an EVM-compatible wallet address to receive messages on the destination chain (e.g., Ethereum Sepolia or Arbitrum Sepolia). You only need the address itself, not the private key, as you are only sending *to* this address. + +## Environment Configuration (`.env` file) + +The starter kit uses a `.env` file to manage sensitive information like mnemonic phrases and RPC URLs. Create a new file named `.env` in the root of the `ccip-starter-kit-ton` directory by copying the example file: + +```bash filename="Terminal" +cp .env.example .env +``` + +Next, open the `.env` file and fill in the required values: + +- `TON_MNEMONIC`: Your 24-word mnemonic phrase for the TON wallet. This is used to derive the private key for signing transactions on TON. + +- `EVM_PRIVATE_KEY`: The private key of your EVM wallet (with `0x` prefix). Required for deploying receiver contracts on EVM chains (e.g., `deploy:evm:receiver`). The corresponding wallet must hold Sepolia ETH. + +- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for Ethereum Sepolia. This is used by verification scripts to check message execution status on the destination chain. You can obtain a free RPC URL from providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or [QuickNode](https://www.quicknode.com/). + +- `ARBITRUM_SEPOLIA_RPC_URL` (optional): The RPC endpoint for Arbitrum Sepolia if you plan to send messages to this chain. + +- `TON_RPC_URL` (optional): Override the default TON RPC endpoint. The default is `https://ton-testnet.api.onfinality.io/public`, which doesn't require an API key. + +- `TON_CENTER_API_KEY` (optional): Only required if you use a toncenter RPC URL (e.g., `https://testnet.toncenter.com/api/v2/jsonRPC`). Get a free API key from [@tonapibot](https://t.me/tonapibot) on Telegram. + +**Example `.env` file:** + +```bash filename=".env" +TON_MNEMONIC="word1 word2 word3 ... word24" +EVM_PRIVATE_KEY=0xYourEVMPrivateKey +ETHEREUM_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY +ARBITRUM_SEPOLIA_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/YOUR_API_KEY +``` + + + +## Native TON Tokens for Transaction Fees + +**TON** tokens are required for all transactions on the TON blockchain, including sending CCIP messages. TON is also used to pay for CCIP fees when sending cross-chain messages. + + + +### Getting TON on Testnet + +To obtain TON tokens on Testnet, use the official TON Testnet faucet: + +1. Visit the [TON Testnet Faucet](https://t.me/testgiver_ton_bot) +2. Open Telegram and start a chat with the `@testgiver_ton_bot` +3. Send your V4R2 testnet wallet address to the bot +4. The bot will send you 2 testnet TON (you can request again every 60 minutes) + +### Checking Your TON Balance + +You can check your wallet balance using TON blockchain explorers: + +- [Testnet Explorer](https://testnet.tonscan.org/): Enter your wallet address to view balance and transaction history +- TON wallet applications like Tonkeeper also display your balance + +Alternatively, you can use the TON SDK in a script to query your balance programmatically: + +```typescript +import { TonClient } from "@ton/ton" + +const client = new TonClient({ + endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", +}) + +const balance = await client.getBalance(yourWalletAddress) +console.log(`Balance: ${balance} nanoTON`) +``` + + + +--- + +# Arbitrary Messaging: TON to EVM +Source: https://docs.chain.link/ccip/tutorials/ton/source/arbitrary-messaging +Last Updated: 2026-03-29 + +This tutorial demonstrates how to send a CCIP arbitrary message from the TON blockchain to an Ethereum Virtual Machine (EVM) chain using Chainlink CCIP. You will learn how to build a CCIP message on TON, send it using a script, and verify its delivery on the destination chain. + + + +## Introduction + +This tutorial covers sending a data-only message from TON Testnet to Ethereum Sepolia without any token transfer. When you send a message using CCIP: + +1. The `Router_CCIPSend` Cell is submitted to the CCIP Router on TON. +2. The Router validates the message, deducts the protocol fee from the attached TON, and forwards the message to the CCIP Decentralized Oracle Network (DON). +3. The DON delivers the message to the `_ccipReceive` function of the receiver contract on Ethereum Sepolia. + +## What You Will Build + +In this tutorial, you will: + +- Use a script to configure a CCIP message with a text payload. +- Send the message from TON Testnet to a receiver contract on Ethereum Sepolia. +- Pay for CCIP transaction fees using native TON. +- Monitor and verify your cross-chain message. + + + +## Understanding Arbitrary Messaging from TON to EVM + +This tutorial focuses on data-only cross-chain messages from your TON Testnet wallet to a contract on Ethereum Sepolia. Key points specific to arbitrary messaging from TON: + +- **Data only**: TON CCIP lanes support arbitrary messaging only. No tokens are transferred — only a bytes payload is delivered to the receiver contract. +- **Fee Payment**: Transaction fees are paid on TON using native TON tokens. Paying with LINK is not available on TON-to-EVM lanes. +- **Message Construction**: A script constructs and delivers the `Router_CCIPSend` Cell by calling helpers from `scripts/utils/utils.ts` and sending it to the CCIP Router address on TON Testnet. + +## How the Script Works + +The `ton2evm/sendMessage.ts` script handles the interaction with the CCIP Router on your behalf. Here is what happens behind the scenes: + +1. **Context Initialization**: The script connects to TON Testnet and loads your wallet from the `TON_MNEMONIC` environment variable. +2. **Argument Parsing**: It reads your command-line arguments (`--destChain`, `--evmReceiver`, `--msg`, and optionally `--tonSender`) to determine the destination chain, receiver address, message content, and sending mode. +3. **Message Construction**: It encodes the EVM receiver address using `encodeEVMAddress`, builds the `GenericExtraArgsV2` Cell using `buildExtraArgsForEVM(100_000, true)`, and assembles the full `Router_CCIPSend` Cell using `buildCCIPMessageForEVM`. Note that `allowOutOfOrderExecution` **must** be `true` — TON-to-EVM lanes require it, and the Router will reject the message otherwise. +4. **Fee Estimation**: It calls `getCCIPFeeForEVM`, which walks the `Router → OnRamp → FeeQuoter` on-chain getter chain to retrieve the protocol fee in nanoTON. +5. **Fee Buffering**: It applies a 10% buffer to the quoted fee and adds a fixed 0.5 TON gas reserve to cover wallet-level transaction costs and source-chain execution. +6. **Sending**: It submits the `Router_CCIPSend` Cell directly from the wallet to the CCIP Router. If `--tonSender` is provided, it instead sends a `CCIPSender_RelayCCIPSend` message to the `MinimalSender` contract, which forwards the Cell to the Router. + +## Running the Arbitrary Message + +### Prerequisites Check + +Before running the script: + +1. Ensure you've completed the setup steps outlined in the [prerequisites](/ccip/tutorials/ton/source/prerequisites). + +2. Make sure your `.env` file contains `TON_MNEMONIC` (your 24-word phrase) and `ETHEREUM_SEPOLIA_RPC_URL`. + +3. Verify you have sufficient TON balance in your testnet wallet. At least **1 TON** is recommended to cover the CCIP protocol fee plus gas. + +4. Deploy the `MessageReceiver` contract on Ethereum Sepolia. This EVM contract receives and stores the CCIP message. The `EVM_PRIVATE_KEY` in your `.env` must correspond to a wallet with Sepolia ETH. + + ```bash filename="Terminal" + npm run deploy:evm:receiver -- --evmChain sepolia + ``` + + On success, the script prints output similar to the following: + + ``` + 🚀 Deploying MessageReceiver contract to sepolia... + + 📤 Deploying from account: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA + 💰 Account balance: 8.08890300048914095 ETH + + ⏳ Deploying MessageReceiver with router: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 + + ✅ MessageReceiver deployed successfully! + 📍 Contract address: 0x960c39e1E53d595cA1932926585c7dd5bF497300 + 📍 Router address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 + + 📝 Next steps: + 1. Wait 1-2 minutes for Etherscan to index the contract + 2. Verify the contract (optional): + npx hardhat verify 0x960c39e1E53d595cA1932926585c7dd5bF497300 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 --network sepolia + 3. Send a test message to the deployed receiver: + npm run ton2evm:send -- --destChain sepolia --evmReceiver 0x960c39e1E53d595cA1932926585c7dd5bF497300 --msg "Hello EVM from TON" --feeToken native + + 🔍 View on explorer: + https://sepolia.etherscan.io/address/0x960c39e1E53d595cA1932926585c7dd5bF497300 + ``` + + **Save the contract address** — you will pass it as `--evmReceiver` in the next step. + + + +### Execute the Script + +Run the following command from your terminal to send a message from TON Testnet to Ethereum Sepolia: + +```bash filename="Terminal" +npm run ton2evm:send -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" +``` + +Replace `` with the contract address from the deployment step above. + +To send via an on-chain `MinimalSender` contract instead (see [Build CCIP Messages — Via Sender Contract](/ccip/tutorials/ton/source/build-messages#sending-the-message)), pass its TON address with `--tonSender`: + +```bash filename="Terminal" +npm run ton2evm:send -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" \ + --tonSender +``` + +### Understanding the Output + +When the script executes successfully, you will see logs similar to the following: + +``` +🧪 Testing TON → EVM Messaging + +🌐 Destination Chain: sepolia +💸 Fee Token: native +✅ Connected to TON, Block: 49273773 +📤 Sending from: EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +💰 Balance: 106.80152516 TON + +🔑 QueryID (seqno): 71 +💸 Estimated CCIP fee: 894032 nanoTON (0.000894032 TON) +💸 Fee with 10% buffer: 983435 nanoTON (0.000983435 TON) +💸 Gas reserve: 0.5 TON +✅ Transaction sent! + +🔍 Monitor your transaction: + https://testnet.tonviewer.com/EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK + +🔍 Monitor delivery on sepolia: + https://sepolia.etherscan.io/address/0x960c39e1E53d595cA1932926585c7dd5bF497300 + +💡 Run verification scripts after a few minutes: + +1. Check router ACK/NACK status: + All recent CCIP sends: + npm run utils:checkLastTxs -- --ccipSendOnly true + This specific send (queryID: 71): + npm run utils:checkLastTxs -- --queryId 71 + +2. Once ACK is confirmed, verify delivery on EVM: + npm run utils:checkEVM -- --destChain sepolia --evmReceiver 0x960c39e1E53d595cA1932926585c7dd5bF497300 --msg "Hello EVM from TON" +``` + +- The **QueryID** (equal to your wallet's current `seqno`) is printed. The TON CCIP Router uses this value to correlate its `Router_CCIPSendACK` or `Router_CCIPSendNACK` response back to your send. +- The TON Testnet Explorer URL lets you inspect the transaction and confirm it was not bounced. +- The script prints the exact verification commands to run next. + +## Verification + +After sending your message, you can verify its delivery on Ethereum Sepolia in the following ways: + +### Check ACK/NACK Status + +First, confirm that the TON CCIP Router accepted your message by checking for an ACK response: + +```bash filename="Terminal" +npm run utils:checkLastTxs -- --ccipSendOnly true +``` + +To filter to a specific send by QueryID: + +```bash filename="Terminal" +npm run utils:checkLastTxs -- --queryId +``` + +An ACK confirms the Router accepted the message and submitted it to the DON for cross-chain delivery. When an ACK is found, the script also prints the CCIP Message ID and a **CCIP Explorer** URL. + + + +### Track on CCIP Explorer + +Use the CCIP Explorer URL printed alongside the ACK to monitor the full cross-chain message lifecycle: + +``` +https://ccip.chain.link/#/side-drawer/msg/ +``` + +### Verify Delivery on EVM + +After you receive a Router ACK, use the `utils:checkEVM` script to verify that the message was delivered to the receiver contract on Ethereum Sepolia. This script queries `MessageFromTON` events on the receiver contract and compares the message content and source chain selector. + +CCIP delivery from TON Testnet typically takes **5–15 minutes** after the Router ACK. After waiting, run: + + + +```bash filename="Terminal" +npm run utils:checkEVM -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" +``` + +*Replace `` with the deployed contract address and ensure `--msg` matches the string you sent exactly (case-sensitive).* + +When the message has been successfully delivered, you will see output similar to the following: + +``` +═══════════════════════════════════════════════════════════════ + TON → EVM Message Verification +═══════════════════════════════════════════════════════════════ + +📍 Receiver Contract: 0x960c39e1E53d595cA1932926585c7dd5bF497300 +🌐 Destination Chain: sepolia +🔍 Looking for message: "Hello EVM from TON" +🔍 Expected source: 1399300952838017768 (TON Testnet) + +📊 Checking contract state... + +📨 Latest message in contract state: + Message ID: 0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + Message: "Hello EVM from TON" +📊 Scanning blocks 10549450 to 10554450 ... + +═══════════════════════════════════════════════════════════════ + ✅ CCIP MESSAGE FOUND +═══════════════════════════════════════════════════════════════ + +📨 Most Recent CCIP Message: + Message ID: 0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + Source Chain: 1399300952838017768 (TON Testnet ✓) + Message: "Hello EVM from TON" + Block: 10554441 + Time: 2026-03-30T15:25:24.000Z + TX Hash: 0xb7a478440813a15d57e290905463fbefa186b609e95020444be179e474bc4b8a + + 📍 Received 2 minute(s) ago + +═══════════════════════════════════════════════════════════════ + VERIFICATION RESULT +═══════════════════════════════════════════════════════════════ + +✅ Message verified successfully! + + ✓ Message content matches: "Hello EVM from TON" + ✓ Source chain is TON Testnet + +🔗 View on explorer: + https://sepolia.etherscan.io/tx/0xb7a478440813a15d57e290905463fbefa186b609e95020444be179e474bc4b8a +``` + +### Verify on Etherscan + +Once the `utils:checkEVM` script confirms delivery, you can perform a final verification on the Sepolia block explorer. + +- Visit the [Sepolia Etherscan Explorer](https://sepolia.etherscan.io/). +- Search for your `MessageReceiver` contract address. +- Under the **Events** tab, you should see a `MessageFromTON` event with the message ID and your encoded payload. + + + +--- + +# CCIP Tutorials: EVM to TON +Source: https://docs.chain.link/ccip/tutorials/ton/destination +Last Updated: 2026-03-29 + +This section provides guides and tutorials for implementing cross-chain communication from Ethereum Virtual Machine (EVM) chains to the TON blockchain using Chainlink's Cross-Chain Interoperability Protocol (CCIP). + +## Getting Started + +Before implementing specific use cases, understand the fundamental concepts and message structure for EVM to TON communication: + +- [Building CCIP Messages from EVM to TON](/ccip/tutorials/ton/destination/build-messages) - Learn the core message structure, required parameters, and implementation details for arbitrary messaging. +- [Prerequisites for EVM to TON Tutorials](/ccip/tutorials/ton/destination/prerequisites) - Set up your development environment with EVM and TON wallets, the starter kit, and testnet tokens. + +## Tutorials by Use Case + +Depending on your specific needs, choose the appropriate tutorial: + +- [Arbitrary Messaging](/ccip/tutorials/ton/destination/arbitrary-messaging) - Send data from an EVM chain to a receiver contract on TON. + +--- + +# Building CCIP Messages from EVM to TON +Source: https://docs.chain.link/ccip/tutorials/ton/destination/build-messages +Last Updated: 2026-03-29 + +## 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. + + + +## 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 format: 4 bytes for the workchain identifier (big-endian signed int32) followed by 32 bytes for the account hash. Use the `encodeTONAddress` helper shown below. + + + +### 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. + + + +### 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 nanoTON reserved for execution on the TON destination chain. +- **Units**: This value is denominated in **nanoTON**, not EVM gas units. 1 TON = 1,000,000,000 nanoTON. A starting value of `100_000_000n` (0.1 TON) covers most receive operations. Any unused nanoTON 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. + + + +## Implementation by Message Type + +### Arbitrary Messaging + +Use this configuration when sending a data payload to a custom smart contract on TON. + + + +## 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. + + + +--- + +# Prerequisites for EVM to TON Tutorials +Source: https://docs.chain.link/ccip/tutorials/ton/destination/prerequisites +Last Updated: 2026-03-29 + +Before starting the EVM to TON tutorials, ensure you have the following: + +## Development Environment + +- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with: + + ```bash filename="Terminal" + node -v + ``` + + Example output: + + ```text + $ node -v + v22.15.0 + ``` + +- **npm**: For installing and managing dependencies. + +- **Git**: For cloning the repository. + +## Starter Kit Repository + +1. Clone the CCIP TON Starter Kit: + + ```bash filename="Terminal" + git clone https://github.com/smartcontractkit/ccip-starter-kit-ton.git + ``` + +2. Navigate to the directory: + + ```bash filename="Terminal" + cd ccip-starter-kit-ton + ``` + +3. Install dependencies: + + ```bash filename="Terminal" + npm install + ``` + +This installs the required TON SDK packages, including [`@ton/core`](https://github.com/ton-org/ton-core), [`@ton/ton`](https://github.com/ton-org/ton), and [`@ton/crypto`](https://github.com/ton-org/ton-crypto), as well as [`ethers`](https://github.com/ethers-io/ethers.js) for the EVM-side scripts. + +## Wallets + +- **EVM Wallet and Private Key**: You need an EVM wallet to send CCIP messages from an EVM chain (e.g., Ethereum Sepolia) and to deploy receiver contracts. + - Set up a wallet like [MetaMask](https://metamask.io/). + - Export the private key for the account you intend to use. Follow the [official MetaMask guide](https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/) to obtain your private key. + +- **TON Wallet**: You need a TON wallet to deploy the receiver contract on TON Testnet. The starter kit uses the **[V4R2 wallet version](https://docs.ton.org/standard/wallets/v4)**. You can create a TON wallet using [Tonkeeper](https://tonkeeper.com/): + + 1. Download and install Tonkeeper + 2. Create a new wallet and save the [24-word recovery phrase (mnemonic)](https://docs.ton.org/standard/wallets/mnemonics) + 3. Add a Testnet Account using the same mnemonic + 4. In wallet settings, select **V4R2** as the wallet version + + + +## Environment Configuration (`.env` file) + +The starter kit uses a `.env` file to manage sensitive information like private keys and RPC URLs. Create a new file named `.env` in the root of the `ccip-starter-kit-ton` directory by copying the example file: + +```bash filename="Terminal" +cp .env.example .env +``` + +Next, open the `.env` file and fill in the required values: + +- `EVM_PRIVATE_KEY`: The private key (with `0x` prefix) of your EVM wallet. Required for sending CCIP messages from EVM chains and deploying receiver contracts on TON. + +- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for Ethereum Sepolia. Required when sending from Ethereum Sepolia. You can obtain a free RPC URL from providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or [QuickNode](https://www.quicknode.com/). + +- `ARBITRUM_SEPOLIA_RPC_URL` (optional): The RPC endpoint for Arbitrum Sepolia if you plan to send messages from this chain. + +- `TON_MNEMONIC`: Your 24-word mnemonic phrase for the TON wallet. Required to deploy the receiver contract on TON Testnet. + +- `TON_RPC_URL` (optional): Override the default TON RPC endpoint. The default is `https://ton-testnet.api.onfinality.io/public`, which does not require an API key. + +- `TON_CENTER_API_KEY` (optional): Only required if you use a toncenter RPC URL (e.g., `https://testnet.toncenter.com/api/v2/jsonRPC`). Get a free API key from [@tonapibot](https://t.me/tonapibot) on Telegram. + +**Example `.env` file:** + +```bash filename=".env" +EVM_PRIVATE_KEY=0xYourEVMPrivateKey +ETHEREUM_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY +ARBITRUM_SEPOLIA_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/YOUR_API_KEY +TON_MNEMONIC="word1 word2 word3 ... word24" +``` + + + +## Testnet Tokens + +### ETH on Ethereum Sepolia + +ETH is required to pay for EVM transaction fees and CCIP fees when sending from Ethereum Sepolia. Use the [Chainlink Faucet](https://faucet.chain.link) to get test ETH. + +### LINK on Ethereum Sepolia (optional) + +When paying CCIP fees in LINK instead of ETH, you need LINK tokens on the source chain. Use the [Chainlink Faucet](https://faucet.chain.link) to get test LINK. + +### TON on Testnet + +A small amount of TON is required to deploy the receiver contract and pay for transactions on TON Testnet. + +1. Visit the [TON Testnet Faucet](https://t.me/testgiver_ton_bot) on Telegram +2. Send your V4R2 testnet wallet address to `@testgiver_ton_bot` +3. The bot will send you 2 testnet TON (you can request again every 60 minutes) + +--- + +# Arbitrary Messaging: EVM to TON +Source: https://docs.chain.link/ccip/tutorials/ton/destination/arbitrary-messaging +Last Updated: 2026-03-29 + +This tutorial demonstrates how to send a CCIP arbitrary message from an Ethereum Virtual Machine (EVM) chain to the TON blockchain using Chainlink CCIP. You will learn how to deploy a receiver contract on TON, send a message from Ethereum Sepolia, and verify its delivery. + + + +## Introduction + +This tutorial covers sending a data-only message from Ethereum Sepolia to a receiver contract on TON Testnet. When you send a message using CCIP: + +1. The `ccipSend` transaction is submitted to the CCIP Router on the source EVM chain. +2. The Router deducts the protocol fee and forwards the message to the CCIP Decentralized Oracle Network (DON). +3. The DON delivers the message to the `Receiver_CCIPReceive` handler of your receiver contract on TON Testnet. +4. Your receiver contract acknowledges delivery by sending `Router_CCIPReceiveConfirm` back to the TON CCIP Router. + +## What You Will Build + +In this tutorial, you will: + +- Deploy a `MinimalReceiver` contract on TON Testnet. +- Configure a CCIP message with a text payload. +- Send the message from Ethereum Sepolia to your deployed receiver. +- Pay for CCIP fees using native ETH or LINK. +- Monitor and verify cross-chain message delivery on TON. + + + +## Deploy the Receiver on TON + +Before sending a message, you need a receiver contract deployed on TON Testnet. The starter kit includes a deploy script for the `MinimalReceiver` contract: + +```bash filename="Terminal" +npm run deploy:ton:receiver:minimal +``` + +On success, the script prints output similar to the following: + +``` +🚀 Deploying MinimalReceiver contract to TON Testnet... + +📤 Deploying from wallet: EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +Explorer: https://testnet.tonviewer.com/EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +💰 Wallet balance: 106.538102666 TON + +⏳ Compiling MinimalReceiver.tolk... +📍 Contract will be deployed at: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +📍 Router address (authorized caller): EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj + +⏳ Sending deployment transaction... + +✅ MinimalReceiver deployment initiated! +📍 Contract address: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +📝 Next steps: +1. Wait 1-2 minutes for the transaction to be confirmed +2. Copy the contract address above — pass it as --tonReceiver when sending messages +3. Verify deployment on TON explorer: + https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +4. Send a test message to the deployed receiver: + npm run evm2ton:send -- --sourceChain --tonReceiver EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT --msg "Hello TON from EVM" --feeToken native +``` + +**Save the contract address** — you will pass it as `--tonReceiver` in the next step. + + + +## How the Script Works + +The `evm2ton/sendMessage.ts` script handles the EVM-side interaction with the CCIP Router on your behalf. Here is what happens behind the scenes: + +1. **Context Initialization**: The script connects to the EVM chain and loads your wallet from the `EVM_PRIVATE_KEY` environment variable. +2. **Argument Parsing**: It reads your command-line arguments (`--sourceChain`, `--tonReceiver`, `--msg`, and `--feeToken`) to determine the source chain, receiver address, message content, and fee payment method. +3. **Address Encoding**: It encodes your TON receiver address into the 36-byte format expected by the EVM-side CCIP message using `encodeTONAddress()`. +4. **Message Construction**: It builds the `EVM2AnyMessage` struct using `buildCCIPMessageForTON(receiverBytes, messageData, 100_000_000n, true, selectedFeeToken)`. The `100_000_000n` nanoTON `gasLimit` covers receiver execution on TON. Note that `allowOutOfOrderExecution` **must** be `true` — TON-bound lanes require it, and the Router will reject the message otherwise. +5. **Fee Estimation**: It calls `getCCIPFeeForTON()` to retrieve the CCIP protocol fee from the on-chain Router, then applies a 10% buffer. +6. **Sending**: For native fees, it calls `router.ccipSend(destChainSelector, message, { value: feeWithBuffer })`. For LINK fees, it checks the current allowance, approves the Router if needed, then calls `router.ccipSend(destChainSelector, message)`. + +## Send the Message + +Run the following command to send a message from Ethereum Sepolia to your deployed receiver on TON: + +```bash filename="Terminal" +npm run evm2ton:send -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "Hello TON from EVM" \ + --feeToken native +``` + +Replace `` with the address from the deployment step. To pay fees in LINK instead of ETH, pass `--feeToken link`. + +### Understanding the Output + +When the script executes successfully, you will see output similar to the following: + +``` +🧪 Testing EVM → TON Messaging + +🌐 Source Chain: sepolia +💸 Fee Token: native +✅ Connected to EVM, Block: 10554561 +📤 Sending from: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA +💰 Balance: 8.088902489560498964 ETH + +✅ Transaction submitted! + Hash: 0x581ea250df1538f6ef999c98db7fb30897d62efecad6614f09d67f7c05f18dec + +⏳ Waiting for confirmation... +✅ Transaction confirmed in block: 10554562 +📋 Message ID: 0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 +🔍 Track on CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + + +⏳ Message is being processed by CCIP network... +⏳ Expected delivery: 5-15 minutes (staging environment) + +🔍 Monitor your transaction: + https://sepolia.etherscan.io/tx/0x581ea250df1538f6ef999c98db7fb30897d62efecad6614f09d67f7c05f18dec + +🔍 Monitor delivery on TON: + https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT + +💡 Run verification script after 10-15 minutes: + npm run utils:checkTON -- --sourceChain sepolia --tonReceiver EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT --msg "Hello TON from EVM" +``` + +The CCIP Message ID and Explorer link let you track the message lifecycle across chains. + +## Verification + +After sending, verify delivery on TON in the following ways. + +### Track on CCIP Explorer + +Use the CCIP Explorer URL printed in the output to monitor the full cross-chain message lifecycle: + +``` +https://ccip.chain.link/#/side-drawer/msg/ +``` + +### Verify Delivery on TON + +CCIP delivery from Ethereum Sepolia to TON Testnet typically takes **5–15 minutes**. After waiting, run the verification script: + + + +```bash filename="Terminal" +npm run utils:checkTON -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "Hello TON from EVM" +``` + +When the message has been successfully delivered, you will see output similar to the following: + +``` +═══════════════════════════════════════════════════════════════ + EVM → TON Message Verification +═══════════════════════════════════════════════════════════════ + +📍 Receiver Contract: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +🌐 Source Chain: sepolia +🔍 Looking for message: "Hello TON from EVM" +🔍 Expected sender: EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj (CCIP Router) + +📊 Fetching recent transactions... + +═══════════════════════════════════════════════════════════════ + ✅ CCIP MESSAGE FOUND +═══════════════════════════════════════════════════════════════ + +📨 Most Recent CCIP Message: + From: EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj (CCIP Router ✓) + Message ID: 0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + Value: 0.1 TON + Message: "Hello TON from EVM" + Time: 2026-03-30T16:10:45.000Z + TX Hash: 21048345fbf52bf7c02f64d587bf6a6dd9567ad1786cd2abeaa1c1399f940f86 + + 📍 Received 1 minute(s) ago + +═══════════════════════════════════════════════════════════════ + VERIFICATION RESULT +═══════════════════════════════════════════════════════════════ + +✅ Message verified successfully! + + ✓ Message content matches: "Hello TON from EVM" + ✓ From CCIP Router + +📊 Total CCIP messages found: 3 + (showing most recent above) + +🔗 View on explorer: + https://testnet.tonviewer.com/transaction/21048345fbf52bf7c02f64d587bf6a6dd9567ad1786cd2abeaa1c1399f940f86 + Receiver: https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +``` + +### Verify on TON Explorer + +You can also verify delivery directly on the TON Testnet block explorer: + +1. Visit [testnet.tonviewer.com](https://testnet.tonviewer.com) +2. Search for your receiver contract address +3. Under **Transactions**, you should see an incoming message from the CCIP Router (`EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj`) containing your payload + + + +--- + # CCIP Explorer Source: https://docs.chain.link/ccip/tools-resources/ccip-explorer Last Updated: 2025-05-19 @@ -65168,7 +67254,7 @@ You can explore several comprehensive guides to learn about cross-chain interope # ChainlinkCCIP Tutorials Source: https://docs.chain.link/ccip/tutorials -Last Updated: 2025-05-19 +Last Updated: 2026-03-29 Chainlink Cross-Chain Interoperability Protocol (CCIP) enables secure cross-chain communication, allowing you to transfer tokens and data across different blockchain networks. These tutorials provide step-by-step instructions to help you understand and implement cross-chain functionality in your applications. @@ -65179,6 +67265,7 @@ Choose the tutorial section based on your blockchain platform: - [EVM Tutorials](/ccip/tutorials/evm) - Tutorials for Ethereum Virtual Machine compatible chains - [SVM Tutorials](/ccip/tutorials/svm) - Tutorials for Solana Virtual Machine chains - [Aptos Tutorials](/ccip/tutorials/aptos) - Tutorials for Aptos chain +- [TON Tutorials](/ccip/tutorials/ton) - Tutorials for the TON blockchain --- @@ -65238,3 +67325,55 @@ Message Types: These tutorials use the Solana Devnet and Ethereum Sepolia testnet for demonstration purposes. When deploying to production, make sure to thoroughly test your implementation and follow all security best practices. + +--- + +# CCIP Tutorials (TON) +Source: https://docs.chain.link/ccip/tutorials/ton +Last Updated: 2026-03-29 + +Chainlink CCIP enables secure cross-chain communication between TON and EVM blockchains. These tutorials will help you implement cross-chain arbitrary messaging in both directions. + +## Getting Started + +Before diving into specific implementations, ensure you understand the fundamentals: + +- [Prerequisites for TON to EVM Tutorials](/ccip/tutorials/ton/source/prerequisites) - Set up your development environment for TON → EVM CCIP development. +- [Prerequisites for EVM to TON Tutorials](/ccip/tutorials/ton/destination/prerequisites) - Set up your development environment for EVM → TON CCIP development. + +## Implementing CCIP Receivers on TON + +Before sending any cross-chain message to TON, you need a receiver contract deployed on TON Testnet: + +- [Implementing CCIP Receivers](/ccip/tutorials/ton/receivers) - Understand the TON CCIP receiver protocol and deploy a `MessageReceiver`, `MinimalReceiver`, or `ReceiverWithValidateAndConfirm` contract. + +## Using TON as a Source Chain + +Send messages from TON to EVM chains: + +- [TON to EVM Guide](/ccip/tutorials/ton/source) - Guide for building CCIP messages from TON to EVM chains. +- [Arbitrary Messaging](/ccip/tutorials/ton/source/arbitrary-messaging) - Send a data payload from a TON wallet to a receiver contract on Ethereum Sepolia. + +## Using TON as a Destination Chain + +Send messages from EVM chains to TON: + +- [EVM to TON Guide](/ccip/tutorials/ton/destination) - Guide for building CCIP messages from EVM chains to TON. +- [Arbitrary Messaging](/ccip/tutorials/ton/destination/arbitrary-messaging) - Send a data payload from Ethereum Sepolia to a receiver contract on TON Testnet. + +## Architecture Reference + +Key Differences from EVM: + +- **Cell-Based Storage:** TON uses a Cell data structure (TL-B format) instead of ABI-encoded byte arrays. All CCIP message fields are serialized into Cells using builder primitives. +- **Actor Model:** TON contracts communicate by passing messages between actors. CCIP delivery follows a multi-step internal message flow: `Receiver_CCIPReceive` → contract logic → `Router_CCIPReceiveConfirm`. +- **Tolk Language:** TON smart contracts in the starter kit are written in Tolk, a statically-typed language that compiles to TVM bytecode. + +Message Types: + +- **Arbitrary Messaging:** Send a data payload to trigger logic execution on the destination chain. Currently the only supported message type on TON CCIP lanes. + + diff --git a/src/content/ccip/tutorials/index.mdx b/src/content/ccip/tutorials/index.mdx index f6d3b04709b..fff4457874f 100644 --- a/src/content/ccip/tutorials/index.mdx +++ b/src/content/ccip/tutorials/index.mdx @@ -3,11 +3,11 @@ section: ccip date: Last Modified title: "ChainlinkCCIP Tutorials" metadata: - description: "Browse CCIP tutorials for EVM and Solana. Learn to transfer tokens, send messages, test locally, and build secure cross‑chain apps step‑by‑step." + description: "Browse CCIP tutorials for EVM, Solana, Aptos, and TON. Learn to transfer tokens, send messages, test locally, and build secure cross‑chain apps step‑by‑step." image: "/images/ccip/concepts/architecture/onchain-evm-architecture.jpg" - excerpt: "CCIP tutorials, cross‑chain, EVM, Solana, tokens, messaging, guides, ERC20" + excerpt: "CCIP tutorials, cross‑chain, EVM, Solana, Aptos, TON, tokens, messaging, guides, ERC20" datePublished: "2024-03-25" - lastModified: "2025-05-19" + lastModified: "2026-03-29" estimatedTime: "30-120 minutes" difficulty: "beginner" isIndex: true @@ -22,3 +22,4 @@ Choose the tutorial section based on your blockchain platform: - [EVM Tutorials](/ccip/tutorials/evm) - Tutorials for Ethereum Virtual Machine compatible chains - [SVM Tutorials](/ccip/tutorials/svm) - Tutorials for Solana Virtual Machine chains - [Aptos Tutorials](/ccip/tutorials/aptos) - Tutorials for Aptos chain +- [TON Tutorials](/ccip/tutorials/ton) - Tutorials for the TON blockchain diff --git a/src/content/ccip/tutorials/ton/destination/arbitrary-messaging.mdx b/src/content/ccip/tutorials/ton/destination/arbitrary-messaging.mdx new file mode 100644 index 00000000000..303f4dd4f37 --- /dev/null +++ b/src/content/ccip/tutorials/ton/destination/arbitrary-messaging.mdx @@ -0,0 +1,256 @@ +--- +section: ccip +date: Last Modified +title: "Arbitrary Messaging: EVM to TON" +isIndex: false +metadata: + description: "Learn how to send arbitrary data messages from an EVM chain to a TON smart contract using Chainlink's Cross-Chain Interoperability Protocol (CCIP). This step-by-step tutorial guides you through deploying a receiver on TON and executing a data-only cross-chain message." + image: "/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg" + excerpt: "arbitrary data, messaging, EVM→TON, native fees, LINK fees, TON Testnet, MinimalReceiver" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "20 minutes" + difficulty: "intermediate" +--- + +import { Aside } from "@components" +import CcipCommon from "@features/ccip/CcipCommon.astro" + +This tutorial demonstrates how to send a CCIP arbitrary message from an Ethereum Virtual Machine (EVM) chain to the TON blockchain using Chainlink CCIP. You will learn how to deploy a receiver contract on TON, send a message from Ethereum Sepolia, and verify its delivery. + + + +## Introduction + +This tutorial covers sending a data-only message from Ethereum Sepolia to a receiver contract on TON Testnet. When you send a message using CCIP: + +1. The `ccipSend` transaction is submitted to the CCIP Router on the source EVM chain. +1. The Router deducts the protocol fee and forwards the message to the CCIP Decentralized Oracle Network (DON). +1. The DON delivers the message to the `Receiver_CCIPReceive` handler of your receiver contract on TON Testnet. +1. Your receiver contract acknowledges delivery by sending `Router_CCIPReceiveConfirm` back to the TON CCIP Router. + +## What You Will Build + +In this tutorial, you will: + +- Deploy a `MinimalReceiver` contract on TON Testnet. +- Configure a CCIP message with a text payload. +- Send the message from Ethereum Sepolia to your deployed receiver. +- Pay for CCIP fees using native ETH or LINK. +- Monitor and verify cross-chain message delivery on TON. + + + +## Deploy the Receiver on TON + +Before sending a message, you need a receiver contract deployed on TON Testnet. The starter kit includes a deploy script for the `MinimalReceiver` contract: + +```bash filename="Terminal" +npm run deploy:ton:receiver:minimal +``` + +On success, the script prints output similar to the following: + +``` +🚀 Deploying MinimalReceiver contract to TON Testnet... + +📤 Deploying from wallet: EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +Explorer: https://testnet.tonviewer.com/EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +💰 Wallet balance: 106.538102666 TON + +⏳ Compiling MinimalReceiver.tolk... +📍 Contract will be deployed at: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +📍 Router address (authorized caller): EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj + +⏳ Sending deployment transaction... + +✅ MinimalReceiver deployment initiated! +📍 Contract address: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +📝 Next steps: +1. Wait 1-2 minutes for the transaction to be confirmed +2. Copy the contract address above — pass it as --tonReceiver when sending messages +3. Verify deployment on TON explorer: + https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +4. Send a test message to the deployed receiver: + npm run evm2ton:send -- --sourceChain --tonReceiver EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT --msg "Hello TON from EVM" --feeToken native +``` + +**Save the contract address** — you will pass it as `--tonReceiver` in the next step. + + + +## How the Script Works + +The `evm2ton/sendMessage.ts` script handles the EVM-side interaction with the CCIP Router on your behalf. Here is what happens behind the scenes: + +1. **Context Initialization**: The script connects to the EVM chain and loads your wallet from the `EVM_PRIVATE_KEY` environment variable. +1. **Argument Parsing**: It reads your command-line arguments (`--sourceChain`, `--tonReceiver`, `--msg`, and `--feeToken`) to determine the source chain, receiver address, message content, and fee payment method. +1. **Address Encoding**: It encodes your TON receiver address into the 36-byte format expected by the EVM-side CCIP message using `encodeTONAddress()`. +1. **Message Construction**: It builds the `EVM2AnyMessage` struct using `buildCCIPMessageForTON(receiverBytes, messageData, 100_000_000n, true, selectedFeeToken)`. The `100_000_000n` nanoTON `gasLimit` covers receiver execution on TON. Note that `allowOutOfOrderExecution` **must** be `true` — TON-bound lanes require it, and the Router will reject the message otherwise. +1. **Fee Estimation**: It calls `getCCIPFeeForTON()` to retrieve the CCIP protocol fee from the on-chain Router, then applies a 10% buffer. +1. **Sending**: For native fees, it calls `router.ccipSend(destChainSelector, message, { value: feeWithBuffer })`. For LINK fees, it checks the current allowance, approves the Router if needed, then calls `router.ccipSend(destChainSelector, message)`. + +## Send the Message + +Run the following command to send a message from Ethereum Sepolia to your deployed receiver on TON: + +```bash filename="Terminal" +npm run evm2ton:send -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "Hello TON from EVM" \ + --feeToken native +``` + +Replace `` with the address from the deployment step. To pay fees in LINK instead of ETH, pass `--feeToken link`. + +### Understanding the Output + +When the script executes successfully, you will see output similar to the following: + +``` +🧪 Testing EVM → TON Messaging + +🌐 Source Chain: sepolia +💸 Fee Token: native +✅ Connected to EVM, Block: 10554561 +📤 Sending from: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA +💰 Balance: 8.088902489560498964 ETH + +✅ Transaction submitted! + Hash: 0x581ea250df1538f6ef999c98db7fb30897d62efecad6614f09d67f7c05f18dec + +⏳ Waiting for confirmation... +✅ Transaction confirmed in block: 10554562 +📋 Message ID: 0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 +🔍 Track on CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + + +⏳ Message is being processed by CCIP network... +⏳ Expected delivery: 5-15 minutes (staging environment) + +🔍 Monitor your transaction: + https://sepolia.etherscan.io/tx/0x581ea250df1538f6ef999c98db7fb30897d62efecad6614f09d67f7c05f18dec + +🔍 Monitor delivery on TON: + https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT + +💡 Run verification script after 10-15 minutes: + npm run utils:checkTON -- --sourceChain sepolia --tonReceiver EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT --msg "Hello TON from EVM" +``` + +The CCIP Message ID and Explorer link let you track the message lifecycle across chains. + +## Verification + +After sending, verify delivery on TON in the following ways. + +### Track on CCIP Explorer + +Use the CCIP Explorer URL printed in the output to monitor the full cross-chain message lifecycle: + +``` +https://ccip.chain.link/#/side-drawer/msg/ +``` + +### Verify Delivery on TON + +CCIP delivery from Ethereum Sepolia to TON Testnet typically takes **5–15 minutes**. After waiting, run the verification script: + + + +```bash filename="Terminal" +npm run utils:checkTON -- \ + --sourceChain sepolia \ + --tonReceiver \ + --msg "Hello TON from EVM" +``` + +When the message has been successfully delivered, you will see output similar to the following: + +``` +═══════════════════════════════════════════════════════════════ + EVM → TON Message Verification +═══════════════════════════════════════════════════════════════ + +📍 Receiver Contract: EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +🌐 Source Chain: sepolia +🔍 Looking for message: "Hello TON from EVM" +🔍 Expected sender: EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj (CCIP Router) + +📊 Fetching recent transactions... + +═══════════════════════════════════════════════════════════════ + ✅ CCIP MESSAGE FOUND +═══════════════════════════════════════════════════════════════ + +📨 Most Recent CCIP Message: + From: EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj (CCIP Router ✓) + Message ID: 0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0xcf5c283fb14942e82498b0a1887bb2c525f0a8a065b5682230af4518dc53bff0 + Value: 0.1 TON + Message: "Hello TON from EVM" + Time: 2026-03-30T16:10:45.000Z + TX Hash: 21048345fbf52bf7c02f64d587bf6a6dd9567ad1786cd2abeaa1c1399f940f86 + + 📍 Received 1 minute(s) ago + +═══════════════════════════════════════════════════════════════ + VERIFICATION RESULT +═══════════════════════════════════════════════════════════════ + +✅ Message verified successfully! + + ✓ Message content matches: "Hello TON from EVM" + ✓ From CCIP Router + +📊 Total CCIP messages found: 3 + (showing most recent above) + +🔗 View on explorer: + https://testnet.tonviewer.com/transaction/21048345fbf52bf7c02f64d587bf6a6dd9567ad1786cd2abeaa1c1399f940f86 + Receiver: https://testnet.tonviewer.com/EQBt4-OlEHzH_MsmHdl_YsL26f3OPpV2jRzZ34zxo9E2xasT +``` + +### Verify on TON Explorer + +You can also verify delivery directly on the TON Testnet block explorer: + +1. Visit [testnet.tonviewer.com](https://testnet.tonviewer.com) +2. Search for your receiver contract address +3. Under **Transactions**, you should see an incoming message from the CCIP Router (`EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj`) containing your payload + + diff --git a/src/content/ccip/tutorials/ton/destination/build-messages.mdx b/src/content/ccip/tutorials/ton/destination/build-messages.mdx new file mode 100644 index 00000000000..20416077dbd --- /dev/null +++ b/src/content/ccip/tutorials/ton/destination/build-messages.mdx @@ -0,0 +1,220 @@ +--- +section: ccip +date: Last Modified +title: "Building CCIP Messages from EVM to TON" +isIndex: false +metadata: + description: "Implement CCIP messages from EVM chains to the TON blockchain. Guide covers message structure, TON address encoding, nanoTON gasLimit, and implementation for arbitrary data messaging." + image: "/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg" + excerpt: "EVM to TON, CCIP message, receiver encoding, extraArgs, nanoTON, gasLimit, GenericExtraArgsV2, encodeTONAddress, arbitrary messaging" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "20 minutes" + difficulty: "advanced" +--- + +import { Aside } from "@components" +import { Tabs } from "@components/Tabs" +import CcipCommon from "@features/ccip/CcipCommon.astro" + +## 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. + + + +## 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 format: 4 bytes for the workchain identifier (big-endian signed int32) followed by 32 bytes for the account hash. Use the `encodeTONAddress` helper shown below. + + + +### 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. + + + +### 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 nanoTON reserved for execution on the TON destination chain. +- **Units**: This value is denominated in **nanoTON**, not EVM gas units. 1 TON = 1,000,000,000 nanoTON. A starting value of `100_000_000n` (0.1 TON) covers most receive operations. Any unused nanoTON 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. + + + +## Implementation by Message Type + +### Arbitrary Messaging + +Use this configuration when sending a data payload to a custom smart contract on TON. + + + Configuration + Pay with Native Token + Pay with LINK + + ``` + { + 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 TON in nanoTON + allowOutOfOrderExecution: true + } + } + ``` + + + ```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 + }) + ``` + + + + ```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) + ``` + + + + + + +## 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. + + diff --git a/src/content/ccip/tutorials/ton/destination/index.mdx b/src/content/ccip/tutorials/ton/destination/index.mdx new file mode 100644 index 00000000000..7c7f7c732b8 --- /dev/null +++ b/src/content/ccip/tutorials/ton/destination/index.mdx @@ -0,0 +1,31 @@ +--- +section: ccip +date: Last Modified +title: "CCIP Tutorials: EVM to TON" +isIndex: true +metadata: + description: "Learn how to implement cross-chain communication from Ethereum to TON using Chainlink CCIP. Tutorials cover arbitrary messaging with detailed implementation guides." + image: "/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg" + excerpt: "EVM→TON, arbitrary messaging, CCIP, fees, TON Testnet, MessageReceiver, Ethereum Sepolia" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "5 minutes" + difficulty: "intermediate" +--- + +import { Aside } from "@components" + +This section provides guides and tutorials for implementing cross-chain communication from Ethereum Virtual Machine (EVM) chains to the TON blockchain using Chainlink's Cross-Chain Interoperability Protocol (CCIP). + +## Getting Started + +Before implementing specific use cases, understand the fundamental concepts and message structure for EVM to TON communication: + +- [Building CCIP Messages from EVM to TON](/ccip/tutorials/ton/destination/build-messages) - Learn the core message structure, required parameters, and implementation details for arbitrary messaging. +- [Prerequisites for EVM to TON Tutorials](/ccip/tutorials/ton/destination/prerequisites) - Set up your development environment with EVM and TON wallets, the starter kit, and testnet tokens. + +## Tutorials by Use Case + +Depending on your specific needs, choose the appropriate tutorial: + +- [Arbitrary Messaging](/ccip/tutorials/ton/destination/arbitrary-messaging) - Send data from an EVM chain to a receiver contract on TON. diff --git a/src/content/ccip/tutorials/ton/destination/prerequisites.mdx b/src/content/ccip/tutorials/ton/destination/prerequisites.mdx new file mode 100644 index 00000000000..de71ffee82f --- /dev/null +++ b/src/content/ccip/tutorials/ton/destination/prerequisites.mdx @@ -0,0 +1,129 @@ +--- +section: ccip +date: Last Modified +title: "Prerequisites for EVM to TON Tutorials" +isIndex: false +metadata: + description: "Complete setup guide for EVM to TON cross-chain development. Install Node.js, configure EVM and TON wallets, and get testnet tokens for CCIP Chainlink tutorials. Step-by-step instructions for developers building cross-chain applications." + image: "/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg" + excerpt: "prerequisites, EVM, TON, wallets, testnet, setup" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "10 minutes" + difficulty: "beginner" +--- + +import { Aside } from "@components" + +Before starting the EVM to TON tutorials, ensure you have the following: + +## Development Environment + +- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with: + + ```bash filename="Terminal" + node -v + ``` + + Example output: + + ```text + $ node -v + v22.15.0 + ``` + +- **npm**: For installing and managing dependencies. +- **Git**: For cloning the repository. + +## Starter Kit Repository + +1. Clone the CCIP TON Starter Kit: + + ```bash filename="Terminal" + git clone https://github.com/smartcontractkit/ccip-starter-kit-ton.git + ``` + +1. Navigate to the directory: + + ```bash filename="Terminal" + cd ccip-starter-kit-ton + ``` + +1. Install dependencies: + + ```bash filename="Terminal" + npm install + ``` + +This installs the required TON SDK packages, including [`@ton/core`](https://github.com/ton-org/ton-core), [`@ton/ton`](https://github.com/ton-org/ton), and [`@ton/crypto`](https://github.com/ton-org/ton-crypto), as well as [`ethers`](https://github.com/ethers-io/ethers.js) for the EVM-side scripts. + +## Wallets + +- **EVM Wallet and Private Key**: You need an EVM wallet to send CCIP messages from an EVM chain (e.g., Ethereum Sepolia) and to deploy receiver contracts. + - Set up a wallet like [MetaMask](https://metamask.io/). + - Export the private key for the account you intend to use. Follow the [official MetaMask guide](https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/) to obtain your private key. + +- **TON Wallet**: You need a TON wallet to deploy the receiver contract on TON Testnet. The starter kit uses the **[V4R2 wallet version](https://docs.ton.org/standard/wallets/v4)**. You can create a TON wallet using [Tonkeeper](https://tonkeeper.com/): + 1. Download and install Tonkeeper + 2. Create a new wallet and save the [24-word recovery phrase (mnemonic)](https://docs.ton.org/standard/wallets/mnemonics) + 3. Add a Testnet Account using the same mnemonic + 4. In wallet settings, select **V4R2** as the wallet version + + + +## Environment Configuration (`.env` file) + +The starter kit uses a `.env` file to manage sensitive information like private keys and RPC URLs. Create a new file named `.env` in the root of the `ccip-starter-kit-ton` directory by copying the example file: + +```bash filename="Terminal" +cp .env.example .env +``` + +Next, open the `.env` file and fill in the required values: + +- `EVM_PRIVATE_KEY`: The private key (with `0x` prefix) of your EVM wallet. Required for sending CCIP messages from EVM chains and deploying receiver contracts on TON. + +- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for Ethereum Sepolia. Required when sending from Ethereum Sepolia. You can obtain a free RPC URL from providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or [QuickNode](https://www.quicknode.com/). + +- `ARBITRUM_SEPOLIA_RPC_URL` (optional): The RPC endpoint for Arbitrum Sepolia if you plan to send messages from this chain. + +- `TON_MNEMONIC`: Your 24-word mnemonic phrase for the TON wallet. Required to deploy the receiver contract on TON Testnet. + +- `TON_RPC_URL` (optional): Override the default TON RPC endpoint. The default is `https://ton-testnet.api.onfinality.io/public`, which does not require an API key. + +- `TON_CENTER_API_KEY` (optional): Only required if you use a toncenter RPC URL (e.g., `https://testnet.toncenter.com/api/v2/jsonRPC`). Get a free API key from [@tonapibot](https://t.me/tonapibot) on Telegram. + +**Example `.env` file:** + +```bash filename=".env" +EVM_PRIVATE_KEY=0xYourEVMPrivateKey +ETHEREUM_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY +ARBITRUM_SEPOLIA_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/YOUR_API_KEY +TON_MNEMONIC="word1 word2 word3 ... word24" +``` + + + +## Testnet Tokens + +### ETH on Ethereum Sepolia + +ETH is required to pay for EVM transaction fees and CCIP fees when sending from Ethereum Sepolia. Use the [Chainlink Faucet](https://faucet.chain.link) to get test ETH. + +### LINK on Ethereum Sepolia (optional) + +When paying CCIP fees in LINK instead of ETH, you need LINK tokens on the source chain. Use the [Chainlink Faucet](https://faucet.chain.link) to get test LINK. + +### TON on Testnet + +A small amount of TON is required to deploy the receiver contract and pay for transactions on TON Testnet. + +1. Visit the [TON Testnet Faucet](https://t.me/testgiver_ton_bot) on Telegram +2. Send your V4R2 testnet wallet address to `@testgiver_ton_bot` +3. The bot will send you 2 testnet TON (you can request again every 60 minutes) diff --git a/src/content/ccip/tutorials/ton/index.mdx b/src/content/ccip/tutorials/ton/index.mdx new file mode 100644 index 00000000000..f9d0f941124 --- /dev/null +++ b/src/content/ccip/tutorials/ton/index.mdx @@ -0,0 +1,62 @@ +--- +section: ccip +date: Last Modified +title: "CCIP Tutorials (TON)" +metadata: + description: "CCIP tutorials for TON. Build receivers, send messages between TON and EVM chains using Chainlink CCIP." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "TON, CCIP, receivers, arbitrary messaging, TON Testnet, Ethereum Sepolia, TL-B, Tolk" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "8 minutes" + difficulty: "intermediate" +isIndex: true +--- + +import { Aside } from "@components" + +Chainlink CCIP enables secure cross-chain communication between TON and EVM blockchains. These tutorials will help you implement cross-chain arbitrary messaging in both directions. + +## Getting Started + +Before diving into specific implementations, ensure you understand the fundamentals: + +- [Prerequisites for TON to EVM Tutorials](/ccip/tutorials/ton/source/prerequisites) - Set up your development environment for TON → EVM CCIP development. +- [Prerequisites for EVM to TON Tutorials](/ccip/tutorials/ton/destination/prerequisites) - Set up your development environment for EVM → TON CCIP development. + +## Implementing CCIP Receivers on TON + +Before sending any cross-chain message to TON, you need a receiver contract deployed on TON Testnet: + +- [Implementing CCIP Receivers](/ccip/tutorials/ton/receivers) - Understand the TON CCIP receiver protocol and deploy a `MessageReceiver`, `MinimalReceiver`, or `ReceiverWithValidateAndConfirm` contract. + +## Using TON as a Source Chain + +Send messages from TON to EVM chains: + +- [TON to EVM Guide](/ccip/tutorials/ton/source) - Guide for building CCIP messages from TON to EVM chains. +- [Arbitrary Messaging](/ccip/tutorials/ton/source/arbitrary-messaging) - Send a data payload from a TON wallet to a receiver contract on Ethereum Sepolia. + +## Using TON as a Destination Chain + +Send messages from EVM chains to TON: + +- [EVM to TON Guide](/ccip/tutorials/ton/destination) - Guide for building CCIP messages from EVM chains to TON. +- [Arbitrary Messaging](/ccip/tutorials/ton/destination/arbitrary-messaging) - Send a data payload from Ethereum Sepolia to a receiver contract on TON Testnet. + +## Architecture Reference + +Key Differences from EVM: + +- **Cell-Based Storage:** TON uses a Cell data structure (TL-B format) instead of ABI-encoded byte arrays. All CCIP message fields are serialized into Cells using builder primitives. +- **Actor Model:** TON contracts communicate by passing messages between actors. CCIP delivery follows a multi-step internal message flow: `Receiver_CCIPReceive` → contract logic → `Router_CCIPReceiveConfirm`. +- **Tolk Language:** TON smart contracts in the starter kit are written in Tolk, a statically-typed language that compiles to TVM bytecode. + +Message Types: + +- **Arbitrary Messaging:** Send a data payload to trigger logic execution on the destination chain. Currently the only supported message type on TON CCIP lanes. + + diff --git a/src/content/ccip/tutorials/ton/receivers.mdx b/src/content/ccip/tutorials/ton/receivers.mdx new file mode 100644 index 00000000000..b2dc1deb7c7 --- /dev/null +++ b/src/content/ccip/tutorials/ton/receivers.mdx @@ -0,0 +1,364 @@ +--- +section: ccip +date: Last Modified +title: "Implementing CCIP Receivers" +isIndex: false +metadata: + description: "Build secure TON CCIP receivers in Tolk. Learn the three mandatory protocol steps, understand Any2TVMMessage, and choose between MinimalReceiver, ReceiverWithValidateAndConfirm, or the full MessageReceiver reference implementation." + image: "/images/ccip/concepts/architecture/ccip-ton-destination-chain.jpg" + excerpt: "TON CCIP receiver, Tolk smart contract, Router validation, CCIPReceiveConfirm, Any2TVMMessage, MinimalReceiver" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "20 minutes" + difficulty: "advanced" +--- + +import { Aside, GitHubCard } from "@components" +import { Tabs } from "@components/Tabs" +import CcipCommon from "@features/ccip/CcipCommon.astro" + + + +# Implementing CCIP Receivers + +A **CCIP Receiver** is a TON smart contract (written in Tolk) that accepts incoming cross-chain messages delivered by the CCIP protocol. When a message is sent from an EVM chain to TON via CCIP, the CCIP Router on TON forwards it to your receiver contract as an internal message. Your contract must validate the delivery, acknowledge it to the protocol, and process the payload. + +## How Message Delivery Works + +When a cross-chain message arrives on TON: + +1. The CCIP off-ramp verifies the message against a Merkle root and routes it through the CCIP Router on TON. +1. The Router sends a `Receiver_CCIPReceive` internal message to your receiver contract, with enough TON attached to cover the confirmation transaction. +1. Your contract performs three mandatory protocol steps, then executes your application logic. +1. Your contract sends `Router_CCIPReceiveConfirm` back to the Router, which marks the message as successfully delivered on-chain. + +## Security Architecture + +### Three Mandatory Protocol Steps + +Every TON CCIP receiver **must** implement all three steps in order: + +**Step 1 — Authorize the Router.** Accept `Receiver_CCIPReceive` messages only from the configured CCIP Router address. Any other sender must be rejected. + +```tolk +assert(in.senderAddress == st.router) throw ERROR_UNAUTHORIZED; +``` + +**Step 2 — Check attached value.** The Router forwards TON with the message to cover the confirmation transaction. Verify the attached value meets `MIN_VALUE`. The Router needs at least **0.02 TON** to send `Router_CCIPReceiveConfirm` back through the protocol chain. Use **0.03 TON** as a baseline and increase it to cover your own execution costs. + +```tolk +assert(in.valueCoins >= MIN_VALUE) throw ERROR_LOW_VALUE; +``` + +**Step 3 — Acknowledge delivery.** Send `Router_CCIPReceiveConfirm` back to the Router with enough TON attached to cover the protocol's confirmation chain. Using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` is the simplest approach — it forwards all remaining value automatically — but any send mode is valid as long as at least **0.02 TON** reaches the Router. + +```tolk +val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, +}); +receiveConfirm.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +``` + + + +### Developer Responsibilities + +Unlike EVM receivers, **source chain and sender validation are not enforced at the protocol level on TON** — only the Router address check (step 1) is a protocol requirement. Your contract is responsible for any application-layer checks: + +- **Source chain validation**: Check `message.sourceChainSelector` against an allowlist of trusted chains. +- **Sender validation**: Check `message.sender` against trusted source-side addresses. + +Without these checks, any address on any chain can send a CCIP message to your receiver and have it processed. + +## Message Structure + +The CCIP Router delivers an `Any2TVMMessage` struct inside each `Receiver_CCIPReceive` message: + +```tolk filename="chainlink-ton/contracts/contracts/lib/receiver/types.tolk" +struct Any2TVMMessage { + messageId: uint256; // Unique message identifier + sourceChainSelector: uint64; // CCIP chain selector of the originating chain + sender: CrossChainAddress; // Encoded sender address from the source chain + data: cell; // Arbitrary payload (your application data) + tokenAmounts: cell?; // Reserved for future token support; currently unused +} +``` + +| Field | Type | Description | +| --------------------- | ------------------- | ---------------------------------------------------------------------------------------- | +| `messageId` | `uint256` | Unique identifier — use for deduplication to prevent replay | +| `sourceChainSelector` | `uint64` | CCIP selector of the originating chain | +| `sender` | `CrossChainAddress` | Encoded source-chain sender address; for EVM sources, these are the 20 EVM address bytes | +| `data` | `cell` | Application payload encoded as a TON Cell | +| `tokenAmounts` | `cell?` | Reserved for future token-transfer support; currently `null` | + +`CrossChainAddress` is a `slice` type. For EVM-to-TON messages, it contains the 20-byte EVM address of the sender. + + + +## Receiver Implementations + +The starter kit provides three receiver contracts at different complexity levels. Choose the one that fits your use case, or use one as a starting template. + + + MinimalReceiver + ReceiverWithValidateAndConfirm + MessageReceiver + + `contracts/minimal_receiver.tolk` — The bare-minimum CCIP receiver. All three mandatory protocol steps are implemented inline, giving you full transparency and control over each check. + + **When to use**: When you want explicit control over every protocol step, need to customize error handling, or prefer not to depend on the early-stage Receiver library. + + #### Entry Point + + ```tolk filename="contracts/minimal_receiver.tolk" + fun onInternalMessage(in: InMessage) { + val msg = lazy MinimalReceiver_InMessage.fromSlice(in.body); + match (msg) { + Receiver_CCIPReceive => { + val st = lazy Storage.load(); + + // 1. Accept messages only from the authorized CCIP Router. + assert(in.senderAddress == st.router) throw ERROR_UNAUTHORIZED; + + // 2. Verify enough value is attached to cover gas costs. + // Router needs ≥0.02 TON for CCIPReceiveConfirm; increase for your own costs. + assert(in.valueCoins >= MIN_VALUE) throw ERROR_LOW_VALUE; + + // 3. Send CCIPReceiveConfirm back to the Router. + val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, + }); + receiveConfirm.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); + + // ★ YOUR LOGIC: call your application function(s) with msg.message + processMessage(msg.message); + } + else => { + // Ignore plain TON transfers; reject unknown opcodes. + assert(in.body.isEmpty()) throw 0xFFFF; + } + } + } + ``` + + + Complete contract source including storage, constants, and imports. + + + Deploy: + + ```bash filename="Terminal" + npm run deploy:ton:receiver:minimal + ``` + + + + `contracts/receiver_with_validateAndConfirm.tolk` — Uses the `validateAndConfirm` helper from the Receiver library to perform all three mandatory protocol steps in a single call. Functionally equivalent to `MinimalReceiver` but with less boilerplate. + + **When to use**: When the default `validateAndConfirm` behavior fits your requirements and you prefer more concise code. See the Receiver library caution above before choosing this approach. + + #### Entry Point + + ```tolk filename="contracts/receiver_with_validateAndConfirm.tolk" + Receiver_CCIPReceive => { + val st = lazy Storage.load(); + + // Performs all three mandatory protocol steps in one call: + // 1. Verifies sender == st.router + // 2. Verifies valueCoins >= MIN_VALUE + // 3. Sends CCIPReceiveConfirm with SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE + msg.validateAndConfirm({ + router: st.router, + minValue: MIN_VALUE, + inMsg: { + senderAddress: in.senderAddress, + valueCoins: in.valueCoins, + }, + confirmationMsg: { + sendMode: SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE, + }, + }); + + // ★ YOUR LOGIC: call your application function(s) with msg.message + processMessage(msg.message); + } + ``` + + + Complete contract source including storage, constants, and imports. + + + Deploy: + + ```bash filename="Terminal" + npm run deploy:ton:receiver:validate-and-confirm + ``` + + + + `chainlink-ton/contracts/contracts/ccip/test/receiver/contract.tolk` — A full-featured receiver used by the Chainlink team for protocol integration tests. It implements all three protocol steps inline and adds two-step ownership, switchable behavior, getter methods, and contract upgrade support — making it a useful reference for production receivers that need administrative controls. + + + + #### Storage and Behavior + + ```tolk filename="chainlink-ton/contracts/contracts/ccip/test/receiver/storage.tolk" + struct Storage { + id: uint32; + ownable: Ownable2Step; // Two-step ownership — admin messages require requireOwner() + authorizedCaller: address; // Must be the CCIP Router address for production use + behavior: TestReceiverBehavior; + } + + enum TestReceiverBehavior : uint8 { + Accept = 0, // Normal — emit event for each received message + RejectAll, // Always throw (simulates a failing receiver) + ConsumeAllGas // Infinite loop (simulates an out-of-gas receiver) + } + ``` + + `Ownable2Step` requires the new owner to explicitly accept the transfer, preventing accidental ownership loss. All admin messages (`UpdateAuthorizedCaller`, `UpdateBehavior`, `Upgradeable_Upgrade`) verify `st.ownable.requireOwner(in.senderAddress)` before making any state change. + + #### Entry Point + + ```tolk filename="chainlink-ton/contracts/contracts/ccip/test/receiver/contract.tolk" + fun onInternalMessage(in: InMessage) { + val msg = lazy TestReceiver_InMessage.fromSlice(in.body); + match (msg) { + Receiver_CCIPReceive => { + var st = lazy Storage.load(); + + // 1. Check that enough value is received to process this message. + assert( in.valueCoins >= ton("0.03") ) throw (Receiver_Error.LowValue as int); + + // 2. Check CCIPReceive only comes from router. + assert( in.senderAddress == st.authorizedCaller) throw (Receiver_Error.Unauthorized as int); + + // 3. Process the message according to your product logic. + match (st.behavior) { + TestReceiverBehavior.Accept => { processAccept(msg, in.senderAddress) }, + TestReceiverBehavior.RejectAll => { processRejectAll() }, + TestReceiverBehavior.ConsumeAllGas => { processConsumeAllGas() }, + } + + // 4. Send CCIPReceiveConfirm back to the Router after processing. + // reserveToncoinsOnBalance preserves the contract's existing balance; + // SEND_MODE_CARRY_ALL_BALANCE then forwards everything above that amount — + // effectively the TON that arrived with this message minus compute/action fees. + // At least 0.02 TON is required for the Router to process the confirmation; + // send ~0.025 TON or more to account for fee fluctuations. + reserveToncoinsOnBalance(0, RESERVE_MODE_INCREASE_BY_ORIGINAL_BALANCE); + val receiveConfirm = createMessage({ + bounce: true, + value: 0, + dest: in.senderAddress, + body: Router_CCIPReceiveConfirm { execId: msg.execId }, + }); + receiveConfirm.send(SEND_MODE_CARRY_ALL_BALANCE); + } + TestReceiver_UpdateAuthorizedCaller => { onUpdateAuthorizedCaller(msg, in.senderAddress) } + TestReceiver_UpdateBehavior => { onUpdateBehavior(msg, in.senderAddress) } + Upgradeable_Upgrade => { + var st = lazy Storage.load(); + st.ownable.requireOwner(in.senderAddress); + onUpgrade(msg); + } + else => { assert(in.body.isEmpty()) throw 0xFFFF } + } + } + ``` + + Two aspects of this implementation differ from `MinimalReceiver`: + + - **Value is checked before sender**: The contract validates `valueCoins >= 0.03 TON` first, then verifies the Router address. Both checks are mandatory; the order is a valid implementation choice. + - **Confirm is sent after processing**: `Router_CCIPReceiveConfirm` is enqueued after the behavior logic runs, not before. `reserveToncoinsOnBalance` preserves the contract's pre-message balance so that `SEND_MODE_CARRY_ALL_BALANCE` forwards only the TON that arrived with this message, minus compute and action fees. + + The `TestReceiverBehavior.RejectAll` and `ConsumeAllGas` branches are testing aids that simulate common failure scenarios. In a production receiver, remove them and replace `processAccept` with your application logic. + + #### Getter Methods + + | Method | Returns | Description | + |---|---|---| + | `getId()` | `uint32` | Contract instance identifier | + | `getAuthorizedCaller()` | `address` | Currently configured Router address | + | `getBehavior()` | `int` | Behavior value: `0` = Accept, `1` = RejectAll, `2` = ConsumeAllGas | + | `typeAndVersion()` | `(slice, slice)` | Facility name and contract version (`"1.6.0"`) | + | `facilityId()` | `uint16` | Facility identifier (`191`) | + | `errorCode(local)` | `uint16` | Namespaced error code for a given local code | + + + Complete source including message handlers, admin functions, and upgrade logic. + + + Deploy: + + ```bash filename="Terminal" + npm run deploy:ton:receiver + ``` + + The deploy script initializes the contract with your deployer wallet as `ownable.owner`, the CCIP Router from `networkConfig.tonTestnet.router` as `authorizedCaller`, and `behavior` set to `Accept` (0). + + + + +## After Deployment + +After deploying, send a test message from an EVM chain to verify delivery: + +```bash filename="Terminal" +npm run evm2ton:send -- \ + --sourceChain \ + --tonReceiver \ + --msg "Hello TON from EVM" \ + --feeToken native +``` + +Then confirm the message was received on TON: + +```bash filename="Terminal" +npm run utils:checkTON -- \ + --sourceChain \ + --tonReceiver \ + --msg "Hello TON from EVM" +``` + + diff --git a/src/content/ccip/tutorials/ton/source/arbitrary-messaging.mdx b/src/content/ccip/tutorials/ton/source/arbitrary-messaging.mdx new file mode 100644 index 00000000000..c4f63065f01 --- /dev/null +++ b/src/content/ccip/tutorials/ton/source/arbitrary-messaging.mdx @@ -0,0 +1,304 @@ +--- +section: ccip +date: Last Modified +title: "Arbitrary Messaging: TON to EVM" +isIndex: false +metadata: + description: "Learn how to send arbitrary messages from TON to EVM using Chainlink's Cross-Chain Interoperability Protocol (CCIP). This step-by-step tutorial guides you through setting up and executing a data-only cross-chain message from a TON wallet to an Ethereum Sepolia receiver contract." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "TON→EVM, arbitrary messaging, CCIP, CCIPSend, TL-B Cell, Ethereum Sepolia, TON Testnet, MessageReceiver" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "20 minutes" + difficulty: "intermediate" +--- + +import { Aside } from "@components" +import CcipCommon from "@features/ccip/CcipCommon.astro" + +This tutorial demonstrates how to send a CCIP arbitrary message from the TON blockchain to an Ethereum Virtual Machine (EVM) chain using Chainlink CCIP. You will learn how to build a CCIP message on TON, send it using a script, and verify its delivery on the destination chain. + + + +## Introduction + +This tutorial covers sending a data-only message from TON Testnet to Ethereum Sepolia without any token transfer. When you send a message using CCIP: + +1. The `Router_CCIPSend` Cell is submitted to the CCIP Router on TON. +1. The Router validates the message, deducts the protocol fee from the attached TON, and forwards the message to the CCIP Decentralized Oracle Network (DON). +1. The DON delivers the message to the `_ccipReceive` function of the receiver contract on Ethereum Sepolia. + +## What You Will Build + +In this tutorial, you will: + +- Use a script to configure a CCIP message with a text payload. +- Send the message from TON Testnet to a receiver contract on Ethereum Sepolia. +- Pay for CCIP transaction fees using native TON. +- Monitor and verify your cross-chain message. + + + +## Understanding Arbitrary Messaging from TON to EVM + +This tutorial focuses on data-only cross-chain messages from your TON Testnet wallet to a contract on Ethereum Sepolia. Key points specific to arbitrary messaging from TON: + +- **Data only**: TON CCIP lanes support arbitrary messaging only. No tokens are transferred — only a bytes payload is delivered to the receiver contract. +- **Fee Payment**: Transaction fees are paid on TON using native TON tokens. Paying with LINK is not available on TON-to-EVM lanes. +- **Message Construction**: A script constructs and delivers the `Router_CCIPSend` Cell by calling helpers from `scripts/utils/utils.ts` and sending it to the CCIP Router address on TON Testnet. + +## How the Script Works + +The `ton2evm/sendMessage.ts` script handles the interaction with the CCIP Router on your behalf. Here is what happens behind the scenes: + +1. **Context Initialization**: The script connects to TON Testnet and loads your wallet from the `TON_MNEMONIC` environment variable. +1. **Argument Parsing**: It reads your command-line arguments (`--destChain`, `--evmReceiver`, `--msg`, and optionally `--tonSender`) to determine the destination chain, receiver address, message content, and sending mode. +1. **Message Construction**: It encodes the EVM receiver address using `encodeEVMAddress`, builds the `GenericExtraArgsV2` Cell using `buildExtraArgsForEVM(100_000, true)`, and assembles the full `Router_CCIPSend` Cell using `buildCCIPMessageForEVM`. Note that `allowOutOfOrderExecution` **must** be `true` — TON-to-EVM lanes require it, and the Router will reject the message otherwise. +1. **Fee Estimation**: It calls `getCCIPFeeForEVM`, which walks the `Router → OnRamp → FeeQuoter` on-chain getter chain to retrieve the protocol fee in nanoTON. +1. **Fee Buffering**: It applies a 10% buffer to the quoted fee and adds a fixed 0.5 TON gas reserve to cover wallet-level transaction costs and source-chain execution. +1. **Sending**: It submits the `Router_CCIPSend` Cell directly from the wallet to the CCIP Router. If `--tonSender` is provided, it instead sends a `CCIPSender_RelayCCIPSend` message to the `MinimalSender` contract, which forwards the Cell to the Router. + +## Running the Arbitrary Message + +### Prerequisites Check + +Before running the script: + +1. Ensure you've completed the setup steps outlined in the [prerequisites](/ccip/tutorials/ton/source/prerequisites). + +1. Make sure your `.env` file contains `TON_MNEMONIC` (your 24-word phrase) and `ETHEREUM_SEPOLIA_RPC_URL`. + +1. Verify you have sufficient TON balance in your testnet wallet. At least **1 TON** is recommended to cover the CCIP protocol fee plus gas. + +1. Deploy the `MessageReceiver` contract on Ethereum Sepolia. This EVM contract receives and stores the CCIP message. The `EVM_PRIVATE_KEY` in your `.env` must correspond to a wallet with Sepolia ETH. + + ```bash filename="Terminal" + npm run deploy:evm:receiver -- --evmChain sepolia + ``` + + On success, the script prints output similar to the following: + + ``` + 🚀 Deploying MessageReceiver contract to sepolia... + + 📤 Deploying from account: 0x8C244f0B2164E6A3BED74ab429B0ebd661Bb14CA + 💰 Account balance: 8.08890300048914095 ETH + + ⏳ Deploying MessageReceiver with router: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 + + ✅ MessageReceiver deployed successfully! + 📍 Contract address: 0x960c39e1E53d595cA1932926585c7dd5bF497300 + 📍 Router address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 + + 📝 Next steps: + 1. Wait 1-2 minutes for Etherscan to index the contract + 2. Verify the contract (optional): + npx hardhat verify 0x960c39e1E53d595cA1932926585c7dd5bF497300 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 --network sepolia + 3. Send a test message to the deployed receiver: + npm run ton2evm:send -- --destChain sepolia --evmReceiver 0x960c39e1E53d595cA1932926585c7dd5bF497300 --msg "Hello EVM from TON" --feeToken native + + 🔍 View on explorer: + https://sepolia.etherscan.io/address/0x960c39e1E53d595cA1932926585c7dd5bF497300 + ``` + + **Save the contract address** — you will pass it as `--evmReceiver` in the next step. + + + +### Execute the Script + +Run the following command from your terminal to send a message from TON Testnet to Ethereum Sepolia: + +```bash filename="Terminal" +npm run ton2evm:send -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" +``` + +Replace `` with the contract address from the deployment step above. + +To send via an on-chain `MinimalSender` contract instead (see [Build CCIP Messages — Via Sender Contract](/ccip/tutorials/ton/source/build-messages#sending-the-message)), pass its TON address with `--tonSender`: + +```bash filename="Terminal" +npm run ton2evm:send -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" \ + --tonSender +``` + +### Understanding the Output + +When the script executes successfully, you will see logs similar to the following: + +``` +🧪 Testing TON → EVM Messaging + +🌐 Destination Chain: sepolia +💸 Fee Token: native +✅ Connected to TON, Block: 49273773 +📤 Sending from: EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK +💰 Balance: 106.80152516 TON + +🔑 QueryID (seqno): 71 +💸 Estimated CCIP fee: 894032 nanoTON (0.000894032 TON) +💸 Fee with 10% buffer: 983435 nanoTON (0.000983435 TON) +💸 Gas reserve: 0.5 TON +✅ Transaction sent! + +🔍 Monitor your transaction: + https://testnet.tonviewer.com/EQBTzZB_jpRo1oR3wLsxhrS7tPLUDteL9NN_83S1fWwanyHK + +🔍 Monitor delivery on sepolia: + https://sepolia.etherscan.io/address/0x960c39e1E53d595cA1932926585c7dd5bF497300 + +💡 Run verification scripts after a few minutes: + +1. Check router ACK/NACK status: + All recent CCIP sends: + npm run utils:checkLastTxs -- --ccipSendOnly true + This specific send (queryID: 71): + npm run utils:checkLastTxs -- --queryId 71 + +2. Once ACK is confirmed, verify delivery on EVM: + npm run utils:checkEVM -- --destChain sepolia --evmReceiver 0x960c39e1E53d595cA1932926585c7dd5bF497300 --msg "Hello EVM from TON" +``` + +- The **QueryID** (equal to your wallet's current `seqno`) is printed. The TON CCIP Router uses this value to correlate its `Router_CCIPSendACK` or `Router_CCIPSendNACK` response back to your send. +- The TON Testnet Explorer URL lets you inspect the transaction and confirm it was not bounced. +- The script prints the exact verification commands to run next. + +## Verification + +After sending your message, you can verify its delivery on Ethereum Sepolia in the following ways: + +### Check ACK/NACK Status + +First, confirm that the TON CCIP Router accepted your message by checking for an ACK response: + +```bash filename="Terminal" +npm run utils:checkLastTxs -- --ccipSendOnly true +``` + +To filter to a specific send by QueryID: + +```bash filename="Terminal" +npm run utils:checkLastTxs -- --queryId +``` + +An ACK confirms the Router accepted the message and submitted it to the DON for cross-chain delivery. When an ACK is found, the script also prints the CCIP Message ID and a **CCIP Explorer** URL. + + + +### Track on CCIP Explorer + +Use the CCIP Explorer URL printed alongside the ACK to monitor the full cross-chain message lifecycle: + +``` +https://ccip.chain.link/#/side-drawer/msg/ +``` + +### Verify Delivery on EVM + +After you receive a Router ACK, use the `utils:checkEVM` script to verify that the message was delivered to the receiver contract on Ethereum Sepolia. This script queries `MessageFromTON` events on the receiver contract and compares the message content and source chain selector. + +CCIP delivery from TON Testnet typically takes **5–15 minutes** after the Router ACK. After waiting, run: + + + +```bash filename="Terminal" +npm run utils:checkEVM -- \ + --destChain sepolia \ + --evmReceiver \ + --msg "Hello EVM from TON" +``` + +_Replace `` with the deployed contract address and ensure `--msg` matches the string you sent exactly (case-sensitive)._ + +When the message has been successfully delivered, you will see output similar to the following: + +``` +═══════════════════════════════════════════════════════════════ + TON → EVM Message Verification +═══════════════════════════════════════════════════════════════ + +📍 Receiver Contract: 0x960c39e1E53d595cA1932926585c7dd5bF497300 +🌐 Destination Chain: sepolia +🔍 Looking for message: "Hello EVM from TON" +🔍 Expected source: 1399300952838017768 (TON Testnet) + +📊 Checking contract state... + +📨 Latest message in contract state: + Message ID: 0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + Message: "Hello EVM from TON" +📊 Scanning blocks 10549450 to 10554450 ... + +═══════════════════════════════════════════════════════════════ + ✅ CCIP MESSAGE FOUND +═══════════════════════════════════════════════════════════════ + +📨 Most Recent CCIP Message: + Message ID: 0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + CCIP Explorer: https://ccip.chain.link/#/side-drawer/msg/0x5dc8b9ce9c091e448fb8280dd27165cde786b9948b62baca4f223731d90606c4 + Source Chain: 1399300952838017768 (TON Testnet ✓) + Message: "Hello EVM from TON" + Block: 10554441 + Time: 2026-03-30T15:25:24.000Z + TX Hash: 0xb7a478440813a15d57e290905463fbefa186b609e95020444be179e474bc4b8a + + 📍 Received 2 minute(s) ago + +═══════════════════════════════════════════════════════════════ + VERIFICATION RESULT +═══════════════════════════════════════════════════════════════ + +✅ Message verified successfully! + + ✓ Message content matches: "Hello EVM from TON" + ✓ Source chain is TON Testnet + +🔗 View on explorer: + https://sepolia.etherscan.io/tx/0xb7a478440813a15d57e290905463fbefa186b609e95020444be179e474bc4b8a +``` + +### Verify on Etherscan + +Once the `utils:checkEVM` script confirms delivery, you can perform a final verification on the Sepolia block explorer. + +- Visit the [Sepolia Etherscan Explorer](https://sepolia.etherscan.io/). +- Search for your `MessageReceiver` contract address. +- Under the **Events** tab, you should see a `MessageFromTON` event with the message ID and your encoded payload. + + diff --git a/src/content/ccip/tutorials/ton/source/build-messages.mdx b/src/content/ccip/tutorials/ton/source/build-messages.mdx new file mode 100644 index 00000000000..60e35125833 --- /dev/null +++ b/src/content/ccip/tutorials/ton/source/build-messages.mdx @@ -0,0 +1,377 @@ +--- +section: ccip +date: Last Modified +title: "Building CCIP Messages from TON to EVM" +isIndex: false +metadata: + description: "Learn how to construct CCIP arbitrary messages from TON to EVM chains. Covers the TL-B Cell layout, receiver encoding, extraArgs, fee estimation, and sending options." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "TON→EVM, CCIP, arbitrary messaging, TL-B Cell, extraArgs, GenericExtraArgsV2, fee estimation, CCIPSend" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "30 minutes" + difficulty: "advanced" +--- + +import { Aside, GitHubCard } from "@components" +import { Tabs } from "@components/Tabs" +import CcipCommon from "@features/ccip/CcipCommon.astro" + +## Introduction + +This guide explains how to construct CCIP messages from the TON blockchain to EVM chains (e.g., Ethereum Sepolia, Arbitrum Sepolia). TON's CCIP integration currently supports **arbitrary messaging only** — token transfers are not supported on TON lanes. + +You send a CCIP message from TON by constructing a [Cell](https://docs.ton.org/foundations/serialization/cells) in the specific [TL-B layout](https://docs.ton.org/languages/tl-b/overview) expected by the CCIP Router contract, then sending that Cell as the body of an internal TON message to the Router address with enough TON attached to cover both the CCIP protocol fee and source-chain execution costs. + + + +## CCIP Message Cell Layout on TON + +CCIP messages from TON are sent by constructing a `Router_CCIPSend` Cell and delivering it as the body of an [internal message](https://docs.ton.org/foundations/messages/internal) to the CCIP Router address on TON Testnet (`EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj`). + +The `buildCCIPMessageForEVM` helper in the [TON Starter Kit](https://github.com/smartcontractkit/ccip-starter-kit-ton/blob/main/scripts/utils/utils.ts) assembles this Cell: + +```typescript filename="scripts/utils/utils.ts" +import { Address, beginCell, Cell } from "@ton/core" + +const CCIP_SEND_OPCODE = 0x31768d95 + +export function buildCCIPMessageForEVM( + queryID: bigint | number, + destChainSelector: bigint | number, + receiverBytes: Buffer, // 32 bytes: 12 zero-bytes + 20-byte EVM address + data: Cell, // message payload + feeToken: Address, // native TON address + extraArgs: Cell // GenericExtraArgsV2 cell +): Cell { + return beginCell() + .storeUint(CCIP_SEND_OPCODE, 32) // Router opcode + .storeUint(queryID, 64) // unique message identifier (wallet seqno) + .storeUint(destChainSelector, 64) // destination chain selector + .storeUint(receiverBytes.length, 8) // receiver byte-length prefix (always 32) + .storeBuffer(receiverBytes) // encoded EVM receiver address + .storeRef(data) // message payload cell + .storeRef(Cell.EMPTY) // tokenAmounts — always empty for TON + .storeAddress(feeToken) // fee token (native TON only) + .storeRef(extraArgs) // GenericExtraArgsV2 cell + .endCell() +} +``` + +The following sections describe each field in detail. + +--- + +### queryID + +- **Type**: `uint64` +- **Purpose**: A unique identifier that lets the TON CCIP Router correlate `Router_CCIPSendACK` and `Router_CCIPSendNACK` responses back to the originating send. +- **Recommended value**: Use the sending wallet's current sequence number (`seqno`). Wallet seqnos are monotonically increasing and unique per wallet, making them collision-free. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const seqno = await walletContract.getSeqno() +// pass BigInt(seqno) as queryID +``` + + + +--- + +### destChainSelector + +- **Type**: `uint64` +- **Purpose**: Identifies the destination EVM chain where the message will be delivered. +- **Supported chains**: See the [CCIP Directory](/ccip/directory/testnet/chain/ton-testnet) for the complete list of supported TON → EVM lanes and their chain selectors. + +```typescript filename="helper-config.ts" +// From helper-config.ts in the Starter Kit +const destChainSelector = BigInt(networkConfig["sepolia"].chainSelector) +// => 16015286601757825753n (Ethereum Sepolia) +``` + +--- + +### receiver + +- **Definition**: The address of the contract on the destination EVM chain that will receive the CCIP message. +- **Encoding**: EVM addresses are 20 bytes, but the TON CCIP Router expects a 32-byte buffer. Left-pad the 20-byte address with 12 zero-bytes. + +```typescript filename="scripts/utils/utils.ts" +export function encodeEVMAddress(evmAddr: string): Buffer { + const addrBytes = Buffer.from(evmAddr.slice(2), "hex") // strip '0x' + return Buffer.concat([Buffer.alloc(12, 0), addrBytes]) // 12 zero-bytes + 20-byte address +} +``` + +**Usage:** + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const receiverBytes = encodeEVMAddress("0xYourEVMReceiverAddress") +// receiverBytes.length === 32 +``` + + + +--- + +### data + +- **Definition**: The raw bytes delivered to the `_ccipReceive` function on the destination EVM contract via `Client.Any2EVMMessage.data`. +- **Format**: A TL-B `Cell` containing your message payload. + +For sending a plain text string: + +```typescript filename="scripts/ton2evm/sendMessage.ts" +import { beginCell } from "@ton/core" + +const data = beginCell().storeStringTail("Hello EVM from TON").endCell() +``` + +The EVM receiver contract receives these bytes as `message.data` and is responsible for interpreting them. The `MessageReceiver.sol` contract in the Starter Kit emits the raw bytes in a `MessageFromTON` event, which can be decoded with `ethers.toUtf8String(message.data)`. + + + +--- + +### tokenAmounts + +- **Value**: Always `Cell.EMPTY`. +- **Reason**: Token transfers are not supported on TON CCIP lanes. All messages from TON carry data only. + +```typescript +.storeRef(Cell.EMPTY) // tokenAmounts — must always be an empty cell +``` + +--- + +### feeToken + +- **Definition**: The token used to pay the CCIP protocol fee on TON. +- **Supported value**: Only native TON is supported. Paying fees in LINK is not available for TON-to-EVM messages. +- **Address**: `EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99` + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const feeToken = Address.parse(networkConfig.tonTestnet.nativeTokenAddress) +// "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd99" +``` + +The CCIP protocol fee is deducted from the TON value attached to the Router message, not from a separate token transfer. + +--- + +### extraArgs + +The `extraArgs` field is a `Cell` encoding [`GenericExtraArgsV2`](/ccip/api-reference/evm/v1.6.1/client#genericextraargsv2) parameters required by the destination EVM chain. The tag `0x181dcf10` is the [`GENERIC_EXTRA_ARGS_V2_TAG`](/ccip/api-reference/evm/v1.6.1/client#generic_extra_args_v2_tag). The Cell layout is: + +| Field | Type | Description | +| -------------------------- | --------- | --------------------------------------------------- | +| `tag` | `uint32` | `0x181dcf10` — identifies GenericExtraArgsV2 format | +| `hasGasLimit` | `bit` | Must be `1` (gas limit is always present) | +| `gasLimit` | `uint256` | EVM gas units allocated for receiver execution | +| `allowOutOfOrderExecution` | `bit` | Must be `1` for TON-to-EVM messages | + +```typescript filename="scripts/utils/utils.ts" +export function buildExtraArgsForEVM(gasLimitEVMUnits: number, allowOutOfOrderExecution: boolean): Cell { + return beginCell() + .storeUint(0x181dcf10, 32) // GenericExtraArgsV2 tag + .storeBit(true) // gasLimit IS present + .storeUint(gasLimitEVMUnits, 256) // gasLimit in EVM gas units + .storeBit(allowOutOfOrderExecution) // must be true + .endCell() +} +``` + + + +--- + +## Estimating the CCIP Fee + +Before sending, query the protocol fee. The fee is returned in [nanoTON](https://docs.ton.org/foundations/fees) and is computed by the FeeQuoter contract, reachable through a chain of on-chain getter calls: + +``` +Router.onRamp(destChainSelector) → OnRamp address +OnRamp.feeQuoter(destChainSelector) → FeeQuoter address +FeeQuoter.validatedFeeCell(ccipSendCell) → fee in nanoTON +``` + +The `getCCIPFeeForEVM` helper in the Starter Kit performs this lookup. The CCIP message Cell passed to it must be fully populated — `queryID`, `destChainSelector`, `receiver`, `data`, `feeToken`, and `extraArgs` must all match the values used in the final send. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +import { TonClient } from "@ton/ton" +import { fromNano } from "@ton/core" + +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +console.log(`CCIP fee: ${fromNano(fee)} TON`) +``` + +### Applying a buffer and gas reserve + +Add a buffer on top of the quoted fee to account for minor variations between quote time and execution: + +- **10% fee buffer**: Covers small fluctuations in the protocol fee. +- **0.5 TON gas reserve**: Covers the wallet-level transaction fee and source-chain execution. This is sent to the Router along with the fee and any surplus is returned via the ACK message. + +```typescript filename="scripts/ton2evm/sendMessage.ts" +const fee = await getCCIPFeeForEVM(client, routerAddress, destChainSelector, ccipSendMessage) +const feeWithBuffer = (fee * 110n) / 100n // +10% +const gasReserve = 500_000_000n // 0.5 TON in nanoTON + +const valueToAttach = feeWithBuffer + gasReserve // total value sent to Router +``` + +--- + +## Sending the Message + +After building the Cell and calculating the total value to attach, you have two options. + + + Direct from Wallet + Via Sender Contract + + Send the `Router_CCIPSend` Cell directly from your wallet to the CCIP Router address. This is the simplest path when your application logic is entirely off-chain. + + ``` + Wallet ──(Router_CCIPSend)──► CCIP Router + ``` + + ```typescript filename="scripts/ton2evm/sendMessage.ts" + import { Address, internal as createInternal } from "@ton/core" + + const routerAddress = Address.parse(networkConfig.tonTestnet.router) + + await walletContract.sendTransfer({ + seqno, + secretKey: keyPair.secretKey, + messages: [ + createInternal({ + to: routerAddress, + value: valueToAttach, // feeWithBuffer + gasReserve + body: ccipSendCell, // the Router_CCIPSend cell + }), + ], + }) + ``` + + + + If your application logic lives on-chain, deploy the `MinimalSender` contract from the Starter Kit. Your wallet sends a `CCIPSender_RelayCCIPSend` message to the Sender, which then forwards the pre-built `Router_CCIPSend` Cell to the Router. + + ``` + Wallet ──(CCIPSender_RelayCCIPSend)──► MinimalSender ──(Router_CCIPSend)──► CCIP Router + ``` + + Build the relay message as follows: + + ```typescript filename="scripts/ton2evm/sendMessage.ts" + import { beginCell, toNano } from "@ton/core" + + const CCIP_SENDER_RELAY_OPCODE = 0x00000001 + + const relayMsg = beginCell() + .storeUint(CCIP_SENDER_RELAY_OPCODE, 32) + .storeAddress(routerAddress) // CCIP Router to forward to + .storeCoins(valueToAttach) // value to attach when forwarding (fee + gas reserve) + .storeRef(ccipSendCell) // the pre-built Router_CCIPSend cell + .endCell() + + const senderOverhead = toNano("0.1") // covers MinimalSender execution costs + + await walletContract.sendTransfer({ + seqno, + secretKey: keyPair.secretKey, + messages: [ + createInternal({ + to: senderAddress, + value: valueToAttach + senderOverhead, // Router value + Sender gas + body: relayMsg, + }), + ], + }) + ``` + + + + The `MinimalSender` contract handles Router responses in its `onInternalMessage` handler: + + ```tolk filename="contracts/minimal_sender.tolk" + // Tolk is TON's smart contract language: https://docs.ton.org/tolk/overview + Router_CCIPSendACK => { + // Message accepted by the Router. + // msg.messageId is the unique CCIP message ID for tracking delivery. + // Add your on-chain success logic here (e.g., update state, emit event). + } + Router_CCIPSendNACK => { + // Message rejected by the Router. + // Both the fee and surplus TON are returned to the Sender. + // msg.error contains the Router exit code. + // Add your on-chain failure logic here (e.g., retry, refund). + throw msg.error; + } + ``` + + + + +--- + +## Reference: Full Message Construction + +The complete flow from wallet setup to sending is available in the TON Starter Kit: + + + Full script including wallet setup, message construction, fee estimation, and send with CLI options for chain, + receiver, message content, and optional Sender contract routing. + + +## Related Tutorials + +To see these concepts in action with a step-by-step implementation guide, check out the following tutorial: + +- [Arbitrary Messaging: TON to EVM](/ccip/tutorials/ton/source/arbitrary-messaging) — Learn how to send data messages from a TON wallet to an EVM receiver contract. + + diff --git a/src/content/ccip/tutorials/ton/source/index.mdx b/src/content/ccip/tutorials/ton/source/index.mdx new file mode 100644 index 00000000000..a37bf945a2e --- /dev/null +++ b/src/content/ccip/tutorials/ton/source/index.mdx @@ -0,0 +1,36 @@ +--- +section: ccip +date: Last Modified +title: "CCIP Tutorials: TON to EVM" +isIndex: true +metadata: + description: "Learn how to implement cross-chain communication from TON to Ethereum using Chainlink CCIP. Tutorials cover environment setup, arbitrary messaging, and message building with detailed implementation guides." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "TON→EVM, CCIP, arbitrary messaging, TL-B Cell, TON Testnet, Ethereum Sepolia, CCIPSend" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "5 minutes" + difficulty: "intermediate" +--- + +import { Aside } from "@components" + +This section provides comprehensive guides and tutorials for implementing cross-chain communication from the TON blockchain to Ethereum Virtual Machine (EVM) chains using Chainlink's Cross-Chain Interoperability Protocol (CCIP). + + + +## Getting Started + +Before implementing specific use cases, it's important to set up your environment and understand the fundamental concepts: + +- [Prerequisites for TON to EVM Tutorials](/ccip/tutorials/ton/source/prerequisites) - Set up your development environment with Node.js, a TON wallet, and testnet tokens. +- [Building CCIP Messages from TON to EVM](/ccip/tutorials/ton/source/build-messages) - Learn the TL-B Cell layout, required fields, fee estimation, and sending options. + +## Tutorials by Use Case + +Depending on your specific needs, choose the appropriate tutorial: + +- [Arbitrary Messaging](/ccip/tutorials/ton/source/arbitrary-messaging) - Send a data payload from TON Testnet to a receiver contract on Ethereum Sepolia. diff --git a/src/content/ccip/tutorials/ton/source/prerequisites.mdx b/src/content/ccip/tutorials/ton/source/prerequisites.mdx new file mode 100644 index 00000000000..1a4fa466dc3 --- /dev/null +++ b/src/content/ccip/tutorials/ton/source/prerequisites.mdx @@ -0,0 +1,187 @@ +--- +section: ccip +date: Last Modified +title: "Prerequisites for TON to EVM Tutorials" +isIndex: false +metadata: + description: "Complete setup guide for TON to EVM cross-chain development. Install TON SDK packages, Node.js, configure wallets, and get testnet TON for CCIP Chainlink tutorials. Step-by-step instructions for developers building cross-chain applications." + image: "/images/ccip/concepts/architecture/ccip-ton-source-chain.jpg" + excerpt: "prerequisites, TON, SDK, wallets, testnet, setup" + datePublished: "2026-03-29" + lastModified: "2026-03-29" + estimatedTime: "10 minutes" + difficulty: "beginner" +--- + +import { Aside } from "@components" + +Before starting the TON to EVM tutorials, ensure you have the following: + +## Development Environment + +- **Node.js v20 or higher**: You can use the [nvm package](http://nvm.sh/) to install and switch between Node.js versions. Once installed, verify the node version with: + + ```bash filename="Terminal" + node -v + ``` + + Example output: + + ```text + $ node -v + v22.15.0 + ``` + +- **npm**: For installing and managing dependencies. +- **Git**: For cloning the repository. + +## Starter Kit Repository + +1. Clone the CCIP TON Starter Kit: + + ```bash filename="Terminal" + git clone https://github.com/smartcontractkit/ccip-starter-kit-ton.git + ``` + +1. Navigate to the directory: + + ```bash filename="Terminal" + cd ccip-starter-kit-ton + ``` + +1. Install dependencies: + + ```bash filename="Terminal" + npm install + ``` + +This installs the required TON SDK packages, including [`@ton/core`](https://github.com/ton-org/ton-core), [`@ton/ton`](https://github.com/ton-org/ton), and [`@ton/crypto`](https://github.com/ton-org/ton-crypto), which are used to interact with the TON blockchain. + +## Understanding TON Network Configuration + +The TON Starter Kit uses a `helper-config.ts` file to manage network settings and contract addresses for TON Testnet. This configuration includes: + +- **CCIP Router Address**: The on-chain address of the CCIP Router contract on TON +- **Chain Selectors**: Unique identifiers for destination EVM chains +- **RPC Endpoints**: URLs for connecting to TON blockchain nodes + +**Example configuration snippet:** + +```typescript filename="helper-config.ts" +tonTestnet: { + chainSelector: '1399300952838017768', + router: 'EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj', + // ... + destChains: { + sepolia: 'sepolia', + arbitrumSepolia: 'arbitrumSepolia' + }, + // ... +}, +sepolia: { + chainSelector: '16015286601757825753', + router: '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59', + // ... +}, +arbitrumSepolia: { + chainSelector: '3478487238524512106', + router: '0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165', + // ... +}, +``` + +## Wallets + +- **TON Wallet**: You'll need a TON wallet to send transactions. The starter kit uses the **[V4R2 wallet version](https://docs.ton.org/standard/wallets/v4)**. You can create a TON wallet using [Tonkeeper](https://tonkeeper.com/) (available on iOS, Android, desktop, and as a browser extension): + 1. Download and install Tonkeeper + 2. Create a new wallet and save the [24-word recovery phrase (mnemonic)](https://docs.ton.org/standard/wallets/mnemonics) + 3. Add a Testnet Account using the same mnemonic + 4. In wallet settings, select **V4R2** as the wallet version + + + + The wallet's private key is derived from the 24-word mnemonic phrase and stored in your environment configuration. + +- **EVM Wallet Address**: You'll need an EVM-compatible wallet address to receive messages on the destination chain (e.g., Ethereum Sepolia or Arbitrum Sepolia). You only need the address itself, not the private key, as you are only sending _to_ this address. + +## Environment Configuration (`.env` file) + +The starter kit uses a `.env` file to manage sensitive information like mnemonic phrases and RPC URLs. Create a new file named `.env` in the root of the `ccip-starter-kit-ton` directory by copying the example file: + +```bash filename="Terminal" +cp .env.example .env +``` + +Next, open the `.env` file and fill in the required values: + +- `TON_MNEMONIC`: Your 24-word mnemonic phrase for the TON wallet. This is used to derive the private key for signing transactions on TON. + +- `EVM_PRIVATE_KEY`: The private key of your EVM wallet (with `0x` prefix). Required for deploying receiver contracts on EVM chains (e.g., `deploy:evm:receiver`). The corresponding wallet must hold Sepolia ETH. + +- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC endpoint for Ethereum Sepolia. This is used by verification scripts to check message execution status on the destination chain. You can obtain a free RPC URL from providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or [QuickNode](https://www.quicknode.com/). + +- `ARBITRUM_SEPOLIA_RPC_URL` (optional): The RPC endpoint for Arbitrum Sepolia if you plan to send messages to this chain. + +- `TON_RPC_URL` (optional): Override the default TON RPC endpoint. The default is `https://ton-testnet.api.onfinality.io/public`, which doesn't require an API key. + +- `TON_CENTER_API_KEY` (optional): Only required if you use a toncenter RPC URL (e.g., `https://testnet.toncenter.com/api/v2/jsonRPC`). Get a free API key from [@tonapibot](https://t.me/tonapibot) on Telegram. + +**Example `.env` file:** + +```bash filename=".env" +TON_MNEMONIC="word1 word2 word3 ... word24" +EVM_PRIVATE_KEY=0xYourEVMPrivateKey +ETHEREUM_SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY +ARBITRUM_SEPOLIA_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/YOUR_API_KEY +``` + + + +## Native TON Tokens for Transaction Fees + +**TON** tokens are required for all transactions on the TON blockchain, including sending CCIP messages. TON is also used to pay for CCIP fees when sending cross-chain messages. + + + +### Getting TON on Testnet + +To obtain TON tokens on Testnet, use the official TON Testnet faucet: + +1. Visit the [TON Testnet Faucet](https://t.me/testgiver_ton_bot) +2. Open Telegram and start a chat with the `@testgiver_ton_bot` +3. Send your V4R2 testnet wallet address to the bot +4. The bot will send you 2 testnet TON (you can request again every 60 minutes) + +### Checking Your TON Balance + +You can check your wallet balance using TON blockchain explorers: + +- [Testnet Explorer](https://testnet.tonscan.org/): Enter your wallet address to view balance and transaction history +- TON wallet applications like Tonkeeper also display your balance + +Alternatively, you can use the TON SDK in a script to query your balance programmatically: + +```typescript +import { TonClient } from "@ton/ton" + +const client = new TonClient({ + endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", +}) + +const balance = await client.getBalance(yourWalletAddress) +console.log(`Balance: ${balance} nanoTON`) +``` + + diff --git a/src/lib/codeSample/language.ts b/src/lib/codeSample/language.ts index 24e4c3b3313..e3f5d300641 100644 --- a/src/lib/codeSample/language.ts +++ b/src/lib/codeSample/language.ts @@ -21,5 +21,6 @@ export function getLanguageIconSrc(language: string): string | undefined { if (["python", "py"].includes(l)) return "/images/language-icons/python.svg" if (["rust", "rs"].includes(l)) return "/images/language-icons/rust.svg" if (["bash", "sh", "shell", "zsh", "terminal"].includes(l)) return "/images/language-icons/terminal.svg" + if (l === "tolk") return "/images/language-icons/tolk.svg" return undefined } diff --git a/src/stores/chainType.ts b/src/stores/chainType.ts index fcfbc240f23..c6379945dd0 100644 --- a/src/stores/chainType.ts +++ b/src/stores/chainType.ts @@ -62,6 +62,7 @@ export function setChainType(chainType: ChainType): void { * /ccip/tutorials/evm/transfer-tokens → 'evm' * /ccip/service-limits/svm → 'solana' * /ccip/api-reference/aptos/v1.6.0 → 'aptos' + * /ccip/tutorials/ton/receivers → 'ton' * * @param pathname - URL pathname to analyze * @returns Detected chain type or null if not found @@ -70,6 +71,7 @@ function detectChainFromPath(pathname: string): ChainType | null { if (/\/(evm|ethereum)(\/|$)/i.test(pathname)) return "evm" if (/\/(svm|solana)(\/|$)/i.test(pathname)) return "solana" if (/\/aptos(\/|$)/i.test(pathname)) return "aptos" + if (/\/ton(\/|$)/i.test(pathname)) return "ton" return null }