# immutability

<!-- generated by site/scripts/gen-skills.ts; do not hand-edit -->

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