UNPKG

@tamago-labs/cronos-mcp

Version:

MCP server for Cronos Web3 Analytics - comprehensive blockchain analytics for Cronos and zkEVM networks with VVS Finance DEX integration

1,363 lines (1,350 loc) 55.6 kB
#!/usr/bin/env node // src/config.ts import { CronosEvm, CronosZkEvm, Client } from "@crypto.com/developer-platform-client"; var NETWORK_CONFIGS = { "cronos-mainnet": { name: "Cronos Mainnet", chain: CronosEvm.Mainnet, rpcUrl: "https://evm.cronos.org", blockExplorer: "https://cronoscan.com", nativeCurrency: "CRO", apiKeyRequired: true }, "cronos-zkevm-mainnet": { name: "Cronos zkEVM Mainnet", chain: CronosZkEvm.Mainnet, rpcUrl: "https://mainnet.zkevm.cronos.org", blockExplorer: "https://explorer.zkevm.cronos.org", nativeCurrency: "zkCRO", apiKeyRequired: true } }; var getArgs = () => process.argv.reduce((args, arg) => { if (arg.slice(0, 2) === "--") { const longArg = arg.split("="); const longArgFlag = longArg[0].slice(2); const longArgValue = longArg.length > 1 ? longArg[1] : true; args[longArgFlag] = longArgValue; } else if (arg[0] === "-") { const flags = arg.slice(1).split(""); flags.forEach((flag) => { args[flag] = true; }); } return args; }, {}); var getApiKeys = () => { const args = getArgs(); return { evmKey: process.env.CRONOS_EVM_API_KEY || args.cronos_evm_api_key || "", zkevmKey: process.env.CRONOS_ZKEVM_API_KEY || args.cronos_zkevm_api_key || "" }; }; var determineNetwork = () => { const args = getArgs(); const { evmKey, zkevmKey } = getApiKeys(); if (args.network) { const network2 = args.network; if (network2 in NETWORK_CONFIGS) { return network2; } throw new Error(`Invalid network: ${network2}. Must be one of: ${Object.keys(NETWORK_CONFIGS).join(", ")}`); } if (evmKey && zkevmKey) { console.error("\u{1F511} Both API keys detected, defaulting to Cronos Mainnet"); console.error("\u{1F4A1} Use --network=cronos-zkevm-mainnet to use zkEVM instead"); return "cronos-mainnet"; } else if (evmKey) { console.error("\u{1F511} Cronos EVM API key detected, using Cronos Mainnet"); return "cronos-mainnet"; } else if (zkevmKey) { console.error("\u{1F511} Cronos zkEVM API key detected, using zkEVM Mainnet"); return "cronos-zkevm-mainnet"; } else { throw new Error("\u274C No API keys provided! Set CRONOS_EVM_API_KEY or CRONOS_ZKEVM_API_KEY"); } }; var getApiKeyForNetwork = (network2) => { const { evmKey, zkevmKey } = getApiKeys(); return network2 === "cronos-mainnet" ? evmKey : zkevmKey; }; var network = determineNetwork(); var networkInfo = NETWORK_CONFIGS[network]; var apiKey = getApiKeyForNetwork(network); if (apiKey) { Client.init({ chain: networkInfo.chain, apiKey }); } else { console.warn(`\u26A0\uFE0F No API key provided for ${network}. Some features may be limited.`); } function validateEnvironment() { try { const { evmKey, zkevmKey } = getApiKeys(); const keyType = network === "cronos-mainnet" ? "CRONOS_EVM_API_KEY" : "CRONOS_ZKEVM_API_KEY"; console.error(`\u2705 Cronos MCP environment configuration valid`); console.error(`\u{1F4CD} Active Network: ${networkInfo.name}`); console.error(`\u{1F4CD} RPC URL: ${networkInfo.rpcUrl}`); console.error(`\u{1F4CD} Block Explorer: ${networkInfo.blockExplorer}`); console.error(`\u{1F4CD} Native Currency: ${networkInfo.nativeCurrency}`); if (apiKey) { console.error(`\u{1F4CD} Active API Key (${keyType}): ****${apiKey.slice(-4)}`); } console.error(`\u{1F310} Network Availability:`); console.error(` \u2022 Cronos Mainnet: ${evmKey ? "\u2705 Available" : "\u274C Missing CRONOS_EVM_API_KEY"}`); console.error(` \u2022 zkEVM Mainnet: ${zkevmKey ? "\u2705 Available" : "\u274C Missing CRONOS_ZKEVM_API_KEY"}`); } catch (error) { console.error("\u274C Invalid environment configuration:", error); throw error; } } function initializeClientForNetwork(targetNetwork) { const config = NETWORK_CONFIGS[targetNetwork]; const key = getApiKeyForNetwork(targetNetwork); if (key) { Client.init({ chain: config.chain, apiKey: key }); console.error(`\u{1F504} Switched to ${config.name} with API key ****${key.slice(-4)}`); } else { const requiredKey = targetNetwork === "cronos-mainnet" ? "CRONOS_EVM_API_KEY" : "CRONOS_ZKEVM_API_KEY"; throw new Error(`Missing ${requiredKey} for ${config.name}`); } } function canSwitchToNetwork(targetNetwork) { return !!getApiKeyForNetwork(targetNetwork); } function getAvailableNetworks() { const { evmKey, zkevmKey } = getApiKeys(); const available = []; if (evmKey) available.push("cronos-mainnet"); if (zkevmKey) available.push("cronos-zkevm-mainnet"); return available; } // src/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // src/agent/analytics.ts import { Token, Wallet, Block, Transaction, Exchange, Defi, CronosId } from "@crypto.com/developer-platform-client"; var CronosAnalyticsAgent = class { network; networkInfo; constructor(targetNetwork) { this.network = targetNetwork || "cronos-mainnet"; this.networkInfo = networkInfo; if (targetNetwork && targetNetwork !== this.network) { if (canSwitchToNetwork(targetNetwork)) { initializeClientForNetwork(targetNetwork); this.network = targetNetwork; } else { const requiredKey = targetNetwork === "cronos-mainnet" ? "CRONOS_EVM_API_KEY" : "CRONOS_ZKEVM_API_KEY"; console.warn(`\u26A0\uFE0F Cannot switch to ${targetNetwork}: Missing ${requiredKey}`); console.warn(`\u{1F4CD} Continuing with default network: ${this.network}`); } } console.error(`\u{1F50D} Cronos Analytics Agent initialized for ${this.network}`); console.error(`\u{1F4CD} Network: ${this.networkInfo.name}`); console.error(`\u{1F517} Block Explorer: ${this.networkInfo.blockExplorer}`); } // === BALANCE & TOKEN ANALYTICS === async getNativeTokenBalance(address) { try { const result = await Token.getNativeTokenBalance(address); return { status: "success", data: { ...result.data, network: this.network, currency: this.networkInfo.nativeCurrency }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get native balance", network: this.network }; } } async getERC20TokenBalance(address, contractAddress) { try { const result = await Token.getERC20TokenBalance(address, contractAddress); return { status: "success", data: { ...result.data, network: this.network, contractAddress }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get ERC20 balance", network: this.network }; } } async getWalletOverview(address) { try { const result = await Wallet.balance(address); return { status: "success", data: { ...result.data, network: this.network, address }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get wallet overview", network: this.network }; } } // === TRANSACTION ANALYTICS === async getTransactionByHash(transactionHash) { try { const result = await Transaction.getTransactionByHash(transactionHash); return { status: "success", data: { ...result.data, network: this.network, blockExplorer: `${this.networkInfo.blockExplorer}/tx/${transactionHash}` }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get transaction details", network: this.network }; } } async getTransactionStatus(transactionHash) { try { const result = await Transaction.getTransactionStatus(transactionHash); return { status: "success", data: { ...result.data, network: this.network, transactionHash }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get transaction status", network: this.network }; } } // === BLOCK ANALYTICS === async getCurrentBlock() { try { const result = await Block.getCurrentBlock(); return { status: "success", data: { ...result.data, network: this.network }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get current block", network: this.network }; } } async getBlockByTag(blockTag, includeTransactions = false) { try { const result = await Block.getBlockByTag(blockTag, includeTransactions ? "true" : "false"); return { status: "success", data: { ...result.data, network: this.network, blockTag, includeTransactions }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get block data", network: this.network }; } } // === DEFI ANALYTICS === async getWhitelistedTokens(protocol) { try { const result = await Defi.getWhitelistedTokens(protocol); return { status: "success", data: { ...result.data, network: this.network, protocol }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get whitelisted tokens", network: this.network }; } } async getAllFarms(protocol) { try { const result = await Defi.getAllFarms(protocol); return { status: "success", data: { ...result.data, network: this.network, protocol }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get farms", network: this.network }; } } async getFarmBySymbol(protocol, symbol) { try { const result = await Defi.getFarmBySymbol(protocol, symbol); return { status: "success", data: { ...result.data, network: this.network, protocol, symbol }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get farm data", network: this.network }; } } // === CRONOS ID ANALYTICS === async resolveCronosId(cronosId) { try { const result = await CronosId.forwardResolve(cronosId); return { status: "success", data: { ...result.data, network: this.network, cronosId }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to resolve CronosId", network: this.network }; } } async reverseResolveCronosId(address) { try { const result = await CronosId.reverseResolve(address); return { status: "success", data: { ...result.data, network: this.network, address }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to reverse resolve address", network: this.network }; } } // === EXCHANGE & MARKET ANALYTICS === async getAllTickers() { try { const result = await Exchange.getAllTickers(); return { status: "success", data: { ...result.data, network: this.network, exchangeInfo: "Crypto.com Exchange data" }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get exchange tickers", network: this.network }; } } async getTickerByInstrument(instrument) { try { const result = await Exchange.getTickerByInstrument(instrument); return { status: "success", data: { ...result.data, network: this.network, instrument }, network: this.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get ticker data", network: this.network }; } } // === UTILITY METHODS === getNetworkInfo() { return { status: "success", data: { network: this.network, name: this.networkInfo.name, rpcUrl: this.networkInfo.rpcUrl, blockExplorer: this.networkInfo.blockExplorer, nativeCurrency: this.networkInfo.nativeCurrency, hasApiKey: !!apiKey, canSwitchNetworks: { "cronos-mainnet": canSwitchToNetwork("cronos-mainnet"), "cronos-zkevm-mainnet": canSwitchToNetwork("cronos-zkevm-mainnet") } }, network: this.network, timestamp: Date.now() }; } // Format large numbers for better readability formatBalance(balance, decimals = 18) { const balanceNum = parseFloat(balance) / Math.pow(10, decimals); if (balanceNum >= 1e9) return `${(balanceNum / 1e9).toFixed(2)}B`; if (balanceNum >= 1e6) return `${(balanceNum / 1e6).toFixed(2)}M`; if (balanceNum >= 1e3) return `${(balanceNum / 1e3).toFixed(2)}K`; return balanceNum.toFixed(4); } // Validate Ethereum address format isValidAddress(address) { return /^0x[a-fA-F0-9]{40}$/.test(address); } // Validate transaction hash format isValidTxHash(hash) { return /^0x[a-fA-F0-9]{64}$/.test(hash); } // Validate CronosId format isValidCronosId(name) { return CronosId.isCronosId(name); } }; // src/mcp/balance/get_balance_tools.ts import { z } from "zod"; var GetNativeBalanceTool = { name: "cronos_get_native_balance", description: "Get native token balance (CRO/zkCRO) for any address on Cronos networks", schema: { address: z.string().describe("Wallet address to check balance for (0x...)").regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address"), network: z.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getNativeTokenBalance(input.address); return { status: "success", message: `\u2705 Native balance retrieved for ${input.address}`, data: { address: input.address, ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/address/${input.address}` } }; } catch (error) { throw new Error(`Failed to get native balance: ${error.message}`); } } }; var GetERC20BalanceTool = { name: "cronos_get_erc20_balance", description: "Get ERC20 token balance for any address and token contract on Cronos networks", schema: { address: z.string().describe("Wallet address to check balance for (0x...)").regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address"), contractAddress: z.string().describe("ERC20 token contract address (0x...)").regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid contract address"), network: z.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getERC20TokenBalance(input.address, input.contractAddress); return { status: "success", message: `\u2705 ERC20 balance retrieved for ${input.address}`, data: { address: input.address, contractAddress: input.contractAddress, ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/token/${input.contractAddress}?a=${input.address}` } }; } catch (error) { throw new Error(`Failed to get ERC20 balance: ${error.message}`); } } }; var GetWalletOverviewTool = { name: "cronos_get_wallet_overview", description: "Get comprehensive wallet overview including all token balances and portfolio summary", schema: { address: z.string().describe("Wallet address to analyze (0x...)").regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address"), network: z.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getWalletOverview(input.address); return { status: "success", message: `\u2705 Wallet overview retrieved for ${input.address}`, data: { address: input.address, ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/address/${input.address}`, analytics: { totalTokens: result.data?.tokens?.length || 0, hasActivity: result.data?.transactionCount > 0 } } }; } catch (error) { throw new Error(`Failed to get wallet overview: ${error.message}`); } } }; var GetNetworkInfoTool = { name: "cronos_get_network_info", description: "Get current network information and statistics", schema: { network: z.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = targetAgent.getNetworkInfo(); return { status: "success", message: `\u2705 Network information retrieved`, data: { ...result.data, capabilities: [ "Native token balance queries", "ERC20 token analytics", "Transaction analysis", "Contract information", "Exchange data integration" ] } }; } catch (error) { throw new Error(`Failed to get network info: ${error.message}`); } } }; // src/mcp/transaction/get_transaction_tools.ts import { z as z2 } from "zod"; var GetTransactionTool = { name: "cronos_get_transaction", description: "Get detailed transaction information by transaction hash", schema: { transactionHash: z2.string().describe("Transaction hash to look up (0x...)").regex(/^0x[a-fA-F0-9]{64}$/, "Must be a valid transaction hash"), network: z2.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getTransactionByHash(input.transactionHash); return { status: "success", message: `\u2705 Transaction details retrieved`, data: { transactionHash: input.transactionHash, ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/tx/${input.transactionHash}` } }; } catch (error) { throw new Error(`Failed to get transaction: ${error.message}`); } } }; var GetTransactionStatusTool = { name: "cronos_get_transaction_status", description: "Get transaction confirmation status and details", schema: { transactionHash: z2.string().describe("Transaction hash to check status (0x...)").regex(/^0x[a-fA-F0-9]{64}$/, "Must be a valid transaction hash"), network: z2.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getTransactionStatus(input.transactionHash); return { status: "success", message: `\u2705 Transaction status retrieved`, data: { transactionHash: input.transactionHash, ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/tx/${input.transactionHash}`, statusSummary: { isConfirmed: result.data?.status === "confirmed", isPending: result.data?.status === "pending", hasFailed: result.data?.status === "failed" } } }; } catch (error) { throw new Error(`Failed to get transaction status: ${error.message}`); } } }; // src/mcp/block/get_block_tools.ts import { z as z3 } from "zod"; var GetCurrentBlockTool = { name: "cronos_get_current_block", description: "Get the current latest block number and information", schema: { network: z3.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getCurrentBlock(); return { status: "success", message: `\u2705 Current block information retrieved`, data: { ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/block/${result.data?.blockNumber || "latest"}` } }; } catch (error) { throw new Error(`Failed to get current block: ${error.message}`); } } }; var GetBlockByTagTool = { name: "cronos_get_block_by_tag", description: "Get block information by block number, 'latest', or 'pending'", schema: { blockTag: z3.string().describe("Block identifier ('latest', 'pending', or block number in hex like '0x1b4')"), includeTransactions: z3.boolean().optional().describe("Include full transaction details (default: false)"), network: z3.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getBlockByTag(input.blockTag, input.includeTransactions || false); return { status: "success", message: `\u2705 Block data retrieved for ${input.blockTag}`, data: { ...result.data, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/block/${input.blockTag}` } }; } catch (error) { throw new Error(`Failed to get block data: ${error.message}`); } } }; // src/mcp/defi/get_defi_tools.ts import { z as z4 } from "zod"; import { DefiProtocol } from "@crypto.com/developer-platform-client"; var GetAllFarmsTool = { name: "cronos_get_all_farms", description: "Get all yield farms for DeFi protocols with APR/APY data", schema: { protocol: z4.enum(["h2finance", "vvsfinance"]).describe("DeFi protocol to query"), network: z4.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const protocol = input.protocol === "h2finance" ? DefiProtocol.H2 : DefiProtocol.VVS; const result = await targetAgent.getAllFarms(protocol); const farms = Array.isArray(result.data) ? result.data : []; const activeFarms = farms.filter((farm) => !farm.isFinished); return { status: "success", message: `\u2705 Farm data retrieved for ${input.protocol}`, data: { ...result.data, protocol: input.protocol, summary: { totalFarms: farms.length, activeFarms: activeFarms.length, finishedFarms: farms.length - activeFarms.length, averageAPR: activeFarms.length > 0 ? activeFarms.reduce((sum, farm) => sum + (farm.baseApr || 0), 0) / activeFarms.length : 0 } } }; } catch (error) { throw new Error(`Failed to get farms: ${error.message}`); } } }; var GetFarmBySymbolTool = { name: "cronos_get_farm_by_symbol", description: "Get specific farm details by LP symbol", schema: { protocol: z4.enum(["h2finance", "vvsfinance"]).describe("DeFi protocol to query"), symbol: z4.string().describe("LP symbol to query (e.g., 'zkCRO-MOON', 'CRO-USDC')"), network: z4.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const protocol = input.protocol === "h2finance" ? DefiProtocol.H2 : DefiProtocol.VVS; const result = await targetAgent.getFarmBySymbol(protocol, input.symbol); return { status: "success", message: `\u2705 Farm details retrieved for ${input.symbol}`, data: { ...result.data, protocol: input.protocol, symbol: input.symbol, yieldInfo: { baseAPR: result.data?.baseApr || 0, baseAPY: result.data?.baseApy || 0, lpAPR: result.data?.lpApr || 0, lpAPY: result.data?.lpApy || 0, isActive: !result.data?.isFinished, rewardEndDate: result.data?.rewardEndAt } } }; } catch (error) { throw new Error(`Failed to get farm data: ${error.message}`); } } }; // src/mcp/cronosid/get_cronosid_tools.ts import { z as z5 } from "zod"; var ResolveCronosIdTool = { name: "cronos_resolve_cronosid", description: "Resolve a CronosId (like alice.cro) to its wallet address", schema: { cronosId: z5.string().describe("CronosId to resolve (e.g., 'alice.cro')").regex(/^[a-zA-Z0-9_-]+\.cro$/, "Must be a valid CronosId ending with .cro"), network: z5.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; if (!targetAgent.isValidCronosId(input.cronosId)) { throw new Error("Invalid CronosId format. Must end with .cro"); } const result = await targetAgent.resolveCronosId(input.cronosId); return { status: "success", message: `\u2705 CronosId resolved: ${input.cronosId}`, data: { cronosId: input.cronosId, ...result.data, blockExplorer: result.data?.address ? `${targetAgent.networkInfo.blockExplorer}/address/${result.data.address}` : void 0 } }; } catch (error) { throw new Error(`Failed to resolve CronosId: ${error.message}`); } } }; var ReverseResolveCronosIdTool = { name: "cronos_reverse_resolve_cronosid", description: "Look up the CronosId associated with a wallet address", schema: { address: z5.string().describe("Wallet address to reverse resolve (0x...)").regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address"), network: z5.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.reverseResolveCronosId(input.address); return { status: "success", message: `\u2705 Address reverse resolved: ${input.address}`, data: { address: input.address, ...result.data, hasCronosId: !!result.data?.name, blockExplorer: `${targetAgent.networkInfo.blockExplorer}/address/${input.address}` } }; } catch (error) { throw new Error(`Failed to reverse resolve address: ${error.message}`); } } }; // src/mcp/vvs/get_vvs_tools.ts import { z as z7 } from "zod"; // src/mcp/vvs/vvs_analytics.ts import axios from "axios"; var VVS_API_BASE = "https://api.vvs.finance/info/api"; var VVSAnalytics = class { agent; constructor(agent) { this.agent = agent; } async getSupplyInfo() { try { const response = await axios.get(`${VVS_API_BASE}/supply`); const data = response.data; return { status: "success", data: { ...data, network: this.agent.network, formatted: { totalSupply: this.formatTokenAmount(data.totalSupply), circulatingSupply: this.formatTokenAmount(data.circulatingSupply), burnedSupply: this.formatTokenAmount(data.burnedSupply), burnRate: (parseFloat(data.burnedSupply) / parseFloat(data.totalSupply) * 100).toFixed(2) + "%" } }, network: this.agent.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get VVS supply info", network: this.agent.network }; } } async getSummary(limit) { try { const response = await axios.get(`${VVS_API_BASE}/summary`); const data = response.data; let pairs = Object.entries(data.data || {}); if (limit && pairs.length > limit) { pairs = pairs.slice(0, limit); } const pairAnalysis = pairs.map(([pairId, pairData]) => ({ pairId, ...pairData, tokens: pairId.split("_"), liquidityUSD: parseFloat(pairData.liquidity || "0"), liquidityCRO: parseFloat(pairData.liquidity_CRO || "0"), volume24hUSD: parseFloat(pairData.base_volume || "0") + parseFloat(pairData.quote_volume || "0") })); const totalLiquidityUSD = pairAnalysis.reduce((sum, pair) => sum + pair.liquidityUSD, 0); const totalVolume24h = pairAnalysis.reduce((sum, pair) => sum + pair.volume24hUSD, 0); return { status: "success", data: { updated_at: data.updated_at, pairs: pairAnalysis, network: this.agent.network, summary: { totalPairs: pairAnalysis.length, totalLiquidityUSD, totalVolume24hUSD: totalVolume24h, averageLiquidityPerPair: totalLiquidityUSD / pairAnalysis.length, topPairByLiquidity: pairAnalysis.sort((a, b) => b.liquidityUSD - a.liquidityUSD)[0]?.pairId } }, network: this.agent.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get VVS summary", network: this.agent.network }; } } async getTokens(limit) { try { const response = await axios.get(`${VVS_API_BASE}/tokens`); const data = response.data; let tokens = Object.entries(data.data || {}); if (limit && tokens.length > limit) { tokens = tokens.slice(0, limit); } const tokenAnalysis = tokens.map(([address, tokenData]) => ({ address, ...tokenData, priceUSD: parseFloat(tokenData.price || "0"), priceCRO: parseFloat(tokenData.price_CRO || "0"), isWCRO: address.toLowerCase() === "0x5c7f8a570d578ed84e63fdfa7b1ee72deae1ae23" })); const wcroToken = tokenAnalysis.find((token) => token.isWCRO); return { status: "success", data: { updated_at: data.updated_at, tokens: tokenAnalysis, network: this.agent.network, summary: { totalTokens: tokenAnalysis.length, wcroPrice: wcroToken?.priceUSD || 0, wcroAddress: "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23", tokensWithUSDPrice: tokenAnalysis.filter((t) => t.priceUSD > 0).length, tokensWithCROPrice: tokenAnalysis.filter((t) => t.priceCRO > 0).length } }, network: this.agent.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get VVS tokens", network: this.agent.network }; } } async getTokenInfo(tokenAddress) { try { const response = await axios.get(`${VVS_API_BASE}/tokens/${tokenAddress}`); const data = response.data; const isWCRO = tokenAddress.toLowerCase() === "0x5c7f8a570d578ed84e63fdfa7b1ee72deae1ae23"; return { status: "success", data: { ...data.data, address: tokenAddress, updated_at: data.updated_at, network: this.agent.network, priceUSD: parseFloat(data.data?.price || "0"), priceCRO: parseFloat(data.data?.price_CRO || "0"), isWCRO, blockExplorer: `${this.agent.networkInfo.blockExplorer}/token/${tokenAddress}` }, network: this.agent.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get token info", network: this.agent.network }; } } async getPairs(limit) { try { const response = await axios.get(`${VVS_API_BASE}/pairs`); const data = response.data; let pairs = Object.entries(data.data || {}); if (limit && pairs.length > limit) { pairs = pairs.slice(0, limit); } const pairAnalysis = pairs.map(([pairId, pairData]) => ({ pairId, ...pairData, tokens: pairId.split("_"), liquidityUSD: parseFloat(pairData.liquidity || "0"), liquidityCRO: parseFloat(pairData.liquidity_CRO || "0"), baseVolume: parseFloat(pairData.base_volume || "0"), quoteVolume: parseFloat(pairData.quote_volume || "0"), price: parseFloat(pairData.price || "0"), hasWCRO: pairId.includes("0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23"), isStablePair: this.isStablePair(pairData) })); const totalLiquidityUSD = pairAnalysis.reduce((sum, pair) => sum + pair.liquidityUSD, 0); const wcroWallets = pairAnalysis.filter((pair) => pair.hasWCRO); return { status: "success", data: { updated_at: data.updated_at, pairs: pairAnalysis, network: this.agent.network, summary: { totalPairs: pairAnalysis.length, totalLiquidityUSD, wcroPairs: wcroWallets.length, stablePairs: pairAnalysis.filter((p) => p.isStablePair).length, topPairsByLiquidity: pairAnalysis.sort((a, b) => b.liquidityUSD - a.liquidityUSD).slice(0, 5).map((p) => ({ pairId: p.pairId, liquidity: p.liquidityUSD })) } }, network: this.agent.network, timestamp: Date.now() }; } catch (error) { return { status: "error", message: error.message || "Failed to get VVS pairs", network: this.agent.network }; } } formatTokenAmount(amount) { const num = parseFloat(amount) / 1e18; if (num >= 1e12) return `${(num / 1e12).toFixed(2)}T`; if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`; if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`; if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`; return num.toFixed(2); } isStablePair(pairData) { const stableTokens = ["USDT", "USDC", "DAI", "BUSD"]; const baseSymbol = pairData.base_symbol?.toUpperCase() || ""; const quoteSymbol = pairData.quote_symbol?.toUpperCase() || ""; return stableTokens.includes(baseSymbol) || stableTokens.includes(quoteSymbol); } }; // src/types.ts import { z as z6 } from "zod"; var DefiProtocolSchema = z6.enum(["h2finance", "vvsfinance"]); var NetworkSchema = z6.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]); var AddressSchema = z6.string().regex(/^0x[a-fA-F0-9]{40}$/, "Must be a valid Ethereum address"); var TxHashSchema = z6.string().regex(/^0x[a-fA-F0-9]{64}$/, "Must be a valid transaction hash"); var CronosIdSchema = z6.string().regex(/^[a-zA-Z0-9_-]+\.cro$/, "Must be a valid CronosId ending with .cro"); // src/mcp/vvs/get_vvs_tools.ts var GetVVSSupplyTool = { name: "cronos_get_vvs_supply", description: "Get VVS token supply information (total, circulating, burned)", schema: { network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getSupplyInfo(); return { status: "success", message: `\u2705 VVS supply information retrieved`, data: { ...result.data, protocol: "VVS Finance", dex: "Leading DEX on Cronos" } }; } catch (error) { throw new Error(`Failed to get VVS supply info: ${error.message}`); } } }; var GetVVSSummaryTool = { name: "cronos_get_vvs_summary", description: "Get VVS DEX summary with top trading pairs and liquidity data", schema: { limit: z7.number().optional().describe("Limit number of pairs returned (default: 50, max: 1000)").default(50), network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getSummary(input.limit || 50); return { status: "success", message: `\u2705 VVS DEX summary retrieved`, data: { ...result.data, protocol: "VVS Finance", description: "Top DEX on Cronos network", wcroInfo: { address: "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23", note: "CRO is represented as WCRO in pairs" } } }; } catch (error) { throw new Error(`Failed to get VVS summary: ${error.message}`); } } }; var GetVVSTokensTool = { name: "cronos_get_vvs_tokens", description: "Get all tokens available on VVS with price information", schema: { limit: z7.number().optional().describe("Limit number of tokens returned (default: 100, max: 1000)").default(100), network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getTokens(input.limit || 100); return { status: "success", message: `\u2705 VVS tokens retrieved`, data: { ...result.data, protocol: "VVS Finance", wcroInfo: { address: "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23", note: "Canonical WCRO address used by VVS" } } }; } catch (error) { throw new Error(`Failed to get VVS tokens: ${error.message}`); } } }; var GetVVSTokenInfoTool = { name: "cronos_get_vvs_token_info", description: "Get specific token information from VVS including USD and CRO prices", schema: { tokenAddress: AddressSchema.describe("Token contract address (0x...)"), network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getTokenInfo(input.tokenAddress); return { status: "success", message: `\u2705 VVS token info retrieved for ${input.tokenAddress}`, data: { ...result.data, protocol: "VVS Finance", tokenAddress: input.tokenAddress } }; } catch (error) { throw new Error(`Failed to get VVS token info: ${error.message}`); } } }; var GetVVSPairsTool = { name: "cronos_get_vvs_pairs", description: "Get all trading pairs on VVS with detailed liquidity and volume data", schema: { limit: z7.number().optional().describe("Limit number of pairs returned (default: 100, max: 1000)").default(100), network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getPairs(input.limit || 100); return { status: "success", message: `\u2705 VVS trading pairs retrieved`, data: { ...result.data, protocol: "VVS Finance", dexInfo: { type: "Automated Market Maker (AMM)", chainName: targetAgent.networkInfo.name, wcroAddress: "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23" } } }; } catch (error) { throw new Error(`Failed to get VVS pairs: ${error.message}`); } } }; var GetVVSTopPairsTool = { name: "cronos_get_vvs_top_pairs", description: "Get top VVS trading pairs by liquidity with analytics", schema: { limit: z7.number().optional().describe("Number of top pairs to return (default: 10, max: 50)").default(10), sortBy: z7.enum(["liquidity", "volume"]).optional().describe("Sort criteria (default: liquidity)"), network: NetworkSchema.optional().describe("Network to query (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const vvsAnalytics = new VVSAnalytics(targetAgent); const result = await vvsAnalytics.getPairs(1e3); if (result.status !== "success" || !result.data?.pairs) { throw new Error("Failed to fetch pairs data"); } const sortBy = input.sortBy || "liquidity"; const limit = input.limit || 10; const sortedPairs = result.data.pairs.sort((a, b) => { if (sortBy === "volume") { return b.baseVolume + b.quoteVolume - (a.baseVolume + a.quoteVolume); } return b.liquidityUSD - a.liquidityUSD; }).slice(0, limit); const analytics = { totalLiquidityInTopPairs: sortedPairs.reduce((sum, pair) => sum + pair.liquidityUSD, 0), totalVolumeInTopPairs: sortedPairs.reduce((sum, pair) => sum + pair.baseVolume + pair.quoteVolume, 0), wcroWallets: sortedPairs.filter((pair) => pair.hasWCRO).length, stablePairs: sortedPairs.filter((pair) => pair.isStablePair).length, averageLiquidity: sortedPairs.reduce((sum, pair) => sum + pair.liquidityUSD, 0) / sortedPairs.length }; return { status: "success", message: `\u2705 Top ${limit} VVS pairs retrieved (sorted by ${sortBy})`, data: { pairs: sortedPairs, analytics, sortBy, limit, protocol: "VVS Finance", updated_at: result.data.updated_at, network: targetAgent.network } }; } catch (error) { throw new Error(`Failed to get top VVS pairs: ${error.message}`); } } }; // src/mcp/market/get_market_tools.ts import { z as z8 } from "zod"; var GetAllTickersTool = { name: "cronos_get_all_tickers", description: "Get all available trading pairs and market data from Crypto.com Exchange", schema: { limit: z8.number().optional().describe("Limit number of results (default: 50, max: 200)").default(50), network: z8.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network context (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getAllTickers(); let tickers = result.data?.result || []; if (input.limit && tickers.length > input.limit) { tickers = tickers.slice(0, input.limit); } return { status: "success", message: `\u2705 Retrieved ${tickers.length} trading pairs`, data: { tickers, count: tickers.length, exchange: "Crypto.com Exchange", network: targetAgent.network, timestamp: Date.now(), summary: { totalPairs: result.data?.result?.length || 0, croBasePairs: tickers.filter((t) => t.i && t.i.endsWith("_CRO")).length, usdBasePairs: tickers.filter((t) => t.i && (t.i.endsWith("_USD") || t.i.endsWith("_USDT") || t.i.endsWith("_USDC"))).length } } }; } catch (error) { throw new Error(`Failed to get all tickers: ${error.message}`); } } }; var GetTickerTool = { name: "cronos_get_ticker", description: "Get specific trading pair data and market information", schema: { instrument: z8.string().describe("Trading pair instrument name (e.g., 'CRO_USD', 'BTC_CRO')").min(1), network: z8.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network context (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const result = await targetAgent.getTickerByInstrument(input.instrument); return { status: "success", message: `\u2705 Ticker data retrieved for ${input.instrument}`, data: { instrument: input.instrument, ...result.data, exchange: "Crypto.com Exchange", network: targetAgent.network, timestamp: Date.now(), analysis: { isCROPair: input.instrument.includes("CRO"), isStablePair: input.instrument.includes("USD") || input.instrument.includes("USDT") || input.instrument.includes("USDC"), priceAvailable: !!result.data?.result?.a, volumeAvailable: !!result.data?.result?.v } } }; } catch (error) { throw new Error(`Failed to get ticker for ${input.instrument}: ${error.message}`); } } }; var GetMarketSummaryTool = { name: "cronos_get_market_summary", description: "Get market summary and CRO-related trading data", schema: { includeCROPairs: z8.boolean().optional().describe("Include CRO trading pairs analysis (default: true)"), network: z8.enum(["cronos-mainnet", "cronos-zkevm-mainnet"]).optional().describe("Network context (defaults to configured network)") }, handler: async (agent, input) => { try { const targetAgent = input.network && input.network !== agent.network ? new CronosAnalyticsAgent(input.network) : agent; const includeCRO = input.includeCROPairs !== false; const allTickersResult = await targetAgent.getAllTickers(); const tickers = allTickersResult.data?.result || []; let croPrice = null; if (includeCRO) { try { const croResult = await targetAgent.getTickerByInstrument("CRO_USD"); croPrice = croResult.data?.result; } catch { try { const croResult = await targetAgent.getTickerByInstrument("CRO_USDT"); croPrice = croResult.data?.result; } catch { } } } return { status: "success", message: `\u2705 Market summary retrieved`, data: { exchange: "Crypto.com Exchange", network: targetAgent.network, timestam