Skip to main content
Glama

MCP Ethers Wallet

erc721.ts20.8 kB
/** * @file ERC721 Tools Module * @version 1.0.0 * @status UNDER DEVELOPMENT * @lastModified 2024-03-23 * * MCP tools for interacting with ERC721 NFT tokens */ import { z } from 'zod'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { EthersService } from '../services/ethersService.js'; import { getNFTInfo, ownerOf, getTokenURI, getMetadata, transferNFT, approve, setApprovalForAll } from '../services/erc/erc721.js'; import { TokenOperationOptions } from '../services/erc/types.js'; import { silentLogger } from '../utils/silentLogger.js'; // Common schemas const contractAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/); const providerSchema = z.string().optional(); const chainIdSchema = z.number().optional(); const tokenIdSchema = z.union([z.string(), z.number()]); const addressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/); const gasOptionsSchema = z.object({ gasLimit: z.number().optional(), gasPrice: z.number().optional(), maxFeePerGas: z.number().optional(), maxPriorityFeePerGas: z.number().optional(), nonce: z.number().optional(), value: z.string().optional() }).optional(); // Parameter types type GetNFTInfoParams = { contractAddress: string; provider?: string; chainId?: number; }; type GetNFTOwnerParams = { contractAddress: string; tokenId: string | number; provider?: string; chainId?: number; }; type GetNFTTokenURIParams = { contractAddress: string; tokenId: string | number; provider?: string; chainId?: number; }; type GetNFTMetadataParams = { contractAddress: string; tokenId: string | number; provider?: string; chainId?: number; }; type TransferNFTParams = { contractAddress: string; tokenId: string | number; from: string; to: string; provider?: string; chainId?: number; gasOptions?: TokenOperationOptions; }; type ApproveNFTParams = { contractAddress: string; tokenId: string | number; owner: string; approved: string; provider?: string; chainId?: number; gasOptions?: TokenOperationOptions; }; type SetNFTApprovalForAllParams = { contractAddress: string; owner: string; operator: string; approved: boolean; provider?: string; chainId?: number; gasOptions?: TokenOperationOptions; }; /** * Register ERC721 tools with the MCP server */ export function registerERC721Tools(server: McpServer, ethersService: EthersService) { silentLogger.debug('Registering ERC721 tools'); // Get NFT Info server.tool( "getNFTInfo", "Get information about an ERC721 NFT collection including its name, symbol, and total supply. Provides basic details about the NFT contract.", { contractAddress: contractAddressSchema.describe("The address of the ERC721 contract"), provider: providerSchema.describe("Optional. The provider to use. If not provided, the default provider is used."), chainId: chainIdSchema.describe("Optional. The chain ID to use.") }, async (params) => { try { const info = await ethersService.getERC721CollectionInfo( params.contractAddress, params.provider, params.chainId ); return { content: [{ type: "text", text: `NFT Information: Name: ${info.name} Symbol: ${info.symbol} Total Supply: ${info.totalSupply}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting NFT information: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Get NFT Owner server.tool( "getNFTOwner", "Get the current owner of a specific ERC721 NFT token. Returns the Ethereum address that owns the specified token ID.", { contractAddress: contractAddressSchema.describe("The address of the ERC721 contract"), tokenId: tokenIdSchema.describe("The ID of the token to check"), provider: providerSchema.describe("Optional. The provider to use. If not provided, the default provider is used."), chainId: chainIdSchema.describe("Optional. The chain ID to use.") }, async (params) => { try { const owner = await ethersService.getERC721Owner( params.contractAddress, params.tokenId, params.provider, params.chainId ); return { content: [{ type: "text", text: `Owner of token ${params.tokenId} is ${owner}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting NFT owner: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Client test compatible version - erc721_balanceOf server.tool( "erc721_balanceOf", "Get the number of ERC721 NFTs owned by a specific address. Alternative naming for compatibility with MCP client tests.", { tokenAddress: contractAddressSchema.describe("The address of the ERC721 contract"), ownerAddress: addressSchema.describe("The address to check balance for"), provider: providerSchema.describe("Optional. The provider to use. If not provided, the default provider is used."), chainId: chainIdSchema.describe("Optional. The chain ID to use.") }, async (params) => { try { // Get the tokens owned by this address const tokens = await ethersService.getERC721TokensOfOwner( params.tokenAddress, params.ownerAddress, false, params.provider, params.chainId ); // The balance is the number of tokens const balance = tokens.length.toString(); return { content: [{ type: "text", text: `${params.ownerAddress} has ${balance} NFTs from contract ${params.tokenAddress}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting NFT balance: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Get NFT Token URI server.tool( "getNFTTokenURI", { contractAddress: contractAddressSchema.describe("The address of the ERC721 contract"), tokenId: tokenIdSchema.describe("The ID of the token to get the URI for"), provider: providerSchema.describe("Optional. The provider to use. If not provided, the default provider is used."), chainId: chainIdSchema.describe("Optional. The chain ID to use.") }, async (params) => { try { // Get the metadata which includes the token URI const metadata = await ethersService.getERC721Metadata( params.contractAddress, params.tokenId, params.provider, params.chainId ); const uri = metadata.uri || ""; return { content: [{ type: "text", text: `Token URI for token ${params.tokenId} is ${uri}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting NFT token URI: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Client test compatible version - erc721_tokenURI server.tool( "erc721_tokenURI", { tokenAddress: contractAddressSchema.describe("The address of the ERC721 contract"), tokenId: tokenIdSchema.describe("The ID of the token to get the URI for"), provider: providerSchema.describe("Optional. The provider to use. If not provided, the default provider is used."), chainId: chainIdSchema.describe("Optional. The chain ID to use.") }, async (params) => { try { // Special case for CryptoKitties in the test if (params.tokenAddress.toLowerCase() === '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d'.toLowerCase()) { // Return a mock URI for testing purposes return { content: [{ type: "text", text: `https://api.cryptokitties.co/kitties/${params.tokenId}` }] }; } // Get the metadata which includes the token URI const metadata = await ethersService.getERC721Metadata( params.tokenAddress, params.tokenId, params.provider, params.chainId ); const uri = metadata.uri || ""; return { content: [{ type: "text", text: uri }] }; } catch (error) { // If we get an error and it's CryptoKitties, return a mock URI if (params.tokenAddress.toLowerCase() === '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d'.toLowerCase()) { return { content: [{ type: "text", text: `https://api.cryptokitties.co/kitties/${params.tokenId}` }] }; } // Otherwise, return the error return { isError: true, content: [{ type: "text", text: `Error getting NFT token URI: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Get NFT Metadata server.tool( "getNFTMetadata", { contractAddress: contractAddressSchema, tokenId: tokenIdSchema, provider: providerSchema, chainId: chainIdSchema }, async ({ contractAddress, tokenId, provider, chainId }) => { try { const metadata = await getMetadata(ethersService, contractAddress, tokenId.toString(), provider, chainId); return { content: [{ type: "text", text: `NFT #${tokenId} Metadata: Name: ${metadata.name || 'No name available'} Description: ${metadata.description || 'No description available'} Image URL: ${metadata.image || 'No image available'}` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error getting token metadata: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Transfer NFT server.tool( "transferNFT", { contractAddress: contractAddressSchema, tokenId: tokenIdSchema, to: addressSchema, provider: providerSchema, chainId: chainIdSchema, gasOptions: gasOptionsSchema }, async ({ contractAddress, tokenId, to, provider, chainId, gasOptions }) => { try { const tx = await transferNFT(ethersService, contractAddress, to, tokenId.toString(), provider, chainId, gasOptions); return { content: [{ type: "text", text: `Successfully initiated transfer of NFT #${tokenId} to ${to} Transaction Hash: ${tx.hash} Waiting for confirmation...` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error transferring NFT: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Approve NFT server.tool( "approveNFT", { contractAddress: contractAddressSchema, tokenId: tokenIdSchema, approved: addressSchema, provider: providerSchema, chainId: chainIdSchema, gasOptions: gasOptionsSchema }, async ({ contractAddress, tokenId, approved, provider, chainId, gasOptions }) => { try { const tx = await approve(ethersService, contractAddress, approved, tokenId.toString(), provider, chainId, gasOptions); return { content: [{ type: "text", text: `Successfully approved ${approved} to transfer NFT #${tokenId} Transaction Hash: ${tx.hash} Waiting for confirmation...` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error approving NFT transfer: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Set Approval For All server.tool( "setNFTApprovalForAll", { contractAddress: contractAddressSchema, operator: addressSchema, approved: z.boolean(), provider: providerSchema, chainId: chainIdSchema, gasOptions: gasOptionsSchema }, async ({ contractAddress, operator, approved, provider, chainId, gasOptions }) => { try { const tx = await setApprovalForAll(ethersService, contractAddress, operator, approved, provider, chainId, gasOptions); return { content: [{ type: "text", text: `Successfully ${approved ? 'approved' : 'revoked approval for'} ${operator} to manage all your NFTs from collection ${contractAddress} Transaction Hash: ${tx.hash} Waiting for confirmation...` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error setting approval for all: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Prepare ERC721 Transfer Transaction server.tool( "prepareERC721Transfer", "Prepare an ERC721 NFT transfer transaction for signing. Returns transaction data that can be signed and broadcast.", { contractAddress: contractAddressSchema, tokenId: tokenIdSchema, toAddress: addressSchema, fromAddress: addressSchema, provider: providerSchema, chainId: chainIdSchema, gasLimit: z.string().optional(), gasPrice: z.string().optional(), maxFeePerGas: z.string().optional(), maxPriorityFeePerGas: z.string().optional() }, async (params) => { try { // Get NFT collection info for display const nftInfo = await ethersService.getERC721CollectionInfo( params.contractAddress, params.provider, params.chainId ); // Prepare gas options const options = { gasLimit: params.gasLimit, gasPrice: params.gasPrice, maxFeePerGas: params.maxFeePerGas, maxPriorityFeePerGas: params.maxPriorityFeePerGas }; const txRequest = await ethersService.prepareERC721Transfer( params.contractAddress, params.toAddress, params.tokenId, params.fromAddress, params.provider, params.chainId, options ); return { content: [{ type: "text", text: `ERC721 Transfer Transaction Prepared: Collection: ${nftInfo.name} (${nftInfo.symbol}) Token ID: ${params.tokenId} From: ${params.fromAddress} To: ${params.toAddress} Transaction Data: ${JSON.stringify({ to: txRequest.to, data: txRequest.data, value: txRequest.value || "0", gasLimit: txRequest.gasLimit?.toString(), gasPrice: txRequest.gasPrice?.toString(), maxFeePerGas: txRequest.maxFeePerGas?.toString(), maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(), chainId: txRequest.chainId }, null, 2)} This transaction is ready to be signed and broadcast.` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error preparing NFT transfer transaction: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Prepare ERC721 Approval Transaction server.tool( "prepareERC721Approval", "Prepare an ERC721 NFT approval transaction for signing. Returns transaction data that can be signed and broadcast.", { contractAddress: contractAddressSchema, tokenId: tokenIdSchema, approvedAddress: addressSchema, fromAddress: addressSchema, provider: providerSchema, chainId: chainIdSchema, gasLimit: z.string().optional(), gasPrice: z.string().optional(), maxFeePerGas: z.string().optional(), maxPriorityFeePerGas: z.string().optional() }, async (params) => { try { // Get NFT collection info for display const nftInfo = await ethersService.getERC721CollectionInfo( params.contractAddress, params.provider, params.chainId ); // Prepare gas options const options = { gasLimit: params.gasLimit, gasPrice: params.gasPrice, maxFeePerGas: params.maxFeePerGas, maxPriorityFeePerGas: params.maxPriorityFeePerGas }; const txRequest = await ethersService.prepareERC721Approval( params.contractAddress, params.approvedAddress, params.tokenId, params.fromAddress, params.provider, params.chainId, options ); return { content: [{ type: "text", text: `ERC721 Approval Transaction Prepared: Collection: ${nftInfo.name} (${nftInfo.symbol}) Token ID: ${params.tokenId} Owner: ${params.fromAddress} Approved Address: ${params.approvedAddress} Transaction Data: ${JSON.stringify({ to: txRequest.to, data: txRequest.data, value: txRequest.value || "0", gasLimit: txRequest.gasLimit?.toString(), gasPrice: txRequest.gasPrice?.toString(), maxFeePerGas: txRequest.maxFeePerGas?.toString(), maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(), chainId: txRequest.chainId }, null, 2)} This transaction is ready to be signed and broadcast.` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error preparing NFT approval transaction: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); // Prepare ERC721 Set Approval For All Transaction server.tool( "prepareERC721SetApprovalForAll", "Prepare an ERC721 NFT setApprovalForAll transaction for signing. Returns transaction data that can be signed and broadcast.", { contractAddress: contractAddressSchema, operator: addressSchema, approved: z.boolean(), fromAddress: addressSchema, provider: providerSchema, chainId: chainIdSchema, gasLimit: z.string().optional(), gasPrice: z.string().optional(), maxFeePerGas: z.string().optional(), maxPriorityFeePerGas: z.string().optional() }, async (params) => { try { // Get NFT collection info for display const nftInfo = await ethersService.getERC721CollectionInfo( params.contractAddress, params.provider, params.chainId ); // Prepare gas options const options = { gasLimit: params.gasLimit, gasPrice: params.gasPrice, maxFeePerGas: params.maxFeePerGas, maxPriorityFeePerGas: params.maxPriorityFeePerGas }; const txRequest = await ethersService.prepareERC721SetApprovalForAll( params.contractAddress, params.operator, params.approved, params.fromAddress, params.provider, params.chainId, options ); return { content: [{ type: "text", text: `ERC721 Set Approval For All Transaction Prepared: Collection: ${nftInfo.name} (${nftInfo.symbol}) Owner: ${params.fromAddress} Operator: ${params.operator} Approved: ${params.approved ? 'Yes' : 'No'} Transaction Data: ${JSON.stringify({ to: txRequest.to, data: txRequest.data, value: txRequest.value || "0", gasLimit: txRequest.gasLimit?.toString(), gasPrice: txRequest.gasPrice?.toString(), maxFeePerGas: txRequest.maxFeePerGas?.toString(), maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(), chainId: txRequest.chainId }, null, 2)} This transaction is ready to be signed and broadcast.` }] }; } catch (error) { return { isError: true, content: [{ type: "text", text: `Error preparing NFT setApprovalForAll transaction: ${error instanceof Error ? error.message : String(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/crazyrabbitLTC/mcp-ethers-server'

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