get_sip009_token_info
Retrieve comprehensive SIP-009 NFT details including ownership, metadata URI, and content for specific token IDs on the Stacks blockchain network.
Instructions
Get complete information about a specific SIP-009 NFT including owner, metadata URI, and actual metadata content.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| contractAddress | Yes | The contract address | |
| contractName | Yes | The contract name of the SIP-009 NFT collection | |
| network | Yes | The Stacks network | |
| tokenId | Yes | The NFT token ID |
Implementation Reference
- The main execution handler for the 'get_sip009_token_info' tool. Fetches NFT owner and token URI using parallel Hiro API read-only contract calls, parses results, fetches and displays metadata, and returns formatted information.export const getSIP009TokenInfoTool: Tool<undefined, typeof SIP009TokenInfoScheme> = { name: "get_sip009_token_info", description: "Get complete information about a specific SIP-009 NFT including owner, metadata URI, and actual metadata content.", parameters: SIP009TokenInfoScheme, execute: async (args, context) => { try { await recordTelemetry({ action: "get_sip009_token_info" }, context); // Get NFT information in parallel const [ownerResult, uriResult] = await Promise.all([ callReadOnlyFunction( args.contractAddress, args.contractName, "get-owner", [`0x${args.tokenId.toString(16).padStart(32, '0')}`], args.network ), callReadOnlyFunction( args.contractAddress, args.contractName, "get-token-uri", [`0x${args.tokenId.toString(16).padStart(32, '0')}`], args.network ), ]); // Parse results const owner = ownerResult.okay && ownerResult.result !== 'none' ? ownerResult.result.replace(/[()]/g, '').replace('some ', '') : 'No owner (token may not exist)'; const metadataUri = uriResult.okay && uriResult.result !== 'none' ? uriResult.result.replace(/[()]/g, '').replace('some ', '').replace(/"/g, '') : null; let metadata = null; if (metadataUri) { metadata = await fetchMetadata(metadataUri); } return `# SIP-009 NFT Information **Contract**: ${args.contractAddress}.${args.contractName} **Token ID**: ${args.tokenId} **Network**: ${args.network} ## Ownership - **Current Owner**: ${owner} ## Metadata - **URI**: ${metadataUri || 'No metadata URI'} ${metadata ? ` ## Metadata Content - **Name**: ${metadata.name || 'N/A'} - **Description**: ${metadata.description || 'N/A'} - **Image**: ${metadata.image || 'N/A'} - **External URL**: ${metadata.external_url || 'N/A'} ${metadata.attributes ? ` ### Attributes ${metadata.attributes.map((attr: any) => `- **${attr.trait_type}**: ${attr.value}`).join('\n')} ` : ''} ` : metadata === null && metadataUri ? ` ⚠️ **Metadata fetch failed** - URI may be inaccessible ` : ''} ## Contract Verification ✅ This contract implements the SIP-009 NFT standard trait. ## Transfer Notes - Post-conditions are REQUIRED for all transfers - Only the current owner can initiate transfers - Use native Stacks post-conditions for security`; } catch (error) { return `❌ Failed to get SIP-009 NFT info: ${error}`; } }, };
- Input schema (Zod) defining required parameters: contractAddress, contractName, tokenId, and network (mainnet/testnet/devnet).const SIP009TokenInfoScheme = z.object({ contractAddress: z.string().describe("The contract address"), contractName: z.string().describe("The contract name of the SIP-009 NFT collection"), tokenId: z.number().describe("The NFT token ID"), network: NetworkScheme.describe("The Stacks network"), });
- src/tools/index.ts:54-54 (registration)Registers the getSIP009TokenInfoTool with the FastMCP server instance.server.addTool(getSIP009TokenInfoTool);
- Helper function to perform read-only contract function calls via Hiro API (stacks-blockchain explorer). Used to fetch 'get-owner' and 'get-token-uri'.async function callReadOnlyFunction( contractAddress: string, contractName: string, functionName: string, functionArgs: any[], network: string ): Promise<any> { const apiUrl = network === "mainnet" ? "https://api.hiro.so" : "https://api.testnet.hiro.so"; try { const response = await fetch( `${apiUrl}/v2/contracts/call-read/${contractAddress}/${contractName}/${functionName}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sender: contractAddress, arguments: functionArgs, }), } ); if (!response.ok) { const data: any = await response.json(); throw new Error(data.error || `HTTP ${response.status}`); } return await response.json(); } catch (error) { throw new Error(`Failed to call ${functionName}: ${error}`); } }
- Helper function to fetch and parse NFT metadata JSON from the token URI, with IPFS support and 5-second timeout.async function fetchMetadata(uri: string): Promise<any> { try { // Handle IPFS URIs if (uri.startsWith('ipfs://')) { uri = uri.replace('ipfs://', 'https://ipfs.io/ipfs/'); } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const response = await fetch(uri, { signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { return null; } }