This file is derived from pumpcade_idl.json at the repo root, which is byte-identical to the on-chain Anchor IDL account BBmdk3qX3dnSTczwAsCFraUEX9WoL6BhZCi9eNP943CX (name:"pumpcade_protocol", version:"0.1.0", spec:"0.1.0"). The cade.market JS bundle does not embed the IDL — the client builds instruction data directly (see frontend.md) — so the bundle is not a cross-check for this file. Where the deployed binary diverges from the IDL, see Current IDL / binary divergences.
Verified: 2026-06-18 (devnet).
| Field | Value |
|---|---|
| name | pumpcade_protocol |
| version | 0.1.0 |
| spec | 0.1.0 |
| description | Created with Anchor |
Seven protocol instructions plus standard Anchor IDL ops (IdlCreateAccount, IdlResize, IdlClose, IdlCreateBuffer, IdlWrite, IdlSetAuthority, IdlSetBuffer).
| Instruction | Effect | IDL signer slot |
|---|---|---|
initialize_factory |
One-shot; sets factory authority + treasury | authority |
configure_fees |
Sets protocol / creator / streamer fee bps. Total cap enforced by error 6015 | authority |
update_treasury |
Redirects treasury account | authority |
create_market |
Allocates Market PDA + token vault PDA | authority |
place_bet |
Transfers tokens from user → vault; deducts protocol / creator / streamer fees inline; writes UserPosition |
user |
resolve_market |
Writes winning_side: bool onto the Market |
authority |
claim_winnings |
Winner pulls payout from vault. Single-use per UserPosition.claimed |
user |
The IDL marks each authority slot as signer: true but declares no relations, has_one, or address constraint pinning it to factory.authority. Any additional restriction is enforced inside the instruction handler — see trust-model.md for empirical verification on create_market.
Resolution and payout are decoupled: resolve_market records the winning side, then each winner independently calls claim_winnings.
factory, authority, vault, token_vault, user_token_account, treasury_token_account, creator_token_account, streamer_token_account, user_position, system_program, token_program.
place_bet references the three fee receivers (treasury_token_account, creator_token_account, streamer_token_account) as separate token accounts.
| PDA | Seeds |
|---|---|
Factory |
["factory"] (singleton) |
Market |
["market", market_id_u64_le] (arg) |
TokenVault |
["vault", market_pda] |
UserPosition |
["position", market_pda, user_pubkey] |
market_id is supplied as a u64 argument to create_market. The Factory's market_count field is a separate on-chain counter; the program does not require the supplied market_id to equal it.
8-byte Anchor discriminator
authority pubkey
treasury pubkey
market_count u64
protocol_fee_bps u16
creator_fee_bps u16
streamer_fee_bps u16
bump u8
8-byte discriminator
market_id u64
authority pubkey
creator pubkey
streamer pubkey
token_mint pubkey
token_vault pubkey
description string (max 200 chars, enforced by error 6000)
creation_time i64
end_time i64
yes_pool u64
no_pool u64
yes_shares_total u64
no_shares_total u64
total_pool u64
total_claimed u64
resolved bool
winning_side Option<bool>
bump u8
token_mint is per-market: each Market chooses its own SPL mint at creation. The program does not enforce a protocol-wide betting currency.
8-byte discriminator
user pubkey
market pubkey
yes_shares u64
no_shares u64
yes_amount u64
no_amount u64
claimed bool
bump u8
claimed is the single-use guard for claim_winnings.
| Code | Name | Message |
|---|---|---|
| 6000 | DescriptionTooLong | max 200 characters |
| 6001 | InvalidEndTime | must be in the future |
| 6002 | InvalidAmount | invalid bet amount |
| 6003 | MarketResolved | already resolved |
| 6004 | MarketEnded | no more bets allowed |
| 6005 | MarketAlreadyResolved | already resolved (duplicate guard) |
| 6006 | MarketNotEnded | not ended yet |
| 6007 | MarketNotResolved | not resolved yet |
| 6008 | AlreadyClaimed | winnings already claimed |
| 6009 | NoWinningPosition | user has no winning position |
| 6010 | InvalidPayout | invalid payout amount |
| 6011 | MathOverflow | math overflow |
| 6012 | Unauthorized | |
| 6013 | NoWinningBets | no one bet on the winning side |
| 6014 | InsufficientVaultFunds | vault can't pay all winners |
| 6015 | FeesTooHigh | total fees > 10% |
| 6016 | InvalidTreasury | |
| 6017 | InvalidCreator | |
| 6018 | InvalidStreamer |
There is no refund/rescue instruction in the IDL.
The IDL events array is empty. The program emits human-readable Program log: strings only (Bet placed: …, Market <id> resolved. Winner: …, Winnings claimed: …, Factory initialized with authority: …, Fees configured: …, Market <id> created: <description> (custom_duration: …, expected_duration: …s)). Off-chain consumers must parse log strings rather than subscribe to Anchor events.
The IDL declares accounts and instructions but not the arithmetic the place_bet/claim_winnings handlers perform. The following are verified read-only on devnet.
place_bet deducts the three Factory fees from the bet before the remainder enters the pool. Verified on tx 5dUTpL…Zr4 (market 55116): a 100,000,000 bet credited 99,000,000 to the vault and 1,000,000 (1% = the 45+10+45 bps total) to the fee receiver. On devnet the treasury/creator/streamer token accounts collapse to the single admin key, so the 45/10/45 split is not separable in transfers — it rests on the Factory bps fields (see on-chain-state.md).
A bet mints fewer shares than its token amount, scaled by how early it lands in the round. Across 89 positions sampled on market 55114, the per-position shares / amount ratio spans 0.2001 → 1.0000 with none above 1.0 — consistent with a 1.0× multiplier for the earliest bets decaying toward ~0.2× near the deadline. Only the 1.0× ceiling and ~0.2× floor are visible from position data; the exact multiplier function was later recovered from the deployed bytecode — see ../reverse-engineering/. Pumpcade's own docs describe the same shape at a high level (docs.cade.market). The sampled market in on-chain-state.md shows the same effect (yes 127.71M amount → 110.02M shares ≈ 0.86×).
claim_winnings pays a winner their principal back plus a proportional cut of the losing pool:
payout = principal + floor(your_winning_shares × losing_pool / total_winning_shares)
Verified to the exact lamport on market 55114 (NO won): 3,960,000 + floor(793,574 × 3,096,720,000 / 1,264,291,840) = 5,903,757, matching the claim tx BW6Pna…nw4g. Winners never lose principal; the losing side funds the winners' profit. The inputs are the Market fields yes_pool/no_pool/yes_shares_total/no_shares_total and UserPosition.{yes,no}_shares.
The on-chain IDL is admin-rewritable. Two divergences between the IDL and the deployed BPF binary are observable from live transactions and direct testing. Both matter for any client that builds instructions or maps errors from the IDL:
The IDL lists five arguments (market_id u64, description string, end_time i64, creator pubkey, streamer pubkey), which sum to 88 + len(description) bytes after the 8-byte discriminator. Live transactions of this instruction carry 8–9 additional trailing bytes beyond that. Submitting an instruction with only the five IDL-declared args fails inside the program with Anchor error 102 (InstructionDidNotDeserialize: The program could not deserialize the given instruction); appending nine zero bytes (0x00 0x00 … 0x00) allows deserialization to proceed. The exact types of the trailing fields are not declared in the IDL; the program's emitted Market <id> created log line names custom_duration and expected_duration, consistent with two additional fields after the IDL-declared ones.
Sample range observed (1.75 h window, 23 create_market instructions): total instruction-data byte sizes from 125 to 155.
The IDL labels code 6013 as NoWinningBets with message No one bet on the winning side. The BPF binary emits Error Code: Unauthorized. Error Number: 6013 for this code in transactions where the instruction handler rejects an unauthorized caller (see trust-model.md for a reproduction). Client code that maps error codes by IDL name will misclassify this case.
- IDL byte-equivalence.
getAccountInfoonBBmdk…943CXreturns 3,544 bytes laid out as Anchor IDL account: 8-byte discriminator + 32-byte authority + 4-byte u32 LE data_len + zlib-compressed JSON. Decompressing yields the same JSON aspumpcade_idl.json. create_markettrailing bytes. Decode thedatafield of any recentcreate_marketinstruction (filter program instructions whose first 8 bytes equal67e261ebc8bcfbfe); the byte length will exceed88 + len(description).- Error 6013 label. Construct a
create_marketinstruction signed by any keypair other thanfactory.authorityand submit it viasendTransactionwithskipPreflight=true; the resulting transaction's log includesError Code: Unauthorized. Error Number: 6013.