Four steps · one round
Commit > Snapshot > Reveal > Roll
Every 10 minutes ToppsRaffle airdrops a graded Topps Card to one $TOPPS holder. The winner is not picked by us — it is recomputed on-chain from a seed we committed to before the round started.
COMMIT
We publish SHA-256 of the seed before the round opens
A 256-bit server seed is generated and its hash is posted on-chain as an SPL memo from the project wallet before the countdown begins. SHA-256 collisions are not feasible — the seed is locked in.
memo: SHA-256(serverSeed) > on-chain
SNAPSHOT
Every $TOPPS holder balance is recorded at the close slot
When the 10-minute window closes we record a Merkle root of every eligible $TOPPS balance at that exact Solana slot. Nobody touches your wallet — we just take a tamper-evident snapshot. The root ships in the reveal memo so anyone can re-derive it.
snapshotMerkleRoot = MerkleRoot(holders[closeSlot])
REVEAL
We publish the seed itself
Once the slot lands we reveal the serverSeed plus the snapshotMerkleRoot. Anyone hashes the seed to confirm it matches the commit posted before the round.
memo: serverSeed || snapshotMerkleRoot
ROLL
HMAC-SHA-256 picks the winning holder
The roll is HMAC(seed, slotHash || drawId). We walk the prefix-sum of $TOPPS weights — the first holder whose slice covers (roll mod totalSupply) wins the Topps Card for that round.
roll = HMAC-SHA-256(seed, slotHash | drawId)
Section 02
Recompute the roll. Right here. In your browser.
The verifier loads the last shipped rounds and runs the same HMAC-SHA-256 routine the worker runs. The proof is the recompute — if our published roll does not match the one your browser computes, the badge turns red.
Reference implementation
Copy-paste. Run it locally. Same answer.
import { createHmac } from 'node:crypto';
export function rollFromSeed(
serverSeedHex: string,
slotHashHex: string,
drawId: number,
): string {
const mac = createHmac('sha256', Buffer.from(serverSeedHex, 'hex'));
mac.update(Buffer.from(slotHashHex, 'hex'));
mac.update(Buffer.from(String(drawId), 'utf8'));
return mac.digest('hex');
}Node ≥ 20 · No dependencies · Same output as the browser widget.