Skip to content

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.

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

json
{
  "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:

ts
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

  1. Standards: must include "Subscriptions"

    • "standards": ["Subscriptions"]
  2. Invariants: must set noCustomOwnershipTimes to false

    • Subscriptions use time-dependent ownership, this invariant must be false or subscriptions cannot function.
    • "invariants": { "noCustomOwnershipTimes": false, ... }
  3. validTokenIds: must be exactly one token ID (per tier)

    • "validTokenIds": [{ "start": "1", "end": "1" }]
  4. 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

json
{
  "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, or recurringOwnershipTimes can be non-zero. For subscriptions, use durationFromTimestamp and keep recurringOwnershipTimes as all zeros: { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }. do not set non-zero values in recurringOwnershipTimes, 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.

Edit this page on GitHub

For agents

How agents read these docs