Provably Fair
Every GGC CoinFlip result is determined by a Commit-Reveal scheme — a cryptographically verifiable mechanism where the outcome is locked on-chain before your bet is confirmed. Neither the server nor any player can change the result once committed.
How Commit-Reveal Works
Server generates a secret seed
Before your bet is placed, the server generates a random 32-byte secret seed. It computes: commitment = keccak256(abi.encode(seed, roundId)) and signs keccak256(abi.encode(commitment, roundId)) with the settler's private key. The seed is stored securely and never revealed until settlement.
You place your bet with the commitment
Your wallet calls placeBet(amount, side, commitment, signature). The smart contract verifies the signature is from the trusted settler address, then locks the commitment on-chain. At this point, the outcome is already determined — but neither you nor the server can change it.
Server reveals the seed to settle
After your bet is confirmed on-chain, the server calls settleRound(roundId, seed). The contract verifies keccak256(abi.encode(seed, roundId)) == stored commitment. Then it derives the result: resultSide = uint8(keccak256(abi.encodePacked(seed, roundId, player, betAmount)) % 2). The seed is now public and permanently on-chain.
You verify the result independently
Anyone can verify: (1) compute keccak256(abi.encode(revealedSeed, roundId)) and compare to the stored commitment. (2) Compute keccak256(abi.encodePacked(revealedSeed, roundId, player, betAmount)) % 2 and compare to resultSide. If both match, the result is provably fair.
Step-by-Step Verification Guide
- 1
Find your Round ID
After placing a bet, the Round ID is shown in the Provably Fair sidebar on the CoinFlip page. You can also find it in the
BetPlacedevent emitted by your bet transaction. - 2
Read the round data on BscScan
0x3c4d853F...0B4a1F18Go to Read Contract → getRound(roundId) and enter your Round ID. Note these fields:
commitment— the locked hashrevealedSeed— the secret seed (visible after settlement)player— your wallet addressbetAmount— your bet in wei (18 decimals)resultSide— 0 = Heads, 1 = Tails
- 3
Verify the commitment matches the seed
Run in your browser console (with ethers.js v6) or any Solidity tool:
// Verify commitment const { ethers } = require("ethers"); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); const encoded = abiCoder.encode( ["bytes32", "uint256"], [revealedSeed, roundId] ); const computedCommitment = ethers.keccak256(encoded); console.log("Stored commitment:", commitment); console.log("Computed commitment:", computedCommitment); console.log("Match:", commitment === computedCommitment); // Must be true! - 4
Reproduce the result yourself
Compute the result using the same formula as the smart contract:
// Derive resultSide (same as Solidity contract) const packed = ethers.solidityPacked( ["bytes32", "uint256", "address", "uint256"], [revealedSeed, roundId, player, betAmount] ); const hash = ethers.keccak256(packed); const computedResultSide = Number(BigInt(hash) % 2n); console.log("Computed resultSide:", computedResultSide); // 0 → Heads | 1 → Tails console.log("On-chain resultSide:", resultSide); console.log("Match:", computedResultSide === resultSide); // Must be true!If both checks pass, the game was provably fair.
- 5
Verify the settler's signature (optional)
The settler address is stored on-chain. Call
settlerSigner()on the contract. Verify the signature from the BetPlaced transaction:// The settler signs: keccak256(abi.encode(commitment, roundId)) const sigMessage = ethers.keccak256( abiCoder.encode( ["bytes32", "uint256"], [commitment, roundId] ) ); const recovered = ethers.verifyMessage( ethers.getBytes(sigMessage), signature ); console.log("Recovered signer:", recovered); console.log("On-chain settlerSigner:", settlerSigner); console.log("Match:", recovered === settlerSigner);
Cryptographic Formulas
keccak256(abi.encode(seed, roundId))Locked on-chain before bet. Proves the seed was chosen before the player's bet.
keccak256(abi.encode(commitment, roundId))Signed by settler's private key. Contract verifies ecrecover == settlerSigner.
uint8(keccak256(abi.encodePacked(seed, roundId, player, betAmount)) % 2)Mixes seed with round-specific data. 0 = Heads, 1 = Tails.
resultSide == selectedSideIf the derived side matches the player's chosen side, the player wins.
Security Model
Can the server change the result after you bet?
No. The commitment (keccak256 of the seed + roundId) is locked on-chain before your bet is confirmed. The server must reveal the exact seed that hashes to that commitment — it cannot be changed retroactively.
Can the server refuse to settle?
The settler sweep runs every 15 seconds automatically. If the server fails temporarily, unsettled rounds are retried on recovery. The commitment is already on-chain and the round can be settled by anyone who knows the seed.
Can the result be manipulated by including player/betAmount in the hash?
No. The player address and betAmount are fixed at the time of placeBet. They cannot be changed after the commitment is locked. Including them in the result hash adds extra entropy and prevents the server from pre-computing results for arbitrary bet amounts.
Does the server know the result before you?
Yes — the server generates the seed before your bet. However, it cannot change the seed after your bet is on-chain (the commitment locks it). This is the standard Commit-Reveal trade-off: the operator knows the outcome but cannot manipulate it once committed.
This is equivalent to how Rollbit, Stake.com, and most provably fair casinos operate.
Contract Address
Frequently Asked Questions
Can the house manipulate the result?
No. Once placeBet() is called with the commitment on-chain, the result is determined solely by the seed that was pre-committed. The contract owner cannot influence, predict, or change the outcome after the commitment is recorded.
What if the server goes offline before settling?
The settler sweep runs every 15 seconds. If the server is temporarily unavailable, unsettled rounds are retried automatically on recovery. The commitment and seed are stored in the database and on-chain respectively.
How is this different from Chainlink VRF?
Chainlink VRF uses a decentralized oracle network to generate randomness, so no single party controls the outcome. Commit-Reveal relies on the server generating the seed honestly before the bet. The trade-off is: VRF is more decentralized but slower (~30s) and more expensive (~$0.25/round); Commit-Reveal is faster (~5s) and cheaper but requires trusting that the server committed before seeing your bet.
Why does the result formula include player address and betAmount?
Including player-specific data (address + betAmount) in the result hash prevents the server from pre-computing results for different bet amounts. Even if the server knows the seed, it cannot predict what result a specific player with a specific bet amount will get until the bet is actually placed.
Where is the source code?
The CoinFlipGame contract source is available in the project repository. You can read the full ABI and verified source code on the BscScan explorer page linked above.