Prerequisites: Complete the Quickstart guide to set up authentication and subscriptions, and book the fixture you want to validate. The snippets assume
jwt is the guest JWT from /auth/guest/start and apiToken is the value returned by /api/token/activate.API Endpoints: Use
https://txline.txodds.com/api/ for mainnet or https://txline-dev.txodds.com/api/ for devnetProgram setup: Use generated types that match your selected network, and use Program Addresses, Program Reference (Mainnet), or Program Reference (Devnet) for program IDs and PDA derivation.
Overview
On-chain validation proves, inside a Solana transaction, that TxODDS published a particular score record or odds update. For stats, it also checks a condition you choose, such as “Participant 1 scored more goals than Participant 2”. The instruction returnstrue only when the proof matches the published root and the condition holds. Otherwise it returns false; it does not fail the transaction.
Use the same network for the API request, IDL/types, program ID, and on-chain root PDA.
Before You Validate: Book the Fixture
Validation is tied to booking. Before you validate a fixture’s stats or odds, the validating wallet must book that fixture withbook_fixture. Booking creates a fixture_ticket for your wallet and that fixture. validateStatV4 and validateOddsV4 check for it and fail without it. This applies even when your subscription bundle already covers the fixture.
- Book once per fixture, with the wallet that will sign the validations.
- One booking covers every later validation of that fixture, stats and odds alike, with no per-call fee.
- The proof must be for the booked fixture. A ticket for one fixture cannot validate another.
The legacy
validateStat instruction and its /api/scores/stat-validation endpoint are not part of the current program interface. Single-stat and two-stat checks are V4 strategies; see Single-Stat and Two-Stat Checks. Fixture proof validation with validateFixture is a read-only simulation and does not need a booking; see Runnable Devnet Examples.Worked Example: Prove a Match Result
This example proves on-chain that Participant 1 beat Participant 2 in a finished soccer match. In the Soccer Feed, stat key1 is Participant 1’s total goals and key 2 is Participant 2’s.
1
Book the fixture
Pick the match from
GET /api/fixtures-schedule, request a quote, and submit book_fixture, as described in Booking Fixtures. Do this once. If your wallet already has the fixture’s fixture_ticket, skip this step.2
Find the final score record
The match result is in the record whose The historical endpoint covers fixtures that started between two weeks and six hours ago. For a more recent match, take the record from the scores stream or updates instead. See Choosing the Score Sequence.
Action is game_finalised. Take its Seq.3
Fetch the proof
Request both goal totals. The order of
statKeys sets the order of payload.leaves, so leaves[0] is Participant 1’s goals and leaves[1] is Participant 2’s.4
Stage a worker ticket
Send
validateFixtureForTree. It checks the proof’s fixture summary against the day’s on-chain scores root and keeps the verified root in a short-lived worker_ticket. This step does not check the booking.5
Validate the result
Describe the condition as a strategy. Here it is “goals 1 minus goals 2 is greater than 0”.Send an Ed25519 verify instruction for the backend
signature, followed by validateStatV4 with the payload, the strategy, and your fixture_ticket. The program checks the booking, then the proof, then evaluates the strategy. The result is true if the proof matches and Participant 1 won. It is false if Participant 1 did not win or if the proof does not match.6
Close the worker ticket
Send
closeWorkerTicket to reclaim the worker ticket’s rent. The fixture_ticket stays, so later validations of this fixture skip step 1.validateOddsV4. The rest of this page is the reference for each step.
The repository includes runnable devnet examples for both paths in
examples/devnet/scripts. See Runnable Devnet Examples for setup and script mapping.Validation Checklist
Before sending a V4 validation, confirm:- The wallet holds a
fixture_ticketfor the fixture being validated. - The proof came from the same API host as your activated subscription.
- The program ID matches the network used for the proof.
- The root PDA (
daily_scores_rootsordaily_batch_roots) uses the epoch day ofpayload.ts:Math.floor(payload.ts / 86_400_000). - The epoch day is encoded as u16 little-endian (2 bytes).
- Every proof hash is decoded to exactly 32 bytes.
- The fixture ID and sequence number refer to the score update you intend to validate.
- Each strategy index refers to the same position in the requested
statKeysarray.
Setup
Fetching Scores Data
Retrieve a snapshot of scores for a booked fixture:Choosing the Score Sequence
Theseq parameter is not a placeholder. Use the sequence value from a real score record observed through one of the scores data endpoints:
/api/scores/snapshot/{fixtureId}/api/scores/updates/{epochDay}/{hourOfDay}/{interval}/api/scores/historical/{fixtureId}/api/scores/stream
/api/scores/stat-validation-v4 with seq=0. Score sequences start at 1 for a fixture and increment as new score records are produced. Depending on your client and response mapper, the payload field may appear as Seq or seq; use that observed value when requesting the validation proof.
Phase and Status Semantics
Pick the score record whose game phase matches the condition you want to prove. For example, “final score of the first half” should use the record for the completed first-half phase or halftime/rest state, not an arbitrary in-running first-half update. If you validate an in-running first-half record, the predicate means the condition was true at that observed moment. That is different from proving the final first-half result. Check the sport-specific phase tables, such as Soccer Feed, and use the score record’sstatusId, StatusId, gameState, or equivalent phase field when deciding whether a record is suitable for settlement.
For final match outcome settlement on the current devnet and mainnet releases, use a scores record with action=game_finalised. These finalisation records set statusId and period to 100, so the same final-outcome validation path covers regulation-time wins, extra-time wins, penalty wins, and abandoned matches.
V4 Validation
validateStatV4 and validateOddsV4 take their proof material from backend-signed payloads and require the wallet to hold a fixture_ticket for the fixture, created by book_fixture. A fixture_ticket places no limit on the number of V4 validations for its fixture and no per-call payment applies. The V4 instructions are not .view() calls: they are sent in a transaction.
Instruction Sequence
Each validation is three instructions, sent as separate transactions:domain and workerId are chosen by the client. Use one domain per validation type so scores and odds tickets do not share an address; the runnable examples use 0 for odds and 1 for scores. workerId is a u16. The same workerId must be passed to validateFixtureForTree, to validate*V4 as its leading argument, and to closeWorkerTicket. After closeWorkerTicket, validate*V4 and a second closeWorkerTicket fail for that (domain, workerId) until validateFixtureForTree stages it again.
The worker_ticket and fixture_ticket PDAs are derived as described in Program Addresses.
Backend Co-Signing
The co-signature protects you as the client. The on-chain validation only runs on a payload signed by TxODDS’ backend admin key, so a successful validation guarantees that TxODDS, not a third party, produced the data that was verified on-chain.validateStatV4 and validateOddsV4 (and book_fixture) must be preceded, in the same transaction, by an Ed25519 signature-verification instruction. The program reads instructions_sysvar and checks that the preceding instruction verified:
- the public key of the network’s backend admin, and
- a message equal to the SHA-256 hash of the Borsh-encoded payload passed to the validation instruction (
StatValidationPayloadorOddsValidationInputV4).
signature field of the API response (base64). A missing instruction, a different signing key, or a payload that does not hash to the signed message makes the instruction fail. The backend admin public key differs per network; see Program Addresses. Check that the key you build the instruction with is the one published there. If you use a different key, the instruction fails.
payload must be the exact object passed to the validation instruction. Place the returned instruction immediately before the validateStatV4 or validateOddsV4 instruction.
V4 Stat Validation
GET /api/scores/stat-validation-v4 takes fixtureId, seq, and a comma-separated statKeys. Use a real seq from an observed score record. The response is { payload, signature }:
rawMask is 512 bytes larger and does not fit a legacy transaction alongside the Ed25519 instruction; send it as a v1 transaction (@solana/web3.js cannot build these; @solana/kit can). Each leaves[i] corresponds to the i-th key in statKeys.
Single-Stat and Two-Stat Checks
Request one stat key and cover it with asingle predicate to check one stat. Request two keys and use a binary predicate to compare them, for example a score difference. Both run through the same three-step sequence as above.
false. The program returns the boolean result as the instruction’s return data. Read it from the confirmed transaction’s meta.returnData, as the runnable scripts do.
V4 Odds Validation
GET /api/odds/validation-v4 takes messageId and ts of a finalized odds update. The response is { payload, signature }:
validateOddsV4 takes OddsValidationInputV4 (ts, oddsLeafHash, subTreeProof). oddsLeafHash is the SHA-256 of the Borsh-encoded Odds struct built from payload.oddsSnapshot, with fields in the order of the IDL’s Odds type. Use domain 0, pass daily_batch_roots as dailyMerkleRoots in step 1, and map summary.oddsSubTreeRoot to subTreeRoot:
fixture_ticket must be for the odds update’s fixture. When you choose an odds update to validate, check that its FixtureId is bookable, for example by matching it against GET /api/fixtures-schedule. An update for a fixture that cannot be booked can never be validated.
Runnable Scripts
Runnable versions of the sequences above:Common Validation Errors
See Troubleshooting for the full activation, streaming, and validation checklist.
Real-Time Scores Streaming
Subscribe to real-time scores updates:Validation Use Cases
On-chain validation enables trustless verification of:- Trading Settlement - Prove score outcomes for bet settlement
- Conditional Logic - Execute smart contract logic based on verified game stats
- Dispute Resolution - Provide cryptographic proof of game data
- Automated Markets - Settle prediction markets with on-chain verification
- Score Differentials - Validate margins and score differences for complex betting scenarios