Skip to content

chainstacklabs/pumpcade-research

Repository files navigation

pumpcade

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 — see LICENSE.

What it is

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.

What we figured out

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_bet handler 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 binary 6013 = Unauthorized as NoWinningBets). Corrected table: reverse-engineering/handlers.md.

  • create_market takes two arguments the IDL hidesis_custom: bool and duration: 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…aC3Gz holds program-upgrade authority, IDL authority, factory authority, the treasury, and the resolve_market signer. Resolution is one bool the admin signs — no oracle account, no proof, no challenge window, no refunds (re-confirmed by decoding a live resolve_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.
  • The frontend runs on devnet by design. cade.market resolves its RPC and cluster from env (NEXT_PUBLIC_SOLANA_CLUSTER, default devnet); 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.

If you're trading it

  • 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.

If you're building on it

  • 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_market needs the two hidden trailing args or it fails to deserialize (Anchor error 102).
  • There are no structured events — the IDL events array is empty. Parse Program 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.

On-chain vs backend: what each surface can do

On-chain (program 8AEL…dfxB, devnet)

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.

Backend (api-dev.cade.market)

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

What's in this repo

  • ADDRESSES.md — the five core protocol addresses, each verified
  • docs/protocol.md — IDL-derived: instructions, PDAs, account schemas, error enum, verified economics
  • docs/on-chain-state.md — RPC-decoded: program metadata, Factory, IDL account, live activity
  • docs/trust-model.md — the authority overlap + the empirical signer test
  • docs/frontend.md — JS bundle + backend: cluster pinning, host, full endpoint catalog
  • reverse-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 offset
  • tools/ — 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, --send to broadcast)

Reproduce

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 logs

Apache-2.0.

About

Pumpcade protocol research

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages