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:

InputWho commits itWhen
Operator RNG seed S_iOperatorCommitted as a hash chain *before* play; revealed on-chain afterward (RngReveal).
Client seedPlayerThe player sends commitment = SHA256("v1-client-seed" ‖ clientSeed) *before* the operator acks; the raw seed is revealed at reveal time.
Slot hash H_acceptSolanaA recent slot hash, fixed in the operator ack before the seed is revealed.
Bet nonce + paramsPlayerPart 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 values

If 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.