Skip to main content
Glama

binance-alpha-mcp

index.js8.76 kB
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { ethers } = require("ethers"); const { z } = require("zod"); const dotenv = require("dotenv"); // Load environment variables dotenv.config(); const WS_ENDPOINT = process.env.WS_ENDPOINT; const RPC_ENDPOINT = process.env.RPC_ENDPOINT; if (!WS_ENDPOINT || !RPC_ENDPOINT) { throw new Error("WS_ENDPOINT and RPC_ENDPOINT environment variables are required"); } // Constants const BINANCE_DEX_ROUTER = "0x5efc784d444126ecc05f22c49ff3fbd7d9f4868a"; const TOKEN_BASKET = { "0x55d398326f99059fF775485246999027B3197955": "USDT", "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d": "USDC", "0x0000000000000000000000000000000000000000": "BNB", }; const PRICE_CACHE_DURATION = 5000; // 5 seconds in ms const MAX_RECORD_AGE = 3600 * 1000; // 1 hour in ms // ABI for OrderRecord event and ERC20 token const ORDER_ABI = [ "event OrderRecord(address inputToken, address outputToken, address sender, uint256 inputAmount, uint256 outputAmount)", ]; const ERC20_ABI = [ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", ]; // Data structures class AppContext { constructor() { this.trades = []; this.tradeUsdValues = {}; this.priceCache = {}; this.tokenInfoCache = { "0x55d398326f99059fF775485246999027B3197955": ["Tether USD", "USDT", 18], "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d": ["USD Coin", "USDC", 18], "0x0000000000000000000000000000000000000000": ["Binance Coin", "BNB", 18], }; this.startTime = Date.now(); // Periodically clean old records setInterval(() => this.cleanup(), MAX_RECORD_AGE / 10); } cleanup() { const now = Date.now(); while (this.trades.length && now - this.trades[0].timestamp > MAX_RECORD_AGE) { const oldTrade = this.trades.shift(); this.tradeUsdValues[oldTrade.inputToken] -= oldTrade.usdValue; if (this.tradeUsdValues[oldTrade.inputToken] <= 0) { delete this.tradeUsdValues[oldTrade.inputToken]; } } } } // Initialize MCP server const server = new McpServer({ name: "BinanceAlpha", version: "1.0.0", }); // Initialize app context const appContext = new AppContext(); async function getUsdPrice(tokenSymbol) { const now = Date.now(); if (appContext.priceCache[tokenSymbol] && now - appContext.priceCache[tokenSymbol][1] < PRICE_CACHE_DURATION) { return appContext.priceCache[tokenSymbol][0]; } try { const response = await fetch( `https://min-api.cryptocompare.com/data/price?fsym=${tokenSymbol}&tsyms=USD` ); const data = await response.json(); const price = data.USD || 1.0; appContext.priceCache[tokenSymbol] = [price, now]; return price; } catch (error) { console.error(`Failed to fetch price for ${tokenSymbol}:`, error); return 1.0; } } async function getTokenInfo(tokenAddress, provider) { if (appContext.tokenInfoCache[tokenAddress]) { return appContext.tokenInfoCache[tokenAddress]; } try { const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider); const [name, symbol, decimals] = await Promise.all([ contract.name(), contract.symbol(), contract.decimals(), ]); const tokenInfo = [name, symbol, Number(decimals)]; appContext.tokenInfoCache[tokenAddress] = tokenInfo; return tokenInfo; } catch (error) { console.error(`Failed to fetch token info for ${tokenAddress}:`, error); return ["Unknown", "Unknown", 18]; } } async function processLog(log, provider) { const inputToken = log.inputToken.toLowerCase(); if (!(inputToken in TOKEN_BASKET)) { return; } const outputToken = log.outputToken.toLowerCase(); const sender = log.sender.toLowerCase(); const inputAmount = log.inputAmount; const outputAmount = log.outputAmount; const inputInfo = await getTokenInfo(inputToken, provider); const outputInfo = await getTokenInfo(outputToken, provider); const inputAmountFloat = parseFloat(ethers.formatUnits(inputAmount, inputInfo[2])); const outputAmountFloat = parseFloat(ethers.formatUnits(outputAmount, outputInfo[2])); const usdPrice = await getUsdPrice(TOKEN_BASKET[inputToken]); const usdValue = inputAmountFloat * usdPrice; const trade = { inputToken, outputToken, sender, inputAmount: inputAmountFloat, outputAmount: outputAmountFloat, usdValue, timestamp: Date.now(), }; appContext.trades.push(trade); appContext.tradeUsdValues[outputToken] = (appContext.tradeUsdValues[outputToken] || 0) + usdValue; } // Tools server.tool( "get_top_tokens", "Returns a markdown table of the top tokens by USD trading volume", z.object({ limit: z.number().optional().default(10), }), async ({ limit }) => { const topTokens = Object.entries(appContext.tradeUsdValues) .sort(([, usdValueA], [, usdValueB]) => usdValueB - usdValueA) .slice(0, limit) .map(async ([token, usdValue]) => { const tokenInfo = appContext.tokenInfoCache[token] || ["Unknown", "Unknown", 18]; return { address: token, name: tokenInfo[0], symbol: tokenInfo[1], usd_volume: usdValue, }; }); const tokens = await Promise.all(topTokens); const minutesSinceStart = (Date.now() - appContext.startTime) / 60000; const period = `period: last ${Math.min(minutesSinceStart, MAX_RECORD_AGE / 60000).toFixed(0)} minutes`; let markdown = `${period}\n`; markdown += `| Symbol | USD Volume | Name | Address |\n`; markdown += `|--------|------------|------|---------|\n`; for (const token of tokens) { markdown += `| ${token.symbol} | $${token.usd_volume.toFixed(2)} | ${token.name} | ${token.address} |\n`; } return { content: [ { type: "text", text: markdown, }, ], }; } ); server.tool( "get_trade_stats", "Returns statistics about trade USD values including min, max, median, and distribution", z.object({ buckets: z.number().optional().default(10), }), async ({ buckets }) => { const usdValues = appContext.trades.map((trade) => trade.usdValue); if (!usdValues.length) { return { content: [ { type: "text", text: "No trade data available", }, ], }; } const sorted = [...usdValues].sort((a, b) => a - b); const min = Math.min(...usdValues); const max = Math.max(...usdValues); const median = sorted[Math.floor(usdValues.length / 2)]; // Calculate distribution const bucketSize = (max - min) / buckets; const distribution = Array(buckets).fill(0); for (const usdValue of usdValues) { if (bucketSize == 0) continue; const bucketIndex = Math.min( Math.floor((usdValue - min) / bucketSize), buckets - 1 ); distribution[bucketIndex]++; } const minutesSinceStart = (Date.now() - appContext.startTime) / 60000; const period = `period: last ${Math.min(minutesSinceStart, MAX_RECORD_AGE / 60000).toFixed(0)} minutes`; let markdown = `${period}\n`; markdown += `min: $${min.toFixed(2)}, max: $${max.toFixed(2)}, median: $${median.toFixed(2)}\n`; markdown += `| range | count |\n`; markdown += `|-------|-------|\n`; for (let i = 0; i < distribution.length; i++) { if (distribution[i] > 0) { const rangeStart = (min + i * bucketSize).toFixed(2); const rangeEnd = (min + (i + 1) * bucketSize).toFixed(2); markdown += `| ${rangeStart}~${rangeEnd} | ${distribution[i]} |\n`; } } return { content: [ { type: "text", text: markdown, }, ], }; } ); // Start blockchain listener async function startListener() { const provider = new ethers.WebSocketProvider(WS_ENDPOINT); const contract = new ethers.Contract(BINANCE_DEX_ROUTER, ORDER_ABI, provider); contract.on( "OrderRecord", (inputToken, outputToken, sender, inputAmount, outputAmount) => { // Use RPC_ENDPOINT for token info queries in processLog const queryProvider = new ethers.JsonRpcProvider(RPC_ENDPOINT); processLog({ inputToken, outputToken, sender, inputAmount: inputAmount.toString(), outputAmount: outputAmount.toString(), }, queryProvider).catch(console.error); } ); } // Start server and listener async function main() { await startListener(); const transport = new StdioServerTransport(); await server.connect(transport); } main().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kukapay/binance-alpha-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server