Product Catalogs and Commerce
Sell SKUs on-chain with independent prices, supply caps, and burn-on-purchase. One purchase approval per product routes payment straight to the store.
A catalog is one token ID per product and one purchase approval per token ID. Each approval carries that product's price, its supply cap, and whether the buyer keeps a receipt token or the token burns on purchase (a consumable). Payment goes from the buyer to the store address in the same transaction as the mint. There is no cart contract and no escrow; a purchase is one MsgTransferTokens from Mint.
An owned receipt can gate a download with BB-402. The standard catalog does not include refunds or expiring ownership; those require a custom design. Burn-on-purchase products leave transaction history, not an owned receipt.
What Makes It Work
| Requirement | Token-standard primitive |
|---|---|
| Independent price per SKU | coinTransfers on each purchase approval, overrideFromWithApproverAddress: false so the buyer pays. See Coin Transfers |
| Exactly one unit per purchase | predeterminedBalances.incrementedBalances.startBalances of amount 1 for that token ID |
| Limited stock | maxNumTransfers.overallMaxNumTransfers = supply; 0 for unlimited. See Approval Trackers |
| Consumable versus keepsake | toListId: "All" mints a receipt to the buyer; toListId = burn address consumes it on purchase |
| Buyer can discard a receipt | One !Mint to burn-address approval over every product ID |
| Prices cannot change under a buyer | All permissions frozen at creation. See Permissions |
The Fields That Matter
{
"approvalId": "product-purchase-1",
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"tokenIds": [{ "start": "1", "end": "1" }],
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": { "startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }] },
"orderCalculationMethod": { "useOverallNumTransfers": true }
},
"coinTransfers": [{ "to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d", "coins": [{ "amount": "25000000", "denom": "ibc/E1116484B327AEE59CDC3DA73D319834781A13DB2A7DFC1F38A30CD45ABF58B8" }], "overrideFromWithApproverAddress": false, "overrideToWithInitiator": false }],
"maxNumTransfers": { "overallMaxNumTransfers": "100", "amountTrackerId": "product-purchase-1" },
"overridesFromOutgoingApprovals": true,
"overridesToIncomingApprovals": true
}
}Product 2 is the same approval on token ID 2 with its own price, and toListId set to the burn address if it is a consumable.
bb build product-catalog --store-address bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d \
--uri ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/collection.json \
--products '[{"name":"Widget","price":25,"denom":"USDC","maxSupply":100},{"name":"Pass","price":5,"denom":"USDC","burn":true}]'This emits a Products collection with two purchase approvals, one burn approval, per-product token metadata, and frozen permissions. An optional storeAddress inside a product object routes that product's payment to a different recipient. It accepts a BitBadges or EVM account address and defaults to the catalog-level recipient when omitted.
The standard catalog cannot change its products, prices, payment recipients, stock limits or metadata after creation. The frontend catalog form only creates new collections. Draft reordering moves metadata with the product; do not renumber deployed product IDs or reset purchase approval/tracker IDs. See Catalog lifecycle.
Custom designs
These are ideas for separately reviewed approval configurations, not options supported by the frozen catalog builder or its standard purchase action. Additional criteria can also change standard conformance and require custom transaction assembly and interfaces.
- Members-only pricing: a second purchase approval on the same token ID with a lower price and
mustOwnTokensof a membership; buyers prioritize the one they qualify for. - Pay in credits: a credit token ownership check alone does not spend credits or require a burn. Atomic payment needs explicit enforcement tying receipt issuance to credit consumption.
- Time-limited license: a custom ownership-time design must be compatible with collection invariants; the catalog builder fixes full ownership times and disables custom ownership times.
- Returns: refund approvals need separately funded escrow and rules binding the original purchase, payer, recipient and refundable amount. Direct seller payments do not fund escrow, and transferable receipts alone do not prove who originally paid.
- Drops: bound each purchase approval's
transferTimesto the sale window, and addperInitiatedByAddressMaxNumTransfers: "1"for one per customer.
Build It
- Skill: Product Catalog
- Guide: Mint and Distribute for paid mints, Token-Gated Access to gate the delivery
- CLI:
bb build product-catalog,bb productsfrom Standards
Load the product-catalog skill. Build a store paid to bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d with three products: Sticker Pack at 3 USDC unlimited, Poster at 20 USDC with 50 in stock, and Day Pass at 5 USDC that burns on purchase. Validate, review, simulate, then give me the review link.