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.

The receipt token is what makes the rest of the standard available: a receipt can gate a download with BB-402, be refunded through a return approval, or expire for a time-limited license.

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.

Variations

  • 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: mustOwnTokens of a credit token plus a burn of those credits in the same transaction.
  • Time-limited license: mint receipts with ownershipTimes so the download gate closes after a year.
  • Returns: a !Mint to burn-address approval with coinTransfers from the mint escrow back to the initiator, bounded by transferTimes to the return window.
  • 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