Read the game. Make the call.
onside.today · technical documentation
Onside is a live World Cup prediction game you open during the match: a continuous feed of rapid "what happens next" micro-predictions generated from the live match state, priced off the live consensus odds, and settled by the feed minutes later. Streaks multiply your points, friends battle you head to head with coins in escrow, divisions promote and relegate you every knockout round, and every eligible result is provable against TxLINE's on-chain Merkle roots on Solana mainnet. When Onside says you called it, the receipt links to the proof.
Built solo for the TxODDS World Cup Hackathon, Consumer & Fan Experiences track, on TxLINE's real-time data.
- What it is
- How it works
- Exactly how TxLINE is used
- Provable fairness, drawn precisely
- The tour
- Built for the data that exists
- Running it locally
- Repository layout
- Two lines Onside never crosses
During a live match, Onside serves one call at a time: next goal, a corner in the next five minutes, will the trailer draw level, a VAR verdict as the review happens. You answer in one tap (with a two-second undo), the feed settles it minutes later from the live data, and your streak multiplies your points. Around that loop sits a full fan hub: 41 prediction markets spanning live windows, momentum moments, player props from the real lineups, halves, and full time; a match tab of whole-match markets re-priced as the game moves; head-to-head coin battles; a six-tier promotion ladder; global, friends, and per-match leaderboards; a 14-quest earn loop; a shop; and a trophy shelf of collectible moments earned by calling them.
The menu is game-aware by construction: a decided outcome is never offered, markets appear only when the game state creates them (a lead to defend, a deficit to chase), and framings adapt to the match (a goal drought turns "who scores next" into "will anyone score"). Momentum detection arms "call it now" moments on sustained sieges, penalties, and VAR reviews, offered for exactly as long as the moment is live.
- Points are stake-independent, so money never buys rank; coins are play-only and never cashable.
- 15 markets are fully provable on-chain (plus both-teams-to-score on its yes side, and the
correct score provable per selection) via TxLINE's
validate_statinstruction family, verified on mainnet in both directions before anything was labelled. - Placement is server-authoritative: the client sends market, selection, and stake; the server re-derives price, window, and tier at that instant, so timing and odds cannot be forged.
- Installable PWA with web push: settle summaries (one push per pass, however many calls you had riding), kickoff reminders, live drama pings to exactly the users with skin in the game, and direct battle challenges.
TxLINE SSE firehoses (scores + odds)
│
▼
ingest worker (Railway) Next.js web (Railway, onside.today)
pure reducer → canonical state ◄──── SSE relay ── browser (PWA)
durable event log offer menu · placement · receipts
settle loop · notifications shop · battles · boards · profiles
│ ▲
▼ │ LISTEN/NOTIFY
Neon Postgres (18 tables)
│
▼
Solana mainnet · txoracle validate_stat / v2 / v3
(free proof simulation + optional real, app-paid proof transactions)
Two persistent services share one repository and one database. The worker owns the TxLINE streams as a single writer: it maintains canonical match state through a pure reducer (the same reducer replays recordings for development and the demo), persists a durable event log and score-by-second change points, reconciles against snapshots on a timer (the stream is never assumed gap-free), settles calls and battles idempotently, and fires Postgres NOTIFY. The web tier serves the UI, places calls server-authoritatively, relays live state to browsers over SSE fed by a LISTEN on a direct database connection, and renders share cards server-side with zero external asset dependencies. Restarts are safe by construction: state, history, and finality reseed from the database, and every settlement, grant, purchase, and notification is idempotent by unique-index design, so replays and reconciles are no-ops.
The full picture, from the reducer's canonical-truth model to the proof-instruction plans per market, is in docs/TECHNICAL.md.
The whole product runs on TxLINE. Every surface consumed, from lib/txline/:
| Surface | Use |
|---|---|
POST /auth/guest/start |
guest JWT issue and refresh (the two-token auth model) |
GET /api/scores/stream (SSE) |
the spine: every live scout event, consumed by the worker |
GET /api/odds/stream (SSE) |
the live StablePrice consensus, ~200 ticks/second in play |
GET /api/fixtures/snapshot (+ ?startEpochDay=) |
live tracking scope + the full slate for rounds and the bracket |
GET /api/scores/snapshot/{id} |
reconcile source (10s live / 30s idle) + finished-score backfill |
GET /api/scores/updates/{id} |
non-deduped recovery: scorer attribution survives coverage lapses |
GET /api/scores/historical/{id} |
full play-by-play for recordings and the replay harness |
GET /api/odds/snapshot/{id} + /updates/{id} |
odds seeding and recording |
GET /api/scores/stat-validation (v1 + statKeys= v2) |
Merkle proof bundles, including period keys |
GET /api/scores/stat-validation-v3 |
the v3 multiproof bundle (halves multi-stat compute, measured) |
validate_stat / _v2 / _v3 (on-chain) |
one call certifies a settled market's exact claim against TxODDS's root |
The product prices off the consensus line (a live over/under inverts to a remaining-goals mean;
the 1X2 splits it between the teams given the current lead), resolves from the scout feed's
canonical aggregates and player-attributed events, and proves eligible results against the
on-chain roots. What the build surfaced about the feed along the way (the canonical Stats map,
the per-event Id, the StatusId phase enum, review-label semantics, and more) shipped back to
TxODDS as dated, evidence-backed feedback, per the hackathon's feedback requirement.
TxLINE anchors per-match team statistics as Merkle roots on Solana. Onside compiles a settled
call's exact claim into predicates (the full-time result is goals₁ − goals₂ compared with zero; a
correct score is two equality checks; half markets use the per-period stat keys) and runs the
txoracle validation instruction. The label follows the returned verdict, and nothing else:
"Verified on-chain" appears only after a verification ran and returned true, everything else says
"Resolved from feed", and both-teams-to-score is labelled selection-aware because only its yes
side is expressible. Verification is a free simulation; the receipt can also mint a real, branded,
app-paid proof transaction, attributed to the caller's wallet in the memo, that the user never
signs or pays for.
Real minted proofs from production: a full-time result proof and a first-half goals proof over period keys. Every proof path was verified on mainnet in both directions (true claims returning true, false claims returning false) before being trusted.
Two disciplines shaped the build. First, design to the real data: TxLINE's feed carries full play-by-play, lineups, VAR grammar, a possession ladder, and per-period stats, and Onside uses all of it, while features the feed cannot support (xG, player shot counts) are deliberately absent. Second, verify empirically before trusting: the two aggregate score representations disagree in both directions on real matches, review-outcome labels are directionally unreliable, goals arrive as bursts of enrichment copies, and retractions sometimes reference nothing, so the reducer counts a goal only when the canonical aggregate and the confirmed event stream agree, and momentum calls resolve only from observable consequences. Every settlement and money path carries a reconcile simulation that folds the same data repeatedly and asserts exactly-once outcomes; the full verification record is in docs/TECHNICAL.md §12.
Prerequisites: Node 22+, a Postgres database (Neon works well), a Privy app (Google + email login, Solana embedded wallets), a TxLINE API token, and a Solana RPC URL.
npm install
cp .env.example .env # fill in the values; every key is documented in the file
npm run db:migrate # applies the committed Drizzle migrations (0000-0005)
npm run dev # the web app on :3000
npm run worker # the ingest worker, in a second terminalA TxLINE token comes from the free self-serve flow: a guest JWT, one free on-chain subscribe on
Solana for service level 12 (real-time World Cup data), then token activation. The
TxLINE quickstart walks it end to end.
No live match on? The worker has a replay mode that drives the entire pipeline, settlement
included, from a recorded fixture: REPLAY_FILE=<recording.jsonl> npm run worker (plus
REPLAY_SPEED and REPLAY_ODDS_FILE). Recordings are captured with npm run record -- <fixtureId> during the event. Recorded TxLINE data is not redistributed in this repo, per the
hackathon data license.
lib/
txline/ REST + SSE client (two-token auth, framing normalization), record types
ingest/ the pure reducer (canonical truth), store, durable event log, replay driver
engine/ offer menu (41 markets), pricing models, resolvers, settlement, scoring, feed runtime
onchain/ the provable set, validate_stat v1/v2/v3 construction, proof routing + receipts
shop/ catalog, guard-first purchase engine, earned badges, collectibles
earn/ the 14-quest loop (live-computed progress, idempotent claims)
battles/ escrow, lifecycle guards, post-accept tally settlement
divisions/ the six-tier ladder (rank-based, self-healing round-end evaluation)
notify/ the guarded-insert notification core, web push, worker trigger hooks
db/ Drizzle schema (18 tables) + committed migrations + the direct LISTEN connection
worker/ the ingest worker (stream consumers, loops, replay mode) + record/replay CLIs
app/ Next.js App Router: 18 screens + 36 API routes + OG image rendering
components/ the client: match room, live feed, scorebar, boards, shop, shelf, PWA plumbing
public/ brand assets, flags, icons; sw.js is generated per build (stamp-sw.mjs)
docs/ TECHNICAL.md (the full technical documentation) + screenshots
Coins are never cashable, tradeable, or redeemable, which keeps the game a game. And the on-chain layer is read-only verification plus an optional app-paid proof transaction: no custody, no settlement of user funds, and the user's wallet never signs or pays for anything.
onside.today · powered by TxLINE live data · receipts on Solana mainnet







