Canonical SlicePot spec (algorithm id PVPHUB_WHEEL_V2). Immutable: any change ships as a new version, never as a silent edit.
The house does not pick the winner after bets are placed:
V1 (PVPHUB_WHEEL_V1) is legacy: ticket from drand only. New rounds use V2 only.
| algorithmVersion | PVPHUB_WHEEL_V2 |
| drandNetwork | quicknet |
| drandChainHash | 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971 |
| selectionRule | FIRST_ROUND_AT_OR_AFTER_CLOSE_PLUS_LEAD |
| genesis / period | 1692803367 / 3s |
Relays: api.drand.sh, api2.drand.sh, drand.cloudflare.com (path includes chain hash).
Public in round.state.fairness and verify payload (no secret):
serverSeedHashtargetDrandRound, targetDrandTimestampbettingEndsAt, drandLeadMs, drandChainHash, algorithmVersionSecret serverSeed (32-byte hex) is not published until reveal.
time(round) = genesis + (round − 1) × period targetDrandRound = first round with time(round) ≥ bettingEndsAt + drandLeadMs // legacy rounds (FIRST_ROUND_STRICTLY_AFTER_*): first round with genesis + round × period > bettingEndsAt
Canonical JSON (object keys sorted recursively) → manifestHash = SHA256(utf8). Fields:
roundId, mode, targetDrandRoundbets[]: acceptedSequence, betId, userPublicId, amountMinorTicket order: sort by acceptedSequence, then userId. Each player gets a contiguous [from, to] of length amountMinor cents. totalBankMinor = sum(amountMinor).
combinedRandomness = SHA256(`${serverSeed}:${drandRandomness}`)
winningTicket = unbiased(combined, totalBankMinor) ∈ [0, total)Unbiased: 256-bit window + rejection sampling; otherwise material = SHA256(material) up to 64 times.
Winner: userId whose range satisfies from ≤ ticket ≤ to.
resultHash = SHA256(
`PVPHUB_WHEEL_V2:${roundId}:${manifestHash}:${targetDrandRound}:${serverSeedHash}:${combinedRandomness}`
)fee = Σ ceil(bet.amountMinor × feeBps / 10000) winnerPayout = totalBankMinor − fee // default feeBps = 300 (3%); welcome = 500 (5%) // 50¢+50¢ × 5% → ceil(2.5)+ceil(2.5)=6¢, prize 94¢
Fewer than 2 unique players, bets not frozen before the beacon, or drand fail/timeout → CANCELLED and full refund. serverSeed is revealed so you can check it against serverSeedHash. Verify status is cancelled (not fair).
https://letmetest.bid/wheel/fairness/:wheelId/:roundIdClient checklist: algorithm → target round → manifestHash → serverSeedHash → drand BLS → combined → resultHash → ticket → fee/payout.
Dev/load bots (Games.WheelBot) may join. They bet under the same PF rules; seed/drand do not depend on bots. Production bot policy should stay disclosed in the UI.