TSP-2 — Card Standard
The maintained executable protocol is PLUMB v2. The v1 layouts below are historical design references and are incompatible with v2 witnesses.
PLUMB implementation for unique assets. See PLUMB for the shared token framework.
Conservation law: owner_count(id) = 1.
See the Gold Standard for design rationale and skill architecture.
What Differs from TSP-1
- Leaf represents an asset (unique item), not an account balance
- Invariant: uniqueness, not divisible supply
- No divisible arithmetic — no
balance, no range checks, no splitting - Per-asset state — metadata, royalty, creator, flags live in the leaf
- Creator immutability —
creator_idis set at mint and can never change - 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)
| Field | Type | Description |
|---|---|---|
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
| Bit | Name | When set | When clear |
|---|---|---|---|
| 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.
Collection Metadata — 10 field elements
metadata = hash(name_hash, description_hash, image_hash, site_hash, custom_hash,
max_supply, royalty_receiver, 0, 0, 0)
| Field | Type | Description |
|---|---|---|
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.
Global Public State
| Field | Type | Description |
|---|---|---|
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.
Op 0: Pay (Transfer Ownership)
Constraints:
- Config verified,
pay_authandpay_hookextracted - Asset leaf verifies against
old_root hash(secret) == leaf.auth_hash- If
pay_auth != 0, dual auth required current_time >= leaf.lock_untilleaf.flags & TRANSFERABLEcollection_id,creator_id,royalty_bps,metadata_hash,flagsunchanged- New leaf:
owner_id = new_owner,auth_hash = new_auth,nonce += 1 - New leaf produces
new_root - Nullifier emitted:
hash(asset_id, nonce)
Op 1: Lock (Time-Lock Asset)
Constraints:
- Config verified,
lock_authandlock_hookextracted - Owner auth required
- If
lock_auth != 0, dual auth required leaf.flags & LOCKABLElock_until_time >= leaf.lock_until(extend only)- All immutable fields unchanged
- Leaf:
lock_until = lock_until_time,nonce += 1
Op 2: Update (Config or Metadata)
Config update:
old_root == new_root(state unchanged)- Admin auth,
admin_auth != 0 - New config fields hash to
new_config
Metadata update:
- Owner auth required
flags & UPDATABLE- Only
metadata_hashchanges nonce += 1
Op 3: Mint (Originate)
Constraints:
- Config verified,
mint_authandmint_hookextracted mint_auth != 0(minting enabled)- Mint authorization verified
asset_idnot in tree (non-membership proof)creator_id = minter_id(immutable forever)collection_id,flags,royalty_bpsset (immutable after mint)flags & MINTABLEnonce = 0,lock_until = 0- New leaf produces
new_root new_asset_count == old_asset_count + 1- If
max_supply != 0:new_asset_count <= max_supply
Op 4: Burn (Release)
Constraints:
- Config verified,
burn_authandburn_hookextracted - Owner auth required
- If
burn_auth != 0, dual auth required current_time >= leaf.lock_untilleaf.flags & BURNABLE- Leaf removed (Merkle deletion)
new_asset_count == old_asset_count - 1- Nullifier emitted:
hash(asset_id, nonce)
Hooks
See PLUMB Hook System. All 5 hook slots are available. See Skill Reference for standard hook programs.
Security Properties
All PLUMB framework security properties apply.
Additional TSP-2 properties:
- Uniqueness — non-membership proof at mint, exactly one leaf per
asset_id - Creator immutability —
creator_idset at mint, preserved by all operations - Flag enforcement — operations rejected if the corresponding flag bit is clear
- Supply cap — if
max_supply != 0, minting rejected whenasset_count >= max_supply - Immutable fields —
collection_id,creator_id,royalty_bps,flagsnever change after mint