TON support
This commit is contained in:
@@ -10,6 +10,8 @@ AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
|
||||
BERACHAIN_RPC_URL=https://rpc.berachain.com
|
||||
SONIC_RPC_URL=https://rpc.soniclabs.com/
|
||||
XRP_RPC_URL=https://xrplcluster.com/
|
||||
TON_RPC_URL=https://toncenter.com/api/v2/jsonRPC
|
||||
TON_API_KEY=your-toncenter-api-key
|
||||
|
||||
# Private keys (DO NOT SHARE)
|
||||
ETH_PRIVATE_KEY=your-eth-private-key
|
||||
@@ -21,6 +23,10 @@ XRP_PRIVATE_KEY=your-xrp-private-key # Private key in hex format
|
||||
XRP_MNEMONIC=your-xrp-mnemonic # Alternative: 12-word recovery phrase
|
||||
XRP_ADDRESS=your-xrp-address # Optional: Used to verify key derivation
|
||||
|
||||
# TON Configuration
|
||||
TON_MNEMONIC=word1 word2 word3 ... word24 # 24-word recovery phrase
|
||||
TON_ADDRESS=your-ton-address # Your TON wallet address
|
||||
|
||||
# Cardano Configuration
|
||||
BLOCKFROST_API_KEY=your-blockfrost-api-key # Register at https://blockfrost.io/
|
||||
CARDANO_NETWORK=mainnet # or 'testnet', 'preview', 'preprod'
|
||||
@@ -41,6 +47,7 @@ ENABLE_BITCOINCASH_TOOLS=true
|
||||
ENABLE_THORCHAIN_TOOLS=true
|
||||
ENABLE_RIPPLE_TOOLS=true
|
||||
ENABLE_CARDANO_TOOLS=true # Enable/disable Cardano tools
|
||||
ENABLE_TON_TOOLS=true # Enable/disable TON tools
|
||||
|
||||
# CoinGecko API Key
|
||||
COINGECKO_API_KEY=CG-Z9ajuvwG1cheNJSByUgmvTgg #replace with your-coingecko-api-key if you hit rate limits
|
||||
39
README.md
39
README.md
@@ -1,6 +1,6 @@
|
||||
# Web3 MCP
|
||||
|
||||
A Model-Context-Protocol server for interacting with multiple blockchains including Solana, Ethereum, THORChain, XRP Ledger, Cardano, and UTXO chains. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables.
|
||||
A Model-Context-Protocol server for interacting with multiple blockchains including Solana, Ethereum, THORChain, XRP Ledger, TON (The Open Network), Cardano, and UTXO chains. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables.
|
||||
|
||||
<a href="https://glama.ai/mcp/servers/an8x6gmzdn"><img width="380" height="200" src="https://glama.ai/mcp/servers/an8x6gmzdn/badge" alt="Web3 Server MCP server" /></a>
|
||||
|
||||
@@ -67,6 +67,13 @@ XRP Ledger Operations:
|
||||
- Check token balances
|
||||
- Create trustlines for tokens
|
||||
|
||||
TON (The Open Network) Operations:
|
||||
- Get TON account balances
|
||||
- View transaction history
|
||||
- Validate TON addresses
|
||||
- Send TON to another address with optional memo/comment
|
||||
- Get current TON network information
|
||||
|
||||
Supported EVM Networks:
|
||||
- Ethereum
|
||||
- Base
|
||||
@@ -107,6 +114,8 @@ ENABLE_LITECOIN_TOOLS=true # Enable/disable Litecoin tools
|
||||
ENABLE_DOGECOIN_TOOLS=true # Enable/disable Dogecoin tools
|
||||
ENABLE_BITCOINCASH_TOOLS=true # Enable/disable Bitcoin Cash tools
|
||||
ENABLE_THORCHAIN_TOOLS=true # Enable/disable THORChain tools
|
||||
ENABLE_RIPPLE_TOOLS=true # Enable/disable XRP Ledger tools
|
||||
ENABLE_TON_TOOLS=true # Enable/disable TON tools
|
||||
|
||||
# Private Keys (required for transactions)
|
||||
ETH_PRIVATE_KEY=your-ethereum-private-key
|
||||
@@ -120,6 +129,12 @@ XRP_MNEMONIC=your-xrp-mnemonic-recovery-phrase
|
||||
# Optional - used to verify the derived address
|
||||
XRP_ADDRESS=your-xrp-account-address
|
||||
|
||||
# TON Configuration
|
||||
TON_MNEMONIC=word1 word2 word3 ... word24 # 24-word recovery phrase for TON wallet
|
||||
TON_ADDRESS=your-ton-wallet-address # Your TON wallet address
|
||||
TON_API_KEY=your-toncenter-api-key # Get from @tonapibot on Telegram (optional but recommended)
|
||||
TON_RPC_URL=https://toncenter.com/api/v2/jsonRPC # Optional - default is TON Center API
|
||||
|
||||
# Cardano Configuration
|
||||
BLOCKFROST_API_KEY=your-blockfrost-api-key # Get a real API key from https://blockfrost.io/
|
||||
CARDANO_NETWORK=mainnet # or 'testnet', 'preview', 'preprod'
|
||||
@@ -148,6 +163,10 @@ BERACHAIN_RPC_URL=https://rpc.berachain.com
|
||||
# XRP Ledger
|
||||
XRP_RPC_URL=https://xrplcluster.com/ # Optional - will use public endpoint if not specified
|
||||
|
||||
# TON Network
|
||||
TON_RPC_URL=https://toncenter.com/api/v2/jsonRPC # Optional - default is TON Center API
|
||||
TON_API_KEY=your-toncenter-api-key # Get from @tonapibot on Telegram (optional but recommended)
|
||||
|
||||
# THORChain Configuration
|
||||
THORCHAIN_NODE_URL=https://thornode.ninerealms.com # Optional - will use public endpoint if not specified
|
||||
THORCHAIN_PRIVATE_KEY=your-thorchain-private-key
|
||||
@@ -190,6 +209,7 @@ The Web3 MCP server allows you to control which blockchain tools are registered
|
||||
- `ENABLE_THORCHAIN_TOOLS`: Enable/disable THORChain tools
|
||||
- `ENABLE_RIPPLE_TOOLS`: Enable/disable XRP Ledger tools
|
||||
- `ENABLE_CARDANO_TOOLS`: Enable/disable Cardano tools
|
||||
- `ENABLE_TON_TOOLS`: Enable/disable TON tools
|
||||
|
||||
Set these variables to `true` or `false` in your `.env` file to control which tools are available to the server. This allows you to:
|
||||
|
||||
@@ -256,6 +276,14 @@ Ask Claude (or your MCP client of choice):
|
||||
- "Show me token balances for rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
|
||||
- "Create a trustline for USD with issuer rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe with a limit of 1000"
|
||||
|
||||
### TON Operations (when ENABLE_TON_TOOLS=true)
|
||||
- "What's the TON balance of EQAAFhjXzKuQ5N0c96nsdZQWATcJm909LYSaCAvWFQF8tvUZ?"
|
||||
- "Show me the transaction history for EQAAFhjXzKuQ5N0c96nsdZQWATcJm909LYSaCAvWFQF8tvUZ"
|
||||
- "Is UQD0BRQt-QdIEbsjuRsMqzDlBkUAEfQixShDECoKEOXRc4eR a valid TON address?"
|
||||
- "Send 0.1 TON to EQAAFhjXzKuQ5N0c96nsdZQWATcJm909LYSaCAvWFQF8tvUZ"
|
||||
- "Send 0.01 TON to UQD0BRQt-QdIEbsjuRsMqzDlBkUAEfQixShDECoKEOXRc4eR with comment 'test payment'"
|
||||
- "What's the current TON Network information?"
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Environment Variables**: All private keys are stored in the .env file and never exposed in the conversation history
|
||||
@@ -291,6 +319,15 @@ The tool can use either a private key or mnemonic phrase for XRP transactions. C
|
||||
- `XRP_ADDRESS`: Optional - Your XRP account address (used to verify the derived address)
|
||||
- `XRP_RPC_URL`: Optional - Custom XRP Ledger node URL (defaults to public endpoint)
|
||||
|
||||
### TON Configuration
|
||||
The tool uses TON Center's API by default and requires a mnemonic phrase for TON transactions. Configure these in your .env file:
|
||||
- `TON_MNEMONIC`: Required - Your 24-word recovery phrase for TON wallet
|
||||
- `TON_ADDRESS`: Required - Your TON wallet address
|
||||
- `TON_API_KEY`: Recommended - API key from @tonapibot on Telegram (for higher rate limits)
|
||||
- `TON_RPC_URL`: Optional - Custom TON RPC URL (defaults to TON Center API)
|
||||
|
||||
The implementation includes automatic retry logic with exponential backoff for rate limit handling.
|
||||
|
||||
### UTXO Chain Data Providers
|
||||
The tool uses several data providers for UTXO chains:
|
||||
- BlockCypher
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
"blockchain",
|
||||
"xrp",
|
||||
"ripple",
|
||||
"cardano"
|
||||
"cardano",
|
||||
"ton"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "Blockchain RPC Model-Context-Protocol Server for Solana, EVM chains, UTXO chains, Ripple (XRP), Cardano and more",
|
||||
"description": "Blockchain RPC Model-Context-Protocol Server for Solana, EVM chains, UTXO chains, Ripple (XRP), Cardano, TON and more",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/akc2267/web3-mcp.git"
|
||||
@@ -78,6 +79,9 @@
|
||||
"ripple-keypairs": "^1.3.1",
|
||||
"wif": "^4.0.0",
|
||||
"xrpl": "^2.14.0",
|
||||
"@ton/ton": "13.5.1",
|
||||
"@ton/crypto": "3.2.0",
|
||||
"@ton/core": "0.52.2",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { registerBitcoinCashTools } from "./UTXO/bitcoincash"
|
||||
import { registerCardanoTools } from "./UTXO/cardano"
|
||||
import { registerThorchainTools } from "./thorchain/thorchain"
|
||||
import { registerRippleTools } from "./ripple/ripple"
|
||||
import { registerTonTools } from "./ton/ton"
|
||||
|
||||
// Export all UTXO tools registration function
|
||||
export function registerUtxoTools(server: McpServer) {
|
||||
@@ -21,4 +22,5 @@ export function registerAllTools(server: McpServer) {
|
||||
registerUtxoTools(server)
|
||||
registerThorchainTools(server)
|
||||
registerRippleTools(server)
|
||||
registerTonTools(server)
|
||||
}
|
||||
1
src/chains/ton/index.ts
Normal file
1
src/chains/ton/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { registerTonTools } from './ton';
|
||||
454
src/chains/ton/ton.ts
Normal file
454
src/chains/ton/ton.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
import { z } from "zod";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { TonClient, Address, toNano, fromNano, beginCell, WalletContractV4, internal, SendMode } from '@ton/ton';
|
||||
import { mnemonicToWalletKey, KeyPair } from '@ton/crypto';
|
||||
import { Contract, Builder } from '@ton/core';
|
||||
|
||||
// Initialize TON client
|
||||
let tonClient: TonClient | null = null;
|
||||
|
||||
// TON Network URL - Default to mainnet
|
||||
const TON_MAINNET_ENDPOINT = process.env.TON_RPC_URL || 'https://toncenter.com/api/v2/jsonRPC';
|
||||
const TON_TESTNET_ENDPOINT = 'https://testnet.toncenter.com/api/v2/jsonRPC';
|
||||
const TON_API_KEY = process.env.TON_API_KEY || '';
|
||||
const TON_EXPLORER = 'https://tonscan.org';
|
||||
|
||||
// Helper function to get or initialize the client
|
||||
function getClient(testnet: boolean = false): TonClient {
|
||||
const endpoint = testnet ? TON_TESTNET_ENDPOINT : TON_MAINNET_ENDPOINT;
|
||||
|
||||
if (!tonClient) {
|
||||
tonClient = new TonClient({
|
||||
endpoint,
|
||||
apiKey: TON_API_KEY
|
||||
});
|
||||
}
|
||||
return tonClient;
|
||||
}
|
||||
|
||||
// Helper function to create a wallet from mnemonic
|
||||
async function createWalletFromMnemonic(mnemonic: string): Promise<KeyPair> {
|
||||
const mnemonicArray = mnemonic.split(' ');
|
||||
const keyPair = await mnemonicToWalletKey(mnemonicArray);
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
// Helper function to determine wallet version and create appropriate contract
|
||||
async function createWalletContract(client: TonClient, address: Address, keyPair: KeyPair): Promise<{ contract: WalletContractV4, walletVersion: string }> {
|
||||
// We'll use WalletV4 (most common and recent standard wallet)
|
||||
// For production use, you might want to detect the wallet version from on-chain data
|
||||
const walletContract = WalletContractV4.create({
|
||||
publicKey: keyPair.publicKey,
|
||||
workchain: 0
|
||||
});
|
||||
|
||||
// Check if the derived address matches the expected address
|
||||
const derivedAddress = walletContract.address;
|
||||
if (!derivedAddress.equals(address)) {
|
||||
console.warn('Warning: Derived wallet address does not match provided address.');
|
||||
console.warn(`Derived: ${derivedAddress.toString()}`);
|
||||
console.warn(`Provided: ${address.toString()}`);
|
||||
|
||||
// We'll use the provided address anyway, but this is a warning sign
|
||||
}
|
||||
|
||||
return {
|
||||
contract: walletContract,
|
||||
walletVersion: 'v4r2'
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to implement exponential backoff for API rate limits
|
||||
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> {
|
||||
let retries = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error: any) {
|
||||
retries++;
|
||||
|
||||
// If we've exceeded our max retries or it's not a rate limit error, rethrow
|
||||
if (retries > maxRetries || !error.message.includes('429')) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Calculate exponential backoff time (1s, 2s, 4s, 8s, 16s)
|
||||
const delay = Math.pow(2, retries - 1) * 1000;
|
||||
console.log(`Rate limit hit, retrying in ${delay}ms (attempt ${retries} of ${maxRetries})...`);
|
||||
|
||||
// Wait for the calculated time before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register all TON tools
|
||||
export function registerTonTools(server: McpServer) {
|
||||
// Get TON Balance
|
||||
server.tool(
|
||||
"getTonBalance",
|
||||
"Get balance for a TON address",
|
||||
{
|
||||
address: z.string().describe("TON address to check"),
|
||||
testnet: z.boolean().optional().describe("Use testnet instead of mainnet"),
|
||||
},
|
||||
async ({ address, testnet = false }) => {
|
||||
try {
|
||||
const client = getClient(testnet);
|
||||
|
||||
// Validate the address
|
||||
let validAddress;
|
||||
try {
|
||||
validAddress = Address.parse(address);
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Invalid TON address format: ${address}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// Get account information including balance with retry logic
|
||||
const accountInfo = await withRetry(() => client.getBalance(validAddress));
|
||||
const balance = fromNano(accountInfo);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `TON Balance for ${validAddress.toString()}:\n${balance} TON`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve TON balance: ${error.message}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get TON transaction history
|
||||
server.tool(
|
||||
"getTonTransactionHistory",
|
||||
"Get transaction history for a TON address",
|
||||
{
|
||||
address: z.string().describe("TON address to check"),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return (default: 10)"),
|
||||
testnet: z.boolean().optional().describe("Use testnet instead of mainnet"),
|
||||
},
|
||||
async ({ address, limit = 10, testnet = false }) => {
|
||||
try {
|
||||
const client = getClient(testnet);
|
||||
|
||||
// Validate the address
|
||||
let validAddress;
|
||||
try {
|
||||
validAddress = Address.parse(address);
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Invalid TON address format: ${address}` }],
|
||||
};
|
||||
}
|
||||
|
||||
// Get transactions with retry logic
|
||||
const transactions = await withRetry(() =>
|
||||
client.getTransactions(validAddress, { limit: limit })
|
||||
);
|
||||
|
||||
if (transactions.length === 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: `No transactions found for ${validAddress.toString()}` }],
|
||||
};
|
||||
}
|
||||
|
||||
const txList = transactions.map((tx: any) => {
|
||||
const timestamp = new Date(tx.time * 1000).toLocaleString();
|
||||
const inMsg = tx.inMessage;
|
||||
const outMsgs = tx.outMessages;
|
||||
|
||||
let txType = "Unknown";
|
||||
let fromAddress = "N/A";
|
||||
let toAddress = "N/A";
|
||||
let amount = "0";
|
||||
let comment = "";
|
||||
|
||||
// Determine if it's incoming or outgoing
|
||||
if (inMsg && inMsg.source) {
|
||||
txType = "Incoming";
|
||||
fromAddress = inMsg.source.toString();
|
||||
toAddress = validAddress.toString();
|
||||
amount = inMsg.value ? fromNano(inMsg.value) : "0";
|
||||
|
||||
// Try to extract comment if present
|
||||
if (inMsg.body) {
|
||||
try {
|
||||
const msgBody = inMsg.body;
|
||||
// Attempt to decode comment if it's an op=0 message
|
||||
if (msgBody && msgBody.beginParse) {
|
||||
const slice = msgBody.beginParse();
|
||||
if (slice.loadUint(32) === 0) { // op=0 indicates text comment
|
||||
comment = slice.loadStringTail();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail if we can't parse the comment
|
||||
}
|
||||
}
|
||||
} else if (outMsgs && outMsgs.length > 0) {
|
||||
txType = "Outgoing";
|
||||
fromAddress = validAddress.toString();
|
||||
|
||||
// Sum total sent amount across all outgoing messages
|
||||
let totalAmount = 0n;
|
||||
outMsgs.forEach((msg: any) => {
|
||||
if (msg.destination) {
|
||||
toAddress = msg.destination.toString();
|
||||
}
|
||||
if (msg.value) {
|
||||
totalAmount += msg.value;
|
||||
}
|
||||
|
||||
// Try to extract comment from the first message if present
|
||||
if (!comment && msg.body) {
|
||||
try {
|
||||
const msgBody = msg.body;
|
||||
// Attempt to decode comment if it's an op=0 message
|
||||
if (msgBody && msgBody.beginParse) {
|
||||
const slice = msgBody.beginParse();
|
||||
if (slice.loadUint(32) === 0) { // op=0 indicates text comment
|
||||
comment = slice.loadStringTail();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent fail if we can't parse the comment
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
amount = fromNano(totalAmount);
|
||||
}
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${txType}
|
||||
Date: ${timestamp}
|
||||
From: ${fromAddress}
|
||||
To: ${toAddress}
|
||||
Amount: ${amount} TON${comment ? `\nComment: ${comment}` : ""}`;
|
||||
}).join('\n---\n');
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `TON Transaction History for ${validAddress.toString()}:\n${txList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve TON transaction history: ${error.message}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Validate TON address
|
||||
server.tool(
|
||||
"validateTonAddress",
|
||||
"Validate a TON address format",
|
||||
{
|
||||
address: z.string().describe("TON address to validate"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
let isValid = false;
|
||||
let normalized = "";
|
||||
|
||||
try {
|
||||
const parsedAddress = Address.parse(address);
|
||||
isValid = true;
|
||||
normalized = parsedAddress.toString();
|
||||
} catch (e) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} has a valid TON address format.\nNormalized format: ${normalized}`
|
||||
: `The address ${address} does NOT have a valid TON address format.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: "text", text: `Error validating TON address: ${error.message}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get TON Network Info
|
||||
server.tool(
|
||||
"getTonNetworkInfo",
|
||||
"Get current TON network information",
|
||||
{
|
||||
testnet: z.boolean().optional().describe("Use testnet instead of mainnet"),
|
||||
},
|
||||
async ({ testnet = false }) => {
|
||||
try {
|
||||
const client = getClient(testnet);
|
||||
|
||||
// Get masterchain info with retry logic
|
||||
const masterchainInfo = await withRetry(() => client.getMasterchainInfo());
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `TON Network Information (${testnet ? 'Testnet' : 'Mainnet'}):
|
||||
Current Workchain: ${masterchainInfo.workchain}
|
||||
Current Shard: ${masterchainInfo.shard}
|
||||
Initial Seqno: ${masterchainInfo.initSeqno}
|
||||
Latest Seqno: ${masterchainInfo.latestSeqno}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve TON Network information: ${error.message}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Send TON transaction using mnemonic
|
||||
server.tool(
|
||||
"sendTonTransaction",
|
||||
"Send TON from your wallet to another address using mnemonic from .env",
|
||||
{
|
||||
toAddress: z.string().describe("TON address to send to"),
|
||||
amount: z.union([z.string(), z.number()]).describe("Amount of TON to send"),
|
||||
comment: z.string().optional().describe("Optional comment to include with the transaction"),
|
||||
testnet: z.boolean().optional().describe("Use testnet instead of mainnet"),
|
||||
},
|
||||
async ({ toAddress, amount, comment, testnet = false }) => {
|
||||
try {
|
||||
// Check if mnemonic is available
|
||||
if (!process.env.TON_MNEMONIC) {
|
||||
throw new Error("TON_MNEMONIC is required in the .env file");
|
||||
}
|
||||
|
||||
// Check if address is available
|
||||
if (!process.env.TON_ADDRESS) {
|
||||
throw new Error("TON_ADDRESS is required in the .env file");
|
||||
}
|
||||
|
||||
// Get client
|
||||
const client = getClient(testnet);
|
||||
|
||||
// Parse sender and destination address
|
||||
const walletAddress = Address.parse(process.env.TON_ADDRESS);
|
||||
let destinationAddress: Address;
|
||||
try {
|
||||
destinationAddress = Address.parse(toAddress);
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid destination address: ${toAddress}`);
|
||||
}
|
||||
|
||||
// Create wallet from mnemonic
|
||||
const keyPair = await createWalletFromMnemonic(process.env.TON_MNEMONIC);
|
||||
|
||||
// Convert amount to nanoTON
|
||||
const amountInNano = toNano(typeof amount === 'number' ? amount.toString() : amount);
|
||||
|
||||
// Create wallet contract and determine wallet version
|
||||
const { contract: walletContract, walletVersion } =
|
||||
await createWalletContract(client, walletAddress, keyPair);
|
||||
|
||||
// Connect to the wallet and ensure we're working with the right contract
|
||||
const wallet = client.open(walletContract);
|
||||
|
||||
// Check wallet balance with retry logic
|
||||
const balance = await withRetry(() => wallet.getBalance());
|
||||
const balanceInTon = fromNano(balance);
|
||||
console.log(`Wallet balance: ${balanceInTon} TON`);
|
||||
|
||||
// Ensure wallet has enough funds for the transaction
|
||||
const sendAmount = BigInt(amountInNano);
|
||||
const gasFee = toNano('0.05'); // rough estimate for fees
|
||||
if (balance < sendAmount + gasFee) {
|
||||
throw new Error(`Insufficient funds: ${balanceInTon} TON available, need at least ${fromNano(sendAmount + gasFee)} TON (including fees)`);
|
||||
}
|
||||
|
||||
// Create transfer payload with comment if provided
|
||||
const body = comment
|
||||
? beginCell().storeUint(0, 32).storeStringTail(comment).endCell()
|
||||
: undefined;
|
||||
|
||||
// Create internal message for the transfer
|
||||
const message = internal({
|
||||
to: destinationAddress,
|
||||
value: amountInNano,
|
||||
body: body,
|
||||
});
|
||||
|
||||
// Send the transaction with retry logic for rate limits
|
||||
console.log(`Sending ${fromNano(amountInNano)} TON to ${destinationAddress.toString()}`);
|
||||
|
||||
// Get seqno with retry
|
||||
const seqno = await withRetry(() => wallet.getSeqno());
|
||||
|
||||
// Get current time to create transaction ID
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const txId = `${timestamp}-${walletAddress.toString().slice(0, 8)}-${destinationAddress.toString().slice(0, 8)}`;
|
||||
|
||||
// Send transfer with retry
|
||||
await withRetry(() =>
|
||||
wallet.sendTransfer({
|
||||
seqno: seqno,
|
||||
secretKey: keyPair.secretKey,
|
||||
messages: [message],
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS,
|
||||
})
|
||||
);
|
||||
|
||||
// For explorers, we'll create links based on the addresses since we can't get the hash directly
|
||||
const explorerLink = testnet
|
||||
? `https://testnet.tonscan.org/address/${destinationAddress.toString()}`
|
||||
: `${TON_EXPLORER}/address/${destinationAddress.toString()}`;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `TON Transaction Sent Successfully!
|
||||
From: ${walletAddress.toString()}
|
||||
To: ${destinationAddress.toString()}
|
||||
Amount: ${typeof amount === 'number' ? amount.toString() : amount} TON
|
||||
${comment ? `Comment: ${comment}\n` : ''}
|
||||
Wallet Version: ${walletVersion}
|
||||
Transaction ID: ${txId}
|
||||
Explorer Link: ${explorerLink}
|
||||
|
||||
Note: The transaction has been sent to the network. Check the explorer link to see when it appears (usually within seconds).`,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
console.error('Error in sendTonTransaction:', error);
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to send TON transaction: ${error.message}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from "./chains/UTXO/index.js";
|
||||
import { registerThorchainTools } from "./chains/thorchain/thorchain.js";
|
||||
import { registerRippleTools } from "./chains/ripple/ripple.js";
|
||||
import { registerTonTools } from "./chains/ton/ton.js";
|
||||
import { registerGeneralTools } from "./general/index.js";
|
||||
|
||||
// Create server instance
|
||||
@@ -94,6 +95,11 @@ if (isEnabled('ENABLE_RIPPLE_TOOLS')) {
|
||||
registerRippleTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_TON_TOOLS')) {
|
||||
console.error('Registering TON tools...');
|
||||
registerTonTools(server);
|
||||
}
|
||||
|
||||
// Register general tools
|
||||
console.error('Registering general tools...');
|
||||
registerGeneralTools(server);
|
||||
|
||||
Reference in New Issue
Block a user