From 7135d5af5aedbb35cffd2b0bc310024399d7c665 Mon Sep 17 00:00:00 2001 From: Alex Cheng Date: Tue, 11 Mar 2025 13:45:37 -0500 Subject: [PATCH] ripple support --- .env.example | 6 + README.md | 39 ++- package.json | 26 +- src/chains/index.ts | 2 + src/chains/ripple/index.ts | 1 + src/chains/ripple/ripple.ts | 505 ++++++++++++++++++++++++++++++++++++ src/env.d.ts | 6 + src/index.ts | 6 + 8 files changed, 579 insertions(+), 12 deletions(-) create mode 100644 src/chains/ripple/index.ts create mode 100644 src/chains/ripple/ripple.ts diff --git a/.env.example b/.env.example index 8823437..669ffba 100644 --- a/.env.example +++ b/.env.example @@ -9,12 +9,17 @@ POLYGON_RPC_URL=https://polygon-rpc.com 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/ # Private keys (DO NOT SHARE) ETH_PRIVATE_KEY=your-eth-private-key SOLANA_PRIVATE_KEY=your-solana-private-key THORCHAIN_PRIVATE_KEY=your-thorchain-private-key THORCHAIN_MNEMONIC=your-thorchain-mnemonic +# XRP Ledger credentials - can use either private key or mnemonic +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 # UTXO Chain API Keys (optional) BLOCKCYPHER_API_KEY=your-blockcypher-api-key @@ -28,6 +33,7 @@ ENABLE_LITECOIN_TOOLS=true ENABLE_DOGECOIN_TOOLS=true ENABLE_BITCOINCASH_TOOLS=true ENABLE_THORCHAIN_TOOLS=true +ENABLE_RIPPLE_TOOLS=true # CoinGecko API Key COINGECKO_API_KEY=CG-Z9ajuvwG1cheNJSByUgmvTgg #replace with your-coingecko-api-key if you hit rate limits \ No newline at end of file diff --git a/README.md b/README.md index d465962..44f01c3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Web3 MCP -A Model-Context-Protocol server for interacting with multiple blockchains including Solana, Ethereum, THORChain, 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, and UTXO chains. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables. Web3 Server MCP server @@ -49,6 +49,15 @@ UTXO Chain Operations: - Validate addresses - Get network info and fees +XRP Ledger Operations: +- Check XRP account balances +- View transaction history +- Validate XRP addresses +- Send XRP to another address +- Get current XRP Ledger information +- Check token balances +- Create trustlines for tokens + Supported EVM Networks: - Ethereum - Base @@ -92,6 +101,14 @@ ENABLE_THORCHAIN_TOOLS=true # Enable/disable THORChain tools # Private Keys (required for transactions) ETH_PRIVATE_KEY=your-ethereum-private-key SOLANA_PRIVATE_KEY=your-base58-encoded-solana-private-key + +# XRP Ledger credentials (required for XRP transactions) +# Either private key or mnemonic is required +XRP_PRIVATE_KEY=your-xrp-private-key-in-hex +# OR +XRP_MNEMONIC=your-xrp-mnemonic-recovery-phrase +# Optional - used to verify the derived address +XRP_ADDRESS=your-xrp-account-address ``` ### Optional Configuration @@ -112,6 +129,9 @@ POLYGON_RPC_URL=https://polygon-rpc.com AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc BERACHAIN_RPC_URL=https://rpc.berachain.com +# XRP Ledger +XRP_RPC_URL=https://xrplcluster.com/ # Optional - will use public endpoint if not specified + # THORChain Configuration THORCHAIN_NODE_URL=https://thornode.ninerealms.com # Optional - will use public endpoint if not specified THORCHAIN_PRIVATE_KEY=your-thorchain-private-key @@ -152,6 +172,7 @@ The Web3 MCP server allows you to control which blockchain tools are registered - `ENABLE_DOGECOIN_TOOLS`: Enable/disable Dogecoin tools - `ENABLE_BITCOINCASH_TOOLS`: Enable/disable Bitcoin Cash tools - `ENABLE_THORCHAIN_TOOLS`: Enable/disable THORChain tools +- `ENABLE_RIPPLE_TOOLS`: Enable/disable XRP Ledger 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: @@ -203,6 +224,15 @@ Ask Claude (or your MCP client of choice): - "Show me the pool information for BTC.BTC" - "Get a swap quote for 0.1 BTC.BTC to ETH.ETH" +### XRP Ledger Operations (when ENABLE_RIPPLE_TOOLS=true) +- "What's the XRP balance of rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe?" +- "Show me the transaction history for rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" +- "Is rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe a valid XRP address?" +- "Send 10 XRP to rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" +- "What's the current XRP Ledger information?" +- "Show me token balances for rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" +- "Create a trustline for USD with issuer rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe with a limit of 1000" + ## Security Notes 1. **Environment Variables**: All private keys are stored in the .env file and never exposed in the conversation history @@ -222,6 +252,13 @@ For EVM operations, you can specify the network by name (ethereum, base, arbitru ### THORChain Configuration The tool uses Nine Realms public endpoints by default, but you can configure a custom THORChain node URL in the .env file for better reliability and rate limits. +### XRP Ledger Configuration +The tool can use either a private key or mnemonic phrase for XRP transactions. Configure these in your .env file: +- `XRP_PRIVATE_KEY`: Your XRP private key in hex format +- `XRP_MNEMONIC`: Alternative to private key - your 12-word recovery phrase +- `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) + ### UTXO Chain Data Providers The tool uses several data providers for UTXO chains: - BlockCypher diff --git a/package.json b/package.json index 17a8dce..8b1f1e9 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "solana", "mcp", "rpc", - "blockchain" + "blockchain", + "xrp", + "ripple" ], "author": "", "license": "ISC", - "description": "Solana RPC Model-Context-Protocol Server", + "description": "Blockchain RPC Model-Context-Protocol Server for Solana, EVM chains, UTXO chains, Ripple (XRP) and more", "repository": { "type": "git", "url": "https://github.com/akc2267/web3-mcp.git" @@ -46,35 +48,37 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { + "@cosmos-client/core": "^0.46.1", + "@jup-ag/api": "^6.0.12", + "@modelcontextprotocol/sdk": "^1.4.1", + "@psf/bitcoincashjs-lib": "^4.0.3", + "@solana/spl-token": "^0.4.1", + "@solana/web3.js": "^1.87.6", "@xchainjs/xchain-bitcoin": "^1.2.2", "@xchainjs/xchain-bitcoincash": "^1.0.10", "@xchainjs/xchain-client": "^1.0.10", "@xchainjs/xchain-crypto": "^0.3.7", "@xchainjs/xchain-doge": "^1.0.10", "@xchainjs/xchain-litecoin": "^1.0.10", + "@xchainjs/xchain-midgard-query": "^1.0.11", "@xchainjs/xchain-thorchain": "^2.0.13", "@xchainjs/xchain-thorchain-amm": "^2.0.23", "@xchainjs/xchain-thorchain-query": "^1.0.13", - "@xchainjs/xchain-midgard-query": "^1.0.11", - "@cosmos-client/core": "^0.46.1", "@xchainjs/xchain-util": "^1.0.8", - "@psf/bitcoincashjs-lib": "^4.0.3", "bchaddrjs": "^0.5.2", "bitcoinjs-lib": "^6.1.5", - "wif": "^4.0.0", - "@jup-ag/api": "^6.0.12", - "@modelcontextprotocol/sdk": "^1.4.1", - "@solana/spl-token": "^0.4.1", - "@solana/web3.js": "^1.87.6", "bs58": "^5.0.0", "cross-fetch": "^4.0.0", "dotenv": "^16.4.5", "ethers": "^6.11.1", "fetch-retry": "^5.0.6", + "ripple-keypairs": "^1.3.1", + "wif": "^4.0.0", + "xrpl": "^2.14.0", "zod": "^3.24.1" }, "devDependencies": { "@types/node": "^22.13.1", "typescript": "^5.7.3" } -} \ No newline at end of file +} diff --git a/src/chains/index.ts b/src/chains/index.ts index 7bd6388..9ba4336 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -4,6 +4,7 @@ import { registerLitecoinTools } from "./UTXO/litecoin" import { registerDogecoinTools } from "./UTXO/dogecoin" import { registerBitcoinCashTools } from "./UTXO/bitcoincash" import { registerThorchainTools } from "./thorchain/thorchain" +import { registerRippleTools } from "./ripple/ripple" // Export all UTXO tools registration function export function registerUtxoTools(server: McpServer) { @@ -17,4 +18,5 @@ export function registerUtxoTools(server: McpServer) { export function registerAllTools(server: McpServer) { registerUtxoTools(server) registerThorchainTools(server) + registerRippleTools(server) } \ No newline at end of file diff --git a/src/chains/ripple/index.ts b/src/chains/ripple/index.ts new file mode 100644 index 0000000..2b7f077 --- /dev/null +++ b/src/chains/ripple/index.ts @@ -0,0 +1 @@ +export { registerRippleTools } from './ripple.js'; \ No newline at end of file diff --git a/src/chains/ripple/ripple.ts b/src/chains/ripple/ripple.ts new file mode 100644 index 0000000..ea63787 --- /dev/null +++ b/src/chains/ripple/ripple.ts @@ -0,0 +1,505 @@ +import { z } from "zod"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { Client, Wallet, Payment, TrustSet } from 'xrpl'; + +// Initialize XRP Ledger client +let xrpClient: Client | null = null; + +// XRP Network URL +const XRP_NETWORK_URL = process.env.XRP_RPC_URL || 'wss://s1.ripple.com'; +const XRP_EXPLORER = 'https://livenet.xrpl.org'; + +// Helper function to get or initialize the client +async function getClient(): Promise { + if (!xrpClient || !xrpClient.isConnected()) { + xrpClient = new Client(XRP_NETWORK_URL); + await xrpClient.connect(); + } + return xrpClient; +} + +// Helper function to format drops to XRP +function dropsToXrp(drops: string): string { + return (parseInt(drops) / 1000000).toFixed(6); +} + +// Helper function to format XRP to drops +function xrpToDrops(xrp: string | number): string { + // Ensure xrp is a string + const xrpStr = typeof xrp === 'number' ? xrp.toString() : xrp; + // Use a string to preserve precision and avoid floating point issues + const drops = Math.floor(parseFloat(xrpStr) * 1000000).toString(); + return drops; +} + +// Helper function to clean up resources +async function cleanUp() { + if (xrpClient && xrpClient.isConnected()) { + await xrpClient.disconnect(); + } +} + +// Helper function to create a wallet from available credentials +async function createWallet() { + // Try with private key first if available + if (process.env.XRP_PRIVATE_KEY) { + try { + console.log("Creating wallet from private key..."); + return Wallet.fromSeed(process.env.XRP_PRIVATE_KEY); + } catch (err) { + console.log("Failed to create wallet from private key:", err); + } + } + + // Try with mnemonic if private key failed or isn't available + if (process.env.XRP_MNEMONIC) { + try { + console.log("Creating wallet from mnemonic..."); + return Wallet.fromMnemonic(process.env.XRP_MNEMONIC); + } catch (err) { + console.log("Failed to create wallet from mnemonic:", err); + } + } + + // If we have the address and we get here, throw an error + if (process.env.XRP_ADDRESS) { + throw new Error('Could not create wallet from available credentials'); + } + + throw new Error('No wallet credentials provided. Please add XRP_PRIVATE_KEY or XRP_MNEMONIC to your .env file'); +} + +// Register all XRP tools +export function registerRippleTools(server: McpServer) { + // Get XRP Balance + server.tool( + "getXrpBalance", + "Get balance for an XRP address", + { + address: z.string().describe("XRP address to check"), + }, + async ({ address }) => { + try { + const client = await getClient(); + const response = await client.request({ + command: 'account_info', + account: address, + ledger_index: 'validated' + }); + + const balance = dropsToXrp(response.result.account_data.Balance); + + return { + content: [ + { + type: "text", + text: `XRP Balance for ${address}:\n${balance} XRP`, + }, + ], + }; + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Failed to retrieve XRP balance: ${error.message}` }], + }; + } + } + ); + + // Get account transactions + server.tool( + "getXrpTransactionHistory", + "Get transaction history for an XRP address", + { + address: z.string().describe("XRP address to check"), + limit: z.number().optional().describe("Maximum number of transactions to return (default: 10)"), + }, + async ({ address, limit = 10 }) => { + try { + const client = await getClient(); + const response = await client.request({ + command: 'account_tx', + account: address, + limit: limit + }); + + if (!response.result.transactions || response.result.transactions.length === 0) { + return { + content: [{ type: "text", text: `No transactions found for ${address}` }], + }; + } + + const txList = response.result.transactions.map((tx: any) => { + const transaction = tx.tx; + let txInfo = ` +Transaction: ${transaction.hash} +Type: ${transaction.TransactionType} +Date: ${new Date(transaction.date ? (transaction.date + 946684800) * 1000 : 0).toLocaleString()}`; + + if (transaction.TransactionType === 'Payment') { + txInfo += ` +From: ${transaction.Account} +To: ${transaction.Destination} +Amount: ${transaction.Amount.currency ? + `${transaction.Amount.value} ${transaction.Amount.currency}` : + `${dropsToXrp(transaction.Amount)} XRP`}`; + } + + return txInfo; + }).join('\n---\n'); + + return { + content: [ + { + type: "text", + text: `XRP Transaction History for ${address}:\n${txList}`, + }, + ], + }; + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Failed to retrieve XRP transaction history: ${error.message}` }], + }; + } + } + ); + + // Validate XRP address + server.tool( + "validateXrpAddress", + "Validate an XRP address format", + { + address: z.string().describe("XRP address to validate"), + }, + async ({ address }) => { + try { + // XRP addresses start with 'r' and are 25-35 characters in length + const isValid = /^r[a-zA-Z0-9]{24,34}$/.test(address); + + return { + content: [ + { + type: "text", + text: isValid + ? `The address ${address} has a valid XRP address format` + : `The address ${address} does NOT have a valid XRP address format`, + }, + ], + }; + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Error validating XRP address: ${error.message}` }], + }; + } + } + ); + + // Get XRP Ledger Info + server.tool( + "getXrpLedgerInfo", + "Get current XRP Ledger information", + {}, + async () => { + try { + const client = await getClient(); + const serverInfo = await client.request({ + command: 'server_info' + }); + + const ledgerInfo = await client.request({ + command: 'ledger', + ledger_index: 'validated' + }); + + // Extract values safely with null checks + const serverState = serverInfo.result.info.server_state || 'Unknown'; + const ledgerIndex = ledgerInfo.result.ledger.ledger_index || 'Unknown'; + const ledgerHash = ledgerInfo.result.ledger.ledger_hash || 'Unknown'; + const closeTime = ledgerInfo.result.ledger.close_time + ? new Date((ledgerInfo.result.ledger.close_time + 946684800) * 1000).toLocaleString() + : 'Unknown'; + + // Safe access to validated_ledger properties + const baseFee = serverInfo.result.info.validated_ledger?.base_fee_xrp || 'Unknown'; + const reserveBase = serverInfo.result.info.validated_ledger?.reserve_base_xrp || 'Unknown'; + const reserveInc = serverInfo.result.info.validated_ledger?.reserve_inc_xrp || 'Unknown'; + + return { + content: [ + { + type: "text", + text: `XRP Ledger Information: +Server Status: ${serverState} +Current Ledger: ${ledgerIndex} +Ledger Hash: ${ledgerHash} +Close Time: ${closeTime} +Base Fee: ${baseFee} XRP +Reserve Base: ${reserveBase} XRP +Reserve Inc: ${reserveInc} XRP`, + }, + ], + }; + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Failed to retrieve XRP Ledger information: ${error.message}` }], + }; + } finally { + // No need to clean up here as we want to keep the connection for other operations + } + } + ); + + // Send XRP transaction using private key or mnemonic + server.tool( + "sendXrpTransaction", + "Send XRP from your wallet to another address using private key from .env", + { + toAddress: z.string().describe("XRP address to send to"), + amount: z.union([z.string(), z.number()]).describe("Amount of XRP to send (string or number)"), + memo: z.string().optional().describe("Optional memo to include with the transaction"), + }, + async ({ toAddress, amount, memo }: { toAddress: string, amount: string | number, memo?: string }) => { + try { + // Ensure amount is a string for display + const amountStr = typeof amount === 'number' ? amount.toString() : amount; + + // Get client + const client = await getClient(); + + // Create wallet + let wallet; + try { + wallet = await createWallet(); + } catch (walletError) { + console.error('Wallet creation error:', walletError); + throw new Error(`Failed to create wallet: ${(walletError as Error).message}`); + } + + // Log debug info + console.log(`Wallet created. Address: ${wallet.address}, Public Key: ${wallet.publicKey}`); + + // Verify wallet address matches expected address if provided + if (process.env.XRP_ADDRESS && wallet.address !== process.env.XRP_ADDRESS) { + console.log(`Warning: Derived wallet address ${wallet.address} does not match provided XRP_ADDRESS ${process.env.XRP_ADDRESS}`); + } + + try { + // Get account info first to confirm the account exists + console.log(`Getting account info for ${wallet.address}...`); + const accountInfo = await client.request({ + command: 'account_info', + account: wallet.address, + ledger_index: 'validated' + }); + console.log(`Account exists with sequence: ${accountInfo.result.account_data.Sequence}`); + + // Create a payment transaction + const payment: Payment = { + TransactionType: 'Payment', + Account: wallet.address, + Destination: toAddress, + // Convert amount to drops and ensure it's a string + Amount: xrpToDrops(amount) + }; + + // Add memo if provided + if (memo) { + payment.Memos = [{ + Memo: { + MemoData: Buffer.from(memo, 'utf8').toString('hex').toUpperCase() + } + }]; + } + + // Prepare transaction with autofill to get all needed fields + console.log('Preparing transaction...'); + const prepared = await client.autofill(payment); + console.log('Prepared transaction:', JSON.stringify(prepared)); + + // Use the wallet's sign method to create the signed tx_blob + console.log('Signing transaction...'); + const signed = wallet.sign(prepared); + console.log('Signed transaction. tx_blob length:', signed.tx_blob.length); + console.log('Signed transaction hash:', signed.hash); + + // First submit the transaction to get immediate feedback + console.log('Submitting transaction...'); + const submitResult = await client.submit(signed.tx_blob); + console.log('Submit response:', JSON.stringify(submitResult)); + + // If the submit was successful, wait for validation + if (submitResult.result.engine_result === 'tesSUCCESS' || submitResult.result.engine_result.startsWith('tes')) { + // Wait for transaction to be validated + console.log(`Transaction submitted successfully with hash: ${signed.hash}. Waiting for validation...`); + + // Use submitAndWait which will wait for validation + try { + const finalResult = await client.submitAndWait(signed.tx_blob); + console.log('Final transaction result:', JSON.stringify(finalResult)); + + return { + content: [ + { + type: "text", + text: `XRP Transaction Sent! +From: ${wallet.address} +To: ${toAddress} +Amount: ${amountStr} XRP +Transaction Hash: ${signed.hash} +Explorer Link: ${XRP_EXPLORER}/transactions/${signed.hash}`, + }, + ], + }; + } catch (waitError) { + // Even if we can't check the result, if submission was successful we return success + console.log('Could not verify transaction, but submission was successful:', waitError); + return { + content: [ + { + type: "text", + text: `XRP Transaction Submitted! +From: ${wallet.address} +To: ${toAddress} +Amount: ${amountStr} XRP +Transaction Hash: ${signed.hash} +Explorer Link: ${XRP_EXPLORER}/transactions/${signed.hash} + +Note: Transaction was submitted successfully but validation status is pending.`, + }, + ], + }; + } + } else { + // If submission wasn't successful, throw an error with the engine result + throw new Error(`Transaction submission failed: ${submitResult.result.engine_result} - ${submitResult.result.engine_result_message}`); + } + } catch (txError) { + console.error('Transaction error details:', txError); + throw new Error(`Transaction error: ${(txError as Error).message}`); + } + } catch (err) { + const error = err as Error; + console.error('Error in sendXrpTransaction:', error); + return { + content: [{ type: "text", text: `Failed to send XRP transaction: ${error.message}` }], + }; + } + } + ); + + // Check token balances on XRP Ledger + server.tool( + "getXrpTokenBalances", + "Get token balances for an XRP address", + { + address: z.string().describe("XRP address to check"), + }, + async ({ address }) => { + try { + const client = await getClient(); + const response = await client.request({ + command: 'account_lines', + account: address + }); + + if (!response.result.lines || response.result.lines.length === 0) { + return { + content: [ + { + type: "text", + text: `No token balances found for ${address}`, + }, + ], + }; + } + + const tokenBalances = response.result.lines.map((line: any) => { + return `${line.balance} ${line.currency} (Issuer: ${line.account})`; + }).join('\n'); + + return { + content: [ + { + type: "text", + text: `Token Balances for ${address}:\n${tokenBalances}`, + }, + ], + }; + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Failed to retrieve XRP token balances: ${error.message}` }], + }; + } + } + ); + + // Create trustline for a token on XRP Ledger + server.tool( + "createXrpTrustline", + "Create a trustline for a token on the XRP Ledger using private key from .env", + { + currency: z.string().describe("Currency code (3-letter ISO code or hex string)"), + issuer: z.string().describe("Issuer's XRP address"), + limit: z.string().describe("Maximum amount of the token to trust"), + }, + async ({ currency, issuer, limit }) => { + try { + // Get client + const client = await getClient(); + + // Create wallet + const wallet = await createWallet(); + + // Create a trustline transaction + const trustSetTx: TrustSet = { + TransactionType: 'TrustSet', + Account: wallet.address, + LimitAmount: { + currency, + issuer, + value: limit + } + }; + + try { + // Prepare and sign the transaction + const prepared = await client.autofill(trustSetTx); + const signed = wallet.sign(prepared); + + // Submit the transaction + const result = await client.submitAndWait(signed.tx_blob); + + return { + content: [ + { + type: "text", + text: `XRP Trustline Created! +Account: ${wallet.address} +Currency: ${currency} +Issuer: ${issuer} +Limit: ${limit} +Transaction Hash: ${result.result.hash} +Explorer Link: ${XRP_EXPLORER}/transactions/${result.result.hash}`, + }, + ], + }; + } catch (signError) { + throw new Error(`Failed to sign/submit transaction: ${(signError as Error).message}`); + } + } catch (err) { + const error = err as Error; + return { + content: [{ type: "text", text: `Failed to create XRP trustline: ${error.message}` }], + }; + } + } + ); + + process.on('beforeExit', async () => { + await cleanUp(); + }); +} \ No newline at end of file diff --git a/src/env.d.ts b/src/env.d.ts index 08a3da0..8da7d54 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -13,5 +13,11 @@ declare namespace NodeJS { BSC_RPC_URL: string; POLYGON_RPC_URL: string; AVALANCHE_RPC_URL: string; + + // Ripple (XRP) + XRP_RPC_URL: string; + XRP_ADDRESS: string; + XRP_PRIVATE_KEY: string; + XRP_PUBLIC_KEY: string; } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9e235ae..56ea4f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { registerBitcoinCashTools, } from "./chains/UTXO/index.js"; import { registerThorchainTools } from "./chains/thorchain/thorchain.js"; +import { registerRippleTools } from "./chains/ripple/ripple.js"; import { registerGeneralTools } from "./general/index.js"; // Create server instance @@ -82,6 +83,11 @@ if (isEnabled('ENABLE_THORCHAIN_TOOLS')) { registerThorchainTools(server); } +if (isEnabled('ENABLE_RIPPLE_TOOLS')) { + console.error('Registering Ripple (XRP) tools...'); + registerRippleTools(server); +} + // Register general tools console.error('Registering general tools...'); registerGeneralTools(server);