Trust and security
The programs make sure no card token exists without a card in the vault, and that anyone can prove it when a card leaves. You still trust Collector Crypt with the physical cards, the attestor with which card belongs to which class, and the upgrade authority with the programs' code.
On this page
Who you trust for what
| Party | Power | Checked by |
|---|---|---|
| Collector Crypt (the issuer) | Holds the cards physically. Its Core collection has permanent transfer and burn delegates held by one wallet, and a permanent freeze delegate, so it can move, burn or freeze a vaulted card at any time. Grade, cert and value are its off-chain record | poke proves a card left; requeue keeps redemptions moving past a frozen card; the record hash is re-checked by the reserves check |
| Attestor key | Decides which card belongs to which class. A wrong or stolen key could attest a card into the wrong class, minting 1,000 tokens of a class against a cheaper card | Every statement is bound to the asset, class, record hash, cert and an expiry; the admin can replace the key with set_attestor |
| Meteora | Can change pool fees or pause DBC and DAMM v2 pools. pokedex cannot halt or override them | Public pools on chain |
| card-vault admin | set_admin, set_attestor, set_collections, init_class, set_class_metadata, and set_shares_per_card before a class's first deposit | Cannot mint, burn or move cards |
| card-launch admin | set_admin, set_fees (treasury share up to 10,000 bps, protocol recipient), set_market (repoint or disable a class's market) | Cannot move card tokens except through the fixed split |
| Upgrade authority | Can replace either program's code. A malicious card-vault upgrade could sign for the vault PDA and take every vaulted card | It is the largest single power; see below |
Holding a card token gives a claim on the vault: burn 1,000 and take the oldest held card of the class.
The upgrade authority
Both programs' upgrade authority is one key, 7VayjyPVcPKgep9Yu86dEBD73m1HVQBRgFrxTCho7fw6. Each program's initialize can only be signed by it, so nobody can initialize a freshly deployed program first.
The security review recommends moving both programs' upgrade authority and both admin roles to a multisig, or making the programs immutable once stable.
What the programs enforce
card-vault:
- Only
depositmints, and only 1,000 tokens per deposited card. The vault PDA is the only mint authority, and card tokens have no freeze authority. - A deposit needs an accepted collection, the depositor as the card's owner, the card in that collection, and a matching, unexpired statement from the configured attestor in the Ed25519 instruction right before it, with every offset inside that instruction.
- A normal deposit cannot mint into a class that stays short of backing.
redeemburns a full card of tokens before it releases exactly the head copy, which must be held.- A held marker per card means one card can never back two copies.
poke,skip,requeueandrestoreare permissionless and only change bookkeeping after checking chain state.set_shares_per_cardis refused once a class has had a deposit.
card-launch:
set_marketaccepts only a Meteora DBC config quoted in the class's token whose fee claimer and leftover receiver are pokedex'sfeesaccount.launch_coinrefuses disabled markets and classes flagged as undercollateralized.- Claims are permissionless but always pay the
feesaccount;splitpays only the class treasury and the protocol recipient;sweep_coinpays only the protocol recipient. - Nothing spends from a class treasury;
burn_treasuryonly burns.
Reviews
Two internal reviews found no direct exploit. Every fix below is in the deployed programs.
| Finding | Fix |
|---|---|
initialize could be front-run after a deploy | Gated on each program's upgrade authority |
| A frozen head copy would block every redemption of its class | requeue moves it to the end of the queue |
| A missing card that came back could never count again | restore |
| The partner's surplus and coin-side fees had no exit | claim_surplus and sweep_coin |
| One card could be counted twice (out, poked, deposited again, then restored) | A held marker per card, created by deposit and restore, closed by redeem and poke |
requeue treated a frozen collection as binding even when the card's own unfrozen permanent freeze delegate overrides it | Fixed to follow Core's precedence |
| Could a depositor keep transfer, burn or freeze delegates on a vaulted card? | No change needed: Core resets owner-managed plugin authorities on transfer, which a test proves against Core's mainnet build |
Known limits
- Pools made outside
launch_coin. Anyone can create a pool on a market's config directly, skipping the backing check. Fees still route to pokedex. Such pools have no launch record, so the keeper does not migrate or claim for them and the app does not list them. - The flag waits for a poke. Between a card leaving the vault and the next poke, launches still see a healthy class. The keeper pokes on every pass.
- Collector Crypt's record is off chain. The record hash binds the attested fields at deposit, but the program cannot check them. The reserves check re-reads them and reports changes.
- Unparseable freeze data reads as not frozen.
requeuethen refuses rather than moving a copy it should not, and a frozen head with an unexpected layout would need a fixed parser before that class's redemptions continue. Deposits are unaffected, because Core refuses frozen transfers itself. - Transfer blockers that are not freezes. Royalty rule sets and Oracle or LifecycleHook adapters, which only the issuer can set, can block a transfer without being detected, so
requeuerefuses and that class's redemptions stall. - Graduated liquidity is locked forever. Card tokens left in a graduated pool can never be burned for a card (Graduation).
- Meteora pools cannot be halted. A flagged class stops new launches only.
The rest of this path
Written from the protocol, security and card documents of the pokedex repository and from its code (card-vault, card-launch, cp-ix). Every figure on these pages comes from there.