UNPKG

arbitrum-mcp-tools

Version:

A comprehensive collection of Model Context Protocol (MCP) tools for interacting with the Arbitrum blockchain. Enables AI assistants like Claude, Cursor, Windsurf, VS Code, Gemini CLI, and OpenAI Codex to perform blockchain operations including account an

187 lines (186 loc) 8.94 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { z } from "zod"; import { formatEther } from "ethers"; import { handleError, formatTokenBalance } from "../common.js"; import { alchemy } from "../../server.js"; import { AssetTransfersCategory } from "alchemy-sdk"; export function registerBatchOperationsTools(server) { // 1. Bulk Balances server.tool("getBatchBalances", "Get balances for multiple addresses in single call", { addresses: z.array(z.string()).describe("Array of addresses to check"), tokenAddress: z .string() .optional() .describe("ERC-20 token address (optional)"), }, (_a) => __awaiter(this, [_a], void 0, function* ({ addresses, tokenAddress }) { try { const balances = yield Promise.all(addresses.map((address) => __awaiter(this, void 0, void 0, function* () { if (tokenAddress) { // Fetch balance for a specific ERC-20 token const tokenBalancesResponse = yield alchemy.core.getTokenBalances(address, [tokenAddress]); const specificTokenBalance = tokenBalancesResponse.tokenBalances.find((tb) => tb.contractAddress.toLowerCase() === tokenAddress.toLowerCase()); const metadata = yield alchemy.core.getTokenMetadata(tokenAddress); const name = metadata.name || "Unknown Token"; const symbol = metadata.symbol || "???"; if (specificTokenBalance && specificTokenBalance.tokenBalance) { const formattedBalance = formatTokenBalance(BigInt(specificTokenBalance.tokenBalance), metadata.decimals || 18); return `${address}: ${formattedBalance} ${symbol} (${name})`; } else { return `${address}: 0 ${symbol} (${name}) (or no balance found for this token)`; } } // Fetch native ETH balance const balance = yield alchemy.core.getBalance(address); return `${address}: ${formatEther(balance.toString())} ETH`; }))); return { content: [ { type: "text", text: `Balances:\n${balances.join("\n")}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error: ${handleError(error)}`, }, ], }; } })); // 2. Multi-Address Analysis server.tool("multiAddressAnalysis", "Compare token holdings and transactions across multiple addresses", { addresses: z.array(z.string()).describe("Array of addresses to analyze"), includeNfts: z.boolean().optional().describe("Include NFTs in analysis"), includeTransactions: z .boolean() .optional() .describe("Include transactions in analysis"), }, (_a) => __awaiter(this, [_a], void 0, function* ({ addresses, includeNfts, includeTransactions }) { try { // Get balances for all addresses const balancePromises = addresses.map((address) => __awaiter(this, void 0, void 0, function* () { const ethBalance = yield alchemy.core.getBalance(address); const tokenBalances = yield alchemy.core.getTokenBalances(address); // Format the balances const formattedEthBalance = formatEther(ethBalance.toString()); const tokens = yield Promise.all(tokenBalances.tokenBalances.map((token) => __awaiter(this, void 0, void 0, function* () { try { const metadata = yield alchemy.core.getTokenMetadata(token.contractAddress); return { name: metadata.name || "Unknown", symbol: metadata.symbol || "???", balance: token.tokenBalance || "0", }; } catch (metadataErr) { // If fetching metadata fails (e.g. unknown contract), fall back to generic values return { name: "Unknown Token", symbol: "???", balance: token.tokenBalance || "0", // Expose contract address for reference/debugging contractAddress: token.contractAddress, error: metadataErr, }; } }))); return { address, ethBalance: formattedEthBalance, tokens, }; })); // Add NFT data if requested const nftPromises = includeNfts ? addresses.map((address) => __awaiter(this, void 0, void 0, function* () { const nfts = yield alchemy.nft.getNftsForOwner(address, { pageSize: 50, }); return { address, nftCount: nfts.totalCount, collections: nfts.ownedNfts.reduce((acc, nft) => { const collectionName = nft.contract.name || "Unknown Collection"; acc[collectionName] = (acc[collectionName] || 0) + 1; return acc; }, {}), }; })) : []; // Add transaction data if requested const txPromises = includeTransactions ? addresses.map((address) => __awaiter(this, void 0, void 0, function* () { const transactions = yield alchemy.core.getAssetTransfers({ fromBlock: "0x0", fromAddress: address, category: [ AssetTransfersCategory.EXTERNAL, AssetTransfersCategory.ERC20, AssetTransfersCategory.ERC721, AssetTransfersCategory.ERC1155, ], }); return { address, txCount: transactions.transfers.length, }; })) : []; // Wait for all promises to resolve const [balances, nfts, txs] = yield Promise.all([ Promise.all(balancePromises), Promise.all(nftPromises), Promise.all(txPromises), ]); // Format the output let output = "Multi-address analysis:\n\n"; balances.forEach((balance) => { output += `Address: ${balance.address}\n`; output += `ETH Balance: ${balance.ethBalance} ETH\n`; output += `Token Count: ${balance.tokens.length}\n`; if (includeNfts) { const addressNfts = nfts.find((n) => n.address === balance.address); if (addressNfts) { output += `NFT Count: ${addressNfts.nftCount}\n`; } } if (includeTransactions) { const addressTxs = txs.find((t) => t.address === balance.address); if (addressTxs) { output += `Transaction Count: ${addressTxs.txCount}\n`; } } output += "---\n"; }); return { content: [{ type: "text", text: output }], }; } catch (error) { return { content: [ { type: "text", text: `Analysis failed: ${handleError(error)}`, }, ], }; } })); }