Prerequisites: Complete the Quickstart guide to set up authentication and subscriptions. 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 devnet
Overview
This guide demonstrates how to validate scores data against on-chain Merkle roots using cryptographic proofs. You’ll learn how to fetch validation data and perform both single-stat and two-stat validations.
Setup
import * as anchor from "@coral-xyz/anchor";
import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import axios from "axios";
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Txoracle as anchor.Program<Txoracle>;
// Create HTTP client with authentication
const httpClient = axios.create({
timeout: 30000,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${jwt}`,
"X-Api-Token": apiToken
},
baseURL: "https://txline.txodds.com",
});
function toBytes32(value: string | number[] | Uint8Array): number[] {
const bytes = Array.isArray(value)
? Uint8Array.from(value)
: value instanceof Uint8Array
? value
: value.startsWith("0x")
? Buffer.from(value.slice(2), "hex")
: Buffer.from(value, "base64");
if (bytes.length !== 32) {
throw new Error(`Expected 32 bytes, received ${bytes.length}`);
}
return Array.from(bytes);
}
function toProofNodes(nodes: Array<{ hash: string | number[] | Uint8Array; isRightSibling: boolean }>) {
return nodes.map((node) => ({
hash: toBytes32(node.hash),
isRightSibling: node.isRightSibling,
}));
}
Fetching Scores Data
Retrieve a snapshot of scores for a specific fixture:
const fixtureId = 17952170;
const response = await httpClient.get(`/api/scores/snapshot/${fixtureId}?asOf=${Date.now()}`);
console.log(`Snapshot for fixture ${fixtureId}:`, response.data);
Search for recent score updates:
const now = new Date();
const targetTime = new Date(now.getTime() - (5 * 300000)); // 25 minutes ago
const epochDay = Math.floor(targetTime.getTime() / 86400000);
const hourOfDay = targetTime.getUTCHours();
const interval = Math.floor(targetTime.getUTCMinutes() / 5);
const updates = await httpClient.get(`/api/scores/updates/${epochDay}/${hourOfDay}/${interval}`);
console.log(`Updates found:`, updates.data);
Single-Stat Validation
Validate a single statistic against on-chain Merkle roots:
// Fetch validation data from API
const response = await httpClient.get("/api/scores/stat-validation", {
params: {
fixtureId: 17952170,
seq: 941,
statKey: 1002
}
});
const validation = response.data;
// Prepare fixture summary
const fixtureSummary = {
fixtureId: new BN(validation.summary.fixtureId),
updateStats: {
updateCount: validation.summary.updateStats.updateCount,
minTimestamp: new BN(validation.summary.updateStats.minTimestamp),
maxTimestamp: new BN(validation.summary.updateStats.maxTimestamp),
},
eventsSubTreeRoot: toBytes32(validation.summary.eventStatsSubTreeRoot),
};
// Prepare Merkle proofs
const fixtureProof = toProofNodes(validation.subTreeProof);
const mainTreeProof = toProofNodes(validation.mainTreeProof);
// Prepare stat to validate
const stat1 = {
statToProve: validation.statToProve,
eventStatRoot: toBytes32(validation.eventStatRoot),
statProof: toProofNodes(validation.statProof),
};
// Define validation predicate
const predicate = {
threshold: 0,
comparison: { greaterThan: {} },
};
// Find the daily scores PDA
const targetTs = validation.summary.updateStats.minTimestamp;
const epochDay = Math.floor(targetTs / (24 * 60 * 60 * 1000));
const [dailyScoresPda] = PublicKey.findProgramAddressSync(
[
Buffer.from("daily_scores_roots"),
new BN(epochDay).toArrayLike(Buffer, "le", 2),
],
program.programId
);
// Execute validation using view (read-only simulation)
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
units: 1_400_000
});
try {
const isValid = await program.methods
.validateStat(
new BN(targetTs),
fixtureSummary,
fixtureProof,
mainTreeProof,
predicate,
stat1,
null, // No second stat
null // No operator
)
.accounts({
dailyScoresMerkleRoots: dailyScoresPda
})
.preInstructions([computeBudgetIx])
.view();
if (isValid) {
console.log("On-chain stat validation passed");
} else {
console.log("On-chain stat validation rejected the predicate");
}
} catch (err) {
console.error("Validation simulation failed:", err);
}
Two-Stat Validation
Validate a comparison between two stats (e.g., score difference). This example builds on the single-stat validation above:
// Fetch validation data including a second stat
const response2 = await httpClient.get("/api/scores/stat-validation", {
params: {
fixtureId: 17952170,
seq: 941,
statKey: 1002,
statKey2: 1003
}
});
const validation2 = response2.data;
// Prepare second stat (stat1 is already defined above)
const stat2 = {
statToProve: validation2.statToProve2,
eventStatRoot: toBytes32(validation2.eventStatRoot),
statProof: toProofNodes(validation2.statProof2),
};
// Define operation and predicate
const op = { subtract: {} };
const predicate2 = {
threshold: 5,
comparison: { lessThan: {} },
};
// Execute two-stat validation (reuses variables from single-stat example)
const isValid2 = await program.methods
.validateStat(
new BN(targetTs),
fixtureSummary,
fixtureProof,
mainTreeProof,
predicate2,
stat1,
stat2,
op
)
.accounts({
dailyScoresMerkleRoots: dailyScoresPda,
})
.preInstructions([computeBudgetIx])
.view();
console.log("Two-stat validation result:", isValid2);
Real-Time Scores Streaming
Subscribe to real-time scores updates:
const streamUrl = "https://txline.txodds.com/api/scores/stream";
const streamResponse = await fetch(streamUrl, {
headers: {
Authorization: `Bearer ${jwt}`,
"X-Api-Token": apiToken,
Accept: "text/event-stream",
"Cache-Control": "no-cache",
},
});
if (!streamResponse.ok) {
throw new Error(`Stream failed: ${streamResponse.status}`);
}
// Reuse the readSseMessages and parseSseData helpers from the Streaming Data guide.
for await (const message of readSseMessages(streamResponse)) {
console.log(message.event ?? "message", parseSseData(message.data));
}
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