The operator cannot bias which holder wins. The roll is an RFC 9381 ECVRF output keyed by our on-chain frozen key, its input bound to the resolved slot hash so it cannot be precomputed, and the winner is the holder whose weighted slice of the snapshot covers the roll. Recompute AND verify everything — the binding, the ECVRF proof, the roll — right here in your browser.
The widget recomputes the VRF input alpha = sha256(slotHash ‖ drawId), verifies the ECVRF proof itself (verifyVRF, RFC 9381) against the operator key, and confirms the published roll IS beta[0:32] — the entire chain, in your browser. Collector Crypt is an independent cross-check that the operator key is the one frozen on-chain.
Verified in your browser: alpha binds the public slot, the ECVRF proof checks out under the published operator key, and the roll IS beta[0:32]. The winner is then the balance-weighted pickWinner(snapshot, roll) walk — the snapshot itself is committed by the Merkle root above (not downloaded here), so the winner + odds rows are informational.
1 card worth $22 opened this window — tiger.sol held 3.77% of the eligible snapshot and won the pot.
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 rollHex
// 3. the winner: map roll into [0,1), walk the snapshot's weight
// prefix-sum — the first holder whose slice covers it wins.Node ≥ 20 · no dependencies · same output as the browser widget.