generate_sip009_template
Create production-ready SIP-009 NFT smart contracts with security features, marketplace options, and metadata storage capabilities for Stacks blockchain development.
Instructions
Generate a complete, production-ready SIP-009 NFT contract template with all security features and optional marketplace functionality.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| baseUri | Yes | The base URI for metadata (e.g., 'https://api.mynft.com/metadata/' or 'ipfs://QmHash/') | |
| collectionName | Yes | The name of the NFT collection (e.g., 'My NFT Collection') | |
| collectionSymbol | Yes | The collection symbol (e.g., 'MNC') | |
| includeMarketplace | Yes | Whether to include basic marketplace functionality | |
| includeMetadataStorage | Yes | Whether to include on-chain metadata storage | |
| maxSupply | No | Optional maximum supply limit |
Implementation Reference
- The execute handler function that generates a complete production-ready SIP-009 NFT Clarity contract template based on input parameters, including optional marketplace and metadata features.execute: async (args, context) => { try { await recordTelemetry({ action: "generate_sip009_template" }, context); const contractName = args.collectionName.toLowerCase().replace(/[^a-z0-9]/g, '-'); return `# SIP-009 NFT Collection: ${args.collectionName} ## Contract Implementation \`\`\`clarity ;; ${args.collectionName} (${args.collectionSymbol}) ;; A complete SIP-009 NFT implementation with all security features ;; Define the NFT using native Clarity primitive (REQUIRED) (define-non-fungible-token ${contractName} uint) ;; Error constants (define-constant ERR-NOT-AUTHORIZED (err u1)) (define-constant ERR-NOT-FOUND (err u2)) (define-constant ERR-ALREADY-EXISTS (err u3)) (define-constant ERR-INVALID-USER (err u4)) ${args.maxSupply ? '(define-constant ERR-MAX-SUPPLY-REACHED (err u5))' : ''} ;; Collection constants (define-constant CONTRACT-OWNER tx-sender) (define-constant COLLECTION-NAME "${args.collectionName}") (define-constant COLLECTION-SYMBOL "${args.collectionSymbol}") (define-constant BASE-URI "${args.baseUri}") ${args.maxSupply ? `(define-constant MAX-SUPPLY u${args.maxSupply})` : ''} ;; Variables (define-data-var last-token-id uint u0) (define-data-var mint-enabled bool true) ${args.includeMetadataStorage ? ` ;; On-chain metadata storage (define-map token-metadata uint { name: (string-ascii 64), description: (string-ascii 256), image: (string-ascii 256) }) ` : ''} ${args.includeMarketplace ? ` ;; Basic marketplace functionality (define-map listings uint { seller: principal, price: uint, expires-at: uint }) ` : ''} ;; Implement the SIP-009 trait (impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait) ;; SIP-009 Required Functions (define-read-only (get-last-token-id) (ok (var-get last-token-id)) ) (define-read-only (get-token-uri (token-id uint)) (if (<= token-id (var-get last-token-id)) (ok (some (concat BASE-URI (uint-to-ascii token-id)))) (ok none) ) ) (define-read-only (get-owner (token-id uint)) (if (<= token-id (var-get last-token-id)) (ok (nft-get-owner? ${contractName} token-id)) (ok none) ) ) (define-public (transfer (token-id uint) (sender principal) (recipient principal)) (begin ;; Validate token exists (asserts! (<= token-id (var-get last-token-id)) ERR-NOT-FOUND) ;; Validate sender owns the token (asserts! (is-eq (some sender) (nft-get-owner? ${contractName} token-id)) ERR-NOT-AUTHORIZED) ;; Validate sender is tx-sender (asserts! (is-eq tx-sender sender) ERR-NOT-AUTHORIZED) ;; Validate recipient is different (asserts! (not (is-eq sender recipient)) ERR-INVALID-USER) ;; Perform transfer (enables post-conditions) (try! (nft-transfer? ${contractName} token-id sender recipient)) ;; Emit transfer event (print { action: "transfer", token-id: token-id, sender: sender, recipient: recipient }) (ok true) ) ) ;; Minting function (define-public (mint (recipient principal)${args.includeMetadataStorage ? ' (metadata {name: (string-ascii 64), description: (string-ascii 256), image: (string-ascii 256)})' : ''}) (let ( (next-id (+ (var-get last-token-id) u1)) ) ;; Check if minting is enabled (asserts! (var-get mint-enabled) ERR-NOT-AUTHORIZED) ;; Check authorization (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED) ${args.maxSupply ? '(asserts! (<= next-id MAX-SUPPLY) ERR-MAX-SUPPLY-REACHED)' : ''} ;; Mint the NFT (try! (nft-mint? ${contractName} next-id recipient)) ${args.includeMetadataStorage ? '(map-set token-metadata next-id metadata)' : ''} ;; Update last token ID (var-set last-token-id next-id) ;; Emit mint event (print { action: "mint", token-id: next-id, recipient: recipient${args.includeMetadataStorage ? ',\n metadata: metadata' : ''} }) (ok next-id) ) ) ;; Burn function (define-public (burn (token-id uint)) (begin ;; Validate token exists and sender owns it (asserts! (<= token-id (var-get last-token-id)) ERR-NOT-FOUND) (asserts! (is-eq (some tx-sender) (nft-get-owner? ${contractName} token-id)) ERR-NOT-AUTHORIZED) ;; Burn the NFT (try! (nft-burn? ${contractName} token-id tx-sender)) ${args.includeMetadataStorage ? '(map-delete token-metadata token-id)' : ''} ;; Emit burn event (print { action: "burn", token-id: token-id, owner: tx-sender }) (ok true) ) ) ${args.includeMetadataStorage ? ` ;; Metadata functions (define-read-only (get-token-metadata (token-id uint)) (map-get? token-metadata token-id) ) ` : ''} ;; Collection info (define-read-only (get-collection-info) (ok { name: COLLECTION-NAME, symbol: COLLECTION-SYMBOL, total-supply: (var-get last-token-id), base-uri: BASE-URI, mint-enabled: (var-get mint-enabled)${args.maxSupply ? ',\n max-supply: MAX-SUPPLY' : ''} }) ) ;; Administrative functions (define-public (toggle-mint) (begin (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED) (var-set mint-enabled (not (var-get mint-enabled))) (ok (var-get mint-enabled)) ) ) ${args.includeMarketplace ? ` ;; Basic Marketplace Functions (define-public (list-nft (token-id uint) (price uint) (expires-at uint)) (begin ;; Verify ownership (asserts! (is-eq (some tx-sender) (nft-get-owner? ${contractName} token-id)) ERR-NOT-AUTHORIZED) (asserts! (> price u0) ERR-INVALID-USER) (asserts! (> expires-at block-height) ERR-INVALID-USER) ;; Create listing (map-set listings token-id { seller: tx-sender, price: price, expires-at: expires-at }) (print { action: "list", token-id: token-id, seller: tx-sender, price: price, expires-at: expires-at }) (ok true) ) ) (define-public (unlist-nft (token-id uint)) (begin (asserts! (is-eq (some tx-sender) (nft-get-owner? ${contractName} token-id)) ERR-NOT-AUTHORIZED) (map-delete listings token-id) (print { action: "unlist", token-id: token-id, seller: tx-sender }) (ok true) ) ) (define-public (buy-nft (token-id uint)) (let ( (listing (unwrap! (map-get? listings token-id) ERR-NOT-FOUND)) (seller (get seller listing)) (price (get price listing)) ) ;; Verify listing is valid (asserts! (<= block-height (get expires-at listing)) ERR-NOT-FOUND) (asserts! (not (is-eq tx-sender seller)) ERR-INVALID-USER) ;; Transfer payment (STX) (try! (stx-transfer? price tx-sender seller)) ;; Transfer NFT (try! (nft-transfer? ${contractName} token-id seller tx-sender)) ;; Remove listing (map-delete listings token-id) (print { action: "sale", token-id: token-id, seller: seller, buyer: tx-sender, price: price }) (ok true) ) ) (define-read-only (get-listing (token-id uint)) (map-get? listings token-id) ) ` : ''} \`\`\` ## Collection Configuration - **Name**: ${args.collectionName} - **Symbol**: ${args.collectionSymbol} - **Base URI**: ${args.baseUri} ${args.maxSupply ? `- **Max Supply**: ${args.maxSupply} NFTs` : '- **Max Supply**: Unlimited'} - **On-chain Metadata**: ${args.includeMetadataStorage ? 'Enabled' : 'Disabled'} - **Marketplace Functions**: ${args.includeMarketplace ? 'Enabled' : 'Disabled'} ## Deployment Steps 1. **Save the contract** as \`${contractName}.clar\` 2. **Prepare metadata** (if using external storage): ${args.baseUri.includes('ipfs://') ? ` - Upload metadata files to IPFS - Update BASE-URI with your IPFS hash ` : ` - Set up metadata endpoint at ${args.baseUri} - Ensure HTTPS accessibility `} 3. **Test locally** with Clarinet: \`\`\`bash clarinet check clarinet test \`\`\` 4. **Deploy to testnet** first: \`\`\`bash clarinet deploy --testnet \`\`\` 5. **Deploy to mainnet** after testing: \`\`\`bash clarinet deploy --mainnet \`\`\` ## Usage Examples ### Minting NFTs \`\`\`clarity ;; Mint to specific address (contract-call? .${contractName} mint 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM${args.includeMetadataStorage ? ` { name: "NFT #1", description: "First NFT in the collection", image: "https://mynft.com/images/1.png" }` : ''}) \`\`\` ### Frontend Integration \`\`\`typescript // Transfer NFT await transferSIP009NFT( contractAddress, '${contractName}', 1, // token ID currentOwner, newOwner ); // Get NFT info const nftInfo = await getSIP009TokenInfo( contractAddress, '${contractName}', 1 ); \`\`\` ${args.includeMarketplace ? ` ### Marketplace Usage \`\`\`typescript // List NFT for sale await listNFT(tokenId, priceInSTX, expirationBlock); // Buy NFT await buyNFT(tokenId); \`\`\` ` : ''} ## Security Features ✅ - ✅ Native asset functions for post-condition support - ✅ Ownership validation for all transfers - ✅ Authorization checks using tx-sender - ✅ Token existence validation - ✅ Event emission for indexer integration ${args.maxSupply ? '- ✅ Supply limit enforcement' : ''} ${args.includeMarketplace ? '- ✅ Marketplace security (payment before transfer)' : ''} ## Metadata JSON Schema \`\`\`json { "name": "NFT Name", "description": "NFT Description", "image": "https://example.com/image.png", "external_url": "https://example.com/nft/1", "attributes": [ { "trait_type": "Rarity", "value": "Rare" } ] } \`\`\` ## Next Steps 1. Customize the contract name and save as \`.clar\` file 2. Set up metadata hosting (IPFS recommended) 3. Add to your Clarinet project 4. Write comprehensive tests 5. Deploy to testnet for testing 6. Deploy to mainnet when ready`; } catch (error) { return `❌ Failed to generate SIP-009 template: ${error}`; } },
- Zod schema defining the input parameters for the tool.parameters: z.object({ collectionName: z.string().describe("The name of the NFT collection (e.g., 'My NFT Collection')"), collectionSymbol: z.string().describe("The collection symbol (e.g., 'MNC')"), baseUri: z.string().describe("The base URI for metadata (e.g., 'https://api.mynft.com/metadata/' or 'ipfs://QmHash/')"), maxSupply: z.number().optional().describe("Optional maximum supply limit"), includeMarketplace: z.boolean().describe("Whether to include basic marketplace functionality"), includeMetadataStorage: z.boolean().describe("Whether to include on-chain metadata storage"), }),
- src/tools/index.ts:57-57 (registration)Registration of the tool in the FastMCP server.server.addTool(generateSIP009TemplateTool);
- src/tools/index.ts:15-15 (registration)Import of the tool from its implementation file.generateSIP009TemplateTool