Every live draw's winner comes from an on-chain ECVRF proof (RFC 9381) under a key we froze on-chain before any draw ran — so the operator cannot bias which holder wins. Verify the proof in your browser or on Collector Crypt. Older draws used commit-reveal; the verifier below auto-detects and checks either.
Paste any draw number. For a VRF draw the widget recomputes the alpha binding, verifies the ECVRF proof itself (verifyVRF, RFC 9381) against the operator key, and confirms the roll IS beta[0:32] - all in your browser. For an older commit-reveal draw it re-runs the same HMAC-SHA-256 recompute. If our published roll does not match, the badge turns red. Paste a PG Packs spin id instead and it opens that pull's own fairness page, which runs the same in-browser ECVRF check on the pack.
Try #2270, #2263, #2245 - or paste a PG Packs spin id from any pull
Want the full story of a single draw — which cards were in the pot, who won at what odds, and this proof, all on one page? Every draw has one: open any row in history and hit its FAIRNESS link, or go to /raffle/fairness/<draw id> directly.
import { verifyVRF, vrfProofToHash } from '@collectorcrypt/vrf-client';
import { sha256 } from '@noble/hashes/sha2';
// alpha binds the resolved slot hash + draw id (32B slot hash || utf8 id)
const alpha = sha256(concat(slotHashBytes, utf8(String(drawId))));
// 1. the ECVRF proof must verify under our FROZEN operator key
const ok = verifyVRF(operatorPubkey, alpha, proof); // RFC 9381 -> true
// 2. the published roll must be the first 32 bytes of the VRF output
const roll = vrfProofToHash(proof).slice(0, 32); // === published rollHexNode ≥ 20 · no dependencies · same output as the browser widget.
The roll is produced the same way everywhere - an ECVRF proof under our frozen key, with alpha bound to a slot hash nobody can precompute. What differs is only what each game does WITH that roll. Every mode has its own per-round fairness page that recomputes the binding, verifies the ECVRF proof in your browser, and confirms the published roll IS beta[0:32].
The roll walks the prefix-sum of every eligible $GACHA balance in the snapshot taken at the close slot - first holder whose slice covers the offset wins.
The roll maps into [0,1) and the spin WON if it lands under your chosen chance. The widget decides WON/LOST locally and flags red if our recorded outcome disagrees.
The roll picks the winner weighted by the USD value each player deposited into the lobby, over the pot recorded when the timer expired.
The band comes from fixed, published odds applied to the roll; the card comes from capped weights inside that band; contention between simultaneous pulls is resolved by the published fallback permutation. All of it runs over the pool snapshot pinned when the pack was bought (snapshotHash + poolVersion), so the pool cannot be edited after the fact.