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
Executable contract
Resolve missing clarifications before building. Postconditions describe the chosen parameters. Reference example postconditions apply only to the example input; they are not defaults for a new request. Unsupported behavior must remain explicit. Golden evaluations are experimental diagnostic evidence, not a source of truth; listing a scenario ID does not prove this artifact was executed or verified.
{
"version": 1,
"prerequisites": [
"Confirm signer and network before handoff.",
"Record exact user requirements separately from external metadata."
],
"postconditions": [
"Generated artifact matches the confirmed input parameters and explicit user intent.",
"Structural checks and lifecycle evidence remain separately scoped; unsigned output is not deployed."
],
"examplePostconditions": [
"Prepare a new collection",
"Disallow forceful post-mint transfers",
"No initial holder-transfer approval",
"Membership originates at Mint",
"Exactly 5 BADGE to the merchant; initiator pays",
"Exactly 30 days in milliseconds",
"One period per mint",
"Mint approval permissions remain locked for every token and time",
"Do not bypass recipient incoming approvals"
],
"clarifications": [
"Calendar month or 30 days?"
],
"unsupported": [
"Unsigned builder proposal only; signer and manager must be bound before deployment.",
"Artifact constraints only. No chain execution, wallet signing, live balance checks or external service verification.",
"Initial holder-transfer restriction is checked; the builder retains manager ability to add non-mint transfer approvals. This is not immutable non-transferability.",
"Recipient payment excludes protocol fees and transaction gas."
],
"goldenScenarioIds": [
"subscription-fixed-price"
],
"example": {
"tool": "build_subscription",
"input": {
"uri": "ipfs://METADATA_COLLECTION",
"interval": "30d",
"price": 5,
"denom": "BADGE",
"recipient": "bb1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zql3w7"
}
}
}{
"version": 1,
"prerequisites": [
"Confirm signer and network before handoff.",
"Record exact user requirements separately from external metadata."
],
"postconditions": [
"Generated artifact matches the confirmed input parameters and explicit user intent.",
"Structural checks and lifecycle evidence remain separately scoped; unsigned output is not deployed."
],
"examplePostconditions": [
"Prepare a new collection",
"Disallow forceful post-mint transfers",
"No initial holder-transfer approval",
"Membership originates at Mint",
"Exactly 5 BADGE to the merchant; initiator pays",
"Exactly 30 days in milliseconds",
"One period per mint",
"Mint approval permissions remain locked for every token and time",
"Do not bypass recipient incoming approvals"
],
"clarifications": [
"Calendar month or 30 days?"
],
"unsupported": [
"Unsigned builder proposal only; signer and manager must be bound before deployment.",
"Artifact constraints only. No chain execution, wallet signing, live balance checks or external service verification.",
"Initial holder-transfer restriction is checked; the builder retains manager ability to add non-mint transfer approvals. This is not immutable non-transferability.",
"Recipient payment excludes protocol fees and transaction gas."
],
"goldenScenarioIds": [
"subscription-fixed-price"
],
"example": {
"tool": "build_subscription",
"input": {
"uri": "ipfs://METADATA_COLLECTION",
"interval": "30d",
"price": 5,
"denom": "BADGE",
"recipient": "bb1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zql3w7",
"transferable": false,
"updatableMint": false
}
}
}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.