Core Concept

Provably Fair

Every Flip and Pull outcome is provably fair — buyers can verify the operator didn't rig the result. The trust comes from a commit-reveal flow with on-chain anchoring: the random seed gets hashed and posted to Solana before the result is decided, so the operator can't change it after seeing the outcome.

The cryptographic guarantee

Three primitives:

The flow (Flip example)

// Phase 1: COMMIT (buyer doesn't know the seed yet)
serverSeed = randomBytes(32)                      // 32-byte random
seedHash = SHA-256(serverSeed.toHex())            // 64-char hex string
solanaTx = anchorOnSolana(seedHash)               // memo tx, returns signature

// Server stores: { playId, seedHash, solanaTx, sealedSeed: encrypt(serverSeed) }
// Returns to buyer: { playId, seedHash, solanaTx }
// Buyer can now verify hash exists on Solana (live, on-chain proof)

// Phase 2: REVEAL (after buyer commits payment)
serverSeed = decrypt(sealedSeed)                  // unlock the original
outcome = serverSeed[31] % 2 === 0 ? 'WIN' : 'LOSE'  // last byte parity

// Server returns: { playId, serverSeed, outcome }
// Buyer's UI verifies: SHA-256(serverSeed) === seedHash → matches → fair

The verification path (buyer-side)

A buyer who's suspicious can verify any past play:

  1. Get the play's seedHash + solanaTx + serverSeed + outcome from /api/play/[id]
  2. Open Solana Explorer for the tx signature → confirm the memo contains seedHash
  3. Check the timestamp — was the tx confirmed BEFORE the outcome was revealed?
  4. Run SHA-256(serverSeed) locally → must equal seedHash
  5. Compute the outcome from the seed using the same formula → must equal what the operator returned

If steps 3-5 all pass, the play was fair. If ANY fails, the operator cheated.

Pull-specific differences

Pull works the same way but uses a different selection formula:

winnerIndex = serverSeed[0] % stackSize
winningCard = sortedStack[winnerIndex]

The stack is sorted by card_id before selection so the order in the buyer's UI doesn't affect outcome.

What the buyer sees

On the play result page, ToT shows the verification panel:

Optional: client seed (extra paranoid mode)

For maximum trust, the buyer can supply their own clientSeed at commit time. The server combines:

combinedSeed = SHA-256(serverSeed + clientSeed)

This means even if the operator somehow chose a seed AFTER seeing the buyer's intent, they can't predict the combined output. ToT supports this but doesn't surface it in the default buyer flow (most buyers don't care; the on-chain anchor is enough).

Operator costs

Each Solana memo tx costs ~5,000 lamports = ~$0.00005 USD on mainnet. At 1,000 plays/month that's $0.05 in anchoring fees. Negligible.

Set SOLANA_PRIVATE_KEY + SOLANA_RPC_URL in ToT's env. Without these, anchoring silently disables and plays are SHA-256-only (still verifiable in-browser, but no on-chain proof).

💡 Why this matters for marketing

Most card games (Whatnot mystery boxes, Fanatics flash sales, etc.) have NO buyer-side verification. The operator picks the outcome and the buyer trusts. Provably-fair is your differentiator — every play page links the buyer to the on-chain proof. Make this visible in the UI and on the buyer-facing manual.

Code references