Dice
Overview
Section titled “Overview”| Property | Value |
|---|---|
| Slug | dice |
| Round type | Single-step |
| RTP | Configurable 80%–99% (default 96%) |
| Modes | Classic (over/under), Range (inside/outside) |
| Roll range | 0–9999 |
| Volatility | Low-High (player-controlled) |
| Devices | Desktop, Mobile |
| Provably Fair | Yes |
Dice is a betting game where the player bets on where a random roll (0–9999) lands. The operator picks one of two UI modes, and the player controls their own risk by choosing how wide their winning window is — the narrower the window, the higher the multiplier.
Game Mechanics
Section titled “Game Mechanics”The operator selects the game mode in the configurator (it changes the slider UI the player sees):
| Mode | Player picks | Bet shape |
|---|---|---|
| Classic | A single target with one slider handle | Roll lands over or under the target |
| Range | A [min..max] window with two handles | Roll lands inside or outside the range |
How It Works
Section titled “How It Works”- The operator has configured a mode (Classic or Range) and an RTP
- The player sets their winning window and side
- The player places a bet
- A random number (0–9999) is rolled
- If the roll lands on the player’s winning side, they win at the computed multiplier
Multiplier & RTP
Section titled “Multiplier & RTP”The multiplier is derived from the player’s winning chance and the configured RTP:
chance = winning_window / 10000multiplier = RTP / chanceThe narrower the window, the higher the multiplier. The maximum window the player may pick is derived from RTP (maxWindow = floor(RTP × 10000) − 100), which keeps the minimum payout strictly above 1×.
Example (RTP 96%):
- Window 5000 (chance 50%) → multiplier ~1.92x
- Window 2500 (chance 25%) → multiplier ~3.84x
- Window 1 (single number) → multiplier ~9600x (the maximum)
Round Flow
Section titled “Round Flow”Dice uses single-step rounds — one bet, one result:
sequenceDiagram
participant Player
participant Game as Dice Game
participant Beexar
participant Wallet as Operator Wallet
Player->>Game: Choose target=75, mode=over, bet=10
Game->>Beexar: Place bet
Beexar->>Beexar: Roll dice → result=82
Note right of Beexar: 82 > 75 → Win!
Beexar->>Wallet: POST /betwin (bet=10, win=39.60, finished=true)
Wallet-->>Beexar: { balance: "529.60", transactions }
Game-->>Player: Show result: 82, You won 39.60!
Configuration Parameters
Section titled “Configuration Parameters”Operators configure dice in the game configurator:
| Parameter | Type | Default | Description |
|---|---|---|---|
rtp | float | 0.96 | Return-to-player, range 0.80–0.99 |
mode | string | range | UI layout: "classic" or "range" |
bet_limits.min | decimal | Configurable | Minimum bet amount |
bet_limits.max | decimal | Configurable | Maximum bet amount |
The roll range (0–9999) and the window bounds are derived from RTP — operators do not set them directly.
Game Result
Section titled “Game Result”The result returned after each round:
| Field | Type | Description |
|---|---|---|
roll | int | Random roll result (0–9999) |
target | int | Player’s chosen target (Classic mode) |
range_min | int | Window lower bound (Range mode) |
range_max | int | Window upper bound (Range mode) |
mode | string | "over", "under", "inside", or "outside" |
won | bool | Whether the player won |
multiplier | float | Applied multiplier |
Provably Fair
Section titled “Provably Fair”Dice uses a per-bet server seed with a pre-commitment (“commit-reveal”):
- Before the bet, the player fetches the SHA-256 commitment of the reserved
server seed via
GET /api/v1/dice/seed/next(server_seed_hash). This call is idempotent — polling it does not rotate or reveal the seed. - The player provides a client seed on the bet.
- The bet reveals the raw
server_seedit used (hash it to verify against the commitment from step 1) and returnsnext_server_seed_hash— the commitment for the next bet, forming a verifiable chain. - The seed rotates on every bet attempt, including a rejected one (e.g.
insufficient funds): the rejected bet’s error carries
next_server_seed_hashinparams, so a revealed seed is never reused.
The roll is a deterministic function of (server_seed, client_seed, nonce, cursor) —
nonce and cursor are 0 for dice (each bet uses a fresh, single-use seed). All four
inputs are recorded so a settled bet can be reproduced from analytics. The exact derivation
a verifier must reproduce byte-for-byte:
In practice it is a one-liner — the first 8 bytes of one HMAC, big-endian, mod 10000:
roll = uint64_be( HMAC_SHA256( key = hex_decode(server_seed), msg = client_seed + ":0:0" )[0:8] ) mod 10000Exactly (the spec a byte-for-byte verifier follows):
commitment = SHA256(server_seed) // server_seed as the 64-char lowercase hex STRING, verbatimkey = hex_decode(server_seed) // 32 RAW bytes — NOT the hex charactersblock(j) = HMAC_SHA256(key, client_seed + ":0:" + j) // nonce = 0; j = 0, 1, 2, … (each block is 32 bytes)keystream = block(0) ‖ block(1) ‖ block(2) ‖ … // concatenated byteslimit = 2^64 − (2^64 mod 10000) // 2^64 mod 10000 = 1616 → limit = 18446744073709550000// read 8-byte big-endian samples from the keystream at offsets 0, 8, 16, 24, 32, …// take the FIRST sample < limit (rejection sampling removes modulo bias):roll = sample mod 10000 // 0..9999 — in practice the very first sample (block(0)[0:8])Three details a correct verifier must get right (the common mistakes):
- the HMAC key is the hex-decoded raw bytes of
server_seed(32 bytes), not the hex characters; - exactly 8 bytes are read per sample, big-endian;
- the mapping uses rejection sampling against
2^64 − (2^64 mod 10000)(taking consecutive 8-byte windows of the keystream until one is below the limit), not a plainmod 10000— this removes modulo bias. The rejection probability is ≈ 9·10⁻¹⁷, so the first sample is used in every real bet.
The commitment hashes the server_seed hex string (verbatim, lowercase): to check a reveal,
compute SHA256(server_seed) and compare it to the server_seed_hash you held before the bet.
Verify it yourself
Section titled “Verify it yourself”A complete, runnable verifier (Node.js — node:crypto):
import { createHmac, createHash } from 'node:crypto'
// Recompute the dice roll from the revealed seed + your client seed.function diceRoll(serverSeed, clientSeed) { const key = Buffer.from(serverSeed, 'hex') // 32 raw bytes — NOT the hex string const N = 10000n const limit = (1n << 64n) - ((1n << 64n) % N) // = 18446744073709550000n // keystream = block(0) ‖ block(1) ‖ …; read 8-byte big-endian samples until one < limit for (let block = 0; ; block++) { const buf = createHmac('sha256', key).update(`${clientSeed}:0:${block}`).digest() // 32 bytes for (let off = 0; off + 8 <= buf.length; off += 8) { const sample = buf.readBigUInt64BE(off) if (sample < limit) return Number(sample % N) // 0..9999 (returns on the very first sample in practice) } }}
// Check the revealed seed matches the commitment you saw before the bet.function verifyCommitment(serverSeed, commitment) { return createHash('sha256').update(serverSeed).digest('hex') === commitment.toLowerCase()}