refactor
This commit is contained in:
@@ -21,5 +21,9 @@ SOCHAIN_API_KEY=your-sochain-api-key
|
||||
|
||||
# Tool Registration Controls (true/false)
|
||||
ENABLE_SOLANA_TOOLS=true # Enable/disable Solana-specific tools
|
||||
ENABLE_ETHEREUM_TOOLS=true # Enable/disable Ethereum and EVM chain tools
|
||||
ENABLE_XCHAIN_TOOLS=true # Enable/disable cross-chain tools (THORChain, etc.)
|
||||
ENABLE_EVM_TOOLS=true # Enable/disable EVM chain tools (Ethereum, Base, Arbitrum, etc.)
|
||||
ENABLE_BITCOIN_TOOLS=true
|
||||
ENABLE_LITECOIN_TOOLS=true
|
||||
ENABLE_DOGECOIN_TOOLS=true
|
||||
ENABLE_BITCOINCASH_TOOLS=true
|
||||
ENABLE_THORCHAIN_TOOLS=true
|
||||
45
README.md
45
README.md
@@ -79,9 +79,15 @@ cp .env.example .env
|
||||
|
||||
```env
|
||||
# Tool Registration Controls
|
||||
ENABLE_SOLANA_TOOLS=true # Enable/disable Solana-specific tools
|
||||
ENABLE_SOLANA_TOOLS=true # Enable/disable Solana tools
|
||||
ENABLE_ETHEREUM_TOOLS=true # Enable/disable Ethereum and EVM chain tools
|
||||
ENABLE_XCHAIN_TOOLS=true # Enable/disable cross-chain tools (THORChain, etc.)
|
||||
|
||||
# UTXO Chain Tools
|
||||
ENABLE_BITCOIN_TOOLS=true # Enable/disable Bitcoin tools
|
||||
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
|
||||
|
||||
# Private Keys (required for transactions)
|
||||
ETH_PRIVATE_KEY=your-ethereum-private-key
|
||||
@@ -139,9 +145,13 @@ npm run build
|
||||
|
||||
The Web3 MCP server allows you to control which blockchain tools are registered through environment variables:
|
||||
|
||||
- `ENABLE_SOLANA_TOOLS`: Enable/disable Solana-specific tools
|
||||
- `ENABLE_SOLANA_TOOLS`: Enable/disable Solana tools
|
||||
- `ENABLE_ETHEREUM_TOOLS`: Enable/disable Ethereum and EVM chain tools
|
||||
- `ENABLE_XCHAIN_TOOLS`: Enable/disable cross-chain tools (THORChain, etc.)
|
||||
- `ENABLE_BITCOIN_TOOLS`: Enable/disable Bitcoin tools
|
||||
- `ENABLE_LITECOIN_TOOLS`: Enable/disable Litecoin tools
|
||||
- `ENABLE_DOGECOIN_TOOLS`: Enable/disable Dogecoin tools
|
||||
- `ENABLE_BITCOINCASH_TOOLS`: Enable/disable Bitcoin Cash tools
|
||||
- `ENABLE_THORCHAIN_TOOLS`: Enable/disable THORChain 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:
|
||||
|
||||
@@ -152,7 +162,7 @@ Set these variables to `true` or `false` in your `.env` file to control which to
|
||||
|
||||
## Usage Examples
|
||||
|
||||
Ask Claude:
|
||||
Ask Claude (or your MCP client of choice):
|
||||
|
||||
### Solana Operations (when ENABLE_SOLANA_TOOLS=true)
|
||||
- "What's my Solana address?" - Shows your address derived from private key in .env
|
||||
@@ -168,13 +178,30 @@ Ask Claude:
|
||||
- "What's the current gas price on Arbitrum?"
|
||||
- "Send 100 USDC to 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933 on Polygon"
|
||||
|
||||
### Cross-chain Operations (when ENABLE_XCHAIN_TOOLS=true)
|
||||
### Bitcoin Operations (when ENABLE_BITCOIN_TOOLS=true)
|
||||
- "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?"
|
||||
- "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
|
||||
- "Validate this Bitcoin address: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
||||
|
||||
### Litecoin Operations (when ENABLE_LITECOIN_TOOLS=true)
|
||||
- "What's the LTC balance of LM2WMpR1Rp6j3Sa59cMXMs1SPzj9eXpGc1?"
|
||||
- "Show me the transaction history for this Litecoin address"
|
||||
- "What's the current Litecoin network fee?"
|
||||
|
||||
### Dogecoin Operations (when ENABLE_DOGECOIN_TOOLS=true)
|
||||
- "Check this DOGE address balance: D8vFz4p1L37jdg47HXKtSHA5uYLYxbGgPD"
|
||||
- "Show me recent Dogecoin transactions"
|
||||
- "What are the current DOGE network fees?"
|
||||
|
||||
### Bitcoin Cash Operations (when ENABLE_BITCOINCASH_TOOLS=true)
|
||||
- "What's the BCH balance of this address?"
|
||||
- "Show me the BCH transaction history"
|
||||
- "Validate this Bitcoin Cash address"
|
||||
|
||||
### THORChain Operations (when ENABLE_THORCHAIN_TOOLS=true)
|
||||
- "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?"
|
||||
- "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?"
|
||||
- "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
|
||||
|
||||
## Security Notes
|
||||
|
||||
|
||||
145
src/chains/UTXO/bitcoin.ts
Normal file
145
src/chains/UTXO/bitcoin.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { Client as BitcoinClient, defaultBTCParams, BlockcypherDataProviders as BTCBlockcypherDataProviders } from '@xchainjs/xchain-bitcoin'
|
||||
import { Network } from '@xchainjs/xchain-client'
|
||||
import { z } from "zod"
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
|
||||
// Initialize client with mainnet by default
|
||||
const bitcoinClient = new BitcoinClient({
|
||||
...defaultBTCParams,
|
||||
network: Network.Mainnet,
|
||||
dataProviders: [BTCBlockcypherDataProviders],
|
||||
})
|
||||
|
||||
// Helper function to format base amounts
|
||||
function formatBaseAmount(baseAmount: any): string {
|
||||
return baseAmount.amount().toString()
|
||||
}
|
||||
|
||||
export function registerBitcoinTools(server: McpServer) {
|
||||
// Bitcoin Balance
|
||||
server.tool(
|
||||
"getBitcoinBalance",
|
||||
"Get balance for a Bitcoin address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await bitcoinClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} BTC`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BTC balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Transaction History
|
||||
server.tool(
|
||||
"getBTCTransactionHistory",
|
||||
"Get transaction history for a Bitcoin address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin address to check"),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return"),
|
||||
offset: z.number().optional().describe("Number of transactions to skip"),
|
||||
},
|
||||
async ({ address, limit = 10, offset = 0 }) => {
|
||||
try {
|
||||
const txs = await bitcoinClient.getTransactions({ address, limit, offset })
|
||||
const txList = txs.txs.map((tx: any) => {
|
||||
const fromAmount = tx.from[0]?.amount ? `${formatBaseAmount(tx.from[0].amount)} BTC` : 'Unknown'
|
||||
const toAmount = tx.to[0]?.amount ? `${formatBaseAmount(tx.to[0].amount)} BTC` : 'Unknown'
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${tx.type}
|
||||
From: ${tx.from[0]?.from || 'Unknown'} (${fromAmount})
|
||||
To: ${tx.to[0]?.to || 'Unknown'} (${toAmount})
|
||||
Asset: ${tx.asset.ticker}
|
||||
Date: ${new Date(tx.date).toLocaleString()}`
|
||||
}).join('\n---\n')
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Transaction History for ${address}:\n${txList}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BTC transaction history: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Address Validation
|
||||
server.tool(
|
||||
"validateBTCAddress",
|
||||
"Validate a Bitcoin address format",
|
||||
{
|
||||
address: z.string().describe("Bitcoin address to validate"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const isValid = bitcoinClient.validateAddress(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} is a valid Bitcoin address`
|
||||
: `The address ${address} is NOT a valid Bitcoin address`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Error validating Bitcoin address: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Network Info
|
||||
server.tool(
|
||||
"getBTCNetworkInfo",
|
||||
"Get current Bitcoin network information",
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const fees = await bitcoinClient.getFeeRates()
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Network Information:
|
||||
Current Network: ${bitcoinClient.getNetwork()}
|
||||
Fee Rates (sats/byte):
|
||||
Fast: ${fees.fast}
|
||||
Average: ${fees.average}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve Bitcoin network information: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
144
src/chains/UTXO/bitcoincash.ts
Normal file
144
src/chains/UTXO/bitcoincash.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Client as BitcoinCashClient, defaultBchParams } from '@xchainjs/xchain-bitcoincash'
|
||||
import { Network } from '@xchainjs/xchain-client'
|
||||
import { z } from "zod"
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
|
||||
// Initialize client with mainnet by default
|
||||
const bitcoinCashClient = new BitcoinCashClient({
|
||||
...defaultBchParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
// Helper function to format base amounts
|
||||
function formatBaseAmount(baseAmount: any): string {
|
||||
return baseAmount.amount().toString()
|
||||
}
|
||||
|
||||
export function registerBitcoinCashTools(server: McpServer) {
|
||||
// Bitcoin Cash Balance
|
||||
server.tool(
|
||||
"getBitcoinCashBalance",
|
||||
"Get balance for a Bitcoin Cash address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin Cash address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await bitcoinCashClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Cash Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} BCH`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BCH balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Transaction History
|
||||
server.tool(
|
||||
"getBCHTransactionHistory",
|
||||
"Get transaction history for a Bitcoin Cash address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin Cash address to check"),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return"),
|
||||
offset: z.number().optional().describe("Number of transactions to skip"),
|
||||
},
|
||||
async ({ address, limit = 10, offset = 0 }) => {
|
||||
try {
|
||||
const txs = await bitcoinCashClient.getTransactions({ address, limit, offset })
|
||||
const txList = txs.txs.map((tx: any) => {
|
||||
const fromAmount = tx.from[0]?.amount ? `${formatBaseAmount(tx.from[0].amount)} BCH` : 'Unknown'
|
||||
const toAmount = tx.to[0]?.amount ? `${formatBaseAmount(tx.to[0].amount)} BCH` : 'Unknown'
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${tx.type}
|
||||
From: ${tx.from[0]?.from || 'Unknown'} (${fromAmount})
|
||||
To: ${tx.to[0]?.to || 'Unknown'} (${toAmount})
|
||||
Asset: ${tx.asset.ticker}
|
||||
Date: ${new Date(tx.date).toLocaleString()}`
|
||||
}).join('\n---\n')
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Cash Transaction History for ${address}:\n${txList}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BCH transaction history: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Address Validation
|
||||
server.tool(
|
||||
"validateBCHAddress",
|
||||
"Validate a Bitcoin Cash address format",
|
||||
{
|
||||
address: z.string().describe("Bitcoin Cash address to validate"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const isValid = bitcoinCashClient.validateAddress(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} is a valid Bitcoin Cash address`
|
||||
: `The address ${address} is NOT a valid Bitcoin Cash address`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Error validating Bitcoin Cash address: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Network Info
|
||||
server.tool(
|
||||
"getBCHNetworkInfo",
|
||||
"Get current Bitcoin Cash network information",
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const fees = await bitcoinCashClient.getFeeRates()
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Cash Network Information:
|
||||
Current Network: ${bitcoinCashClient.getNetwork()}
|
||||
Fee Rates (sats/byte):
|
||||
Fast: ${fees.fast}
|
||||
Average: ${fees.average}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve Bitcoin Cash network information: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
144
src/chains/UTXO/dogecoin.ts
Normal file
144
src/chains/UTXO/dogecoin.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Client as DogeClient, defaultDogeParams } from '@xchainjs/xchain-doge'
|
||||
import { Network } from '@xchainjs/xchain-client'
|
||||
import { z } from "zod"
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
|
||||
// Initialize client with mainnet by default
|
||||
const dogeClient = new DogeClient({
|
||||
...defaultDogeParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
// Helper function to format base amounts
|
||||
function formatBaseAmount(baseAmount: any): string {
|
||||
return baseAmount.amount().toString()
|
||||
}
|
||||
|
||||
export function registerDogecoinTools(server: McpServer) {
|
||||
// Dogecoin Balance
|
||||
server.tool(
|
||||
"getDogeBalance",
|
||||
"Get balance for a Dogecoin address",
|
||||
{
|
||||
address: z.string().describe("Dogecoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await dogeClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Dogecoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} DOGE`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve DOGE balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Transaction History
|
||||
server.tool(
|
||||
"getDOGETransactionHistory",
|
||||
"Get transaction history for a Dogecoin address",
|
||||
{
|
||||
address: z.string().describe("Dogecoin address to check"),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return"),
|
||||
offset: z.number().optional().describe("Number of transactions to skip"),
|
||||
},
|
||||
async ({ address, limit = 10, offset = 0 }) => {
|
||||
try {
|
||||
const txs = await dogeClient.getTransactions({ address, limit, offset })
|
||||
const txList = txs.txs.map((tx: any) => {
|
||||
const fromAmount = tx.from[0]?.amount ? `${formatBaseAmount(tx.from[0].amount)} DOGE` : 'Unknown'
|
||||
const toAmount = tx.to[0]?.amount ? `${formatBaseAmount(tx.to[0].amount)} DOGE` : 'Unknown'
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${tx.type}
|
||||
From: ${tx.from[0]?.from || 'Unknown'} (${fromAmount})
|
||||
To: ${tx.to[0]?.to || 'Unknown'} (${toAmount})
|
||||
Asset: ${tx.asset.ticker}
|
||||
Date: ${new Date(tx.date).toLocaleString()}`
|
||||
}).join('\n---\n')
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Dogecoin Transaction History for ${address}:\n${txList}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve DOGE transaction history: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Address Validation
|
||||
server.tool(
|
||||
"validateDOGEAddress",
|
||||
"Validate a Dogecoin address format",
|
||||
{
|
||||
address: z.string().describe("Dogecoin address to validate"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const isValid = dogeClient.validateAddress(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} is a valid Dogecoin address`
|
||||
: `The address ${address} is NOT a valid Dogecoin address`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Error validating Dogecoin address: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Network Info
|
||||
server.tool(
|
||||
"getDOGENetworkInfo",
|
||||
"Get current Dogecoin network information",
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const fees = await dogeClient.getFeeRates()
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Dogecoin Network Information:
|
||||
Current Network: ${dogeClient.getNetwork()}
|
||||
Fee Rates (sats/byte):
|
||||
Fast: ${fees.fast}
|
||||
Average: ${fees.average}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve Dogecoin network information: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
4
src/chains/UTXO/index.ts
Normal file
4
src/chains/UTXO/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { registerBitcoinTools } from './bitcoin.js';
|
||||
export { registerLitecoinTools } from './litecoin.js';
|
||||
export { registerDogecoinTools } from './dogecoin.js';
|
||||
export { registerBitcoinCashTools } from './bitcoincash.js';
|
||||
144
src/chains/UTXO/litecoin.ts
Normal file
144
src/chains/UTXO/litecoin.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { Client as LitecoinClient, defaultLtcParams } from '@xchainjs/xchain-litecoin'
|
||||
import { Network } from '@xchainjs/xchain-client'
|
||||
import { z } from "zod"
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
|
||||
// Initialize client with mainnet by default
|
||||
const litecoinClient = new LitecoinClient({
|
||||
...defaultLtcParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
// Helper function to format base amounts
|
||||
function formatBaseAmount(baseAmount: any): string {
|
||||
return baseAmount.amount().toString()
|
||||
}
|
||||
|
||||
export function registerLitecoinTools(server: McpServer) {
|
||||
// Litecoin Balance
|
||||
server.tool(
|
||||
"getLitecoinBalance",
|
||||
"Get balance for a Litecoin address",
|
||||
{
|
||||
address: z.string().describe("Litecoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await litecoinClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Litecoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} LTC`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve LTC balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Transaction History
|
||||
server.tool(
|
||||
"getLTCTransactionHistory",
|
||||
"Get transaction history for a Litecoin address",
|
||||
{
|
||||
address: z.string().describe("Litecoin address to check"),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return"),
|
||||
offset: z.number().optional().describe("Number of transactions to skip"),
|
||||
},
|
||||
async ({ address, limit = 10, offset = 0 }) => {
|
||||
try {
|
||||
const txs = await litecoinClient.getTransactions({ address, limit, offset })
|
||||
const txList = txs.txs.map((tx: any) => {
|
||||
const fromAmount = tx.from[0]?.amount ? `${formatBaseAmount(tx.from[0].amount)} LTC` : 'Unknown'
|
||||
const toAmount = tx.to[0]?.amount ? `${formatBaseAmount(tx.to[0].amount)} LTC` : 'Unknown'
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${tx.type}
|
||||
From: ${tx.from[0]?.from || 'Unknown'} (${fromAmount})
|
||||
To: ${tx.to[0]?.to || 'Unknown'} (${toAmount})
|
||||
Asset: ${tx.asset.ticker}
|
||||
Date: ${new Date(tx.date).toLocaleString()}`
|
||||
}).join('\n---\n')
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Litecoin Transaction History for ${address}:\n${txList}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve LTC transaction history: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Address Validation
|
||||
server.tool(
|
||||
"validateLTCAddress",
|
||||
"Validate a Litecoin address format",
|
||||
{
|
||||
address: z.string().describe("Litecoin address to validate"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const isValid = litecoinClient.validateAddress(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} is a valid Litecoin address`
|
||||
: `The address ${address} is NOT a valid Litecoin address`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Error validating Litecoin address: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Network Info
|
||||
server.tool(
|
||||
"getLTCNetworkInfo",
|
||||
"Get current Litecoin network information",
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const fees = await litecoinClient.getFeeRates()
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Litecoin Network Information:
|
||||
Current Network: ${litecoinClient.getNetwork()}
|
||||
Fee Rates (sats/byte):
|
||||
Fast: ${fees.fast}
|
||||
Average: ${fees.average}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve Litecoin network information: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ const ERC20_ABI = [
|
||||
"function approve(address spender, uint256 amount) returns (bool)"
|
||||
];
|
||||
|
||||
export function registerEthereumTools(server: McpServer) {
|
||||
export function registerEvmTools(server: McpServer) {
|
||||
// Get native token balance for any EVM network
|
||||
server.tool(
|
||||
"getEvmBalance",
|
||||
20
src/chains/index.ts
Normal file
20
src/chains/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
import { registerBitcoinTools } from "./UTXO/bitcoin"
|
||||
import { registerLitecoinTools } from "./UTXO/litecoin"
|
||||
import { registerDogecoinTools } from "./UTXO/dogecoin"
|
||||
import { registerBitcoinCashTools } from "./UTXO/bitcoincash"
|
||||
import { registerThorchainTools } from "./thorchain"
|
||||
|
||||
// Export all UTXO tools registration function
|
||||
export function registerUtxoTools(server: McpServer) {
|
||||
registerBitcoinTools(server)
|
||||
registerLitecoinTools(server)
|
||||
registerDogecoinTools(server)
|
||||
registerBitcoinCashTools(server)
|
||||
}
|
||||
|
||||
// Export all tools registration function
|
||||
export function registerAllTools(server: McpServer) {
|
||||
registerUtxoTools(server)
|
||||
registerThorchainTools(server)
|
||||
}
|
||||
173
src/chains/thorchain.ts
Normal file
173
src/chains/thorchain.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
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 { Network } from '@xchainjs/xchain-client'
|
||||
import { Asset, assetFromString, assetToString } 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'
|
||||
import axios from 'axios'
|
||||
import { register9Rheader } from '@xchainjs/xchain-util'
|
||||
import fetch from 'cross-fetch'
|
||||
|
||||
config()
|
||||
|
||||
// Initialize Nine Realms headers
|
||||
register9Rheader(axios)
|
||||
|
||||
// Initialize Thorchain clients
|
||||
const thorchainClient = new ThorchainClient({
|
||||
network: Network.Mainnet,
|
||||
phrase: process.env.THORCHAIN_MNEMONIC || ''
|
||||
})
|
||||
|
||||
const thornode = new Thornode(Network.Mainnet)
|
||||
const thorchainQuery = new ThorchainQuery()
|
||||
const thorchainAmm = new ThorchainAMM(thorchainQuery)
|
||||
|
||||
// Helper function to format base amounts
|
||||
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"),
|
||||
},
|
||||
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.` }],
|
||||
}
|
||||
}
|
||||
|
||||
// Convert amount to base units
|
||||
const amountInBaseUnits = numAmount.multipliedBy(10 ** 8).toFixed(0);
|
||||
|
||||
// Format the quote request parameters
|
||||
const quoteParams = {
|
||||
amount: amountInBaseUnits,
|
||||
from_asset: assetToString(fromAsset),
|
||||
to_asset: assetToString(toAsset).replace('-B1A', ''), // Remove B1A suffix
|
||||
destination: '', // Optional destination address
|
||||
streaming_interval: '1',
|
||||
streaming_quantity: '0'
|
||||
};
|
||||
|
||||
// Get quote from THORNode directly
|
||||
const response = await fetch(`https://thornode.ninerealms.com/thorchain/quote/swap?${new URLSearchParams(quoteParams)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`THORNode API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const quote = await response.json();
|
||||
|
||||
// Helper function to format asset amounts with proper decimals
|
||||
const formatAssetAmount = (amount: string | number, decimals: number = 8) => {
|
||||
const num = Number(amount) / Math.pow(10, decimals);
|
||||
return num.toLocaleString('en-US', { maximumFractionDigits: decimals });
|
||||
};
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Swap Quote:\n` +
|
||||
`Expected Output: ${formatAssetAmount(quote.expected_amount_out)} ${quoteParams.to_asset}\n` +
|
||||
`Fees:\n` +
|
||||
`- Affiliate Fee: ${formatAssetAmount(quote.fees.affiliate)} ${quote.fees.asset}\n` +
|
||||
`- Outbound Fee: ${formatAssetAmount(quote.fees.outbound)} ${quote.fees.asset}\n` +
|
||||
`- Liquidity Fee: ${formatAssetAmount(quote.fees.liquidity)} ${quote.fees.asset}\n` +
|
||||
`- Total Fee: ${formatAssetAmount(quote.fees.total)} ${quote.fees.asset}\n` +
|
||||
`Slippage: ${quote.fees.slippage_bps / 100}%\n` +
|
||||
`Expires: ${new Date(quote.expiry * 1000).toLocaleString()}\n` +
|
||||
`Total Swap Time: ~${quote.total_swap_seconds} seconds`,
|
||||
}],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to get swap quote: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,442 +0,0 @@
|
||||
import { Client as BitcoinClient, defaultBTCParams, BlockcypherDataProviders as BTCBlockcypherDataProviders } from '@xchainjs/xchain-bitcoin'
|
||||
import { Client as LitecoinClient, defaultLtcParams } from '@xchainjs/xchain-litecoin'
|
||||
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 { 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'
|
||||
import fetch from 'cross-fetch'
|
||||
|
||||
register9Rheader(axios)
|
||||
|
||||
// Initialize clients with mainnet by default
|
||||
const bitcoinClient = new BitcoinClient({
|
||||
...defaultBTCParams,
|
||||
network: Network.Mainnet,
|
||||
dataProviders: [BTCBlockcypherDataProviders],
|
||||
})
|
||||
|
||||
const litecoinClient = new LitecoinClient({
|
||||
...defaultLtcParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
const dogeClient = new DogeClient({
|
||||
...defaultDogeParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
// Initialize Thorchain clients
|
||||
const thorchainClient = new ThorchainClient({
|
||||
network: Network.Mainnet,
|
||||
phrase: process.env.THORCHAIN_MNEMONIC || ''
|
||||
})
|
||||
|
||||
const thornode = new Thornode(Network.Mainnet)
|
||||
const thorchainQuery = new ThorchainQuery()
|
||||
const thorchainAmm = new ThorchainAMM(thorchainQuery)
|
||||
|
||||
const bitcoinCashClient = new BitcoinCashClient({
|
||||
...defaultBchParams,
|
||||
network: Network.Mainnet,
|
||||
})
|
||||
|
||||
// Helper function to format base amounts for all UTXO chains
|
||||
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"),
|
||||
},
|
||||
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.` }],
|
||||
}
|
||||
}
|
||||
|
||||
// Convert amount to base units
|
||||
const amountInBaseUnits = numAmount.multipliedBy(10 ** 8).toFixed(0);
|
||||
|
||||
// Format the quote request parameters
|
||||
const quoteParams = {
|
||||
amount: amountInBaseUnits,
|
||||
from_asset: assetToString(fromAsset),
|
||||
to_asset: assetToString(toAsset).replace('-B1A', ''), // Remove B1A suffix
|
||||
destination: '', // Optional destination address
|
||||
streaming_interval: '1',
|
||||
streaming_quantity: '0'
|
||||
};
|
||||
|
||||
// Get quote from THORNode directly
|
||||
const response = await fetch(`https://thornode.ninerealms.com/thorchain/quote/swap?${new URLSearchParams(quoteParams)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`THORNode API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const quote = await response.json();
|
||||
|
||||
// Helper function to format asset amounts with proper decimals
|
||||
const formatAssetAmount = (amount: string | number, decimals: number = 8) => {
|
||||
const num = Number(amount) / Math.pow(10, decimals);
|
||||
return num.toLocaleString('en-US', { maximumFractionDigits: decimals });
|
||||
};
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Swap Quote:\n` +
|
||||
`Expected Output: ${formatAssetAmount(quote.expected_amount_out)} ${quoteParams.to_asset}\n` +
|
||||
`Fees:\n` +
|
||||
`- Affiliate Fee: ${formatAssetAmount(quote.fees.affiliate)} ${quote.fees.asset}\n` +
|
||||
`- Outbound Fee: ${formatAssetAmount(quote.fees.outbound)} ${quote.fees.asset}\n` +
|
||||
`- Liquidity Fee: ${formatAssetAmount(quote.fees.liquidity)} ${quote.fees.asset}\n` +
|
||||
`- Total Fee: ${formatAssetAmount(quote.fees.total)} ${quote.fees.asset}\n` +
|
||||
`Slippage: ${quote.fees.slippage_bps / 100}%\n` +
|
||||
`Expires: ${new Date(quote.expiry * 1000).toLocaleString()}\n` +
|
||||
`Total Swap Time: ~${quote.total_swap_seconds} seconds`,
|
||||
}],
|
||||
}
|
||||
} 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(
|
||||
"getBitcoinBalance",
|
||||
"Get balance for a Bitcoin address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await bitcoinClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} BTC`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BTC balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Litecoin Tools
|
||||
server.tool(
|
||||
"getLitecoinBalance",
|
||||
"Get balance for a Litecoin address",
|
||||
{
|
||||
address: z.string().describe("Litecoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await litecoinClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Litecoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} LTC`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve LTC balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Dogecoin Tools
|
||||
server.tool(
|
||||
"getDogeBalance",
|
||||
"Get balance for a Dogecoin address",
|
||||
{
|
||||
address: z.string().describe("Dogecoin address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await dogeClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Dogecoin Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} DOGE`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve DOGE balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Bitcoin Cash Tools
|
||||
server.tool(
|
||||
"getBitcoinCashBalance",
|
||||
"Get balance for a Bitcoin Cash address",
|
||||
{
|
||||
address: z.string().describe("Bitcoin Cash address to check"),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const balances = await bitcoinCashClient.getBalance(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Bitcoin Cash Balance for ${address}:\n${formatBaseAmount(balances[0].amount)} BCH`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed to retrieve BCH balance: ${error.message}` }],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Common Tools for all UTXO chains
|
||||
// Transaction History
|
||||
const getTransactionHistory = async (client: any, address: string, chain: string, limit = 10, offset = 0) => {
|
||||
try {
|
||||
const txs = await client.getTransactions({ address, limit, offset })
|
||||
const txList = txs.txs.map((tx: any) => {
|
||||
const fromAmount = tx.from[0]?.amount ? `${formatBaseAmount(tx.from[0].amount)} ${chain}` : 'Unknown'
|
||||
const toAmount = tx.to[0]?.amount ? `${formatBaseAmount(tx.to[0].amount)} ${chain}` : 'Unknown'
|
||||
|
||||
return `
|
||||
Transaction: ${tx.hash}
|
||||
Type: ${tx.type}
|
||||
From: ${tx.from[0]?.from || 'Unknown'} (${fromAmount})
|
||||
To: ${tx.to[0]?.to || 'Unknown'} (${toAmount})
|
||||
Asset: ${tx.asset.ticker}
|
||||
Date: ${new Date(tx.date).toLocaleString()}`
|
||||
}).join('\n---\n')
|
||||
|
||||
return { success: true, content: txList }
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
// Register history tools for each chain
|
||||
const chains = [
|
||||
{ name: 'Bitcoin', symbol: 'BTC', client: bitcoinClient },
|
||||
{ name: 'Litecoin', symbol: 'LTC', client: litecoinClient },
|
||||
{ name: 'Dogecoin', symbol: 'DOGE', client: dogeClient },
|
||||
{ name: 'Bitcoin Cash', symbol: 'BCH', client: bitcoinCashClient },
|
||||
]
|
||||
|
||||
chains.forEach(({ name, symbol, client }) => {
|
||||
server.tool(
|
||||
`get${symbol}TransactionHistory`,
|
||||
`Get transaction history for a ${name} address`,
|
||||
{
|
||||
address: z.string().describe(`${name} address to check`),
|
||||
limit: z.number().optional().describe("Maximum number of transactions to return"),
|
||||
offset: z.number().optional().describe("Number of transactions to skip"),
|
||||
},
|
||||
async ({ address, limit = 10, offset = 0 }) => {
|
||||
const result = await getTransactionHistory(client, address, symbol, limit, offset)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: result.success
|
||||
? `${name} Transaction History for ${address}:\n${result.content}`
|
||||
: `Failed to retrieve ${name} transaction history: ${result.error}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Add address validation
|
||||
server.tool(
|
||||
`validate${symbol}Address`,
|
||||
`Validate a ${name} address format`,
|
||||
{
|
||||
address: z.string().describe(`${name} address to validate`),
|
||||
},
|
||||
async ({ address }) => {
|
||||
try {
|
||||
const isValid = client.validateAddress(address)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: isValid
|
||||
? `The address ${address} is a valid ${name} address`
|
||||
: `The address ${address} is NOT a valid ${name} address`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error validating ${name} address: ${error.message}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Add network info
|
||||
server.tool(
|
||||
`get${symbol}NetworkInfo`,
|
||||
`Get current ${name} network information`,
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const fees = await client.getFeeRates()
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${name} Network Information:
|
||||
Current Network: ${client.getNetwork()}
|
||||
Fee Rates (sats/byte):
|
||||
Fast: ${fees.fast}
|
||||
Average: ${fees.average}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to retrieve ${name} network information: ${error.message}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Export both tool registration functions
|
||||
export function registerAllTools(server: McpServer) {
|
||||
registerUtxoTools(server)
|
||||
registerThorchainTools(server)
|
||||
}
|
||||
43
src/index.ts
43
src/index.ts
@@ -23,8 +23,14 @@ if (result.error) {
|
||||
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 { registerAllTools } from "./chains/xchain.js";
|
||||
import { registerEvmTools } from "./chains/evm/evm.js";
|
||||
import {
|
||||
registerBitcoinTools,
|
||||
registerLitecoinTools,
|
||||
registerDogecoinTools,
|
||||
registerBitcoinCashTools,
|
||||
} from "./chains/UTXO/index.js";
|
||||
import { registerThorchainTools } from "./chains/thorchain.js";
|
||||
|
||||
// Create server instance
|
||||
const server = new McpServer({
|
||||
@@ -44,14 +50,35 @@ if (isEnabled('ENABLE_SOLANA_TOOLS')) {
|
||||
registerSolanaTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_ETHEREUM_TOOLS')) {
|
||||
console.error('Registering Ethereum tools...');
|
||||
registerEthereumTools(server);
|
||||
if (isEnabled('ENABLE_EVM_TOOLS')) {
|
||||
console.error('Registering EVM chain tools...');
|
||||
registerEvmTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_XCHAIN_TOOLS')) {
|
||||
console.error('Registering cross-chain tools...');
|
||||
registerAllTools(server);
|
||||
// UTXO Chain Tools
|
||||
if (isEnabled('ENABLE_BITCOIN_TOOLS')) {
|
||||
console.error('Registering Bitcoin tools...');
|
||||
registerBitcoinTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_LITECOIN_TOOLS')) {
|
||||
console.error('Registering Litecoin tools...');
|
||||
registerLitecoinTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_DOGECOIN_TOOLS')) {
|
||||
console.error('Registering Dogecoin tools...');
|
||||
registerDogecoinTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_BITCOINCASH_TOOLS')) {
|
||||
console.error('Registering Bitcoin Cash tools...');
|
||||
registerBitcoinCashTools(server);
|
||||
}
|
||||
|
||||
if (isEnabled('ENABLE_THORCHAIN_TOOLS')) {
|
||||
console.error('Registering THORChain tools...');
|
||||
registerThorchainTools(server);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
Reference in New Issue
Block a user