diff --git a/README.md b/README.md index 9773b29..b82a4b8 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, 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, 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 @@ -21,6 +21,12 @@ Ethereum & EVM Chain Operations: - Send ERC-20 tokens (using private key from .env) - Approve ERC-20 token spending (using private key from .env) +THORChain Operations: +- Check RUNE balances +- Get detailed pool information +- Get swap quotes between any supported assets +- Cross-chain swaps via THORChain protocol + UTXO Chain Operations: - Bitcoin (BTC) - Check address balances @@ -84,6 +90,9 @@ BSC_RPC_URL=https://bsc-dataseed.binance.org POLYGON_RPC_URL=https://polygon-rpc.com AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc +# THORChain Configuration +THORCHAIN_NODE_URL=https://thornode.ninerealms.com # Optional - will use public endpoint if not specified + # Private Keys (required for transactions) ETH_PRIVATE_KEY=your-ethereum-private-key SOLANA_PRIVATE_KEY=your-base58-encoded-solana-private-key @@ -110,7 +119,6 @@ npm run build } } } - ``` ## Usage Examples @@ -131,6 +139,12 @@ Ask Claude: - "What's the current gas price on Arbitrum?" - "Send 100 USDC to 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933 on Polygon" +### THORChain Operations +- "What's the RUNE balance of thor13zpdckczd0jvyhwxmrwnpap8gmy9m5kk2gzum3?" +- "Show me the pool information for BTC.BTC" +- "Get a swap quote for 0.1 BTC.BTC to ETH.ETH" +- "What's the current state of the RUNE.RUNE pool?" + ### UTXO Chain Operations - "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?" - "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" @@ -153,6 +167,9 @@ You can configure custom RPC endpoints in your .env file for better reliability ### Network Selection For EVM operations, you can specify the network by name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche). The tool will automatically use the appropriate RPC endpoint and network configuration. +### 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. + ### UTXO Chain Data Providers The tool uses several data providers for UTXO chains: - BlockCypher @@ -176,4 +193,4 @@ Contributions are welcome! Please submit pull requests with any improvements or ## License -ISC License +ISC License \ No newline at end of file diff --git a/package.json b/package.json index 9e5c476..17a8dce 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,16 @@ "dependencies": { "@xchainjs/xchain-bitcoin": "^1.2.2", "@xchainjs/xchain-bitcoincash": "^1.0.10", - "@xchainjs/xchain-client": "^0.16.2", + "@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-util": "^0.13.1", + "@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", diff --git a/src/chains/xchain.ts b/src/chains/xchain.ts index d2000a4..e4b182b 100644 --- a/src/chains/xchain.ts +++ b/src/chains/xchain.ts @@ -3,12 +3,23 @@ import { Client as LitecoinClient, defaultLtcParams } from '@xchainjs/xchain-lit import { Client as DogeClient, defaultDogeParams } from '@xchainjs/xchain-doge' import { Client as BitcoinCashClient, defaultBchParams } from '@xchainjs/xchain-bitcoincash' import { Network } from '@xchainjs/xchain-client' -import { Asset } from '@xchainjs/xchain-util' +import { Client as ThorchainClient } from '@xchainjs/xchain-thorchain' +import { ThorchainAMM } from '@xchainjs/xchain-thorchain-amm' +import { ThorchainQuery } from '@xchainjs/xchain-thorchain-query' +import { Thornode } from '@xchainjs/xchain-thorchain-query' +import { Asset, Chain, CryptoAmount, assetFromString, assetToString, baseAmount } from '@xchainjs/xchain-util' +import { BigNumber } from 'bignumber.js' import { z } from "zod" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { config } from 'dotenv' config() +// Initialize Nine Realms headers +import axios from 'axios' +import { register9Rheader } from '@xchainjs/xchain-util' + +register9Rheader(axios) + // Initialize clients with mainnet by default const bitcoinClient = new BitcoinClient({ ...defaultBTCParams, @@ -26,6 +37,15 @@ const dogeClient = new DogeClient({ network: Network.Mainnet, }) +// Initialize Thorchain clients +const thorchainClient = new ThorchainClient({ + network: Network.Mainnet, +}) + +const thornode = new Thornode(Network.Mainnet) +const thorchainQuery = new ThorchainQuery() +const thorchainAmm = new ThorchainAMM() + const bitcoinCashClient = new BitcoinCashClient({ ...defaultBchParams, network: Network.Mainnet, @@ -36,6 +56,132 @@ function formatBaseAmount(baseAmount: any): string { return baseAmount.amount().toString() } +export function registerThorchainTools(server: McpServer) { + // Get THORChain balance + server.tool( + "getThorchainBalance", + "Get RUNE balance for an address", + { + address: z.string().describe("THORChain address to check"), + }, + async ({ address }) => { + try { + const balances = await thorchainClient.getBalance(address) + return { + content: [{ + type: "text", + text: `THORChain Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} RUNE`, + }], + } + } catch (err) { + const error = err as Error + return { + content: [{ type: "text", text: `Failed to retrieve RUNE balance: ${error.message}` }], + } + } + } + ) + + // Get pool info + server.tool( + "getThorchainPoolInfo", + "Get information about a THORChain liquidity pool", + { + asset: z.string().describe("Asset symbol (e.g., 'BTC.BTC', 'ETH.ETH')"), + }, + async ({ asset }) => { + try { + const pool = await thornode.getPool(asset) + return { + content: [{ + type: "text", + text: `Pool Information for ${asset}:\n` + + `Status: ${pool.status}\n` + + `Asset Depth: ${pool.balance_asset}\n` + + `RUNE Depth: ${pool.balance_rune}\n` + + `LP Units: ${pool.LP_units}\n` + + `Synth Units: ${pool.synth_units}`, + }], + } + } catch (err) { + const error = err as Error + return { + content: [{ type: "text", text: `Failed to retrieve pool information: ${error.message}` }], + } + } + } + ) + + // Get swap quote + server.tool( + "getThorchainSwapQuote", + "Get a quote for swapping assets on THORChain", + { + fromAsset: z.string().describe("Source asset (e.g., 'BTC.BTC')"), + toAsset: z.string().describe("Destination asset (e.g., 'ETH.ETH')"), + amount: z.string().describe("Amount to swap (e.g., '0.1')"), + }, + async ({ fromAsset: fromAssetString, toAsset: toAssetString, amount: amountString }) => { + try { + // Parse assets + const fromAsset = assetFromString(fromAssetString); + const toAsset = assetFromString(toAssetString); + if (!fromAsset || !toAsset) { + return { + content: [{ type: "text", text: `Invalid asset format. Expected format: 'CHAIN.SYMBOL' (e.g., 'BTC.BTC', 'ETH.ETH')` }], + } + } + + // Parse amount + let numAmount; + try { + numAmount = new BigNumber(amountString); + if (numAmount.isNaN() || numAmount.isLessThanOrEqualTo(0)) { + throw new Error('Invalid amount'); + } + } catch (error) { + return { + content: [{ type: "text", text: `Invalid amount format. Please provide a valid positive number.` }], + } + } + + // Create amount using baseAmount factory function + const amount = new CryptoAmount( + baseAmount(numAmount), + fromAsset + ); + + const quoteParams = { + fromAsset, + destinationAsset: toAsset, + amount: amount + }; + + const quote = await thorchainQuery.quoteSwap(quoteParams); + + return { + content: [{ + type: "text", + text: `Swap Quote:\n` + + `From: ${quote.memo}\n` + + `To: ${quote.toAddress}\n` + + `Expected Output: ${quote.txEstimate.netOutput.formatedAssetString()}\n` + + `Affiliate Fee: ${quote.txEstimate.totalFees.affiliateFee.formatedAssetString()}\n` + + `Outbound Fee: ${quote.txEstimate.totalFees.outboundFee.formatedAssetString()}\n` + + `Expires: ${quote.expiry.toLocaleString()}`, + }], + } + } catch (err) { + const error = err as Error + return { + content: [{ type: "text", text: `Failed to get swap quote: ${error.message}` }], + } + } + } + ) + +} + export function registerUtxoTools(server: McpServer) { // Bitcoin Tools server.tool( @@ -271,3 +417,9 @@ Fee Rates (sats/byte): ) }) } + +// Export both tool registration functions +export function registerAllTools(server: McpServer) { + registerUtxoTools(server) + registerThorchainTools(server) +} diff --git a/src/index.ts b/src/index.ts index 66a3c0c..9134031 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { registerSolanaTools } from "./chains/solana.js"; import { registerEthereumTools } from "./chains/ethereum.js"; -import { registerUtxoTools } from "./chains/xchain.js"; +import { registerAllTools } from "./chains/xchain.js"; // Create server instance const server = new McpServer({ @@ -35,7 +35,7 @@ const server = new McpServer({ // Register chain-specific tools registerSolanaTools(server); registerEthereumTools(server); -registerUtxoTools(server); +registerAllTools(server); async function main() { const transport = new StdioServerTransport();