diff --git a/axelar-chains-config/info/devnet-amplifier.json b/axelar-chains-config/info/devnet-amplifier.json index 172ed81559..f1d8f1c879 100644 --- a/axelar-chains-config/info/devnet-amplifier.json +++ b/axelar-chains-config/info/devnet-amplifier.json @@ -1301,6 +1301,36 @@ "codeId": 854, "address": "axelar1a4e4aful6hchmxd3cffurz24pca52wmt3pf78rsmzglzujnueqgq7eypgl" }, + "ton": { + "governanceAddress": "axelar1zlr7e5qf3sz7yf890rkh9tcnu87234k6k7ytd9", + "serviceName": "validators", + "sourceGatewayAddress": "kQDXcLI49JhbipboxSbaKlhUU2YCi_AOSsaG7Fe0uLMF5drj", + "votingThreshold": [ + "1", + "1" + ], + "blockExpiry": 10, + "confirmationHeight": 1, + "msgIdFormat": "hex_tx_hash_and_event_index", + "addressFormat": "ton", + "codeId": 1291, + "address": "axelar1snc8xyp47cyh7jgvprcezz00r56plfh9vy669gznqu6pktwlez3skvfd0a" + }, + "bitcoin-dev": { + "governanceAddress": "axelar1zlr7e5qf3sz7yf890rkh9tcnu87234k6k7ytd9", + "serviceName": "validators", + "sourceGatewayAddress": "0xCa85f85C72df5f8428a440887CA7c449D94e0D0c", + "votingThreshold": [ + "1", + "1" + ], + "blockExpiry": 10, + "confirmationHeight": 1, + "msgIdFormat": "hex_tx_hash", + "addressFormat": "eip55", + "codeId": 854, + "address": "axelar1y46xz0x0dd2h29jynh0n9xudtmk0cg8jmxqr0cqv2pj6jfxw3s0qjq63me" + }, "storeCodeProposalId": "78", "storeCodeProposalCodeHash": "d9412440820a51bc48bf41a77ae39cfb33101ddc6562323845627ea2042bf708" }, @@ -1335,6 +1365,14 @@ "plume-2": { "codeId": 848, "address": "axelar1unafus3vpt7nawfzdjf7u26g2rl855lpkqussxmtl5r2njaz39mqrmyv7g" + }, + "ton": { + "codeId": 1292, + "address": "axelar1lxgdq53a406k6ehv4yue04e7sxr7vfujkf44jfwgtx5xugzru0ssnsc03z" + }, + "bitcoin-dev": { + "codeId": 848, + "address": "axelar1y6uwt6sma4ay7awdtecp47flapexq8cm6dd0csfuausupgw47mes3glt9f" } }, "XrplMultisigProver": { @@ -1463,6 +1501,36 @@ "domainSeparator": "0x974a372dccb2a81196b546432ad388af9cb49d0a5e4567d444c51ea63edb62f9", "address": "axelar1mfn6hevkpj54qkkdy2rznp2azgvqff0rt2cegmflxgqfm0nqx20q6aqnp5" }, + "ton": { + "governanceAddress": "axelar1zlr7e5qf3sz7yf890rkh9tcnu87234k6k7ytd9", + "adminAddress": "axelar1fgg7mer9ljjj94jzxeha3svg8u3kp8sxqzygh7", + "destinationChainID": "43113", + "signingThreshold": [ + "1", + "1" + ], + "serviceName": "validators", + "verifierSetDiffThreshold": 1, + "encoder": "ton", + "keyType": "ed25519", + "domainSeparator": "0x6973c72935604464b28827141b0a463af8e3487616de69c5aa0c785392c9fb9f", + "codeId": 1312, + "address": "axelar1lxgdq53a406k6ehv4yue04e7sxr7vfujkf44jfwgtx5xugzru0ssnsc03z" + }, + "bitcoin-dev": { + "governanceAddress": "axelar1zlr7e5qf3sz7yf890rkh9tcnu87234k6k7ytd9", + "adminAddress": "axelar1880jp05pwxmfs30ynr9qvh6p63l54yl7nqr2dt", + "signingThreshold": [ + "1", + "1" + ], + "serviceName": "validators", + "verifierSetDiffThreshold": 1, + "keyType": "ecdsa", + "domainSeparator": "0xbe20bbed22c0d35d6116a8312fbe9c90411f96cfa7893aaa7437bf088413ed79", + "codeId": 855, + "address": "axelar18y9apg4wl8yu06ccqsfjf4ceezen7pwvnrqjwqn3ugqdzq5thhhsvfqu7h" + }, "storeCodeProposalId": "82", "storeCodeProposalCodeHash": "00428ef0483f103a6e1a5853c4b29466a83e5b180cc53a00d1ff9d022bc2f03a" }, diff --git a/package-lock.json b/package-lock.json index 691f13f51a..7d520e57f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mysten/ledgerjs-hw-app-sui": "^0.4.1", "@mysten/sui": "^1.3.0", "@stellar/stellar-sdk": "^13.0.0", + "@ton/ton": "^15.2.1", "axios": "^1.7.2", "csv-parser": "^3.0.0", "path": "^0.12.7", @@ -2609,6 +2610,58 @@ "resolved": "https://registry.npmjs.org/@suchipi/femver/-/femver-1.0.0.tgz", "integrity": "sha512-bprE8+K5V+DPX7q2e2K57ImqNBdfGHDIWaGI5xHxZoxbKOuQZn4wzPiUxOAHnsUr3w3xHrWXwN7gnG/iIuEMIg==" }, + "node_modules/@ton/core": { + "version": "0.60.1", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.60.1.tgz", + "integrity": "sha512-8FwybYbfkk57C3l9gvnlRhRBHbLYmeu0LbB1z9N+dhDz0Z+FJW8w0TJlks8CgHrAFxsT3FlR2LsqFnsauMp38w==", + "license": "MIT", + "peer": true, + "dependencies": { + "symbol.inspect": "1.0.1" + }, + "peerDependencies": { + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@ton/crypto": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz", + "integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ton/crypto-primitives": "2.1.0", + "jssha": "3.2.0", + "tweetnacl": "1.0.3" + } + }, + "node_modules/@ton/crypto-primitives": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz", + "integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==", + "license": "MIT", + "peer": true, + "dependencies": { + "jssha": "3.2.0" + } + }, + "node_modules/@ton/ton": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-15.2.1.tgz", + "integrity": "sha512-ICzozzATRfymkVfFVZrfVpKnCc5PLxAVeaB62mx/HsgllsjnR64UuoLuE6hqWHcA3/Hft9YLGdk2/rOHGZM6qA==", + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "dataloader": "^2.0.0", + "symbol.inspect": "1.0.1", + "teslabot": "^1.3.0", + "zod": "^3.21.4" + }, + "peerDependencies": { + "@ton/core": ">=0.60.0", + "@ton/crypto": ">=3.2.0" + } + }, "node_modules/@trivago/prettier-plugin-sort-imports": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz", @@ -4519,6 +4572,12 @@ "node": ">=0.12" } }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "license": "MIT" + }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -7188,6 +7247,16 @@ "node": "*" } }, + "node_modules/jssha": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz", + "integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -9540,6 +9609,12 @@ "node": ">=0.10" } }, + "node_modules/symbol.inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol.inspect/-/symbol.inspect-1.0.1.tgz", + "integrity": "sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ==", + "license": "ISC" + }, "node_modules/sync-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", @@ -9667,6 +9742,12 @@ "node": ">=6" } }, + "node_modules/teslabot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/teslabot/-/teslabot-1.5.0.tgz", + "integrity": "sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg==", + "license": "MIT" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10804,6 +10885,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.28", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.28.tgz", + "integrity": "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2eea2e9eec..5902d0bc60 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@mysten/ledgerjs-hw-app-sui": "^0.4.1", "@mysten/sui": "^1.3.0", "@stellar/stellar-sdk": "^13.0.0", + "@ton/ton": "^15.2.1", "axios": "^1.7.2", "csv-parser": "^3.0.0", "path": "^0.12.7", diff --git a/ton/Readme.md b/ton/Readme.md new file mode 100644 index 0000000000..6dfbbe8f55 --- /dev/null +++ b/ton/Readme.md @@ -0,0 +1,29 @@ + +# Ton operational scripts + +This folder contains operational scripts for the Ton AxelarGateway. It assumes an already deployed AxelarGateway.fc and an Executable.fc. + +Required enviroment variables + +``` +MNEMONIC= +TONCENTER_API_KEY= +TON_GATEWAY_ADDRESS= +``` + +## Appoving a message + +Run this with the hex encoded payload procuded by the Ton MultisigProver. + +```bash +node ton/approveMessages.js b5ee9c7241020c0100018a000208000000280102016180000000000000000000000000000000800000000000000000000000000000000000000000000000000000000035f72a40030101c00400e2d0b0005189ac84d3a1d675847700777891dde751fa107d64837bf6337d32df6790000000000000000000000000000000018b92f5624a0587f7ed939e1c0fe4a6e4688d3a945402e2e51785d1bb98a5efe47b1d85c89fdc1920681e1d30b98c0ed4ecb64132f32b7bfcc76c58450fe7f0000102d005044035d25b76a49eebc07a7419b922fc11bd7bba1970b579d2a380ddd6606c5a1ff80607080900883078343737653062373438626132303064613436383963633836323965306431323363623862346139346132386333333537303661343032376465653766326261352d30001c6176616c616e6368652d66756a69005430783831653633654138463634464564423938353845423645323137364234333146426431306431654302000a0b00404a1a80a7b0326b22310dced59d8b52efddf313e77f9b48f226b69b8efedbe24d0006746f6e477e6fee +``` + + +## Executing a message + +Run this to execute an approve message + +```bash +node ton/relayerExecute.js "0x477e0b748ba200da4689cc8629e0d123cb8b4a94a28c335706a4027dee7f2ba5-0" "avalanche-fuji" "0x81e63eA8F64FEdB9858EB6E2176B431FBd10d1eC" "48656c6c6f2066726f6d204176616c616e63686521" "0:4a1a80a7b0326b22310dced59d8b52efddf313e77f9b48f226b69b8efedbe24d" "ton" "0x35d25b76a49eebc07a7419b922fc11bd7bba1970b579d2a380ddd6606c5a1ff8" +``` diff --git a/ton/approveMessages.js b/ton/approveMessages.js new file mode 100644 index 0000000000..7f9bd69257 --- /dev/null +++ b/ton/approveMessages.js @@ -0,0 +1,57 @@ +const { Command } = require('commander'); +const { Address, Cell, internal } = require('@ton/ton'); +const { getTonClient, loadWallet, waitForTransaction, GATEWAY_ADDRESS } = require('./common'); + +// Constants +const APPROVE_MESSAGES_COST = '2'; + +function createApproveMessagesCell(encodedPayload) { + return Cell.fromBoc(Buffer.from(encodedPayload, 'hex'))[0]; +} + +async function run(encodedPayload) { + try { + const client = getTonClient(); + const { contract, key } = await loadWallet(client); + + const gateway = Address.parse(GATEWAY_ADDRESS); + const approveMessagesCell = createApproveMessagesCell(encodedPayload); + + const message = internal({ + to: gateway, + value: APPROVE_MESSAGES_COST, + body: approveMessagesCell, + }); + + const seqno = await contract.getSeqno(); + console.log('Current wallet seqno:', seqno); + + console.log('Sending approve messages transaction...'); + const transfer = await contract.sendTransfer({ + secretKey: key.secretKey, + messages: [message], + seqno: seqno, + amount: APPROVE_MESSAGES_COST, + }); + + console.log('Approve messages transaction sent successfully!'); + + await waitForTransaction(contract, seqno); + + } catch (error) { + console.error('Error in approve messages:', error); + throw error; + } +} + +// Set up command line interface +if (require.main === module) { + const program = new Command(); + program + .name('approveMessages') + .description('Approve messages on TON gateway') + .argument('', 'Encoded payload in hex format') + .action(run); + + program.parse(); +} diff --git a/ton/common.js b/ton/common.js new file mode 100644 index 0000000000..984728c3b6 --- /dev/null +++ b/ton/common.js @@ -0,0 +1,55 @@ +// ton/common.js +const { TonClient, WalletContractV5R1 } = require('@ton/ton'); +const { mnemonicToWalletKey } = require('@ton/crypto'); +require('dotenv').config(); + +// Constants +const TONCENTER_ENDPOINT = 'https://testnet.toncenter.com/api/v2/jsonRPC'; +const GATEWAY_ADDRESS = process.env.TON_GATEWAY_ADDRESS; + +if (!GATEWAY_ADDRESS) { + throw new Error('Please set TON_GATEWAY_ADDRESS in your .env file'); +} + +// Helper function to initialize TON client +function getTonClient() { + if (!process.env.TONCENTER_API_KEY) { + throw new Error('Please set TONCENTER_API_KEY environment variable. Get it from https://t.me/tontestnetapibot'); + } + + return new TonClient({ + endpoint: TONCENTER_ENDPOINT, + apiKey: process.env.TONCENTER_API_KEY, + }); +} + +// Helper function to load wallet +async function loadWallet(client) { + const mnemonic = process.env.MNEMONIC?.split(' ') || []; + if (mnemonic.length !== 24) { + throw new Error('Please set MNEMONIC environment variable with 24 words'); + } + + const key = await mnemonicToWalletKey(mnemonic); + const wallet = WalletContractV5R1.create({ publicKey: key.publicKey, workchain: 0 }); + return { contract: client.open(wallet), key, wallet }; +} + +// Helper function to wait for transaction confirmation +async function waitForTransaction(contract, seqno) { + let currentSeqno = seqno; + while (currentSeqno === seqno) { + console.log('Waiting for transaction confirmation...'); + await new Promise(resolve => setTimeout(resolve, 1500)); + currentSeqno = await contract.getSeqno(); + } + console.log('Transaction confirmed!'); +} + +module.exports = { + getTonClient, + loadWallet, + waitForTransaction, + TONCENTER_ENDPOINT, + GATEWAY_ADDRESS, +}; diff --git a/ton/relayerExecute.js b/ton/relayerExecute.js new file mode 100644 index 0000000000..bfb38d7101 --- /dev/null +++ b/ton/relayerExecute.js @@ -0,0 +1,132 @@ +const { Command } = require('commander'); +const { Address, internal, beginCell } = require('@ton/ton'); +const { getTonClient, loadWallet, waitForTransaction, GATEWAY_ADDRESS } = require('./common'); + +// Constants +const RELAYER_EXECUTE_COST = '0.3'; +const OP_RELAYER_EXECUTE = 0x00000008; +const BYTES_PER_CELL = 96; + +function bufferToCell(buffer) { + function buildCellChain(startIndex) { + const builder = beginCell(); + const endIndex = Math.min(startIndex + BYTES_PER_CELL, buffer.length); + + for (let i = startIndex; i < endIndex; i++) { + builder.storeUint(buffer[i], 8); + } + + if (endIndex < buffer.length) { + const nextCell = buildCellChain(endIndex); + builder.storeRef(nextCell); + } + + return builder.endCell(); + } + + return buildCellChain(0); +} + +function buildRelayerExecuteMessageBody( + messageString, + relayerAddress, + sourceChainString, + sourceAddressString, + payloadBuffer, + contractAddress, + destinationChainString, + payloadHash, +) { + const messageIdCell = bufferToCell(Buffer.from(messageString, 'utf8')); + const sourceChain = bufferToCell(Buffer.from(sourceChainString, 'utf8')); + const sourceContractAddress = bufferToCell(Buffer.from(sourceAddressString, 'utf8')); + const payload = bufferToCell(payloadBuffer); + const destinationChain = bufferToCell(Buffer.from(destinationChainString, 'utf8')); + const contractAddressBuffer = contractAddress.hash; + const contractAddressCell = bufferToCell(contractAddressBuffer); + + const message = beginCell() + .storeRef(messageIdCell) + .storeRef(sourceChain) + .storeRef(sourceContractAddress) + .storeRef( + beginCell() + .storeRef(payload) + .storeRef(contractAddressCell) + .storeRef(destinationChain) + .storeUint(payloadHash, 256) + .endCell(), + ) + .endCell(); + + return beginCell() + .storeUint(OP_RELAYER_EXECUTE, 32) + .storeRef(message) + .storeAddress(relayerAddress) + .endCell(); +} + +async function run(messageId, sourceChain, sourceAddress, payload, executableAddress, destinationChain, payloadHash) { + try { + const client = getTonClient(); + const { contract, key, wallet } = await loadWallet(client); + const gateway = Address.parse(GATEWAY_ADDRESS); + + const payloadBuffer = Buffer.from(payload, 'hex'); + const executableAddr = Address.parseRaw(executableAddress); + + const relayerExecuteCell = buildRelayerExecuteMessageBody( + messageId, + wallet.address, + sourceChain, + sourceAddress, + payloadBuffer, + executableAddr, + destinationChain, + BigInt(payloadHash) + ); + + const message = internal({ + to: gateway, + value: RELAYER_EXECUTE_COST, + body: relayerExecuteCell, + }); + + const seqno = await contract.getSeqno(); + console.log('Current wallet seqno:', seqno); + + console.log('Sending relayer execute transaction...'); + const transfer = await contract.sendTransfer({ + secretKey: key.secretKey, + messages: [message], + seqno: seqno, + amount: RELAYER_EXECUTE_COST, + }); + + console.log('Relayer execute transaction sent successfully!'); + + await waitForTransaction(contract, seqno); + + } catch (error) { + console.error('Error in relayer execute:', error); + throw error; + } +} + +// Set up command line interface +if (require.main === module) { + const program = new Command(); + program + .name('relayerExecute') + .description('Execute relayer message on TON gateway') + .argument('', 'Message ID (e.g. 0x678771abd95ff19d3285e1a43a25a5e5f4e5c8e4dcabec0e1cb342bc18c63366-0)') + .argument('', 'Source chain name (e.g. avalanche-fuji)') + .argument('', 'Source address (e.g. 0x81e63eA8F64FEdB9858EB6E2176B431FBd10d1eC)') + .argument('', 'Payload in hex (e.g. 48656c6c6f2066726f6d204176616c616e63686521)') + .argument('', 'Executable contract address (e.g. 0:4a1a80a7b0326b22310dced59d8b52efddf313e77f9b48f226b69b8efedbe24d)') + .argument('', 'Destination chain name (e.g. ton)') + .argument('', 'Payload hash (e.g. 0x35d25b76a49eebc07a7419b922fc11bd7bba1970b579d2a380ddd6606c5a1ff8)') + .action(run); + + program.parse(); +}