Independent technical research on the Pumpcade prediction-market protocol on Solana — reversed from the deployed program, the on-chain state, and the live cade.market frontend/backend.
Devnet only. Pumpcade is not on Solana mainnet at the time of last verification. Not affiliated with Pumpcade or cade.market. Every claim here is reproducible from public RPC, the live cade.market JS bundle/backend, the on-chain Anchor IDL, or the deployed bytecode. Where useful we cross-check Pumpcade's own docs at
docs.cade.market(treated as a claim to verify, not ground truth). Last verified: 2026-06-24. Per-file verification dates are in each doc. No warranty — seeLICENSE.
Pumpcade is an Anchor program (pumpcade_protocol v0.1.0) live on Solana devnet at 8AEL…dfxB. It runs pool-bet (parimutuel) prediction markets: a singleton Factory PDA holds protocol config, each market is a Market PDA + token vault, and bettors hold per-market UserPosition PDAs. You bet YES or NO with an SPL token; at the deadline an admin writes the winning side; then each winner pulls their payout. Resolution and payout are decoupled — the chain never auto-distributes.
Two surfaces sit on top of each other: the on-chain program (the source of truth — bets, payouts, market state) and a self-hosted Go backend at api-dev.cade.market (indexing, oracles, faucet, auth). The backend's bet/claim/create endpoints just relay on-chain instructions, so most user actions exist on both — but each surface can do things the other can't.
The findings that aren't obvious from the docs or the IDL — each links to its receipts.
-
The exact bet-pricing curve, recovered from the deployed bytecode. Shares aren't 1:1 with your stake — they're time-weighted by how early you bet. Pumpcade's docs describe the shape (a full-value early window decaying to a ~0.2× floor); the exact formula is published in neither those docs nor the IDL. We disassembled the stripped sBPF binary, found the
place_bethandler by behavior, pulled the IEEE-754 constants out of the soft-float math, and confirmed the curve against live on-chain logs to three decimals. →reverse-engineering/ratio = (end_time - now) / (end_time - creation_time) # fraction of the round still left multiplier = 1.0 if ratio >= 0.9 # flat "full value" head (~first 10%) = 0.8 * (ratio / 0.9)**2 + 0.2 otherwise # quadratic decay to a 0.2 floor shares = floor(bet_amount * 0.99 * multiplier) # 0.99 = after the flat 1% fee -
The published IDL is wrong about error codes from 6002 up — every code is shifted +1, because the live binary has one extra error variant (
ExpectedDurationMustBePositive) the IDL omits. A client that maps errors by the IDL mislabels every failure ≥6002 (e.g. it reads binary6013 = UnauthorizedasNoWinningBets). Corrected table:reverse-engineering/handlers.md. -
create_markettakes two arguments the IDL hides —is_custom: boolandduration: i64, the "custom market" path. This explains the trailing-bytes anomaly in live transactions. →reverse-engineering/handlers.md. -
One key controls everything, and it resolves markets by hand. A single wallet
E9mj…aC3Gzholds program-upgrade authority, IDL authority, factory authority, the treasury, and theresolve_marketsigner. Resolution is one bool the admin signs — no oracle account, no proof, no challenge window, no refunds (re-confirmed by decoding a liveresolve_market: 3 accounts, no price feed). The admin-only constraint is enforced inside the handlers, not in the IDL — proven by submitting non-admin signers and getting Anchor error 6013. →docs/trust-model.md. -
The economics are verified to the lamport, not estimated:
- 1% fee, taken upfront before your stake enters the pool (45 protocol + 10 creator + 45 streamer bps). →
docs/protocol.md - Principal-protected parimutuel payout:
payout = principal + floor(your_winning_shares × losing_pool / total_winning_shares). Winners get their stake back plus a cut of the losing pool; the losing side funds the profit. Checked exact against a live claim tx. →docs/protocol.md - Time-weighted shares observed across 89 positions, ratio spanning 0.20→1.00 — matching the curve above.
- 1% fee, taken upfront before your stake enters the pool (45 protocol + 10 creator + 45 streamer bps). →
-
The frontend runs on devnet by design. cade.market resolves its RPC and cluster from env (
NEXT_PUBLIC_SOLANA_CLUSTER, defaultdevnet); the IDL is no longer shipped in the bundle; auth is Sign-In-With-Solana → JWT; a faucet dispenses 10k test USDC per wallet per 24h. →docs/frontend.md.
- Bet early — the first ~10% of the round mints full-value shares, then share value decays quadratically to a 0.2× floor at the deadline. For a 1-minute market the full-value window is the first ~6 seconds (~30s for a 5-minute one).
- Inside that early window, timing doesn't matter — information does. Every bet in the flat head mints the same shares, so there's no penalty for waiting a few seconds to read the opening odds and the price feed. Sniping is about being informed, not first.
- Read the true odds straight from the chain. Every bet logs its exact multiplier and shares, so a market's real state is readable from logs — no estimation needed.
- Counterparty risk is the main risk. One key decides who wins, by hand, with no oracle and no refunds — and can rewrite the program (curve, fee, payout) at any time. Cap what you put in any single market, claim winnings promptly (it's pull-payment), and re-check after any upgrade.
- Don't map errors by the on-chain IDL — it's off by one from 6002 up. Use the corrected table in
reverse-engineering/handlers.md. create_marketneeds the two hidden trailing args or it fails to deserialize (Anchor error 102).- There are no structured events — the IDL
eventsarray is empty. ParseProgram log:strings (Bet placed: …,Market <id> resolved …,Winnings claimed: …). - The chain enforces less than the backend. On-chain you can use any SPL mint, set an arbitrary
end_time, and run multiple simultaneous markets on one mint (verified — 405 overlapping windows on a single mint). One-per-token and fixed durations are backend rules only.
Seven instructions; discriminators, accounts, and the error enum are in docs/protocol.md. "Exclusive" = no backend equivalent.
| Action | Instruction | Who can call | On-chain-exclusive? |
|---|---|---|---|
| Place a YES/NO bet | place_bet |
any wallet | also via backend /predict |
| Claim winnings | claim_winnings |
any winner | also via backend /solana/claim |
| Read raw Factory/Market/UserPosition state | getAccountInfo |
anyone | chain is source of truth; backend indexes a subset |
| Create a market | create_market |
admin only (handler rejects others, error 6013) | backend /solana/create-market materializes a backend-defined market via the admin |
| Resolve a market (write winning side) | resolve_market |
admin only | yes — no backend endpoint |
| Configure fee bps | configure_fees |
admin only | yes — governance |
| Redirect treasury | update_treasury |
admin only | yes — governance |
| Initialize factory | initialize_factory |
admin only, one-shot | yes — already done at deploy |
Only possible on-chain: the four admin/governance instructions above; betting/claiming directly, bypassing the backend and its JWT; creating a market with an arbitrary end_time and any SPL mint; multiple simultaneous markets on one mint; and reading the full ~52k Market-PDA set vs the backend's indexed ~7.5k.
Full endpoint catalog in docs/frontend.md. "Exclusive" = no on-chain equivalent.
| Action | Endpoint(s) | Backend-exclusive? |
|---|---|---|
| Place a bet / claim / create market | /api/markets/{id}/predict, /api/solana/claim, /api/solana/create-market |
no — relays the on-chain instruction |
| Faucet 10k test USDC (24h/wallet) | /api/faucet |
yes — no on-chain faucet |
| Query markets by type/status, paginate, totals, predictions feed, bot/actor labels, series | /api/markets*, /api/market-series/{id} |
yes |
Market duration & market_type taxonomy (15 live types, series windows) |
/api/extension/market-configs |
yes — chain accepts any end_time |
| Oracle prices (pyth, coingecko, pumpfun, hyperliquid, tomtom, espn, kalshi, checkprice) | /api/{oracle}/… |
yes |
| Jupiter swap quote/route | /api/jupiter/quote, /api/jupiter/swap |
yes |
| Sign-In-With-Solana → JWT; profiles, positions, winnings, balances | /api/auth/*, /api/users/*, /api/user/* |
yes |
| Livestream embeds, practice mode, browser-extension configs | (client + backend) | yes |
ADDRESSES.md— the five core protocol addresses, each verifieddocs/protocol.md— IDL-derived: instructions, PDAs, account schemas, error enum, verified economicsdocs/on-chain-state.md— RPC-decoded: program metadata, Factory, IDL account, live activitydocs/trust-model.md— the authority overlap + the empirical signer testdocs/frontend.md— JS bundle + backend: cluster pinning, host, full endpoint catalogreverse-engineering/— recovering the protocol from raw bytecode: a step-by-step walkthrough ending at the bet-pricing curve, plus a per-handler reverse and the error-enum offsettools/— Python scripts that reproduce every on-chain claim (read/verify)trading/— scripts that act: place/claim bets, snipe new markets, listen to activity, drive the backend (simulate by default,--sendto broadcast)
cp .env.example .env # optional: Solana devnet RPC URL
uv sync
uv run tools/verify_program.py # program ID → ProgramData → upgrade authority
uv run tools/read_factory.py # factory authority, treasury, fees, market_count
uv run reverse-engineering/verify_multiplier.py # confirm the share curve against live bet logsApache-2.0.