Thorchain query support

This commit is contained in:
Alex Cheng
2025-02-13 13:42:36 -06:00
parent abcb76ce4e
commit 8f1a6aa424
4 changed files with 182 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
# Web3 MCP # 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.
<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> <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>
@@ -21,6 +21,12 @@ Ethereum & EVM Chain Operations:
- Send ERC-20 tokens (using private key from .env) - Send ERC-20 tokens (using private key from .env)
- Approve ERC-20 token spending (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: UTXO Chain Operations:
- Bitcoin (BTC) - Bitcoin (BTC)
- Check address balances - Check address balances
@@ -84,6 +90,9 @@ BSC_RPC_URL=https://bsc-dataseed.binance.org
POLYGON_RPC_URL=https://polygon-rpc.com POLYGON_RPC_URL=https://polygon-rpc.com
AVALANCHE_RPC_URL=https://api.avax.network/ext/bc/C/rpc 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) # Private Keys (required for transactions)
ETH_PRIVATE_KEY=your-ethereum-private-key ETH_PRIVATE_KEY=your-ethereum-private-key
SOLANA_PRIVATE_KEY=your-base58-encoded-solana-private-key SOLANA_PRIVATE_KEY=your-base58-encoded-solana-private-key
@@ -110,7 +119,6 @@ npm run build
} }
} }
} }
``` ```
## Usage Examples ## Usage Examples
@@ -131,6 +139,12 @@ Ask Claude:
- "What's the current gas price on Arbitrum?" - "What's the current gas price on Arbitrum?"
- "Send 100 USDC to 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933 on Polygon" - "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 ### UTXO Chain Operations
- "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?" - "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?"
- "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" - "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 ### 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. 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 ### UTXO Chain Data Providers
The tool uses several data providers for UTXO chains: The tool uses several data providers for UTXO chains:
- BlockCypher - BlockCypher
@@ -176,4 +193,4 @@ Contributions are welcome! Please submit pull requests with any improvements or
## License ## License
ISC License ISC License

View File

@@ -48,11 +48,16 @@
"dependencies": { "dependencies": {
"@xchainjs/xchain-bitcoin": "^1.2.2", "@xchainjs/xchain-bitcoin": "^1.2.2",
"@xchainjs/xchain-bitcoincash": "^1.0.10", "@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-crypto": "^0.3.7",
"@xchainjs/xchain-doge": "^1.0.10", "@xchainjs/xchain-doge": "^1.0.10",
"@xchainjs/xchain-litecoin": "^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", "@psf/bitcoincashjs-lib": "^4.0.3",
"bchaddrjs": "^0.5.2", "bchaddrjs": "^0.5.2",
"bitcoinjs-lib": "^6.1.5", "bitcoinjs-lib": "^6.1.5",

View File

@@ -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 DogeClient, defaultDogeParams } from '@xchainjs/xchain-doge'
import { Client as BitcoinCashClient, defaultBchParams } from '@xchainjs/xchain-bitcoincash' import { Client as BitcoinCashClient, defaultBchParams } from '@xchainjs/xchain-bitcoincash'
import { Network } from '@xchainjs/xchain-client' 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 { z } from "zod"
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { config } from 'dotenv' import { config } from 'dotenv'
config() config()
// Initialize Nine Realms headers
import axios from 'axios'
import { register9Rheader } from '@xchainjs/xchain-util'
register9Rheader(axios)
// Initialize clients with mainnet by default // Initialize clients with mainnet by default
const bitcoinClient = new BitcoinClient({ const bitcoinClient = new BitcoinClient({
...defaultBTCParams, ...defaultBTCParams,
@@ -26,6 +37,15 @@ const dogeClient = new DogeClient({
network: Network.Mainnet, 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({ const bitcoinCashClient = new BitcoinCashClient({
...defaultBchParams, ...defaultBchParams,
network: Network.Mainnet, network: Network.Mainnet,
@@ -36,6 +56,132 @@ function formatBaseAmount(baseAmount: any): string {
return baseAmount.amount().toString() 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) { export function registerUtxoTools(server: McpServer) {
// Bitcoin Tools // Bitcoin Tools
server.tool( server.tool(
@@ -271,3 +417,9 @@ Fee Rates (sats/byte):
) )
}) })
} }
// Export both tool registration functions
export function registerAllTools(server: McpServer) {
registerUtxoTools(server)
registerThorchainTools(server)
}

View File

@@ -24,7 +24,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerSolanaTools } from "./chains/solana.js"; import { registerSolanaTools } from "./chains/solana.js";
import { registerEthereumTools } from "./chains/ethereum.js"; import { registerEthereumTools } from "./chains/ethereum.js";
import { registerUtxoTools } from "./chains/xchain.js"; import { registerAllTools } from "./chains/xchain.js";
// Create server instance // Create server instance
const server = new McpServer({ const server = new McpServer({
@@ -35,7 +35,7 @@ const server = new McpServer({
// Register chain-specific tools // Register chain-specific tools
registerSolanaTools(server); registerSolanaTools(server);
registerEthereumTools(server); registerEthereumTools(server);
registerUtxoTools(server); registerAllTools(server);
async function main() { async function main() {
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();