# nft-collection

<!-- generated by site/scripts/gen-skills.ts; do not hand-edit -->

# NFT Collection

Instruction text for agents that use the NFT Collection skill, loaded by `bb dev skills nft-collection` and the MCP `get_skill_instructions` tool.

## Try it

Paste this into Claude Code, Codex, or Cursor with the BitBadges MCP server wired. The agent loads the skill, builds, verifies, and hands back a link to review and sign.

```text
Load the nft-collection skill and build me one: non-fungible token collection with unique token IDs, metadata URIs, and badge-based ownership. Use the session tools, run validate, review, and simulate in parallel, fix anything critical, then call get_review_url and give me the link.
```

From a shell, `bb dev skills nft-collection` prints the same instructions.

## Summary

Required standards: ["NFTs"]
- For tradable NFTs: ["NFTs", "NFTMarketplace", "NFTPricingDenom:ubadge"]

- validTokenIds: set to the range of unique token IDs (e.g. [{ "start": "1", "end": "100" }])
- Each token ID represents a unique NFT; amount in transfers is typically "1"
- Use {id} placeholder in tokenMetadata URI for per-token metadata (e.g. "ipfs://QmHash/{id}")
- Mint approvals must have overridesFromOutgoingApprovals: true
- Ownership times are usually forever for NFTs

## Instructions

## NFT Collection Configuration

When creating an NFT collection, follow this pattern:

### Required Configuration

1. **Standards**: Include "NFTs" in the standards array
   - Example: "standards": ["NFTs"]
   - For tradable NFTs: "standards": ["NFTs", "NFTMarketplace", "NFTPricingDenom:ubadge"]

2. **validTokenIds**: Set to the range of unique token IDs
   - Example for 100 NFTs: [{ "start": "1", "end": "100" }]
   - Each token ID represents a unique NFT

3. **Token Metadata**: Each tokenMetadata entry must include tokenIds matching the range
   - Use {id} placeholder for per-token metadata URIs

### Pattern Example

```json
{
  "updateValidTokenIds": true,
  "validTokenIds": [{ "start": "1", "end": "100" }],
  "updateCollectionMetadata": true,
  "collectionMetadata": {
    "uri": "ipfs://QmCollectionMetadata",
    "customData": ""
  },
  "updateTokenMetadata": true,
  "tokenMetadata": [{
    "uri": "ipfs://QmTokenMetadata/{id}",
    "customData": "",
    "tokenIds": [{ "start": "1", "end": "100" }]
  }],
  "updateCollectionApprovals": true,
  "collectionApprovals": [{
    "fromListId": "Mint",
    "toListId": "All",
    "initiatedByListId": "bb1creator...",
    "approvalId": "manager-mint",
    "tokenIds": [{ "start": "1", "end": "100" }],
    "transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "approvalCriteria": {
      "overridesFromOutgoingApprovals": true
    }
  }],
  "updateStandards": true,
  "standards": ["NFTs"]
}
```

### Sequential Minting with predeterminedBalances

For NFTs, use predeterminedBalances with incrementTokenIdsBy: "1" to mint tokens sequentially (token 1, then 2, then 3, etc.):

```json
{
  "approvalCriteria": {
    "overridesFromOutgoingApprovals": true,
    "predeterminedBalances": {
      "manualBalances": [],
      "incrementedBalances": {
        "startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
        "incrementTokenIdsBy": "1",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
        "allowOverrideWithAnyValidToken": false
      },
      "orderCalculationMethod": {
        "useOverallNumTransfers": true,
        "usePerToAddressNumTransfers": false,
        "usePerFromAddressNumTransfers": false,
        "usePerInitiatedByAddressNumTransfers": false,
        "useMerkleChallengeLeafIndex": false,
        "challengeTrackerId": ""
      }
    },
    "maxNumTransfers": {
      "overallMaxNumTransfers": "100",
      "perInitiatedByAddressMaxNumTransfers": "1",
      "perToAddressMaxNumTransfers": "0",
      "perFromAddressMaxNumTransfers": "0",
      "amountTrackerId": "nft-mint-tracker",
      "resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
    }
  }
}
```

Key: incrementTokenIdsBy: "1" means each mint gets the next sequential token ID. Use maxNumTransfers to cap total mints and per-user mints.

Important: predeterminedBalances and approvalAmounts are incompatible, use one or the other. NFTs use predeterminedBalances (not approvalAmounts).

### NFT-Specific Gotchas

- Each token ID is unique and represents a distinct NFT
- Amount in transfers is typically "1" (one NFT per transfer)
- Ownership times are usually forever for NFTs
- Mint approvals must have overridesFromOutgoingApprovals: true
- Use {id} in metadata URIs for per-token metadata

## Common Mistakes

- Don't reuse token IDs across editions without understanding ownership times, each token ID is unique and represents a distinct NFT.
- Don't forget tokenIds in canUpdateTokenMetadata permission structure, the permission must specify which token ID ranges it covers.
- Don't use {id} in metadata name, description, or image fields, the {id} placeholder only works in the URI string itself (e.g. "ipfs://abc/{id}").
- Don't forget overridesFromOutgoingApprovals: true on Mint approvals, required for all minting operations.
- Don't use custom list IDs, only reserved IDs are valid: "All", "Mint", "!Mint", "AllWithoutMint", or direct bb1... addresses.
