A betting market that settles itself using TxLINE's own proof checker, with no admin key and no vote.
Live app: https://daayara.github.io/proofball
Program ID (devnet): 7JNc8D9Pt87apERHFsXoTnibWEvxF9n5wCJJinJopaPv
Track: TxODDS Prediction Markets and Settlement - Superteam Earn Hackathon
- Get devnet SOL from https://faucet.solana.com (needed for transaction fees)
- Open https://daayara.github.io/proofball
- Connect Phantom or Solflare set to devnet
- Click GET TEST TOKENS - the faucet sends 100 tokens to your wallet automatically
- Enter an amount and click BET YES or BET NO on any open market
- View your position on Solana Explorer using the link on the market card
| Match | Condition | Market PDA |
|---|---|---|
| World Cup Final - Spain vs Argentina | Total goals > 2 | Boc3xuvzNCP8XuwAL3nrLU1YbfkP54zQWQVfSPBoLwbv |
| World Cup Final - Spain vs Argentina | Total corners > 9 | GEANj5otGxg3mohx8ketfrnHvZ6rVDcXAAfnwpSsEbbq |
| France vs Spain | Total yellow cards > 4 | 6s1ufwxVcQ1dA3hP25uBZVbwHYbAnKMekgGR6vXFCUy5 |
| England vs Argentina | Total goals > 2 | GjyVpoBamxXsimAo6v5dJg7QdBc5HxQaD6gu2JVKnLyL |
Test token mint: JsjcE84H8Mjgcgarwdz6c7gKCbEryngKmN6YM8BfBDn
Faucet API: https://proofball-faucet.vercel.app/api/faucet?wallet=YOUR_WALLET
Proofball allows anyone to create a yes or no market on a stat from a football match, like "total corners in the match is more than 9" or "team A gets more yellow cards than team B." People put money on yes or no. When the match is done, anyone can call settle and the program asks TxLINE's own on-chain program to check the stat using the Merkle proof TxLINE already publishes. If the condition is true, the yes side wins. If not, the no side wins. Money pays out automatically based on that result.
There is no admin override anywhere in this program. Nobody can decide the outcome by hand. The only way a market settles is by passing TxLINE's own proof check.
Most prediction markets on Solana settle through a price oracle (Pyth) or through a vote (UMA style dispute systems). Sports stats are not a clean price feed and a vote takes time and can be wrong. TxLINE solves the data problem already - it timestamps every match stat and anchors a Merkle root on chain, with proofs you can check yourself. Proofball is built specifically to use that, not to build another generic oracle on top.
The other thing we built on purpose: a way to check a settled market without trusting our own website. That is the verify/ folder. It is a small script that pulls the same proof data straight from TxLINE's API and TxLINE's program and rebuilds the Merkle root by hand. You do not need our frontend to believe a market settled correctly, you can run the script yourself.
- Someone creates a market tied to a fixture id and one or two stat keys (using TxLINE's own encoding, see their soccer feed docs)
- People place positions, yes or no, before the close time
- After the match, anyone calls settle_market with the proof data pulled from TxLINE's
GET /api/scores/stat-validationendpoint - Our program does not check the proof itself. It calls TxLINE's own validateStat instruction through a cross program call and reads back true or false
- The market is marked settled with that result
- Winners call claim_payout and get their share of the pool
We never touch the actual cryptography. TxLINE wrote that code, audited it presumably, and runs it on chain already. We just call it.
GET /api/scores/stat-validation- the off-chain endpoint that returns the proof data for a statGET /api/fixtures/snapshot- upcoming World Cup fixture IDs for market creationvalidateStatinstruction on TxLINE's own program, called through CPIdaily_scores_rootsPDA, derived the same way TxLINE's docs show- Soccer stat key encoding from
/documentation/scores/soccer-feed, theperiod * 1000 + base_keyformula
Program IDs:
- Devnet:
6pW64gN1s2uqjHkn1unFeEjAwJkPGHoppGvS715wyP2J - Mainnet:
9ExbZjAapQww1vfcisDmrngPinHTEfpjYRWMunJgcKaA
The IDL has been pulled and checked. programs/proofball/src/txline.rs now uses the actual discriminator, field names, and types from TxLINE's published program, not guesses from doc examples. Specifically:
validate_stattakes exactly one account,daily_scores_merkle_roots, no signer needed- The real discriminator is hardcoded from the IDL:
[107, 197, 232, 90, 191, 136, 105, 185] - Real type names:
TraderPredicate,StatTerm,ScoresBatchSummary,BinaryExpression, matching their IDL exactly Comparisononly has 3 variants (greater_than, less_than, equal_to), and stat values and thresholds are i32, not i64
One thing confirmed by reading TxLINE's own IDL: their program already ships a full one-to-one trade system (create_trade, settle_trade), but it needs both traders to sign before a trade exists, with fixed stakes agreed up front between two named wallets. There is no pooled market where many people can each put in different amounts on yes or no without already having found a matched counterparty. That is the gap Proofball fills, confirmed against their actual account list, not assumed.
programs/proofball/ the Anchor program - market creation, positions, settlement, payout
app/ vanilla HTML/JS frontend, reads market state from chain on load
docs/ GitHub Pages deployment of the frontend
faucet/ Next.js faucet API deployed on Vercel for test token distribution
verify/ standalone script that checks a settled market without our frontend
scripts/ setup helpers and market creation scripts
tests/ Anchor test for the full create, bet, settle, claim flow
# one time setup
avm install latest
avm use latest
solana-keygen new --no-bip39-passphrase
# get devnet SOL
solana airdrop 2 --url devnet
# build and test the program
anchor build
anchor test
# create markets on devnet
npx ts-node --transpile-only scripts/create-markets.ts
# run the independent verification CLI against a settled market
cd verify
npm install
npm run verify -- <market pubkey>TxLINE endpoints and accounts used:
POST /auth/guest/startto get a guest JWTPOST /api/token/activateto exchange an on-chain subscription for an API tokenGET /api/fixtures/snapshotfor upcoming World Cup fixture IDsGET /api/scores/snapshotfor match score dataGET /api/scores/stat-validationfor Merkle proof data to pass into settle_marketvalidateStatinstruction on TxLINE's deployed program, called through CPI from our Anchor programdaily_scores_rootsPDA, seeded as["daily_scores_roots", epochDay as u16]pricing_matrixPDA, seeded as["pricing_matrix"]token_treasury_v2PDA and its associated Token-2022 account for the subscription flow
What worked well:
The stat key encoding (period * 1000 + base_key) is clean and easy to build markets around. Once you understand the scheme you can construct any prop condition from a fixture without guessing. Having validateStat already handle two-stat combinations on-chain (add or subtract, then compare against a threshold) saved us from writing our own comparison logic. The Merkle proof structure in the IDL is clearly laid out once you find the real IDL file. The discriminator for validateStat being published directly in the IDL was very helpful - it meant we could build the CPI call without guessing at the Anchor hash.
The fact that TxLINE's own program already does one-to-one matched trades and Proofball fills the gap with pooled markets is a real architectural story, not a forced one. Reading the IDL and seeing both programs' trade-offs clearly made the submission stronger. Alex from the TxLINE Discord support team was genuinely responsive and helpful during the build - without his assistance the treasury PDA seed and activation flow would have taken significantly longer to unblock.
Where we hit friction:
The API access flow for hackathon participants was the biggest source of friction. The guest JWT alone returns 403 Missing API Token on every data endpoint. You need two headers (Authorization and X-Api-Token), but the path to getting the second one requires an on-chain subscription plus a call to /api/token/activate. None of this is in the quickstart. A single page for hackathon builders titled "get your free API token in 5 steps" would have saved several hours.
The subscription itself had undocumented constraints. The weeks argument must be a multiple of 4, which you only discover from the on-chain error message. The PDA seed for the treasury is "token_treasury_v2" but there is no mention of this in the docs and the IDL does not show seed definitions for this account. We got it from Discord. The treasury vault is the ATA of that PDA using Token-2022, also not documented anywhere.
The IDL download link on the documentation page returns "Asset not found". The only way to get the real IDL is to copy it manually from the IDL tab in the browser. The camelCase version of the IDL is what Anchor actually needs, but the snake_case version is what you see first and the difference is not called out anywhere.
No order book, no AMM, no liquidity pool design. Single pool, proportional payout, two sides. The point of this project is the settlement trust model, not building another exchange. Wider market types and better odds mechanics are an easy add later once the core settlement path is proven solid.