Skip to main content
Glama
tools.ts64.5 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { getSupportedNetworks, getRpcUrl } from './chains.js'; import * as services from './services/index.js'; import { type Address, type Hex, type Hash, zeroAddress, parseEther } from 'viem'; import { normalize } from 'viem/ens'; import { registerWalletTools } from './wallet-tools.js'; import { routerService } from './services/router.js'; import { LOCK_CONTRACT_ADDRESS } from './constants/contract.js'; /** * Register all EVM-related tools with the MCP server * * All tools that accept Ethereum addresses also support ENS names (e.g., 'vitalik.eth'). * ENS names are automatically resolved to addresses using the Ethereum Name Service. * * @param server The MCP server instance */ export function registerEVMTools(server: McpServer) { // Register wallet tools registerWalletTools(server); // NETWORK INFORMATION TOOLS // Get chain information server.tool( 'get_chain_info', 'Get information about an EVM network', { network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ network = 'ethereum' }) => { try { const chainId = await services.getChainId(network); const blockNumber = await services.getBlockNumber(network); const rpcUrl = getRpcUrl(network); return { content: [ { type: 'text', text: JSON.stringify( { network, chainId, blockNumber: blockNumber.toString(), rpcUrl }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching chain info: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // ENS LOOKUP TOOL // Resolve ENS name to address server.tool( 'resolve_ens', 'Resolve an ENS name to an Ethereum address', { ensName: z.string().describe("ENS name to resolve (e.g., 'vitalik.eth')"), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. ENS resolution works best on Ethereum mainnet. Defaults to Ethereum mainnet." ) }, async ({ ensName, network = 'ethereum' }) => { try { // Validate that the input is an ENS name if (!ensName.includes('.')) { return { content: [ { type: 'text', text: `Error: Input "${ensName}" is not a valid ENS name. ENS names must contain a dot (e.g., 'name.eth').` } ], isError: true }; } // Normalize the ENS name const normalizedEns = normalize(ensName); // Resolve the ENS name to an address const address = await services.resolveAddress(ensName, network); return { content: [ { type: 'text', text: JSON.stringify( { ensName: ensName, normalizedName: normalizedEns, resolvedAddress: address, network }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error resolving ENS name: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get supported networks server.tool( 'get_supported_networks', 'Get a list of supported EVM networks', {}, async () => { try { const networks = getSupportedNetworks(); return { content: [ { type: 'text', text: JSON.stringify( { supportedNetworks: networks }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching supported networks: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // BLOCK TOOLS // Get block by number server.tool( 'get_block_by_number', 'Get a block by its block number', { blockNumber: z.number().describe('The block number to fetch'), network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ blockNumber, network = 'ethereum' }) => { try { const block = await services.getBlockByNumber(blockNumber, network); return { content: [ { type: 'text', text: services.helpers.formatJson(block) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching block ${blockNumber}: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get latest block server.tool( 'get_latest_block', 'Get the latest block from the EVM', { network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ network = 'ethereum' }) => { try { const block = await services.getLatestBlock(network); return { content: [ { type: 'text', text: services.helpers.formatJson(block) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching latest block: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // BALANCE TOOLS // Get ETH balance server.tool( 'get_balance', 'Get the native token balance (ETH, MATIC, etc.) for an address', { address: z .string() .describe( "The wallet address or ENS name (e.g., '0x1234...' or 'vitalik.eth') to check the balance for" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ address, network = 'ethereum' }) => { try { const balance = await services.getETHBalance(address, network); return { content: [ { type: 'text', text: JSON.stringify( { address, network, wei: balance.wei.toString(), ether: balance.ether }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching balance: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get ERC20 balance server.tool( 'get_erc20_balance', 'Get the ERC20 token balance of an Ethereum address', { address: z.string().describe('The Ethereum address to check'), tokenAddress: z.string().describe('The ERC20 token contract address'), network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ address, tokenAddress, network = 'ethereum' }) => { try { const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); return { content: [ { type: 'text', text: JSON.stringify( { address, tokenAddress, network, balance: { raw: balance.raw.toString(), formatted: balance.formatted, decimals: balance.token.decimals } }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching ERC20 balance for ${address}: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get ERC20 token balance server.tool( 'get_token_balance', 'Get the balance of an ERC20 token for an address', { tokenAddress: z .string() .describe( "The contract address or ENS name of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')" ), ownerAddress: z .string() .describe( "The wallet address or ENS name to check the balance for (e.g., '0x1234...' or 'vitalik.eth')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ tokenAddress, ownerAddress, network = 'ethereum' }) => { try { const balance = await services.getERC20Balance( tokenAddress, ownerAddress, network ); return { content: [ { type: 'text', text: JSON.stringify( { tokenAddress, owner: ownerAddress, network, raw: balance.raw.toString(), formatted: balance.formatted, symbol: balance.token.symbol, decimals: balance.token.decimals }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching token balance: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // TRANSACTION TOOLS // Get transaction by hash server.tool( 'get_transaction', 'Get detailed information about a specific transaction by its hash. Includes sender, recipient, value, data, and more.', { txHash: z .string() .describe("The transaction hash to look up (e.g., '0x1234...')"), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet." ) }, async ({ txHash, network = 'ethereum' }) => { try { const tx = await services.getTransaction(txHash as Hash, network); return { content: [ { type: 'text', text: services.helpers.formatJson(tx) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching transaction ${txHash}: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get transaction receipt server.tool( 'get_transaction_receipt', 'Get a transaction receipt by its hash', { txHash: z.string().describe('The transaction hash to look up'), network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ txHash, network = 'ethereum' }) => { try { const receipt = await services.getTransactionReceipt( txHash as Hash, network ); return { content: [ { type: 'text', text: services.helpers.formatJson(receipt) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching transaction receipt ${txHash}: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Estimate gas server.tool( 'estimate_gas', 'Estimate the gas cost for a transaction', { to: z.string().describe('The recipient address'), value: z .string() .optional() .describe("The amount of ETH to send in ether (e.g., '0.1')"), data: z .string() .optional() .describe('The transaction data as a hex string'), network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ to, value, data, network = 'ethereum' }) => { try { const params: any = { to: to as Address }; if (value) { params.value = services.helpers.parseEther(value); } if (data) { params.data = data as `0x${string}`; } const gas = await services.estimateGas(params, network); return { content: [ { type: 'text', text: JSON.stringify( { network, estimatedGas: gas.toString() }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error estimating gas: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // TRANSFER TOOLS // Transfer ETH server.tool( 'transfer_eth', 'Transfer native tokens (ETH, MATIC, etc.) to an address', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), to: z .string() .describe( "The recipient address or ENS name (e.g., '0x1234...' or 'vitalik.eth')" ), amount: z .string() .describe( "Amount to send in ETH (or the native token of the network), as a string (e.g., '0.1')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, to, amount, network = 'ethereum' }) => { try { const txHash = await services.transferETH( privateKey, to, amount, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash, to, amount, network }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring ETH: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Transfer ERC20 server.tool( 'transfer_erc20', 'Transfer ERC20 tokens to another address', { privateKey: z .string() .describe( 'Private key of the sending account (this is used for signing and is never stored)' ), tokenAddress: z .string() .describe('The address of the ERC20 token contract'), toAddress: z.string().describe('The recipient address'), amount: z .string() .describe( "The amount of tokens to send (in token units, e.g., '10' for 10 tokens)" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, tokenAddress, toAddress, amount, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.transferERC20( tokenAddress as Address, toAddress as Address, amount, formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, tokenAddress, recipient: toAddress, amount: result.amount.formatted, symbol: result.token.symbol }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring ERC20 tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Approve ERC20 token spending server.tool( 'approve_token_spending', 'Approve another address (like a DeFi protocol or exchange) to spend your ERC20 tokens. This is often required before interacting with DeFi protocols.', { privateKey: z .string() .describe( 'Private key of the token owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), tokenAddress: z .string() .describe( "The contract address of the ERC20 token to approve for spending (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC on Ethereum)" ), spenderAddress: z .string() .describe( 'The contract address being approved to spend your tokens (e.g., a DEX or lending protocol)' ), amount: z .string() .describe( "The amount of tokens to approve in token units, not wei (e.g., '1000' to approve spending 1000 tokens). Use a very large number for unlimited approval." ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet." ) }, async ({ privateKey, tokenAddress, spenderAddress, amount, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.approveERC20( tokenAddress as Address, spenderAddress as Address, amount, formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, tokenAddress, spender: spenderAddress, amount: result.amount.formatted, symbol: result.token.symbol }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error approving token spending: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Transfer NFT (ERC721) server.tool( 'transfer_nft', 'Transfer an NFT (ERC721 token) from one address to another. Requires the private key of the current owner for signing the transaction.', { privateKey: z .string() .describe( 'Private key of the NFT owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), tokenAddress: z .string() .describe( "The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)" ), tokenId: z .string() .describe("The ID of the specific NFT to transfer (e.g., '1234')"), toAddress: z .string() .describe('The recipient wallet address that will receive the NFT'), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default." ) }, async ({ privateKey, tokenAddress, tokenId, toAddress, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.transferERC721( tokenAddress as Address, toAddress as Address, BigInt(tokenId), formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, collection: tokenAddress, tokenId: result.tokenId, recipient: toAddress, name: result.token.name, symbol: result.token.symbol }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring NFT: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Transfer ERC1155 token server.tool( 'transfer_erc1155', 'Transfer ERC1155 tokens to another address. ERC1155 is a multi-token standard that can represent both fungible and non-fungible tokens in a single contract.', { privateKey: z .string() .describe( 'Private key of the token owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), tokenAddress: z .string() .describe( "The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')" ), tokenId: z .string() .describe("The ID of the specific token to transfer (e.g., '1234')"), amount: z .string() .describe( "The quantity of tokens to send (e.g., '1' for a single NFT or '10' for 10 fungible tokens)" ), toAddress: z .string() .describe('The recipient wallet address that will receive the tokens'), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, tokenAddress, tokenId, amount, toAddress, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.transferERC1155( tokenAddress as Address, toAddress as Address, BigInt(tokenId), amount, formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, contract: tokenAddress, tokenId: result.tokenId, amount: result.amount, recipient: toAddress }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring ERC1155 tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Transfer ERC20 tokens server.tool( 'transfer_token', 'Transfer ERC20 tokens to an address', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), tokenAddress: z .string() .describe( "The contract address or ENS name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')" ), toAddress: z .string() .describe( "The recipient address or ENS name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')" ), amount: z .string() .describe( "Amount of tokens to send as a string (e.g., '100' for 100 tokens). This will be adjusted for the token's decimals." ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, tokenAddress, toAddress, amount, network = 'ethereum' }) => { try { const result = await services.transferERC20( tokenAddress, toAddress, amount, privateKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, tokenAddress, toAddress, amount: result.amount.formatted, symbol: result.token.symbol, network }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // CONTRACT TOOLS // Read contract server.tool( 'read_contract', "Read data from a smart contract by calling a view/pure function. This doesn't modify blockchain state and doesn't require gas or signing.", { contractAddress: z .string() .describe('The address of the smart contract to interact with'), abi: z .array(z.any()) .describe( 'The ABI (Application Binary Interface) of the smart contract function, as a JSON array' ), functionName: z .string() .describe( "The name of the function to call on the contract (e.g., 'balanceOf')" ), args: z .array(z.any()) .optional() .describe( "The arguments to pass to the function, as an array (e.g., ['0x1234...'])" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet." ) }, async ({ contractAddress, abi, functionName, args = [], network = 'ethereum' }) => { try { // Parse ABI if it's a string const parsedAbi = typeof abi === 'string' ? JSON.parse(abi) : abi; const params = { address: contractAddress as Address, abi: parsedAbi, functionName, args }; const result = await services.readContract(params, network); return { content: [ { type: 'text', text: services.helpers.formatJson(result) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error reading contract: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Write to contract server.tool( 'write_contract', 'Write data to a smart contract by calling a state-changing function. This modifies blockchain state and requires gas payment and transaction signing.', { contractAddress: z .string() .describe('The address of the smart contract to interact with'), abi: z .array(z.any()) .describe( 'The ABI (Application Binary Interface) of the smart contract function, as a JSON array' ), functionName: z .string() .describe( "The name of the function to call on the contract (e.g., 'transfer')" ), args: z .array(z.any()) .describe( "The arguments to pass to the function, as an array (e.g., ['0x1234...', '1000000000000000000'])" ), privateKey: z .string() .describe( 'Private key of the sending account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet." ) }, async ({ contractAddress, abi, functionName, args, privateKey, network = 'ethereum' }) => { try { // Parse ABI if it's a string const parsedAbi = typeof abi === 'string' ? JSON.parse(abi) : abi; const contractParams: Record<string, any> = { address: contractAddress as Address, abi: parsedAbi, functionName, args }; const txHash = await services.writeContract( privateKey as Hex, contractParams, network ); return { content: [ { type: 'text', text: JSON.stringify( { network, transactionHash: txHash, message: 'Contract write transaction sent successfully' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error writing to contract: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Check if address is a contract server.tool( 'is_contract', 'Check if an address is a smart contract or an externally owned account (EOA)', { address: z .string() .describe( "The wallet or contract address or ENS name to check (e.g., '0x1234...' or 'uniswap.eth')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ address, network = 'ethereum' }) => { try { const isContract = await services.isContract(address, network); return { content: [ { type: 'text', text: JSON.stringify( { address, network, isContract, type: isContract ? 'Contract' : 'Externally Owned Account (EOA)' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error checking if address is a contract: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get ERC20 token information server.tool( 'get_token_info', 'Get comprehensive information about an ERC20 token including name, symbol, decimals, total supply, and other metadata. Use this to analyze any token on EVM chains.', { tokenAddress: z .string() .describe( "The contract address of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC on Ethereum)" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet." ) }, async ({ tokenAddress, network = 'ethereum' }) => { try { const tokenInfo = await services.getERC20TokenInfo( tokenAddress as Address, network ); return { content: [ { type: 'text', text: JSON.stringify( { address: tokenAddress, network, ...tokenInfo }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching token info: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get ERC20 token balance server.tool( 'get_token_balance_erc20', 'Get ERC20 token balance for an address', { address: z.string().describe('The address to check balance for'), tokenAddress: z.string().describe('The ERC20 token contract address'), network: z .string() .optional() .describe('Network name or chain ID. Defaults to Ethereum mainnet.') }, async ({ address, tokenAddress, network = 'ethereum' }) => { try { const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); return { content: [ { type: 'text', text: JSON.stringify( { address, tokenAddress, network, balance: { raw: balance.raw.toString(), formatted: balance.formatted, decimals: balance.token.decimals } }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching ERC20 balance for ${address}: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Get NFT (ERC721) information server.tool( 'get_nft_info', 'Get detailed information about a specific NFT (ERC721 token), including collection name, symbol, token URI, and current owner if available.', { tokenAddress: z .string() .describe( "The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)" ), tokenId: z .string() .describe("The ID of the specific NFT token to query (e.g., '1234')"), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default." ) }, async ({ tokenAddress, tokenId, network = 'ethereum' }) => { try { const nftInfo = await services.getERC721TokenMetadata( tokenAddress as Address, BigInt(tokenId), network ); // Check ownership separately let owner = null; try { // This may fail if tokenId doesn't exist owner = await services.getPublicClient(network).readContract({ address: tokenAddress as Address, abi: [ { inputs: [{ type: 'uint256' }], name: 'ownerOf', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' } ], functionName: 'ownerOf', args: [BigInt(tokenId)] }); } catch (e) { // Ownership info not available } return { content: [ { type: 'text', text: JSON.stringify( { contract: tokenAddress, tokenId, network, ...nftInfo, owner: owner || 'Unknown' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching NFT info: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Check NFT ownership server.tool( 'check_nft_ownership', 'Check if an address owns a specific NFT', { tokenAddress: z .string() .describe( "The contract address or ENS name of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for BAYC or 'boredapeyachtclub.eth')" ), tokenId: z.string().describe("The ID of the NFT to check (e.g., '1234')"), ownerAddress: z .string() .describe( "The wallet address or ENS name to check ownership against (e.g., '0x1234...' or 'vitalik.eth')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ tokenAddress, tokenId, ownerAddress, network = 'ethereum' }) => { try { const isOwner = await services.isNFTOwner( tokenAddress, ownerAddress, BigInt(tokenId), network ); return { content: [ { type: 'text', text: JSON.stringify( { tokenAddress, tokenId, ownerAddress, network, isOwner, result: isOwner ? 'Address owns this NFT' : 'Address does not own this NFT' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error checking NFT ownership: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Add tool for getting ERC1155 token URI server.tool( 'get_erc1155_token_uri', 'Get the metadata URI for an ERC1155 token (multi-token standard used for both fungible and non-fungible tokens). The URI typically points to JSON metadata about the token.', { tokenAddress: z .string() .describe( "The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')" ), tokenId: z .string() .describe( "The ID of the specific token to query metadata for (e.g., '1234')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet." ) }, async ({ tokenAddress, tokenId, network = 'ethereum' }) => { try { const uri = await services.getERC1155TokenURI( tokenAddress as Address, BigInt(tokenId), network ); return { content: [ { type: 'text', text: JSON.stringify( { contract: tokenAddress, tokenId, network, uri }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching ERC1155 token URI: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Add tool for getting ERC721 NFT balance server.tool( 'get_nft_balance', 'Get the total number of NFTs owned by an address from a specific collection. This returns the count of NFTs, not individual token IDs.', { tokenAddress: z .string() .describe( "The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)" ), ownerAddress: z .string() .describe( "The wallet address to check the NFT balance for (e.g., '0x1234...')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default." ) }, async ({ tokenAddress, ownerAddress, network = 'ethereum' }) => { try { const balance = await services.getERC721Balance( tokenAddress as Address, ownerAddress as Address, network ); return { content: [ { type: 'text', text: JSON.stringify( { collection: tokenAddress, owner: ownerAddress, network, balance: balance.toString() }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching NFT balance: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Add tool for getting ERC1155 token balance server.tool( 'get_erc1155_balance', 'Get the balance of a specific ERC1155 token ID owned by an address. ERC1155 allows multiple tokens of the same ID, so the balance can be greater than 1.', { tokenAddress: z .string() .describe( "The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')" ), tokenId: z .string() .describe( "The ID of the specific token to check the balance for (e.g., '1234')" ), ownerAddress: z .string() .describe( "The wallet address to check the token balance for (e.g., '0x1234...')" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet." ) }, async ({ tokenAddress, tokenId, ownerAddress, network = 'ethereum' }) => { try { const balance = await services.getERC1155Balance( tokenAddress as Address, ownerAddress as Address, BigInt(tokenId), network ); return { content: [ { type: 'text', text: JSON.stringify( { contract: tokenAddress, tokenId, owner: ownerAddress, network, balance: balance.toString() }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching ERC1155 token balance: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // WALLET TOOLS // Get address from private key server.tool( 'get_address_from_private_key', 'Get the EVM address derived from a private key', { privateKey: z .string() .describe( 'Private key in hex format (with or without 0x prefix). SECURITY: This is used only for address derivation and is not stored.' ) }, async ({ privateKey }) => { try { // Ensure the private key has 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as Hex) : (`0x${privateKey}` as Hex); const address = services.getAddressFromPrivateKey(formattedKey); return { content: [ { type: 'text', text: JSON.stringify( { address, privateKey: '0x' + privateKey.replace(/^0x/, '') }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error deriving address from private key: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Deploy a new ERC20 token server.tool( 'deploy_token', 'Deploy a new ERC20 token contract with the specified name and symbol', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), name: z.string().describe("The name of the token (e.g., 'My Token')"), symbol: z.string().describe("The symbol of the token (e.g., 'MTK')"), decimals: z .number() .optional() .describe('The number of decimals for the token (default: 18)'), totalSupply: z .number() .optional() .describe( 'The total supply of tokens in whole units (default: 100,000,000)' ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, name, symbol, decimals = 18, totalSupply = 100000000, network = 'bsc-testnet' }) => { try { const result = await services.deployERC20Token( privateKey as Hex, name, symbol, decimals, totalSupply, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, contractAddress: result.contractAddress, name, symbol, decimals, totalSupply, network }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error deploying token: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Deploy tax token server.tool( 'deploy_tax_token', 'Deploy a new ERC20 token with tax functionality on buy/sell transactions', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), name: z.string().describe("Token name (e.g., 'My Token')"), symbol: z.string().describe("Token symbol/ticker (e.g., 'MTK')"), dexRouter: z .string() .describe('DEX router contract address for liquidity pair creation'), developmentFund: z .string() .optional() .describe( 'Address that receives collected transaction fees (defaults to zero address)' ), percentageBuyFee: z .number() .optional() .describe( 'Fee percentage charged on buy transactions (e.g., 5 for 5%, default: 0)' ), percentageSellFee: z .number() .optional() .describe( 'Fee percentage charged on sell transactions (e.g., 5 for 5%, default: 0)' ), totalSupply: z .number() .optional() .describe('Total supply of tokens to mint (default: 100,000,000)'), network: z .string() .optional() .describe( "Network name (e.g., 'bsc', 'ethereum', 'optimism', 'arbitrum', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to BSC." ) }, async ({ privateKey, name, symbol, dexRouter, developmentFund, percentageBuyFee = 0, percentageSellFee = 0, totalSupply = 100000000, network = 'bsc' }) => { try { const result = await services.deployERC20TokenTax( privateKey as Hex, name, symbol, dexRouter as Address, developmentFund as Address, percentageBuyFee, percentageSellFee, totalSupply, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, network, transactionHash: result.txHash, contractAddress: result.contractAddress, name, symbol, dexRouter, developmentFund, buyFeePercentage: percentageBuyFee, sellFeePercentage: percentageSellFee, totalSupply }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error deploying tax token: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); server.tool( 'set_swap_and_liquify_enabled', 'Enable or disable swap and liquify functionality for a tax token', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), contractAddress: z .string() .describe('The address of the tax token contract'), enabled: z .boolean() .describe( 'Whether to enable (true) or disable (false) swap and liquify' ), network: z .string() .optional() .describe( "Network name (e.g., 'bsc', 'ethereum', etc.) or chain ID. Defaults to BSC." ) }, async ({ privateKey, contractAddress, enabled, network = 'bsc' }) => { try { const txHash = await services.setSwapAndLiquifyEnabled( privateKey as Hex, contractAddress, enabled, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, network, transactionHash: txHash, contractAddress, enabled }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error setting swap and liquify state: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); server.tool( 'enable_trading', 'Enable trading for a tax token contract with specified parameters', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), contractAddress: z .string() .describe('The address of the tax token contract'), maxHolder: z .string() .describe( 'Maximum amount of tokens a wallet can hold (in token units)' ), maxBuy: z .string() .describe( 'Maximum amount of tokens that can be bought in a single transaction (in token units)' ), swapAtAmount: z .string() .describe('Amount of tokens that triggers a swap (in token units)'), network: z .string() .optional() .describe( "Network name (e.g., 'bsc', 'ethereum', etc.) or chain ID. Defaults to BSC." ) }, async ({ privateKey, contractAddress, maxHolder, maxBuy, swapAtAmount, network = 'bsc' }) => { try { const txHash = await services.enableTrading( privateKey as Hex, contractAddress, maxHolder, maxBuy, swapAtAmount, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, network, transactionHash: txHash, contractAddress, maxHolder, maxBuy, swapAtAmount }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error enabling trading: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); server.tool( 'add_liquidity', 'Add ETH and token liquidity to create or increase a Uniswap/PancakeSwap liquidity pool position', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), tokenAddress: z .string() .describe('The ERC20 token contract address to pair with ETH'), amountToken: z .string() .describe('Amount of tokens to add as liquidity (in token units)'), amountETH: z .string() .describe('Amount of ETH to add as liquidity (in ETH units)'), dexRouter: z .string() .describe('DEX router contract address for liquidity pair creation'), slippage: z .number() .optional() .describe('Maximum allowed slippage percentage (default: 0.5)'), network: z .string() .optional() .describe( "Network name (e.g., 'bsc', etc.) or chain ID. Defaults to BSC." ) }, async ({ privateKey, tokenAddress, dexRouter, amountToken, amountETH, slippage = 0.5, network = 'bsc' }) => { try { // Get wallet private key const txHash = await routerService.addLiquidityETH( privateKey as Hex, tokenAddress, amountToken, amountETH, slippage, dexRouter, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, network, transactionHash: txHash, tokenAddress, amountToken, amountETH, message: 'Successfully added liquidity to PancakeSwap pool' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error adding liquidity: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); server.tool( 'lock_lp_token', 'Lock LP tokens in the UniswapV2Lock contract to demonstrate locked liquidity', { privateKey: z .string() .describe( 'Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored.' ), lpTokenAddress: z.string().describe('The LP token address to lock'), amount: z .string() .describe('Amount of LP tokens to lock (in LP token units)'), unlockDate: z .number() .describe('Unix timestamp when tokens can be unlocked'), network: z .string() .optional() .describe( "Network name (e.g., 'bsc', 'ethereum', etc.) or chain ID. Defaults to BSC." ) }, async ({ privateKey, lpTokenAddress, amount, unlockDate, network = 'bsc' }) => { try { // Get public address from private key const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const address = services.getAddressFromPrivateKey(formattedKey); // Prepare args for lockLPToken function const args = [ lpTokenAddress, amount, unlockDate, zeroAddress, // Referral address true, // Fee in ETH address // If no withdrawer specified, use sender address ]; const contractParams = { address: LOCK_CONTRACT_ADDRESS as Address, abi: [ { inputs: [ { internalType: 'address', name: '_lpToken', type: 'address' }, { internalType: 'uint256', name: '_amount', type: 'uint256' }, { internalType: 'uint256', name: '_unlock_date', type: 'uint256' }, { internalType: 'address payable', name: '_referral', type: 'address' }, { internalType: 'bool', name: '_fee_in_eth', type: 'bool' }, { internalType: 'address payable', name: '_withdrawer', type: 'address' } ], name: 'lockLPToken', outputs: [], stateMutability: 'payable', type: 'function' } ], functionName: 'lockLPToken', args, value: parseEther('0.2') // Send 0.2 ETH as fee }; const txHash = await services.writeContractWithStoredWallet( privateKey as Hex, contractParams, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, network, transactionHash: txHash, lpTokenAddress, amount, unlockDate, message: 'Successfully locked LP tokens in UniswapV2Lock contract' }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error locking LP tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); }

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/chulanpro5/evm-mcp-server'

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