# TSP-2 — Card Standard
PLUMB implementation for unique assets. See [PLUMB](plumb.md) for the
shared token framework.
Conservation law: `owner_count(id) = 1`.
See the [Gold Standard](../docs/explanation/gold-standard.md) for design
rationale and skill architecture.
---
## What Differs from TSP-1
1. Leaf represents an asset (unique item), not an account balance
2. Invariant: uniqueness, not divisible supply
3. No divisible arithmetic — no `balance`, no range checks, no splitting
4. Per-asset state — metadata, royalty, creator, flags live in the leaf
5. Creator immutability — `creator_id` is set at mint and can never change
6. Flag-gated operations — transferable, burnable, updatable bits control
which PLUMB operations are allowed per asset
Operations are still Pay, Lock, Update, Mint, Burn. What changes is what
the circuit enforces inside each.
---
## Asset Leaf — 10 field elements
```
leaf = hash(asset_id, owner_id, nonce, auth_hash, lock_until,
collection_id, metadata_hash, royalty_bps, creator_id, flags)
```
| `asset_id` | Field | Globally unique asset identifier |
| `owner_id` | Field | Current owner (account_id hash) |
| `nonce` | Field | Monotonic counter |
| `auth_hash` | Field | Hash of owner's authorization secret |
| `lock_until` | Field | Timestamp lock (0 = unlocked) |
| `collection_id` | Field | Collection membership (0 = standalone) |
| `metadata_hash` | Field | Hash of item metadata |
| `royalty_bps` | Field | Royalty basis points (0-10000) |
| `creator_id` | Field | Original creator (immutable after mint) |
| `flags` | Field | Bitfield controlling allowed operations |
First 5 fields occupy the same positions as TSP-1. Last 5 — reserved
zeros in TSP-1 — carry per-asset state in TSP-2.
### Flags Bitfield
| 0 | `TRANSFERABLE` | Pay (transfer) allowed | Pay rejected |
| 1 | `BURNABLE` | Burn allowed | Burn rejected |
| 2 | `UPDATABLE` | Metadata update allowed | Metadata frozen forever |
| 3 | `LOCKABLE` | Lock (time-lock) allowed | Lock rejected |
| 4 | `MINTABLE` | Re-mint into collection allowed | Collection closed |
Flags are set at mint time and cannot be changed after creation.
A soulbound credential: `flags = 0`. A game item: `flags = 31`
(all operations). A standard collectible: `flags = 11` (transferable +
burnable + lockable).
### Collection Binding
When `collection_id != 0`, the asset belongs to a collection identified
by its config hash. Collection membership is immutable after mint.
### Creator Immutability
`creator_id` is set at mint and preserved by every subsequent operation.
Provides an unforgeable provenance chain. The Royalties skill reads
`royalty_bps` from the leaf and `royalty_receiver` from collection
metadata.
---
## Token Config — 10 field elements
See [PLUMB Config](plumb.md#2-token-config--10-field-elements).
---
## Collection Metadata — 10 field elements
```
metadata = hash(name_hash, description_hash, image_hash, site_hash, custom_hash,
max_supply, royalty_receiver, 0, 0, 0)
```
| `name_hash` | Field | Hash of collection name |
| `description_hash` | Field | Hash of collection description |
| `image_hash` | Field | Hash of collection image/avatar |
| `site_hash` | Field | Hash of collection website URL |
| `custom_hash` | Field | Hash of application-specific data |
| `max_supply` | Field | Maximum number of assets (0 = unlimited) |
| `royalty_receiver` | Field | Account that receives royalties on transfers |
| *reserved* | Field x3 | Extension space |
---
## Merkle Tree
See [PLUMB Merkle Tree](plumb.md#7-merkle-tree).
---
## Global Public State
| `state_root` | Digest | Merkle root of all assets |
| `asset_count` | Field | Total number of assets in tree |
| `config_hash` | Digest | Token configuration commitment |
| `metadata_hash` | Digest | Collection metadata commitment |
| `current_time` | Field | Block timestamp for time-lock checks |
---
## Operations
All operations follow the [PLUMB proof envelope](plumb.md#5-proof-envelope).
### Op 0: Pay (Transfer Ownership)
**Constraints:**
1. Config verified, `pay_auth` and `pay_hook` extracted
2. Asset leaf verifies against `old_root`
3. `hash(secret) == leaf.auth_hash`
4. If `pay_auth != 0`, dual auth required
5. `current_time >= leaf.lock_until`
6. `leaf.flags & TRANSFERABLE`
7. `collection_id`, `creator_id`, `royalty_bps`, `metadata_hash`, `flags` unchanged
8. New leaf: `owner_id = new_owner`, `auth_hash = new_auth`, `nonce += 1`
9. New leaf produces `new_root`
10. Nullifier emitted: `hash(asset_id, nonce)`
### Op 1: Lock (Time-Lock Asset)
**Constraints:**
1. Config verified, `lock_auth` and `lock_hook` extracted
2. Owner auth required
3. If `lock_auth != 0`, dual auth required
4. `leaf.flags & LOCKABLE`
5. `lock_until_time >= leaf.lock_until` (extend only)
6. All immutable fields unchanged
7. Leaf: `lock_until = lock_until_time`, `nonce += 1`
### Op 2: Update (Config or Metadata)
**Config update:**
1. `old_root == new_root` (state unchanged)
2. Admin auth, `admin_auth != 0`
3. New config fields hash to `new_config`
**Metadata update:**
1. Owner auth required
2. `flags & UPDATABLE`
3. Only `metadata_hash` changes
4. `nonce += 1`
### Op 3: Mint (Originate)
**Constraints:**
1. Config verified, `mint_auth` and `mint_hook` extracted
2. `mint_auth != 0` (minting enabled)
3. Mint authorization verified
4. `asset_id` not in tree (non-membership proof)
5. `creator_id = minter_id` (immutable forever)
6. `collection_id`, `flags`, `royalty_bps` set (immutable after mint)
7. `flags & MINTABLE`
8. `nonce = 0`, `lock_until = 0`
9. New leaf produces `new_root`
10. `new_asset_count == old_asset_count + 1`
11. If `max_supply != 0`: `new_asset_count <= max_supply`
### Op 4: Burn (Release)
**Constraints:**
1. Config verified, `burn_auth` and `burn_hook` extracted
2. Owner auth required
3. If `burn_auth != 0`, dual auth required
4. `current_time >= leaf.lock_until`
5. `leaf.flags & BURNABLE`
6. Leaf removed (Merkle deletion)
7. `new_asset_count == old_asset_count - 1`
8. Nullifier emitted: `hash(asset_id, nonce)`
---
## Hooks
See [PLUMB Hook System](plumb.md#9-hook-system). All 5 hook slots are available. See [Skill Reference](skills.md) for standard hook programs.
---
## Security Properties
All [PLUMB framework security properties](plumb.md#10-security-properties) apply.
Additional TSP-2 properties:
1. **Uniqueness** — non-membership proof at mint, exactly one leaf per `asset_id`
2. **Creator immutability** — `creator_id` set at mint, preserved by all operations
3. **Flag enforcement** — operations rejected if the corresponding flag bit is clear
4. **Supply cap** — if `max_supply != 0`, minting rejected when `asset_count >= max_supply`
5. **Immutable fields** — `collection_id`, `creator_id`, `royalty_bps`, `flags` never change after mint