A peer-to-peer football prediction market on Solana β "What do you want to bet on?"
Park The Bus is a fully peer-to-peer prediction market. Bettors post a Request for Quote (RFQ) for any event β a match result, a scorer, or something custom like "Ronaldo to cry" β along with the odds and stake they want. Market makers compete to quote the best odds. The best quote wins, funds escrow in an on-chain program, and a transparent on-chain council settles the outcome. Reputation updates on-chain after every market.
Zero house edge. Zero AMM. Real humans quoting real odds.
Status: Hackathon MVP, live on Solana devnet. The
parkthebusprogram is deployed and initialized, the web app runs against it with real on-chain RFQs in the feed, and the contract is covered by 26 LiteSVM tests (see Project status).
- Why it exists
- How it works
- Architecture
- The on-chain program
- Manipulation prevention
- Tech stack
- Repository layout
- Getting started
- Environment variables
- Project status
- Roadmap
- Disclaimer
Existing platforms each leave the same gap:
| Park The Bus | Polymarket | Betfair | Sportybet | |
|---|---|---|---|---|
| Custom events ("Ronaldo cries") | β | β | β | β |
| User-quoted odds | β | β (AMM) | Partial | β |
| P2P, no house | β | Partial | β | β |
| Parlays | β | β | β | β |
| On-chain manipulation prevention | β | Partial | β | β |
| Open market creation | β | β (governance) | β | β |
| House commission | 0% | ~2% spread | 5% | Baked into odds |
The differentiator: custom-event RFQs + competitive P2P odds negotiation + on-chain manipulation prevention, fully on Solana.
Why Solana specifically: the core mechanic is competitive quoting β market makers post many quotes, most of which never match. That's only rational where posting a quote costs a fraction of a cent. A single market is ~15β20 transactions (1 RFQ + ~10 quotes + accept + council votes + settlement); on Solana that's ~$0.01, on an EVM L1 it's ~$32. This is exactly why Polymarket runs its order matching off-chain β and why Park The Bus can stay fully on-chain and verifiable.
Bettor Market Makers Council Chain
β β β β
β post_rfq (event, odds, β β β
β stake, 0.01 SOL deposit)β β RFQ PDA βββββΆβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β submit_quote (free) β β
β ββββββββββββββββββββββββββββΆ Quote PDAs ββββΆβ
β accept_quote (best) β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββΆ stake + MM β
β β β collateral escrow β
β β β in Position PDA β
β β match plays out β β
β β sign_settlement β β
β βββββββββββββββββββββββββ (threshold votes) β
β β execute_settlement β β
βββββββββββββββββ payout / refund per outcome βββββββββββββββββββββββββββ
β β reputation updated on-chain β
- Bettor posts an RFQ β picks a match (or types a custom event), sets stake, minimum odds, and expiry. A refundable 0.01 SOL spam-prevention deposit is locked. Custom events go to the council for approval; standard events are auto-approved.
- Market makers quote β they browse the open RFQ feed and submit competing quotes. Quotes are free (no SOL locked) β only the winning quote ever pulls collateral.
- Bettor accepts the best quote β this atomically escrows the bettor's stake and pulls the MM's collateral into a per-position PDA. Collateral =
stake Γ (odds β 1), i.e. exactly the bettor's potential winnings. - The council settles β after the match, council members vote on the outcome (
BettorWins/MmWins/Void). Once the vote threshold is met, anyone can callexecute_settlementto release the escrow. - Reputation updates on-chain β win/loss, volume, and dispute history are recorded per wallet, readable by anyone.
Parlays are supported as all-or-nothing units: one MM quotes the whole parlay, each leg's result is recorded on-chain, and any losing leg settles the whole position to the MM.
An AI advisor (Google Gemini, server-side only) gives the bettor pricing guidance, gives the MM an EV/break-even read on their quote, and produces a plain-English summary of any position before signing.
flowchart TD
subgraph Browser["π₯οΈ Web app (React + Vite)"]
UI["Pages & components"]
Wallet["Phantom via wallet-adapter"]
Anchor["@coral-xyz/anchor client"]
end
subgraph Proxy["π Express proxy (server-side keys)"]
AI["/api/ai/* β Gemini advisor"]
Odds["/api/odds β The Odds API"]
Fix["/api/fixtures, /api/results β api-football"]
end
subgraph Solana["βοΈ Solana devnet"]
Program["parkthebus program\n(single program, 11 instructions)"]
PDAs["PDAs: Config Β· MmVault Β· Rfq Β· Quote\nPosition Β· Settlement Β· Reputation"]
end
External["The Odds API Β· api-football Β· Gemini"]
UI --> Wallet
UI --> Anchor
UI -->|/api| Proxy
Anchor -->|signed txs| Program
Program --- PDAs
Proxy --> External
- Frontend talks to the chain directly through the wallet + Anchor client. No backend sits between the user and their funds β signing happens in Phantom.
- The Express proxy exists only to keep third-party API keys server-side (Gemini, odds, fixtures). It never touches funds. If the proxy is unavailable, the app degrades gracefully β fixtures fall back to a bundled World Cup schedule and the AI advisor falls back to templated guidance.
- Escrow is native SOL held as lamports in system-owned PDAs (one per position). There is no SPL token account in the MVP; the USDC-on-mainnet switch is isolated to the escrow module.
A single Anchor program, parkthebus, with rfq / settlement / reputation modules. (The 3-program CPI split is a documented post-MVP refactor β a working single-program loop demos better than a broken three-program one.)
- Program ID:
6xzNc5rA9bMi8DzH1ZMp1CKrnC51XvurYTX5ygaGqm2i - 11 instructions Β· 7 account types Β· native-SOL escrow
| Instruction | Caller | What it does |
|---|---|---|
initialize |
Authority | Registers the council + vote threshold and the RFQ counter |
deposit_collateral |
Market maker | Tops up the MM's collateral vault (quotes stay free) |
post_rfq |
Bettor | Creates the RFQ PDA and locks the 0.01 SOL deposit |
submit_quote |
Market maker | Creates a free competing Quote PDA (no SOL locked) |
accept_quote |
Bettor | Escrows stake + pulls MM collateral, opens a Position |
approve_market |
Council | Approves/rejects a custom-event RFQ awaiting review |
cancel_rfq |
Bettor | Cancels an unmatched RFQ (refunds deposit + rent) |
expire_rfq |
Anyone | Permissionless crank: cleans up an expired, unmatched RFQ |
sign_settlement |
Council | Votes on a matched position's outcome (threshold-gated) |
record_leg_result |
Council | Records one parlay leg's Won/Lost result |
execute_settlement |
Anyone | Releases escrow once the vote threshold is met |
Config (council + threshold + RFQ counter) Β· MmVault (MM collateral) Β· RfqAccount Β· QuoteAccount Β· PositionAccount (escrow + matched odds) Β· SettlementAccount (votes + outcome) Β· ReputationAccount (per-wallet history).
Bettor stakes X SOL at odds Y
Payout if correct = X Γ Y
MM locks = X Γ (Y β 1) β exactly the bettor's potential winnings
Example: 0.5 SOL at 2.4x β payout 1.2 SOL, MM locks 0.7 SOL
Settlement outcomes: BettorWins (bettor takes payout), MmWins (MM takes stake + own collateral back), Void (match postponed/abandoned β both parties refunded). Funds are conserved in all three paths β verified by the test suite.
Enforced on-chain, not by policy:
- Spam deposit β every RFQ locks 0.01 SOL, refunded on clean settlement. Kills junk markets.
- Council auto-exclusion β a council member with an active position in a market cannot vote on its settlement.
- No double-voting β a council pubkey can appear at most once in a settlement's votes; duplicate council keys are rejected at
initialize. - Threshold settlement β funds release only at the configured vote threshold (
size / 2 + 1). No single admin can settle alone. Devnet runs a 2-of-2 council, scalable to N-of-M via the on-chainConfig(no code rewrite). - Kickoff / expiry guards β clock-checked on-chain: no accepting after expiry, no quoting after kickoff.
- Fail-closed β if the council can't reach consensus, funds stay safely escrowed rather than being released incorrectly.
| Layer | Choice |
|---|---|
| Smart contract | Anchor 1.0 (Agave 3.x, Rust) β single parkthebus program |
| Program tests | LiteSVM (Rust, cargo test) β 26 tests |
| Frontend | React 19 + Vite + TypeScript + Tailwind CSS |
| Wallet | @solana/wallet-adapter (Phantom) |
| Chain client | @coral-xyz/anchor + @solana/web3.js |
| Proxy server | Express (TypeScript) |
| AI | Google Gemini (gemini-2.5-flash), server-side only |
| Market data | The Odds API + api-football (with mock fallback) |
| Network | Solana devnet (native SOL; USDC-ready for mainnet) |
ParkTheBus/
βββ programs/parkthebus/ # Anchor program
β βββ src/
β β βββ lib.rs # program entrypoints (11 instructions)
β β βββ instructions/ # one file per instruction
β β βββ state.rs # 7 account types
β β βββ constants.rs Β· error.rs Β· util.rs
β βββ tests/ # LiteSVM Rust tests (rfq / settlement / parlay)
βββ app/
β βββ src/
β β βββ pages/ # Home, NewRFQ, RFQDetail, Portfolio,
β β β # MarketMaker, AdminSettle, Reputation
β β βββ components/ # RFQ card, quote list, parlay builder, AI advisorβ¦
β β βββ hooks/ # useActions (txs), useData (account fetches)
β β βββ lib/ # anchor client, PDAs, api proxy client, formatters
β β βββ idl/ # program IDL + types
β β βββ config.ts # RPC, program id, council, constants
β βββ server/ # Express proxy (Gemini + odds + fixtures)
βββ docs/PRD.md # full product requirements (v2.0)
βββ Anchor.toml Β· Cargo.toml
βββ README.md
- Node.js β₯ 18
- Rust + the Solana/Anchor toolchain (only needed to build/deploy the program) β
curl -fsSL https://www.solana.new/setup.sh | bash
cd app
npm install
npm run dev # http://localhost:5173The app reads its config from app/src/config.ts (sensible defaults baked in). To point at a custom RPC or program, copy app/.env.local and edit:
VITE_SOLANA_RPC=https://devnet.helius-rpc.com/?api-key=YOUR_KEY
VITE_PROGRAM_ID=6xzNc5rA9bMi8DzH1ZMp1CKrnC51XvurYTX5ygaGqm2i
VITE_API_BASE=/apiA free dedicated devnet RPC (Helius/QuickNode/Alchemy) is recommended β the public endpoint rate-limits getProgramAccounts.
The app works without it (bundled fixtures + templated AI), but the proxy lights up live odds, fixtures, and the Gemini advisor.
cd app/server
npm install
cp .env.example .env # fill in keys (all optional)
npm run dev # http://localhost:8787, vite proxies /api β hereanchor build
cargo test # 26 LiteSVM tests
anchor deploy --provider.cluster devnet # needs a funded deploy walletAfter deploying, run initialize with the council pubkeys + threshold, then seed a few demo RFQs.
Frontend (app/.env.local, all optional β defaults in config.ts): VITE_SOLANA_RPC, VITE_PROGRAM_ID, VITE_API_BASE.
Server (app/server/.env, all optional β graceful fallback without them):
| Var | Purpose | Free tier |
|---|---|---|
GEMINI_API_KEY |
AI advisor / MM assistant / contract summary | aistudio.google.com/apikey |
ODDS_API_KEY |
Live implied odds | the-odds-api.com β 500 req/mo |
FOOTBALL_API_KEY |
Fixtures & results | api-football.com β 100 req/day |
PORT |
Proxy port (default 8787) | β |
API keys are only ever used server-side and never reach the browser.
Park The Bus is a hackathon MVP. Honest snapshot:
- β On-chain program β deployed to devnet under the program ID above and initialized with the council Config. 11 instructions, 7 account types, audited across three review passes, 26 LiteSVM tests passing. Funds proven conserved across all settlement outcomes; no path lets a single party force a payout.
- β Web app β all 7 pages and the full RFQ β quote β accept β settle flow are built; the feed reads live RFQ accounts straight from the chain, and the AI advisor is wired end-to-end (frontend β proxy β Gemini) with mock fallback.
- β Live demo data β the devnet feed is seeded with open RFQs (standard, custom, and a parlay) so the market isn't empty.
Remaining for production: mainnet deployment behind a security audit, USDC settlement, and scaling the council (see Roadmap).
- Now β FIFA World Cup 2026, Solana devnet, native SOL.
- Mainnet β security audit, USDC settlement (architecture already isolated).
- Any sport β NBA, cricket, F1.
- Any event β elections, awards, crypto prices.
- Settlement layer as public infrastructure β other apps read the reputation/settlement layer via CPI.
- DAO governance β community-owned council, appeal layer,
$PTB.
The real product is the settlement layer. Betting is just the first use case.
This is experimental, unaudited-for-production software built for a hackathon and running on devnet with test SOL. Do not use with real funds. Prediction markets and betting may be regulated in your jurisdiction β this project is for educational and demonstration purposes only.
See docs/PRD.md for the full product specification.

