Loyalty Points and Credits
Sell prepaid credits at a fixed exchange rate, or pay a reward for every completed quest. Soulbound fungible tokens with a coin transfer on the mint approval.
A credit token is a fungible, non-transferable balance a user buys at a fixed rate: 1 USDC mints 100 credits, 0.5 USDC mints 50. The mint approval carries the price and allowAmountScaling, so one approval serves every purchase size. There is no post-mint approval, so credits cannot be resold; the app debits them off-chain and reconciles to the on-chain purchase record.
A quest token is the reverse flow. The issuer funds an escrow with reward * maxClaims, and each claimant who passes the gate mints one token and receives the reward coin in the same transaction.
What Makes It Work
| Requirement | Token-standard primitive |
|---|---|
| Fixed exchange rate at any size | predeterminedBalances with startBalances.amount = credits per unit and allowAmountScaling: true. See Predetermined Balances |
| Pay per unit | coinTransfers of "1" base unit of the payment denom per scaled step. See Coin Transfers |
| Cannot be transferred or sold | No !Mint approval. noForcefulPostMintTransfers: true |
| Displays as a balance with decimals | An alias path with the payment denom's decimals. See Alias Denoms |
| Reward paid on claim | coinTransfers with overrideFromWithApproverAddress: true (escrow pays) and overrideToWithInitiator: true (claimant receives) |
| One claim per person, capped total | merkleChallenges with maxUsesPerLeaf: "1" and maxNumTransfers.overallMaxNumTransfers. See Merkle Challenges |
The Fields That Matter
The credit mint approval at 100 credits per USDC:
{
"approvalId": "credit-scaled",
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"tokenIds": [{ "start": "1", "end": "1" }],
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "100", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
"allowAmountScaling": true,
"maxScalingMultiplier": "18446744073709551615"
}
},
"coinTransfers": [{ "to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d", "coins": [{ "amount": "1", "denom": "ibc/E1116484B327AEE59CDC3DA73D319834781A13DB2A7DFC1F38A30CD45ABF58B8" }] }],
"overridesFromOutgoingApprovals": true,
"mustPrioritize": true
}
}amount: "1" is one micro-USDC. A user who mints 1000000 credits pays 10000 micro-USDC, which is 0.01 USDC, so the displayed rate is 100 credits per 0.000001 USDC at base units; the alias path makes the wallet show it as intended.
bb build credit-token --payment-denom USDC --recipient bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d \
--symbol CREDIT --tokens-per-unit 100 --uri ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/collection.jsonThis emits a Credit Token collection with the single scaled mint approval, a ucredit alias path, autoApproveAllIncomingTransfers: true for holders, and every permission frozen.
Variations
- Quests: a
Questscollection withmintEscrowCoinsToTransferofreward * maxClaimsand a claim approval gated by a Merkle challenge or an on-chain check (mustOwnTokens,dynamicStoreChallenges,evmQueryChallenges). See the quest variant in Create a Collection. - Off-chain gates: a claim with plugins (email, Discord, a form) emits the Merkle proof the approval checks.
- Points that expire: mint with
ownershipTimesso unused points lapse at year end, as in a subscription. - Redeem for goods: pair credits with a product catalog whose purchase approval requires
mustOwnTokensof credits. - Spendable points: add a
!Mintto burn-address approval so users can burn points on-chain instead of the app tracking usage.
Build It
- Skill: Credit Token, Quest
- Guide: Subscriptions and Time-Based Tokens (section 3 builds a credit token), Distribute with Claims
- CLI:
bb build credit-token,bb credit-tokensfrom Standards
Load the credit-token skill. Build API credits called DEMO where 1 USDC buys 1000 credits paid to bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d, non-transferable, permissions frozen. Validate, review, simulate, then give me the review link.