Skip to content

Repository files navigation

GoalPost 🥅

Trustless peer-to-peer World Cup prop bets on Solana, settled by cryptographic proof — no oracle multisig, no admin keys, no trust.

Built for the TxLINE World Cup hackathon. Two people disagree about a match stat ("There will be more than 9 corners in Brazil vs France"). GoalPost lets them stake tokens on it peer-to-peer, and the smart contract itself verifies the outcome against TxODDS' Merkle-proofed match data anchored on Solana — then pays the winner automatically.


How it Works

  1. Create — Alice locks 25 USDT in a program-owned vault and defines a predicate over TxLINE stat keys: corners(home) + corners(away) > 9. She's betting YES.
  2. Accept — Bob disagrees. He matches her 25 USDT before kickoff. He's NO. The 50 USDT pot sits in a vault PDA that only the program can move.
  3. Settle — when the outcome is provable, anyone (in practice our keeper bot) fetches a Merkle proof from TxLINE's API and submits it on-chain. Our program CPIs into TxLINE's validate_stat instruction, which hashes the proof against the daily Merkle root that TxODDS publishes on-chain. If a single byte of the proof is fake, the whole transaction reverts. If it verifies, our program's deterministic logic picks the winner and pays the entire pot in the same transaction.
  4. Receipt — the market page shows the full proof chain, decoded straight from the settlement transaction. Anyone can re-verify. Nobody had to trust GoalPost, the keeper, or even TxLINE's web API.

The settlement rules (the clever part)

validate_stat proves the stat value is authentic, not that the match is over. GoalPost layers two deterministic rules on top (in lib.rs, settle()):

  • Finality: every proven stat leaf carries the game phase at update time. Phase 5/10/13 = full-time/AET/pens → the value is final, so the predicate result decides the winner in either direction.
  • Monotonicity: goals/cards/corners only ever go up. So a TRUE GreaterThan predicate can settle mid-match — a goal can't be un-scored. (Add preserves this; Subtract doesn't, so handicap markets wait for full-time.)
  • A FALSE predicate on an in-play proof proves nothing → transaction reverts (ProofNotConclusive).
  • Backstops: settle_timeout (NO side collects if nothing was provable by the deadline), cancel_market (unmatched refund), mutual_void (abandoned fixtures).

Running everything

Prerequisites

  • Node 20+ and npm (Windows is fine for everything except the Rust build)
  • WSL2 with the Solana toolchain — only needed to (re)build/deploy the program: anchor-cli 0.31.1, solana-cli 2.x (already set up on this machine)
  • wallet.json at repo root — devnet keypair with a few SOL (already present; airdrop via solana airdrop 2 in WSL if low)
  • .env at repo root with TxLINE credentials (already present; recreate with npm run onboard if the token expires)

0. Install

npm install          # installs all workspaces (web, keeper, shared)

1. TxLINE onboarding (only if .env is missing/expired)

npm run onboard      # guest JWT → subscribe() on-chain → activate API token → writes .env
npm run smoke        # sanity-check every endpoint we depend on

2. The Anchor program (only when you change the Rust)

# in WSL, from /mnt/d/tx-odds-hackathon
anchor build                                   # produces target/deploy/goalpost.so + target/idl/
solana program deploy target/deploy/goalpost.so \
  --program-id target/deploy/goalpost-keypair.json --keypair wallet.json --url devnet

# then copy the fresh IDL into the web app (from Windows):
cp target/idl/goalpost.json apps/web/src/idl/goalpost.json

⚠️ Gotchas that already bit us (don't rediscover them):

  • idls/txoracle.json has TxLINE's pubkey constants stripped — re-vendoring the IDL verbatim breaks the build (Anchor macro bug, FEEDBACK #13).
  • Don't cargo update — Cargo.lock pins many crates for the platform-tools rustc (MSRV hell).

3. Tests (9 devnet integration tests, ~3 min)

npx tsx tests/goalpost.test.ts     # from repo root, plain Windows terminal is fine

Creates a fresh mint + taker each run, exercises every instruction against the deployed program with real Merkle proofs from finished fixture 17952170.

4. The web app

npm run dev -w web                 # → http://localhost:3000
npm run build -w web               # production build (what Vercel runs)

Needs apps/web/.env.local (gitignored, already present):

TXLINE_API_ORIGIN=https://txline-dev.txodds.com
TXLINE_JWT=...                     # same values as root .env
TXLINE_API_TOKEN=...
NEXT_PUBLIC_STAKE_MINT=...         # demo mint (DEMO_MINT in root .env)

Browsing is walletless; connect Phantom/Solflare (set to devnet) to create/accept markets.


Key addresses (devnet)

What Address
GoalPost program 5GH4qp7L5LtfjLNU5ybhQ1RBsZTdDvaQVsoqGp71M5uG
TxLINE txoracle program 6pW64gN1s2uqjHkn1unFeEjAwJkPGHoppGvS715wyP2J
Dev wallet (creator/keeper) 67iphNsk8gDjhMymedYwhvkGSXYKkiyMa5JPCqX1ah3E
Replay fixture 17952170 (finished 2026-06-04, 1-1, corners 11+2)

About

Trustless peer-to-peer World Cup prop markets on Solana, settled by TxLINE cryptographic Merkle proofs. No oracle multisig, no admin keys, no trust.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages