getNFTInfo
Retrieve ERC-721 NFT holdings and tokenURIs for a wallet address from a specific smart contract on EVM chains like Ethereum, Polygon, and Arbitrum.
Instructions
특정 컨트랙트에서 지갑 주소가 보유한 ERC-721 NFT 목록과 tokenURI를 조회합니다
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | 조회할 지갑 주소 (0x...) | |
| chain | No | EVM 체인 | ethereum |
| contractAddress | No | 특정 NFT 컨트랙트 주소 (미지정 시 오류 반환) |
Implementation Reference
- src/tools/getNFTInfo.ts:64-164 (handler)The handler function implements the logic for fetching NFT information by checking balance, token IDs via enumeration, and tokenURIs via RPC.
async function handler(args: z.infer<typeof inputSchema>): Promise<ToolResult<NFTInfoData>> { const { address, chain, contractAddress } = args; // contractAddress 없이는 인덱서 없이 NFT 열거 불가 if (!contractAddress) { return makeError( "contractAddress is required. Enumerating all NFTs without an indexer is not supported.", "INVALID_INPUT", ); } const cacheKey = `nftinfo:${chain}:${contractAddress.toLowerCase()}:${address.toLowerCase()}`; const cached = cache.get<NFTInfoData>(cacheKey); if (cached.hit) return makeSuccess(chain, cached.data, true); try { const client = getClient(chain); const ownerAddr = address as `0x${string}`; const contractAddr = contractAddress as `0x${string}`; // 1단계: balanceOf로 보유 수량 확인 const balance = await client.readContract({ address: contractAddr, abi: ERC721_ABI, functionName: "balanceOf", args: [ownerAddr], }) as bigint; const tokenCount = Number(balance); // 보유 토큰 없으면 빈 배열 반환 if (tokenCount === 0) { const data: NFTInfoData = { owner: address, contractAddress, totalOwned: 0, tokens: [], }; cache.set(cacheKey, data, NFT_INFO_CACHE_TTL); return makeSuccess(chain, data, false); } // 최대 MAX_TOKENS개까지만 조회 const fetchCount = Math.min(tokenCount, MAX_TOKENS); // 2단계: tokenOfOwnerByIndex로 각 인덱스의 tokenId 조회 const tokenIdResults = await Promise.allSettled( Array.from({ length: fetchCount }, (_, i) => client.readContract({ address: contractAddr, abi: ERC721_ABI, functionName: "tokenOfOwnerByIndex", args: [ownerAddr, BigInt(i)], }), ), ); // 성공한 tokenId 목록 수집 const tokenIds: bigint[] = []; for (const result of tokenIdResults) { if (result.status === "fulfilled") { tokenIds.push(result.value as bigint); } } // 3단계: 각 tokenId에 대해 tokenURI 조회 const tokenURIResults = await Promise.allSettled( tokenIds.map((tokenId) => client.readContract({ address: contractAddr, abi: ERC721_ABI, functionName: "tokenURI", args: [tokenId], }), ), ); // NFTItem 배열 구성 const tokens: NFTItem[] = tokenIds.map((tokenId, i) => { const uriResult = tokenURIResults[i]; return { tokenId: tokenId.toString(), tokenURI: uriResult.status === "fulfilled" ? (uriResult.value as string) : "", contractAddress, }; }); const data: NFTInfoData = { owner: address, contractAddress, totalOwned: tokenCount, tokens, }; cache.set(cacheKey, data, NFT_INFO_CACHE_TTL); return makeSuccess(chain, data, false); } catch (err) { const message = sanitizeError(err); return makeError(`Failed to fetch NFT info: ${message}`, "RPC_ERROR"); } } - src/tools/getNFTInfo.ts:58-62 (schema)Zod schema definition for validating the input arguments to the getNFTInfo tool.
const inputSchema = z.object({ address: z.string().describe("조회할 지갑 주소 (0x...)"), chain: z.enum(SUPPORTED_CHAINS).default("ethereum").describe("EVM 체인"), contractAddress: z.string().optional().describe("특정 NFT 컨트랙트 주소 (미지정 시 오류 반환)"), }); - src/tools/getNFTInfo.ts:166-176 (registration)The register function adds the getNFTInfo tool to the MCP server.
export function register(server: McpServer) { server.tool( "getNFTInfo", "특정 컨트랙트에서 지갑 주소가 보유한 ERC-721 NFT 목록과 tokenURI를 조회합니다", inputSchema.shape, async (args) => { const result = await handler(args as z.infer<typeof inputSchema>); return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] }; }, ); }