ReadMy.md

Corner Clash

Corner Clash

Corner Clash pairs a broadcast‑style live football engine with on‑chain prediction markets that settle on provably‑verified match data. You watch a World Cup fixture play out as a stylized live simulation, and — while it’s live — bet on outcomes (winner, goals, corners, cards…) through Solana markets whose results are proven against TxLINE’s on‑chain‑verified stats.

It’s one product with two planes working together:

  • The visual plane — play‑by‑play drives a real‑time isometric match animation (ball, players, events, xG, commentary). Built from TexLine earlier but had to move to ESPN bcz Texlin didn’t provide real time player and ball cordinates
  • The truth plane — TxLINE (TxODDS) provides Merkle‑proof‑verifiable scores/stats that act as the settlement oracle for the markets. No trusting our word: outcomes are proven on‑chain.

What it does

  • Live match engine — tune into a live World Cup match or replay a recent one as a stylized isometric simulation, streamed play‑by‑play over SSE. Scorebar, clock, xG, key‑moments feed, and broadcast‑style commentary.
  • On‑chain prediction markets — for the match you’re watching, trade 8 standard markets. Connect a wallet (embedded or external), grab demo tokens, and place bets.
  • Provable settlement — markets resolve against TxLINE’s verifiable score/stat feed, so winners are determined by cryptographically‑checkable facts, not an operator.
  • Lazy market creation — markets don’t need pre‑seeding: the first bet opens the market on‑chain (the platform co‑signs the authorization, the bettor pays the fee) and places the bet atomically.
  • Live trade activity — every on‑chain bet/claim is indexed from a Helius webhook and shown in a real‑time Activity feed per match.

