Skip to content

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

RequirementToken-standard primitive
Independent price per SKUcoinTransfers on each purchase approval, overrideFromWithApproverAddress: false so the buyer pays. See Coin Transfers
Exactly one unit per purchasepredeterminedBalances.incrementedBalances.startBalances of amount 1 for that token ID
Limited stockmaxNumTransfers.overallMaxNumTransfers = supply; 0 for unlimited. See Approval Trackers
Consumable versus keepsaketoListId: "All" mints a receipt to the buyer; toListId = burn address consumes it on purchase
Buyer can discard a receiptOne !Mint to burn-address approval over every product ID
Prices cannot change under a buyerAll permissions frozen at creation. See Permissions

The Fields That Matter

json
{
  "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
  }
}
Token Ownership Requirements
Off
No ownership check
Coin Transfers per Use
On
  • 25 USDCbb1p0rrel3...w70d
Merkle Challenges
Off
No proof required
Predetermined Balances
On
Start: x1 of IDs 1
Order: useOverallNumTransfers
Amount Restrictions
Off
No amount limit
Max Transfers
On
Overall: 100
Address Checks
Off
No initiator check
Sender and Recipient Approvals
On
Skips the sender's outgoing approvals
Skips the recipient's incoming approvals

The purchase approval on bitbadges.io: one unit per buy at 25 USDC, 100 in stock, and both user-level checks skipped.

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.

bash
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 mustOwnTokens of 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 transferTimes to the sale window, and add perInitiatedByAddressMaxNumTransfers: "1" for one per customer.

Build It

text
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.

Edit this page on GitHub