subscription
Time-based subscription token with recurring payment approvals and auto-deletion on expiry
Subscription
Instruction text for agents that use the Subscription skill, loaded by bb dev skills subscription and the MCP get_skill_instructions tool.
Try it
Paste this into Claude Code, Codex, or Cursor with the BitBadges MCP server wired. The agent loads the skill, builds, verifies, and hands back a link to review and sign.
Load the subscription skill and build me one: time-based subscription token with recurring payment approvals and auto-deletion on expiry. Use the session tools, run validate, review, and simulate in parallel, fix anything critical, then call get_review_url and give me the link.From a shell, bb dev skills subscription prints the same instructions.
Summary
Required standards: ["Subscriptions"]
- validTokenIds: must be exactly one token ID [{ "start": "1", "end": "1" }]
- Subscription faucet approval requirements:
- fromListId: "Mint"
- overridesFromOutgoingApprovals: true
- coinTransfers: at least 1 entry, both override flags false
- predeterminedBalances.incrementedBalances.durationFromTimestamp: must be non-zero (duration in ms)
- allowOverrideTimestamp: must be true
- incrementTokenIdsBy: "0", incrementOwnershipTimesBy: "0"
- orderCalculationMethod: must have exactly one method true (default: useOverallNumTransfers)
- Duration constants: monthly = "2592000000", annual = "31536000000", daily = "86400000"
- Critical: recurringOwnershipTimes must be all-zeros { startTime: "0", intervalLength: "0", chargePeriodLength: "0" }, chain enforces mutual exclusivity with durationFromTimestamp
Instructions
Subscription Collection Configuration
When creating a subscription collection, you must follow these exact requirements:
Preferred path: preset (one short tool call)
The faucet approval is fully canonical. Use subscription.faucet:
add_preset_approval({
presetId: "subscription.faucet",
params: {
paymentRecipient: "bb1...",
paymentDenom: "<ibc/... or native>",
paymentAmount: "<base units>",
durationMs: "2592000000" // monthly; daily="86400000", annual="31536000000"
}
})list_presets({skill: "subscription"}) for params. Fall back to add_approval for exotic variants (multi-token tiers, manager-only mints, etc.).
Required Structure
-
Standards: must include "Subscriptions"
- "standards": ["Subscriptions"]
-
Invariants: must set noCustomOwnershipTimes to false
- Subscriptions use time-dependent ownership, this invariant must be false or subscriptions cannot function.
- "invariants": { "noCustomOwnershipTimes": false, ... }
-
validTokenIds: must be exactly one token ID (per tier)
- "validTokenIds": [{ "start": "1", "end": "1" }]
-
Subscription Faucet Approval Requirements:
- fromListId: must be "Mint"
- tokenIds: must be exactly 1 token: [{ "start": "1", "end": "1" }]
- coinTransfers: must have at least 1 entry, no override flags (both false)
- predeterminedBalances.incrementedBalances:
- durationFromTimestamp: must be non-zero (subscription duration in milliseconds)
- allowOverrideTimestamp: must be true
- incrementTokenIdsBy: "0"
- incrementOwnershipTimesBy: "0"
- orderCalculationMethod: must have exactly one method set to true (default: useOverallNumTransfers)
- overridesFromOutgoingApprovals: true (required for Mint approvals)
Duration Constants (in milliseconds)
- Monthly: "2592000000" (30 days)
- Annual: "31536000000" (365 days)
- Daily: "86400000" (24 hours)
Complete Example
{
"standards": ["Subscriptions"],
"validTokenIds": [{ "start": "1", "end": "1" }],
"collectionApprovals": [{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"approvalId": "subscription-mint",
"tokenIds": [{ "start": "1", "end": "1" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"coinTransfers": [{
"to": "bb1creator...",
"coins": [{ "denom": "ubadge", "amount": "5000000000" }]
}],
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
"durationFromTimestamp": "2592000000",
"allowOverrideTimestamp": true,
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
},
"orderCalculationMethod": {
"useOverallNumTransfers": true
}
},
"overridesFromOutgoingApprovals": true
}
}]
}{
"standards": ["Subscriptions"],
"validTokenIds": [{ "start": "1", "end": "1" }],
"collectionApprovals": [{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"approvalId": "subscription-mint",
"tokenIds": [{ "start": "1", "end": "1" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"coinTransfers": [{
"to": "bb1creator...",
"coins": [{ "denom": "ubadge", "amount": "5000000000" }],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}],
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{ "amount": "1", "tokenIds": [{ "start": "1", "end": "1" }], "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }] }],
"incrementTokenIdsBy": "0",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "2592000000",
"allowOverrideTimestamp": true,
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
"allowOverrideWithAnyValidToken": false
},
"orderCalculationMethod": {
"useOverallNumTransfers": true,
"usePerToAddressNumTransfers": false,
"usePerFromAddressNumTransfers": false,
"usePerInitiatedByAddressNumTransfers": false,
"useMerkleChallengeLeafIndex": false,
"challengeTrackerId": ""
},
"manualBalances": []
},
"overridesFromOutgoingApprovals": true,
"merkleChallenges": []
}
}]
}Subscription-Specific Gotchas
- Must have exactly 1 token ID (not multiple)
- coinTransfers override flags must be false (not true)
- durationFromTimestamp must be non-zero
- allowOverrideTimestamp must be true
- Critical mutual exclusivity: The chain enforces that only one of
durationFromTimestamp,incrementOwnershipTimesBy, orrecurringOwnershipTimescan be non-zero. For subscriptions, usedurationFromTimestampand keeprecurringOwnershipTimesas all zeros:{ "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }. do not set non-zero values inrecurringOwnershipTimes, the template already has the correct structure.
Common Mistakes
- Don't set recurringOwnershipTimes to non-zero values, it is mutually exclusive with durationFromTimestamp. Keep all fields as "0".
- Don't forget durationFromTimestamp must be non-zero, this is the subscription duration in milliseconds (e.g. "2592000000" for 30 days).
- Don't forget allowOverrideTimestamp: true, subscriptions need this so each mint gets its own start timestamp.
- Don't use multiple token IDs, subscriptions must use exactly one token ID [{ "start": "1", "end": "1" }].
- Don't set coinTransfers override flags to true, for standard subscription payments, both overrideFromWithApproverAddress and overrideToWithInitiator must be false.
- Don't set noCustomOwnershipTimes: true in invariants, subscriptions require noCustomOwnershipTimes: false (or omit the invariant) because each subscription period mints a new ownershipTime window.