RATIOED
Docs/06

Verifying on-chain

You don't have to trust our servers about who won. Every settled battle puts its result and a hash of its full metric history on Solana, and the history itself is public.

The program

AccountAddress
ProgramDiy3raLiExkBHky4e2WaPvDfXjCF44q89fYFduY7pck3
Config PDA5Xqrcav4LM1Ai4rpioWR8gmGJgb6TWozaKP78JyB5QQZ
Treasury PDAA9inq8QiFDEWxetvddWtWqHuWzgWFursRhXovN4RRBDZ

Each battle has a Market account at the PDA ["market", battle_id as u64 little-endian]. The pot is held in that account's own balance. Once settled, the Market stores the winner, the score in basis points, the scoring version, the raw metrics as [orig_S0, orig_S1, reply_S0, reply_S1] × [likes, reposts, quotes, replies, views], and snapshot_hash.

Market PDA
import { PublicKey } from "@solana/web3.js";

const id = 1234n; // battle id from the URL: /c/1234
const seed = Buffer.alloc(8);
seed.writeBigUInt64LE(id);
const [market] = PublicKey.findProgramAddressSync(
  [Buffer.from("market"), seed],
  new PublicKey(PROGRAM_ID),
);

snapshot.json

The full history behind a result is served at /c/<id>/snapshot.json as soon as the battle settles. It contains the scoring version (currently v1), the initial and final metrics for both posts, every interval poll in between, and the score breakdown. The response also carries an x-snapshot-sha256 header.

snapshot_hash = sha256(canonical JSON of snapshot.json), where canonical means object keys sorted alphabetically at every level and no whitespace. The file as served may list keys in a different order, so sort before hashing.

How to recompute

  1. Download /c/<id>/snapshot.json, canonicalize it and take the sha256 (script below).
  2. Read the Market account for that battle and hex-encode snapshot_hash. It must equal your hash. If it does, the snapshot is exactly what the oracle committed to at settlement.
  3. Check the metrics: the initial and final values in the snapshot must match the metrics array on the Market account.
  4. Re-run the scoring formula on final − initial. You should get the Market's score_bps and winner.
verify.mjs (Node 18+)
// verify.mjs — node verify.mjs https://<site> <battle-id>
import { createHash } from "node:crypto";

const [base, id] = process.argv.slice(2);
const res = await fetch(`${base}/c/${id}/snapshot.json`);
const snapshot = await res.json();

// Canonical form: object keys sorted (recursively), no whitespace.
const canon = (v) =>
  Array.isArray(v) ? v.map(canon)
  : v && typeof v === "object"
    ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])]))
    : v;

const hash = createHash("sha256").update(JSON.stringify(canon(snapshot)), "utf8").digest("hex");
console.log("recomputed  ", hash);
console.log("site header ", res.headers.get("x-snapshot-sha256"));
// Compare both with Market.snapshot_hash (32 bytes, hex-encode it) read from chain.
Use JavaScript number formatting
The breakdown contains decimals. The hash was computed over JSON.stringify output, so canonicalize with JavaScript (or anything that prints numbers the same way) to get a matching hash.

What this does and doesn't prove

The hash proves the published snapshot is the one used on-chain, and the formula proves the winner follows from it. It can't prove the metrics themselves are what X reported: those come from X's API via our oracle. To limit that trust, the oracle key can only settle a market after its deadline and only once, and the full poll history is public so anyone can compare it with what they saw on X.