# Products

> Multi-product storefront with per-product pricing, supply limits, and optional burn-on-purchase. Each product is a separate token ID.

**Category:** Token Types

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

#### Purchase Approval (per product)

```json
{
  "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)

```json
{
  "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)

1. \`set\_valid\_token\_ids\` — set \[{ start: "1", end: "\<NUM\_PRODUCTS>" }]
2. \`set\_standards\` — set \["Products"]
3. \`set\_invariants\` — set { noCustomOwnershipTimes: true }
4. \`add\_approval\` xN — one purchase approval per product
5. \`add\_approval\` — optional burn approval
6. \`set\_collection\_metadata\` — store name, description, image
7. \`set\_token\_metadata\` xN — metadata for each product
8. \`set\_permissions\` — preset "fully-immutable"
9. \`validate\_transaction\` — verify structure
10. \`simulate\_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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bitbadges.io/token-standard/skills/product-catalog.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
