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
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It mentions 'production-ready' and 'security features' but doesn't disclose critical behavioral traits like whether this generates source code, configuration files, or deployment scripts; what format the output takes; whether it requires specific permissions; or any rate limits. The description is too vague about what 'generate' actually entails operationally.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose. While concise, it could be more structured by separating functional description from quality attributes. Every word earns its place, but it's slightly dense with multiple concepts ('complete, production-ready, security features, optional marketplace functionality') in one clause.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given this is a code generation tool with 6 parameters, no annotations, and no output schema, the description is insufficient. It doesn't explain what 'generate' means in practice (creates files? returns code as text?), doesn't mention output format or structure, and provides no context about the generated template's characteristics beyond vague 'production-ready' and 'security features' claims.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 6 parameters thoroughly. The description adds no additional parameter semantics beyond what's in the schema - it doesn't explain relationships between parameters (e.g., how includeMarketplace interacts with other features) or provide usage examples. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('generate') and resources ('complete, production-ready SIP-009 NFT contract template'). It distinguishes from siblings by specifying SIP-009 (vs SIP-010 templates) and mentioning security features and optional marketplace functionality, which differentiates it from generic contract generation tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. While it mentions 'SIP-009 NFT contract template,' it doesn't specify when to choose this over other contract generation tools (like generate_sip010_template) or when to use it versus building contracts manually. No exclusions or prerequisites are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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