SnapEX Games: provably fair

How the result of every round is fixed before the bet, the formulas of all six games and a verifier that recomputes any round in your browser.

How it works

  1. Before you bet, you see the SHA-256 hash of the current server seed (64 random hex characters). The seed is fixed from that moment and cannot be changed.
  2. The result also depends on your client seed. You can change it at any time; the hash of the next server seed is published before you choose the client seed for it.
  3. Every round uses the next nonce of your seed pair: 0, 1, 2… One pair is shared by all six games.
  4. When you rotate the pair (the “Provably fair” tab of any game), the server seed is revealed. Check that its SHA-256 matches the hash you saw, then recompute any round played on it — below or with your own script.

“My bets” on every game page shows the server seed hash, client seed and nonce of each round.

Verify a round

The check runs in your browser (Web Crypto), not on our server. The fields are filled with an example pair — not a real player’s round.

Random numbers

Every game takes as many numbers as it needs from the same stream:

hash(server_seed) = SHA256(server_seed)            -- server_seed is a 64-char hex string, hashed as text

block[cursor] = HMAC_SHA256(key = server_seed (as text),
                            message = client_seed + ":" + nonce + ":" + cursor)   -- cursor = 0, 1, 2...
words = each block split into 8 big-endian uint32 values, blocks taken in order
f[i] = word[i] / 2^32                               -- 0 <= f < 1
scale(word, n) = floor(word * n / 2^32)             -- integer from 0 to n-1

Formulas for each game

Dice 1 number · RTP 99%

roll = floor(word[0] * 10001 / 2^32) / 100          -- 0.00 ... 100.00
"Under X" wins if roll < X, chance = X%
"Over X"  wins if roll > X, chance = (100 - X)%
payout = bet * 99 / chance                          -- chance from 0.02% to 98%

Crash 1 number · RTP 99%

crash = max(1.00, floor(99 * 2^32 / (2^32 - word[0])) / 100)
      = max(1.00, floor(99 / (1 - f[0])) / 100)
P(crash >= x) = 0.99 / x  ->  99% RTP for any cash-out target

A round stops by itself at ×10,000 (or where the payout reaches the $10,000 per-round maximum for your stake).

Mines 24 numbers · RTP 99%

pool = [0, 1, ..., 24]
for i = 0 ... 23:  take pool[ scale(word[i], 25 - i) ] out of the pool
the last remaining tile is the 25th
mines = the first N tiles taken
multiplier after k safe tiles = floor(0.99 * C(25, k) / C(25 - N, k) * 100) / 100

Tiles are numbered 0–24 left to right, top to bottom.

Plinko 8 / 12 / 16 numbers · RTP 99%

for each row r:  word[r] >= 2^31  ->  right,  otherwise left
bin = number of "right" bounces (0 ... rows)
payout = bet * table[rows][risk][bin]

The nine tables (multiplier for each bin, from left to right):

8 · low 5.6 2.1 1.1 1 0.5 1 1.1 2.1 5.6
8 · medium 13 3 1.3 0.7 0.4 0.7 1.3 3 13
8 · high 29 4 1.5 0.3 0.2 0.3 1.5 4 29
12 · low 10 3 1.6 1.4 1.1 1 0.5 1 1.1 1.4 1.6 3 10
12 · medium 33 11 4 2 1.1 0.6 0.3 0.6 1.1 2 4 11 33
12 · high 170 24 8.1 2 0.7 0.2 0.2 0.2 0.7 2 8.1 24 170
16 · low 16 9 2 1.4 1.4 1.2 1.1 1 0.5 1 1.1 1.2 1.4 1.4 2 9 16
16 · medium 110 41 10 5 3 1.5 1 0.5 0.3 0.5 1 1.5 3 5 10 41 110
16 · high 1000 130 26 9 4 2 0.2 0.2 0.2 0.2 0.2 2 4 9 26 130 1000

Roulette 1 number · RTP 97.3%

