Skip to the content
PokedexDocs

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

PartyPowerChecked 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 recordpoke proves a card left; requeue keeps redemptions moving past a frozen card; the record hash is re-checked by the reserves check
Attestor keyDecides 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 cardEvery statement is bound to the asset, class, record hash, cert and an expiry; the admin can replace the key with set_attestor
MeteoraCan change pool fees or pause DBC and DAMM v2 pools. pokedex cannot halt or override themPublic pools on chain
card-vault adminset_admin, set_attestor, set_collections, init_class, set_class_metadata, and set_shares_per_card before a class's first depositCannot mint, burn or move cards
card-launch adminset_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 authorityCan replace either program's code. A malicious card-vault upgrade could sign for the vault PDA and take every vaulted cardIt 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 deposit mints, 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.
  • redeem burns 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, requeue and restore are permissionless and only change bookkeeping after checking chain state.
  • set_shares_per_card is refused once a class has had a deposit.

card-launch:

  • set_market accepts only a Meteora DBC config quoted in the class's token whose fee claimer and leftover receiver are pokedex's fees account.
  • launch_coin refuses disabled markets and classes flagged as undercollateralized.
  • Claims are permissionless but always pay the fees account; split pays only the class treasury and the protocol recipient; sweep_coin pays only the protocol recipient.
  • Nothing spends from a class treasury; burn_treasury only burns.

Reviews

Two internal reviews found no direct exploit. Every fix below is in the deployed programs.

FindingFix
initialize could be front-run after a deployGated on each program's upgrade authority
A frozen head copy would block every redemption of its classrequeue moves it to the end of the queue
A missing card that came back could never count againrestore
The partner's surplus and coin-side fees had no exitclaim_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 itFixed 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. requeue then 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 requeue refuses 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

Solana mainnet

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.

UpDown to moveEnter to open