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, and Windsurf to perform blockchain operations including account analysis, contract interaction, cross
174 lines (173 loc) • 8.29 kB
JavaScript
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 "../../index.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* () {
const metadata = yield alchemy.core.getTokenMetadata(token.contractAddress);
return {
name: metadata.name || "Unknown",
symbol: metadata.symbol || "???",
balance: token.tokenBalance || "0",
};
})));
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)}`,
},
],
};
}
}));
}