nft-collection
Non-fungible token collection with unique token IDs, metadata URIs, and badge-based ownership
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.
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
-
Standards: Include "NFTs" in the standards array
- Example: "standards": ["NFTs"]
- For tradable NFTs: "standards": ["NFTs", "NFTMarketplace", "NFTPricingDenom:ubadge"]
-
validTokenIds: Set to the range of unique token IDs
- Example for 100 NFTs: [{ "start": "1", "end": "100" }]
- Each token ID represents a unique NFT
-
Token Metadata: Each tokenMetadata entry must include tokenIds matching the range
- Use {id} placeholder for per-token metadata URIs
Pattern Example
{
"updateValidTokenIds": true,
"validTokenIds": [{ "start": "1", "end": "100" }],
"updateCollectionMetadata": true,
"collectionMetadata": {
"uri": "ipfs://QmCollectionMetadata"
},
"updateTokenMetadata": true,
"tokenMetadata": [{
"uri": "ipfs://QmTokenMetadata/{id}",
"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"]
}{
"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.):
{
"approvalCriteria": {
"overridesFromOutgoingApprovals": true,
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
"incrementTokenIdsBy": "1",
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
},
"orderCalculationMethod": {
"useOverallNumTransfers": true
}
},
"maxNumTransfers": {
"overallMaxNumTransfers": "100",
"perInitiatedByAddressMaxNumTransfers": "1",
"amountTrackerId": "nft-mint-tracker",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
}
}
}{
"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.