United States Of America
X

Verify a Draw

Use the steps below to independently verify a STIMMY draw. You will download the snapshot JSON, fetch the committed drand round, and reproduce the commitment, the seed and the winning ticket locally.

Steps

  1. Download the snapshot JSON for a draw from the Snapshots page.
  2. Open the draw's Details on the home page and note the drand round number, the commitment hash and the commitment transaction.
  3. Fetch the round from drand at api.drand.sh/52db9ba7…/public/<round> and copy its randomness (a 64-character hex string).
  4. Create a file named snapshot.json and paste the snapshot contents.
  5. Paste the round number and randomness into the code below, then run it with Node (>= 20) to recompute the snapshot hash, the commitment, the seed, the winning ticket and the winner.
  6. Check the commitment: the calldata of the commitment transaction on the Robinhood Chain explorer must equal the computed commit hash, and its block must predate the drand round's publish time. That is what proves nobody knew the randomness when the snapshot was fixed.
  7. Optionally, pick random wallet addresses in the snapshot and cross-check their balances on-chain at the snapshot block (balanceOf at that block number) to further validate the snapshot integrity.
Tip: The seed is keccak256(randomness ‖ snapshotHash) and the winner's ticket is seed % totalTickets. The holder whose ticketRange contains that ticket is the winner.
Stuck: If you are stuck we are happy to help users independently verify, just send us a DM on X.
// Verify a STIMMY draw from public data alone. No trust in the website required.
//
// 1. Download the draw's snapshot JSON from the Snapshots page and save it next
//    to this file as snapshot.json.
// 2. Open the draw's "Details" and copy its drand round number. Fetch the round:
//      https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/<round>
//    and paste its "randomness" below. (Optionally verify the round's BLS
//    signature against quicknet's public key with any drand client.)
// 3. npm install viem   then   node verify.mjs

import { keccak256, stringToHex, concatHex, numberToHex } from "viem";
import snapshot from "./snapshot.json" with { type: "json" };

const DRAND_ROUND = 12345678; // from the draw's details
const RANDOMNESS = "…64 hex chars from api.drand.sh…"; // the round's "randomness" field

// 1. The canonical snapshot serialisation, hashed. Order is snapshot order.
const lines = [
  `${snapshot.block.number}:${snapshot.block.hash}:${snapshot.totalTickets}`,
  ...snapshot.holders.map((h) => `${h.address.toLowerCase()}:${h.balance}:${h.tickets}`),
];
const snapshotHash = keccak256(stringToHex(lines.join("\n")));

// 2. The commitment that was anchored on Robinhood Chain BEFORE the round existed.
//    Compare it to the calldata of the draw's commitment transaction, and that
//    transaction's block timestamp to the round's publish time
//    (1692803367 + (round - 1) * 3, in unix seconds).
const commitHash = keccak256(concatHex([snapshotHash, numberToHex(BigInt(DRAND_ROUND), { size: 8 })]));

// 3. The seed, and from it the winning ticket and the winner.
const seed = keccak256(concatHex([`0x${RANDOMNESS}`, snapshotHash]));
const winningTicket = Number(BigInt(seed) % BigInt(snapshot.totalTickets));
const winner = snapshot.holders.find(
  (h) => winningTicket >= h.ticketRange[0] && winningTicket <= h.ticketRange[1],
);
if (!winner) throw new Error(`No holder contains winningTicket ${winningTicket}`);

console.log("Snapshot hash:  ", snapshotHash);
console.log("Commit hash:    ", commitHash);
console.log("Seed:           ", seed);
console.log("Winning ticket: ", winningTicket);
console.log("Winner:         ", winner);
liberty 1
15-01──────000
treasury stamp