The operator cannot bias which depositor wins this Slab Showdown lobby. The roll is an RFC 9381 ECVRF output keyed by our on-chain frozen key, with its input bound to the resolved slot hash so it cannot be precomputed. sha256(proof) is committed on-chain. Recompute the binding, verify the ECVRF proof, confirm the roll, and re-derive the weighted winner — right here in your browser.
The widget recomputes the VRF input alpha = sha256(slotHash ‖ lobbyId), verifies the ECVRF proof itself (verifyVRF, RFC 9381) against the operator key, confirms the published roll IS beta[0:32], then re-derives the USD-weighted winner from that roll — 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 (verifyVRF, RFC 9381) under the published operator key, the roll IS beta[0:32], and the USD-weighted winner pick lands on the published winner. The one link not provable from this page alone — that the published key is the one we froze on-chain before the lobby — you confirm on Collector Crypt with the owner / label / memo / alpha / proof above.
3 slabs worth $51 from 2 players — 8Hpv…sDPT won the pot with a 61% share.
import { sha256 } from '@noble/hashes/sha256';
import { verifyVRF, hexToBytes } from '@collectorcrypt/ecvrf';
// alpha = sha256(slotHash(32B) || utf8(lobbyId)) — the public VRF input.
export function vrfAlphaHex(slotHashHex: string, lobbyId: number): string {
const slot = Buffer.from(slotHashHex.replace(/^0x/, ''), 'hex');
const id = new TextEncoder().encode(String(lobbyId));
const msg = new Uint8Array(slot.length + id.length);
msg.set(slot, 0); msg.set(id, slot.length);
return Buffer.from(sha256(msg)).toString('hex');
}
// The published roll IS the VRF output beta[0:32].
export function rollFromBeta(betaHex: string): string {
return '0x' + betaHex.replace(/^0x/, '').slice(0, 64);
}
// USD-weighted winner pick — mirror of pickRoyaleWinnerFromRoll.
// Weight each depositor by round(valueUsd * 1e6) micro-USD, drop the
// service addresses (royale wallet + cold rake-burn), then walk the
// prefix-sum of weights with roll mod totalWeight.
export function weightedWinner(
rollHex: string,
cards: { depositor: string; valueUsd: number }[],
exclude: string[],
): string {
const ex = new Set(exclude);
const w = new Map<string, bigint>();
for (const c of cards) {
if (ex.has(c.depositor)) continue;
const micro = BigInt(Math.round(c.valueUsd * 1_000_000));
w.set(c.depositor, (w.get(c.depositor) ?? 0n) + micro);
}
const rows = [...w.entries()].map(([address, balance]) => ({ address, balance }))
.sort((a, b) => (a.address < b.address ? -1 : a.address > b.address ? 1 : 0));
let total = 0n; const cum: bigint[] = [];
for (const r of rows) { total += r.balance; cum.push(total); }
const off = ((BigInt(rollHex) % total) + total) % total;
let lo = 0, hi = cum.length;
while (lo < hi) { const m = (lo + hi) >> 1; if (cum[m] <= off) lo = m + 1; else hi = m; }
return rows[lo].address;
}
// Verify the ECVRF proof itself in-browser (no server, no CC dependency):
// verifyVRF(hexToBytes(operatorKey), hexToBytes(alpha), hexToBytes(proof)) === true
// Cross-check operatorKey is the on-chain frozen one at
// https://vrf.collectorcrypt.com/#/verify.Node ≥ 20 · no dependencies · same output as the browser widget.