How it works

                          ┌──────────────────────────────────────────────┐
                          │                Railway                        │
  TxLINE (World Cup)  ───▶│  worker (cron)  ─ python worker/main.py ─▶ ┐  │
                          │                                            │  │
  ESPN play‑by‑play ─────▶│  web (Express)                     Postgres │  │
  (positions, xG,         │   ├─ /api/v2/live  (SSE match stream)   ▲   │  │
  players, commentary)    │   ├─ /api/fixtures/list (from DB) ───────┘   │  │
                          │   ├─ /api/trading/*  (Solana markets)        │  │
  Helius webhook ────────▶│   └─ /api/webhooks/helius (trade indexer)    │  │
  (Solana tx)             │                                              │  │
                          └───────────────┬──────────────────────────────┘  │
                                          │ serves the SPA                    │
                                          ▼                                   │
                             React SPA  (/ main · /v0 classic)                │
                             ├─ Match engine (canvas) ◀── ESPN SSE            │
                             └─ Trading panel  ──────────▶ Solana devnet ◀────┘
                                                            (corner_clash program)
  1. A cron worker (worker/main.py) syncs World Cup fixtures from TxLINE into Postgres.
  2. You open / → the SPA loads fixtures from the DB and auto‑plays a live match (or the most recent completed match’s recap).
  3. The match engine opens an SSE stream and animates the isometric pitch from ESPN play‑by‑play (ball travels each pass, players are named, goals/cards/corners animate, commentary captions).
  4. For a live match, the trading panel shows the 8 markets. You connect a Privy Solana wallet, faucet some demo tokens, and bet.
  5. Your first bet on a market creates it on‑chain (platform co‑signs, you pay) and places the bet in one transaction; later bets just buy.
  6. A Helius webhook decodes the program’s events and stores each trade → shown in the Activity tab.
  7. After the match, markets settle against TxLINE‑verified stats; winners claim their payout.

Architecture

Layer What Tech
Frontend Single‑page app. / = match engine + trading panel side‑by‑side; /v0 = classic TxLINE replay. React 19, Vite 6, React Router 7, Privy, @solana/web3.js, Canvas 2D
Backend Express server: serves the SPA + all APIs; proxies TxLINE; streams ESPN over SSE; builds Solana transactions; indexes trades. Node 20+, Express 5, pg, Server‑Sent Events
Database Sole source of truth — users, fixtures, markets, trades. No JSON files in the live path. Railway Postgres
Worker Cron job that syncs World Cup fixtures TxLINE → Postgres. Python 3 (+ psycopg)
Indexer Helius transaction webhook → decodes corner_clash events → stores the trade feed. Helius, Anchor event decoding
On‑chain The betting program (markets, buy, settle, claim). Settlement verifies TxLINE proofs. Solana devnet, Anchor 0.32, SPL tokens

The match engine (why it’s built this way)

The pitch is a 1,500‑line imperative <canvas> renderer (IsometricMatch) — stadium, crowd, players, ball, particle effects, event animations. It’s driven from React via a ref and the useMatch hook (which owns the SSE streaming, replay controller, scoreboard/stat state). Keeping the canvas engine imperative and wrapping it in React is deliberate: reimplementing isometric rendering in the DOM would be slower and pointless. Everything around the canvas (scorebar, controls, feed, picker) is native React.

Two data sources, on purpose

  • ESPN carries spatial data (ball/player field positions, player names, xG, commentary) that TxLINE does not — so ESPN drives the animation.
  • TxLINE carries verifiable data (Merkle‑proof scores/stats) — so TxLINE drives settlement and the “verified on‑chain ✓” score chip.

They’re intentionally decoupled: the animation never blocks on settlement, and settlement never depends on ESPN.


The prediction markets

Eight standard markets are offered per fixture (lib/trading.jsmarketPlans):

Market Outcomes
Match winner Home · Draw · Away
Total goals Over/Under 2.5
{Home} goals Over/Under 0.5
Total corners Over/Under 8.5
{Home} corners Over/Under 4.5
Yellow cards total Over/Under 3.5
First‑half goals Over/Under 0.5
Any red card Yes · No

Each outcome is a predicate over TxLINE stat keys (goals, corners, cards, half‑time goals). Settlement submits the matching TxLINE proof on‑chain, so a market can only resolve to a fact the feed can prove.

On‑chain programs

  • corner_clash — the market program (create_market, buy_position, lock_market, settle_market, claim, void_market, refund). Deployed on devnet: G5usFnHwxSrCJCGpDUbVdYym6dR1b7UTuN4r3bk8dm2H.
  • txoracle — TxLINE’s on‑chain oracle (6pW64gN1s2uqjHkn1unFeEjAwJkPGHoppGvS715wyP2J) providing the verified score/fixture Merkle roots + proofs.

Market creation is authorized by the config admin but paid by the bettor: the server co‑signs a create_market + buy_position transaction, the user signs as fee/rent payer, and submits it — so the first buyer opens the market without the platform funding account rent.


Tech stack

  • Web: React 19 · Vite 6 · React Router 7 · @privy-io/react-auth (embedded + external Solana wallets) · @solana/web3.js · lucide-react · HTML Canvas 2D
  • Server: Node ≥20 · Express 5 · pg (Postgres) · Server‑Sent Events
  • Chain: Solana (devnet) · Anchor 0.32 · SPL Token · Helius (indexing)
  • Data / DB: Railway Postgres · ESPN API · TxLINE (TxODDS) API
  • Worker: Python 3 · psycopg
  • Hosting: Railway (web service + Postgres + cron worker)

Project structure

server.mjs                Express entry (mounts routers, serves the SPA)
core.mjs                  request handlers (TxLINE proxy, ESPN SSE, trading, webhook, static)
routes/                  thin Express routers (misc · live · v2 · trading · webhooks)
lib/
  trading.js             Solana market logic (create/buy/settle/claim, market plans)
  db.js                  Postgres pool + repositories
  helius.js              decode corner_clash events from webhook payloads
trading/                 the React SPA (Vite root)
  src/
    main.jsx             router + Privy provider
    App.jsx              trading panel
    pages/               V2Page (main) · V0Page (classic) · Nav
    match/               match engine (useMatch hook, MatchView, BottomTabs)
      engine/            vendored canvas engine (simulation.js, replay.js, …)
public/                  classic replay app + static assets
contracts/               Anchor workspace (programs/corner_clash)
worker/main.py           fixtures sync (TxLINE → Postgres)
migrations/              SQL schema + runner (scripts/migrate.mjs)
scripts/                 railway start/sync, db migrate, txline setup, devnet smokes

Local development

Prerequisites: Node ≥20, Python 3, a Postgres database (Railway), and (for contract work) the Solana + Anchor toolchains.

1. Install

npm install

2. Configure .env.local (copy from .env.example)

# Live data (TxLINE / TxODDS)
TXLINE_BASE_URL=https://txline-dev.txodds.com
TXLINE_JWT=...
TXLINE_API_TOKEN=...

# Database — REQUIRED (the app is DB‑only, no JSON fallback)
DATABASE_URL=postgresql://...          # Railway Postgres (public proxy host for local)

# Wallet auth
PRIVY_APP_ID=...                        # dashboard.privy.io

# On‑chain admin (config authority) — co‑signs market creation, mints the demo faucet
ADMIN_SECRET_KEY=[12,34,...]            # id.json byte array, or base58

# Trade indexer
HELIUS_WEBHOOK_AUTH=...                 # shared secret for POST /api/webhooks/helius

PORT=4173
HOST=127.0.0.1

3. Prepare the database

npm run db:migrate            # create the schema
python worker/main.py         # sync World Cup fixtures into Postgres (needs DATABASE_URL)

4. Run

# API server (serves the built SPA + APIs)
npm run serve                 # → http://localhost:4173

# Frontend with hot reload (proxies /api to the server above)
npm run web:dev

Open http://localhost:4173/. It auto‑selects a live match, or the most recent completed match’s recap.


Deployment (Railway)

Two services from this one repo, plus a Postgres plugin:

  • webnpm run build (build) → npm start (start). Serves the SPA + APIs. Set HOST=0.0.0.0.
  • worker — a cron service (SERVICE_ROLE=worker, e.g. */5 * * * *) that runs worker/main.py to refresh fixtures in Postgres.

Set the env vars above in each service’s Variables. Create a Helius transaction webhook for the corner_clash program id pointing at POST /api/webhooks/helius (with the HELIUS_WEBHOOK_AUTH header) to power the Activity feed.

Redeploying the on‑chain program (from contracts/, needs the upgrade authority):

anchor build            # or: cargo build-sbf
anchor deploy --provider.cluster devnet

Scripts

Command Purpose
npm run serve Run the Express server (serves built SPA + APIs)
npm run web:dev Vite dev server for the frontend (HMR)
npm run build Build the SPA to dist/
npm run db:migrate / :status Apply / list Postgres migrations
python worker/main.py Sync World Cup fixtures → Postgres
npm run txline:setup Create a free TxLINE devnet subscription + credentials
npm run contracts:smoke:devnet Devnet market create/buy smoke test
npm test Node test runner

0 comments

Loading comments…