product-catalog
Multi-product storefront with per-product pricing, supply limits, and optional burn-on-purchase. Each product is a separate token ID.
Products
Instruction text for agents that use the Products skill, loaded by bb dev skills product-catalog 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 product-catalog skill and build me one: multi-product storefront with per-product pricing, supply limits, and optional burn-on-purchase. Each product is a separate token ID. 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 product-catalog prints the same instructions.
Summary
Required standards: ["Products"]
- N token IDs (one per product), starting at 1
- N+1 approvals: 1 purchase approval per product + 1 optional burn approval
- Each purchase approval: fromListId="Mint", toListId="All" (or burn address if burn-on-purchase), 1 coinTransfer paying the store address
- Payment goes directly to store address (not to escrow): overrideFromWithApproverAddress: false
- Each product has independent price, supply limit (maxNumTransfers), and burn-on-purchase toggle
- predeterminedBalances.incrementedBalances.startBalances: 1x of that product's token ID
- Optional burn approval: !Mint → burn address, no coinTransfers
- invariants: { noCustomOwnershipTimes: true }
- All permissions frozen after creation
- Don't use overrideFromWithApproverAddress: payment goes directly to store, not from escrow
- Don't use allowAmountScaling: fixed price per item
- Don't use votingChallenges, merkleChallenges, or mustOwnTokens
- Do use unique approvalId per product (e.g. "product-purchase-1", "product-purchase-2")
- Do set maxNumTransfers to supply limit (0 = unlimited)
Instructions
Products Configuration
Mental Model
A multi-product storefront where each product is a separate token ID. Buyers pay coins to mint a product token. Each product has its own price, supply limit, and optional burn-on-purchase setting. Payment goes directly to the store owner's address (not escrow).
Collection Structure
- Token IDs 1..N (one per product)
- Standard: "Products"
- validTokenIds: [{ start: "1", end: "<NUM_PRODUCTS>" }]
- invariants: { noCustomOwnershipTimes: true }
- All permissions frozen after creation
Approval Structure
Each product gets its own purchase approval. There's also an optional global burn approval.
Preferred path: CLI
Use bb build product-catalog --json catalog.json --creator <address> --output-file proposal.json.
Inspect bb dev capabilities build_product_catalog for the current input schema. Each products entry accepts
name, price (display units), denom, maxSupply, burn and metadata. Optional product.storeAddress
overrides the catalog's default storeAddress for that product only; BitBadges and EVM account
addresses are accepted. One purchase still pays exactly one recipient.
Use bb products show <collectionId> to inspect the deployed catalog, then
bb products purchase <collectionId> --token-id <id> --creator <buyer> to build a purchase.
Review, simulate and explicitly request browser signing as documented by the CLI lifecycle.
Catalog lifecycle
Catalogs created by this builder freeze products, prices, recipients, supply caps and metadata. Publish a new collection for new terms. Do not rebuild an existing catalog using this creation builder: changing token IDs, approval IDs or tracker IDs can detach receipts and purchase counts. The frontend catalog form is creation-only; its reorder/delete actions apply only to unsigned drafts and move each product's metadata with its terms. Custom mutable collections require an advanced update that preserves identities and obeys their existing permissions.
Payments are direct to the seller, not held for conditional release. Burning a product receipt does not refund payment, restock the product or prove delivery. Cancellations, refunds and timed licenses are not built-in catalog actions; they need a separately reviewed design before creation.
Advanced path: presets (one call per product + optional burn)
Call products.purchase once per product (productIndex 1..N). Add products.burn once if you need the global burn/redeem:
add_preset_approval({
presetId: "products.purchase",
params: {
productIndex: 1,
storeAddress: "bb1...",
priceAmount: "<base units>",
denom: "<denom>",
supplyLimit: "0", // "0" = unlimited
burnOnPurchase: false // true = token minted straight to burn (receipt-style)
}
})
// Optional:
add_preset_approval({ presetId: "products.burn", params: { numProducts: 3 } })Purchase Approval (per product)
{
"approvalId": "product-purchase-1",
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"tokenIds": [{ "start": "1", "end": "1" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"overridesFromOutgoingApprovals": true,
"overridesToIncomingApprovals": true,
"coinTransfers": [{
"to": "<STORE_ADDRESS>",
"coins": [{ "amount": "<PRICE>", "denom": "<DENOM>" }]
}],
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }]
},
"orderCalculationMethod": { "useOverallNumTransfers": true }
},
"maxNumTransfers": { "overallMaxNumTransfers": "<SUPPLY_LIMIT_OR_0>" }
}
}{
"approvalId": "product-purchase-1",
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"tokenIds": [{ "start": "1", "end": "1" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"overridesFromOutgoingApprovals": true,
"overridesToIncomingApprovals": true,
"coinTransfers": [{
"to": "<STORE_ADDRESS>",
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false,
"coins": [{ "amount": "<PRICE>", "denom": "<DENOM>" }]
}],
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
"incrementTokenIdsBy": "0",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"allowAmountScaling": false,
"maxScalingMultiplier": "0"
},
"orderCalculationMethod": { "useOverallNumTransfers": true }
},
"maxNumTransfers": { "overallMaxNumTransfers": "<SUPPLY_LIMIT_OR_0>" }
}
}For burn-on-purchase products, set toListId to the burn address instead of "All". The buyer never holds the token, it's minted directly to burn, and the purchase receipt is the transaction itself.
Burn Approval (optional, for all products)
{
"approvalId": "product-burn",
"fromListId": "!Mint",
"toListId": "<BURN_ADDRESS>",
"initiatedByListId": "All",
"tokenIds": [{ "start": "1", "end": "<NUM_PRODUCTS>" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"overridesFromOutgoingApprovals": true,
"overridesToIncomingApprovals": true,
"coinTransfers": [],
"maxNumTransfers": { "overallMaxNumTransfers": "0" }
}
}Creation Flow (Tool Calls)
set_valid_token_ids: set [{ start: "1", end: "<NUM_PRODUCTS>" }]set_standards: set ["Products"]set_invariants: set { noCustomOwnershipTimes: true }add_approvalxN: one purchase approval per productadd_approval: optional burn approvalset_collection_metadata: store name, description, imageset_token_metadataxN: metadata for each productset_permissions: preset "fully-immutable"validate_transaction: verify structuresimulate_transaction: dry run
Common Mistakes
- Don't use overrideFromWithApproverAddress on purchase approvals, payment goes directly to the store address, not from escrow
- Don't use allowAmountScaling: each purchase is exactly 1 item at fixed price
- Don't use a single approval for multiple products, each product needs its own approval with its own tokenIds, price, and supply limit
- Don't forget unique approvalIds, duplicate IDs will cause the chain to reject the transaction
- Don't set maxNumTransfers > 0 on the burn approval, burns should be unlimited
- Don't use votingChallenges, merkleChallenges, or mustOwnTokens, purchases are open to all
- Don't forget to set toListId to burn address for burn-on-purchase products