TxPredict is a full-tournament, trustless prediction market for the FIFA World Cup 2026, built for the TxODDS hackathon on Superteam Earn.
It solves a fundamental Web3 problem: how do you run a decentralized sports betting market without trusting anyone — no oracle admins, no multisig keepers, no centralized price feeds?
The answer: TxLINE's cryptographically signed, Solana-anchored data feeds + a custom Anchor smart contract that CPIs into validate_stat to verify match outcomes on-chain before releasing a single lamport.
| Feature | Description |
|---|---|
| 📡 Real-Time SSE Odds | Live consensus odds from TxLINE StablePrice feed, streamed via SSE |
| ⚽ Full Tournament | All 104 FIFA World Cup 2026 matches, live status tracking |
| 🎯 3 Prediction Markets | Match Winner (1X2), Total Goals (Over/Under 2.5), Both Teams to Score |
| 🤖 Automated Settlement Bot | FastAPI asyncio worker polls scores every 30s, dispatches payouts automatically |
| 🔐 Merkle Proof Verification | Ed25519 signature verification using TweetNaCl — real cryptographic validation |
| ⚓ Anchor Smart Contract | PDA vault escrow with CPI into TxLINE validate_stat for trustless settlement |
| 📊 Live Odds Charts | SVG charts showing real-time odds movement per fixture |
| 🏅 Global Leaderboard | Platform-wide predictor rankings by win rate, profit, and streak |
| 📱 PWA + Mobile | Installable Progressive Web App with hamburger navigation |
| 🛡️ Developer Hub | Interactive Merkle proof terminal with step-by-step verification |
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENT BROWSER (SPA) │
│ Dashboard │ Schedule │ Analytics │ Leaderboard │ Developer Hub │
│ Phantom Wallet + TweetNaCl │
└────────────────────────────┬────────────────────────────────────────┘
│ HTTP / SSE
▼
┌─────────────────────────────────────────────────────────────────────┐
│ FASTAPI BACKEND (Python) │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ REST Proxy │ │ SSE Relay │ │ Settlement Bot │ │
│ │ /api/fixtures │ │ /sse/odds │ │ (asyncio task) │ │
│ │ /api/odds/:id │ │ /sse/scores │ │ Polls every 30s │ │
│ │ /api/scores/:id │ │ │ │ auto-dispatches │ │
│ └─────────────────┘ └──────────────────┘ └────────┬────────┘ │
│ │ │
│ ┌─────────────────┐ ┌──────────────────┐ │ │
│ │ Prediction API │ │ Leaderboard API │ │ │
│ │ /api/predictions│ │ /api/leaderboard│ vault_pda.js │
│ │ submit/portfolio│ │ │ │ │
│ └─────────────────┘ └──────────────────┘ │ │
└───────────────────────────────────────────┬─────────────┼───────────┘
│ │
SSE / REST │ Solana RPC
▼ ▼
┌─────────────────────────────┐ ┌─────────────────────────────────┐
│ TxLINE API │ │ SOLANA BLOCKCHAIN │
│ │ │ │
│ • /fixtures/snapshot │ │ ┌──────────────────────────┐ │
│ • /odds/snapshot/:id │ │ │ TxPredict Anchor Program │ │
│ • /scores/snapshot/:id │ │ │ (PDA Vault Escrow) │ │
│ • /scores/historical/:id │ │ │ │ │
│ • /odds/stream (SSE) │ │ │ • initialize_wager() │ │
│ • /scores/stream (SSE) │ │ │ • place_bet() │ │
│ • /auth/guest/start │ │ │ • settle_wager() ──CPI──►│ │
│ • /token/activate │ │ │ • claim_payout() │ │
│ │ │ │ • reclaim_expired() │ │
│ Cryptographic Layer: │ │ └──────────┬───────────────┘ │
│ • Ed25519 signatures │ │ │ CPI │
│ • Merkle root anchoring │ │ ┌──────────▼───────────────┐ │
│ • validate_stat program │◄──┼──│ TxLINE validate_stat │ │
│ │ │ │ (Merkle proof verifier) │ │
└─────────────────────────────┘ │ └──────────────────────────┘ │
└─────────────────────────────────┘
TxLINE SSE Score Feed Solana Blockchain
│ │
│ GameState: "finished" │
▼ │
Settlement Bot │
├─ Fetch score receipt │
├─ Extract Merkle proof bytes │
└─ Call settle_wager() ──────────────────────► │
CPI to validate_stat
├─ Verify Ed25519 sig ✓
├─ Verify Merkle path ✓
└─ Record winning outcome ✓
│
Winners call claim_payout()
Vault PDA transfers SOL directly
No admin. No trust. Math only.
| # | Endpoint | Method | Purpose |
|---|---|---|---|
| 1 | /auth/guest/start |
POST | Obtain guest JWT for API authentication |
| 2 | /api/token/activate |
POST | Activate API token post on-chain subscription |
| 3 | /api/fixtures/snapshot |
GET | Fetch all 104 World Cup fixture metadata |
| 4 | /api/odds/snapshot/{fixtureId} |
GET | Latest StablePrice consensus odds for a match |
| 5 | /api/scores/snapshot/{fixtureId} |
GET | Current score + match state for a fixture |
| 6 | /api/scores/historical/{fixtureId} |
GET | Complete historical score update timeline |
| 7 | /api/odds/stream |
GET (SSE) | Real-time odds updates via Server-Sent Events |
| 8 | /api/scores/stream |
GET (SSE) | Real-time score updates via Server-Sent Events |
Why a proxy architecture? Our FastAPI backend acts as a secure proxy:
- API JWT/tokens are never exposed to client browsers
- SSE streams are relayed with proper backpressure handling
- Fixture snapshots are cached with concurrent batching (15-connection semaphore)
- Completed match scores are permanently cached to reduce upstream load
Located in anchor_program/
| Instruction | Description |
|---|---|
initialize_wager |
Create PDA escrow for a fixture/market pair |
place_bet |
Deposit SOL into vault; store odds + outcome hash |
settle_wager |
CPI into TxLINE validate_stat — verify Merkle proof on-chain, record winner |
claim_payout |
Winner withdraws stake × odds from vault (2.5% fee deducted) |
reclaim_expired |
Safety: refund user after 48h if oracle never settles |
// Wager state account
wager_pda = find_pda(["wager", fixture_id_le_bytes, market_hash], PROGRAM_ID)
// SOL escrow vault (system-owned, signed by bump)
vault_pda = find_pda(["vault", fixture_id_le_bytes, market_hash], PROGRAM_ID)- Outcome hashes — outcomes are stored as
sha256(outcome_string)to save on-chain space while remaining verifiable - Fixed-point odds —
odds_fp = decimal_odds * 10000avoids floating point in Rust - 2.5% platform fee — deducted atomically from each payout in the same transaction
- 48h expiry — users can always reclaim if oracle is unavailable (no permanent lockup)
- CPI trustlessness —
settle_wagerreverts entirely if the Merkle proof fails; impossible to settle with a forged score
cd anchor_program
# Install Anchor CLI
cargo install --git https://github.com/coral-xyz/anchor avm --locked
avm install 0.30.1 && avm use 0.30.1
# Build
anchor build
# Run tests (local validator)
anchor test
# Deploy to Devnet
anchor deploy --provider.cluster devnetTxPredict's Developer Hub implements real Ed25519 signature verification using TweetNaCl directly in the browser:
TxLINE Score Receipt
│
├─ message bytes (fixture_id + score data)
├─ signature (Ed25519, 64 bytes)
└─ public key (TxLINE signer, 32 bytes)
│
▼
nacl.sign.detached.verify(message, signature, publicKey)
│
├─ PASS → ✓ CRYPTO SECURED
└─ FAIL → ✗ SIGNATURE INVALID
The Merkle path is then validated step-by-step:
- Hash the score leaf node
- Hash with each sibling on the path
- Compare final root against TxLINE's Solana-anchored root
- Python 3.11+
- Node.js 18+
- Phantom Wallet (browser extension) set to Devnet
# 1. Clone the repository
git clone https://github.com/YOUR_USERNAME/txpredict.git
cd txpredict
# 2. Configure environment
cp .env.example .env
# Fill in your TXLINE_JWT and TXLINE_API_TOKEN
# 3. Set up Python backend
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# 4. Start the server
cd src
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# 5. Open http://localhost:8000# Obtain from: https://txline.txodds.com/documentation/quickstart
TXLINE_JWT=your_guest_jwt_token
TXLINE_API_TOKEN=your_activated_api_token
# Optional: "mainnet" or "devnet" (default: mainnet)
TXLINE_NETWORK=mainnet# Generate a devnet vault keypair
solana-keygen new -o .devnet-keypair.json
# Fund it with devnet SOL
solana airdrop 2 $(solana-keygen pubkey .devnet-keypair.json) --url devnet
# Verify balance
solana balance $(solana-keygen pubkey .devnet-keypair.json) --url devnet- Zero framework — Vanilla HTML/CSS/JS, instant load, no build step
- Glassmorphism dark theme —
rgba+backdrop-filter: blur()throughout - Typography — Space Grotesk (UI) + JetBrains Mono (data/terminal)
- SPA routing — Hash-based (
#/,#/schedule, etc.) - SSE streaming — Native
EventSourceAPI matching TxLINE's protocol - PWA —
manifest.json+ service worker for install-to-home-screen - Mobile — Hamburger menu, responsive grid, touch-optimized
txpredict/
├── frontend/ # Vanilla SPA (HTML + CSS + JS)
│ ├── index.html # Single entry point, all views
│ ├── styles.css # Design system + glassmorphism
│ ├── app.js # SPA routing, TxLINE integration, charts
│ ├── manifest.json # PWA manifest
│ └── sw.js # Service worker
│
├── backend/ # FastAPI Python server
│ ├── src/
│ │ ├── main.py # API routes + settlement worker
│ │ ├── txline_client.py # TxLINE API + SSE client
│ │ ├── solana_engine.py # Solana RPC + Anchor program bridge
│ │ └── wagers.json # Active wager database
│ └── requirements.txt
│
├── anchor_program/ # Solana Anchor smart contract
│ ├── programs/txpredict/src/lib.rs # Full PDA escrow program
│ ├── tests/txpredict.ts # Integration tests
│ ├── Anchor.toml # Devnet config
│ └── Cargo.toml
│
├── vault_pda.js # Vault settlement dispatcher
├── subscribe.cjs # On-chain TxLINE subscription
└── Dockerfile # Container build
What we loved:
- SSE streaming maps perfectly to
EventSource— zero latency odds updates - Consistent JSON schema across all endpoints — parsing is straightforward
- Free World Cup tier during the hackathon is extremely generous
- The fixture snapshot gave us everything needed to bootstrap the full schedule
- Merkle proof receipts are a genuinely innovative data integrity primitive
Where we hit friction:
- The on-chain subscription flow requires non-trivial Solana/Anchor knowledge upfront — a simplified "API key" mode for hackathons would help
- The historical scores endpoint's 2-week to 6-hour time window constraint could be documented more prominently
- Would love a WebSocket option alongside SSE for environments where EventSource is unavailable
MIT — Built for the TxODDS World Cup Prediction Markets & Settlement Hackathon on Superteam Earn.