number = scale(word[0], 37)                         -- 0 ... 36, each 1/37
pays (stake included): number x36, dozen / column x3, red-black / even-odd / 1-18 / 19-36 x2
zero loses every bet except a bet on 0

Classic Slots 3 numbers · RTP 96%

stop[i] = scale(word[i], 32)                        -- i = 0, 1, 2; each reel strip has 32 symbols
window of reel i = strip[i][stop-1], strip[i][stop], strip[i][stop+1]   (wrapping around)
lines: middle, top, bottom, two diagonals
payout = bet / 5 * sum of line pays

Reel strips (stop = index from 0) and line pays:

Reel 1 0:melon 1:lemon 2:bell 3:melon 4:orange 5:cherry 6:seven 7:lemon 8:orange 9:lemon 10:grapes 11:gem 12:melon 13:orange 14:melon 15:cherry 16:grapes 17:bell 18:cherry 19:bell 20:lemon 21:orange 22:grapes 23:cherry 24:orange 25:cherry 26:grapes 27:gem 28:cherry 29:lemon 30:cherry 31:lemon
Reel 2 0:cherry 1:grapes 2:gem 3:bell 4:lemon 5:cherry 6:seven 7:cherry 8:orange 9:melon 10:grapes 11:lemon 12:grapes 13:cherry 14:lemon 15:cherry 16:bell 17:orange 18:bell 19:orange 20:melon 21:orange 22:cherry 23:lemon 24:melon 25:lemon 26:gem 27:grapes 28:cherry 29:orange 30:lemon 31:melon
Reel 3 0:melon 1:cherry 2:lemon 3:cherry 4:lemon 5:grapes 6:cherry 7:bell 8:gem 9:orange 10:cherry 11:grapes 12:melon 13:lemon 14:melon 15:grapes 16:cherry 17:orange 18:gem 19:bell 20:lemon 21:orange 22:melon 23:orange 24:lemon 25:bell 26:lemon 27:cherry 28:seven 29:cherry 30:orange 31:grapes

Line pays (units; payout = bet ÷ 5 × sum of the lines)

  • Seven × 3 — 1,000
  • Diamond × 3 — 150
  • Bell × 3 — 80
  • Watermelon × 3 — 40
  • Grapes × 3 — 30
  • Orange × 3 — 25
  • Lemon × 3 — 20
  • Cherry × 3 — 15
  • Any three of bell / gem / seven — 15
  • Cherry + cherry on reels 1–2 — 6

Lines: middle, top, bottom, two diagonals.

Check it yourself (Python 3, no libraries)

import hmac, hashlib, struct

def words(server_seed, client_seed, nonce, count):
    out, cursor = [], 0
    while len(out) < count:
        msg = f"{client_seed}:{nonce}:{cursor}".encode()
        out += struct.unpack(">8I", hmac.new(server_seed.encode(), msg, hashlib.sha256).digest())
        cursor += 1
    return out[:count]

def scale(w, n): return w * n // 2**32

def mines(w, n):
    pool, order = list(range(25)), []
    for i in range(24): order.append(pool.pop(scale(w[i], 25 - i)))
    return sorted(order[:n])

s, c, n = "7e347011f1144055ee29838050723a5663fa660519a1f039aca9ec17924bcbf7", "80ca3b044457", 329
w = words(s, c, n, 24)
print("hash    ", hashlib.sha256(s.encode()).hexdigest())
print("dice    ", scale(w[0], 10001) / 100)
print("crash   ", max(100, 99 * 2**32 // (2**32 - w[0])) / 100)
print("roulette", scale(w[0], 37))
print("plinko16", sum(x >= 2**31 for x in w[:16]))
print("mines(3)", mines(w, 3))
print("slots   ", [scale(x, 32) for x in w[:3]])

For the example pair it prints the same results as the verifier above.

Return to player

Mines 99%, Crash 99%, Dice 99% (exactly 9900 / 10001 = 98.99%), Plinko 98.9–99.1% depending on the table, Roulette 97.3% (European, single zero), Slots 96.07%.