Provably Fair Verification
Provably fair means a settled game can be independently recomputed from public, on-chain data — you do not have to trust Arcade Cafe's word that a result was honest.
Arcade Cafe runs on Protocol V1: a Solana program for custody and settlement, with an off-chain operator that executes games and periodically commits state + bet-log roots on-chain. Every bet is replayable from on-chain inputs, and a provably-bad outcome can be force-refunded on-chain via challenge_outcome.
The Randomness Model
Each outcome is a deterministic function of four inputs, three of which are committed *before* the result is known:
| Input | Who commits it | When |
|---|---|---|
Operator RNG seed S_i | Operator | Committed as a hash chain *before* play; revealed on-chain afterward (RngReveal). |
| Client seed | Player | The player sends commitment = SHA256("v1-client-seed" ‖ clientSeed) *before* the operator acks; the raw seed is revealed at reveal time. |
Slot hash H_accept | Solana | A recent slot hash, fixed in the operator ack before the seed is revealed. |
| Bet nonce + params | Player | Part of the signed bet payload. |
Because the operator commits its seed chain ahead of time and the player commits their seed before the operator picks H_accept, neither side can grind the result: the operator never sees the player's seed before fixing its inputs, and the player can't change their seed after seeing the operator's.
There is no HMAC and no per-game "server seed" — that was the legacy model. V1 uses domain-separated SHA-256 over the four inputs above, matching the on-chain program byte-for-byte.
The Outcome Function
The result is derived by rejection-sampling SHA-256 digests (this is exactly what the on-chain program and the standalone verifier compute):
digest(counter) = SHA256(
DOMAIN, # "v1-dice-outcome" or "v1-sweeper-outcome"
revealedSeed, # 32 bytes, S_i from the on-chain RngReveal
clientSeedRaw, # 32 bytes, the revealed player seed
betNonce, # u64 little-endian
encodedParams, # canonical Borsh-encoded game params
slotHash, # 32 bytes, ack.slotHash
[counter], # 1 byte, 0,1,2,... until an unbiased value is found
)Dice reads the digest in 4-byte big-endian chunks and takes the first value below floor(2^32 / 10000) * 10000 (rejection sampling to avoid modulo bias), then slot = value % 10000 (a roll of slot / 100). Win/payout follow the direction/targetSlot and the 1.5% house edge.
Sweeper reads the digest byte-by-byte and takes the first value below floor(256 / columns) * columns, then resultColumn = value % columns. Each won row compounds the per-row 3% edge; cashout settles at the current multiplier.
What You Need To Verify
After settlement, the bet-log entry and the on-chain RngReveal give you everything:
{
"revealedSeed": "…32-byte hex (RngReveal.seed at ack.rngEpoch/rngIndex)…",
"clientSeedRaw": "…32-byte hex…",
"slotHash": "…32-byte hex (ack.slotHash)…",
"betNonce": 7,
"wager": 1000000,
"params": { "direction": 0, "targetSlot": 5000 },
"claimedSlot": 4217,
"claimedWin": true,
"claimedPayout": 1970000
}To verify:
1. commitment == SHA256("v1-client-seed" ‖ clientSeedRaw) (binds the player seed)
2. SHA256(revealedSeed) chains back to the committed RngReveal head (binds the operator seed)
3. recompute slot/column from the outcome function above
4. recomputed slot/column, win, and payout == the claimed valuesIf any recomputed value differs, the outcome is invalid — and anyone can submit challenge_outcome on-chain to force a RefundCredit for the underpayment.
Independent Verifier
A standalone TypeScript verifier that mirrors the on-chain function is published at `/verifiers/arcade-cafe-proof.ts`. It exports verifyDiceProof and verifySweeperProof, needs only node:crypto (SHA-256), and does not call Arcade Cafe to decide whether a result is valid. Run it against any settled bet's public data.