Skip to main content
Glama

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
NameRequiredDescriptionDefault
baseUriYesThe base URI for metadata (e.g., 'https://api.mynft.com/metadata/' or 'ipfs://QmHash/')
collectionNameYesThe name of the NFT collection (e.g., 'My NFT Collection')
collectionSymbolYesThe collection symbol (e.g., 'MNC')
includeMarketplaceYesWhether to include basic marketplace functionality
includeMetadataStorageYesWhether to include on-chain metadata storage
maxSupplyNoOptional 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"),
    }),
  • Registration of the tool in the FastMCP server.
    server.addTool(generateSIP009TemplateTool);
  • Import of the tool from its implementation file.
    generateSIP009TemplateTool

Latest Blog Posts

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/exponentlabshq/stacks-clarity-mcp'

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