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/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..3bfc41b6585 100644 --- a/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap +++ b/src/config/sidebar/__tests__/__snapshots__/ccip-dynamic.test.ts.snap @@ -694,6 +694,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..0be89afaa57 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", @@ -319,6 +325,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"], + // }, ], }, { @@ -577,6 +588,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 +769,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/llms-full.txt b/src/content/ccip/llms-full.txt index 072c188f740..eae50ae5a86 100644 --- a/src/content/ccip/llms-full.txt +++ b/src/content/ccip/llms-full.txt @@ -23503,6 +23503,1449 @@ 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 using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` to forward all remaining TON for the protocol's confirmation chain. + +```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 +66611,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 +66622,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 +66682,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..a3fb027dbcc --- /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/ccip-hl-v1.6.gif" + 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..93bb843ae35 --- /dev/null +++ b/src/content/ccip/tutorials/ton/destination/build-messages.mdx @@ -0,0 +1,231 @@ +--- +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/ccip-hl-v1.6.gif" + 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 { Address } from "@ton/core" + import { ethers } from "ethers" + + const TON_TESTNET_CHAIN_SELECTOR = 1399300952838017768n + + const tonContractAddr = "EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj" + const receiver = encodeTONAddress(Address.parse(tonContractAddr)) + 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 { Address } from "@ton/core" + import { ethers } from "ethers" + + const TON_TESTNET_CHAIN_SELECTOR = 1399300952838017768n + const LINK_ADDRESS = "0x779877A7B0D9E8603169DdbD7836e478b4624789" // LINK on Sepolia + + const tonContractAddr = "EQB9QIw22sgwNKMfqsMKGepkhnjXYJmXlzCgcBSAlaiF9VCj" + const receiver = encodeTONAddress(Address.parse(tonContractAddr)) + 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..a86df49df73 --- /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/ccip-hl-v1.6.gif" + 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..39f121001d4 --- /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/ccip-hl-v1.6.gif" + 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..0913af20e70 --- /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/ccip-hl-v1.6.gif" + 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..8ba0c3c8419 --- /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/ccip-hl-v1.6.gif" + 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 using `SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE` to forward all remaining TON for the protocol's confirmation chain. + +```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..5d4272d58f8 --- /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/ccip-hl-v1.6.gif" + 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..88531dc3b65 --- /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/ccip-hl-v1.6.gif" + 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..3fc6b3f9a8e --- /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/ccip-hl-v1.6.gif" + 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..b31d33a79bd --- /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/ccip-hl-v1.6.gif" + 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 }