Skip to content

Lock collection permissions to make properties permanently immutable or permanently permitted

Transferability & Update Rules

Instruction text for agents that use the Transferability & Update Rules skill, loaded by bb dev skills immutability 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 immutability skill and build me one: lock collection permissions to make properties permanently immutable or permanently permitted. 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 immutability prints the same instructions.

Summary

Controls whether collection properties can be changed after creation.

  • Two states: FROZEN (permanentlyForbiddenTimes: FOREVER) or NEUTRAL (empty [])
  • NEUTRAL [] = manager can update now and can freeze it later. Use this for editable fields.
  • FROZEN = permanent and irreversible. Use for fields that should never change.
  • Avoid permanentlyPermittedTimes: it permanently prevents locking. Almost never needed.
  • canUpdateCollectionApprovals: controls transfer rule mutability
    • Security: If manager can update Mint approvals, they can mint unlimited tokens
    • Default to frozen unless user requests updatable
  • List IDs in permissions: only use reserved IDs ("All", "Mint", "!Mint", direct "bb1..." addresses)
  • permanentlyForbiddenTimes: [{ "start": "1", "end": "18446744073709551615" }] = frozen forever

Instructions

Transferability & Update Rules Configuration

When configuring collection permissions for transferability and update rules, you must follow these critical requirements:

Critical Permission Rules

canUpdateCollectionApprovals:

  • Critical: Controls whether transfer rules (approvals) can be changed after creation
  • Security risk: If the manager can update approvals from the "Mint" address, they can mint any amount
  • Default: Should be forbidden (frozen) for collections where transfer rules should be locked
  • Format: Uses CollectionApprovalPermission format (see below)

CollectionApprovalPermission Format

json
{
  "fromListId": "All",
  "toListId": "All",
  "initiatedByListId": "All",
  "transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
  "tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
  "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
  "approvalId": "All",
  "permanentlyPermittedTimes": [],
  "permanentlyForbiddenTimes": [{ "start": "1", "end": "18446744073709551615" }]
}

Key Fields:

  • List IDs (fromListId, toListId, initiatedByListId): Use only reserved list IDs:
    • "All": Any address
    • "Mint": Mint address
    • "!Mint": Everything except Mint
    • "bb1...": Direct address
    • "!bb1...": Everything except the specific address
    • "bb1abc:bb1xyz": Colon-separated addresses
    • Do not use: Custom list IDs, only reserved IDs or direct addresses
  • approvalId: "All" to restrict all approvals, or a specific approvalId string

Example: Immutable Transfer Rules (All Frozen)

json
{
  "collectionPermissions": {
    "canUpdateCollectionApprovals": [{
      "fromListId": "All",
      "toListId": "All",
      "initiatedByListId": "All",
      "transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
      "tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
      "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
      "approvalId": "All",
      "permanentlyForbiddenTimes": [{ "start": "1", "end": "18446744073709551615" }],
      "permanentlyPermittedTimes": []
    }]
  }
}

Example: Restricting Only Mint Approvals

json
{
  "canUpdateCollectionApprovals": [{
    "fromListId": "Mint",
    "toListId": "All",
    "initiatedByListId": "All",
    "transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
    "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "approvalId": "All",
    "permanentlyForbiddenTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "permanentlyPermittedTimes": []
  }]
}

Example: Restricting Specific Approval ID

json
{
  "canUpdateCollectionApprovals": [{
    "fromListId": "All",
    "toListId": "All",
    "initiatedByListId": "All",
    "transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
    "ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "approvalId": "mint-approval",
    "permanentlyForbiddenTimes": [{ "start": "1", "end": "18446744073709551615" }],
    "permanentlyPermittedTimes": []
  }]
}

Empty Permission Arrays: critical

Important: If a permission entry has both permanentlyPermittedTimes and permanentlyForbiddenTimes as empty arrays, the entire permission entry is redundant and should be replaced with an empty array.

For ActionPermission (canDeleteCollection, canArchiveCollection, etc.):

  • Wrong: "canArchiveCollection": [{ "permanentlyPermittedTimes": [], "permanentlyForbiddenTimes": [] }]
  • Correct: "canArchiveCollection": []

For TokenIdsActionPermission (canUpdateTokenMetadata, canUpdateValidTokenIds):

  • Wrong: "canUpdateTokenMetadata": [{ "tokenIds": [...], "permanentlyPermittedTimes": [], "permanentlyForbiddenTimes": [] }]
  • Correct: "canUpdateTokenMetadata": []

For CollectionApprovalPermission (canUpdateCollectionApprovals):

  • If both time arrays are empty, use empty array: "canUpdateCollectionApprovals": []

Permission Presets

Three common permission configurations:

1. Fully Immutable: Everything frozen. Nothing can change after creation.

  • All permissions set to permanentlyForbiddenTimes: FOREVER
  • Use when: the collection should never change

2. Manager Controlled: Manager can change everything except delete.

  • canDeleteCollection: frozen
  • Everything else: NEUTRAL [] (editable now, can be frozen later)
  • Use when: the manager needs full control (issuer-controlled tokens, evolving collections)

3. Locked Approvals (recommended default): Approvals and supply frozen, metadata editable.

  • canDeleteCollection: frozen
  • canUpdateStandards: frozen
  • canUpdateManager: frozen
  • canUpdateValidTokenIds: frozen
  • canUpdateCollectionApprovals: frozen
  • canUpdateCollectionMetadata: NEUTRAL []
  • canUpdateTokenMetadata: NEUTRAL []
  • canArchiveCollection: NEUTRAL []
  • canUpdateCustomData: NEUTRAL []
  • Use when: supply and rules should be immutable but metadata needs updates

Understanding permission states:

  • Empty array [] = NEUTRAL (manager can update now and can lock it later, preserves maximum flexibility)
  • permanentlyForbiddenTimes: FOREVER = FROZEN (can never be changed, permanent and irreversible)
  • permanentlyPermittedTimes: FOREVER = PERMANENTLY ALLOWED (can never be frozen, almost never needed)

Important: For editable fields, always use NEUTRAL (empty []) instead of permanentlyPermittedTimes. Neutral gives the same current behavior (manager can update) but preserves the option to freeze it later. Only use permanentlyPermittedTimes if the user explicitly requests a guarantee that a field can never be locked.

Security Considerations

  • Mint Transfer Rules: If canUpdateCollectionApprovals is allowed for Mint, the manager could mint unlimited tokens
  • Post-Mint Transfer Rules: If post-mint transfer rules can be updated, the manager could change transferability
  • Best Practice: Default to locked (frozen) transfer rules unless user explicitly requests updatable rules
  • Only allow updates for dynamic collections where the user explicitly requests flexibility

Common Mistakes

  • Don't use custom list IDs in permissions, only reserved IDs: "All", "Mint", "!Mint", or direct bb1... addresses.
  • Don't leave both permanentlyPermittedTimes and permanentlyForbiddenTimes as empty arrays in a permission entry, this is redundant. Replace the entire entry with an empty array [].
  • Don't forget that unfrozen Mint approval permissions means the manager can mint unlimited tokens, freeze canUpdateCollectionApprovals for Mint if supply should be fixed.
  • Don't confuse empty permission array [] (neutral/unset) with a frozen permission, empty means the field is still updatable.

Edit this page on GitHub

For agents

How agents read these docs