Trust and technology
Fair play and verification
Commit-reveal bound to a Solana slot hash, and how to check it.
On this page
The short version
FlySports results are verifiable. Before betting opens, the platform commits to a secret. When betting closes, the race is bound to a hash that Solana only produces afterwards, which nobody could know in advance. After the race settles, the secret is revealed. Anyone can combine the two to get the race seed, re-run the open-source simulation, and check that the winner on-chain is the winner the simulation produces.
We call this commit-reveal bound to a Solana slot hash. We deliberately do not call it “provably fair” or “trustless”, because it does not remove every point of trust. The sections below say exactly what it does and does not give you.
How a race seed is made
- Commit. When a race is created the backend picks a random secret (a salt) and publishes
commit_hash = sha256(salt ‖ race_id)on-chain, together with the race card and a future lock slot. This is before betting opens, so the commitment is fixed before anyone has bet. - Bet. Bets are accepted until the chain reaches the lock slot. The slot was fixed at creation, before anyone could see how the pools filled up, so nobody can choose when to close betting to steer the outcome.
- Lock. After the lock slot, anyone can call
lock_race(it is permissionless). The program stores the hash of that slot from Solana's own record of recent slot hashes. This hash did not exist while bets were open. - Reveal and settle. The settlement oracle reveals the salt. The program checks it against
commit_hash, then computesseed = sha256(salt ‖ lock_slot_hash)itself and stores it. The salt, the seed, the winner and a hash of the full result are all written on-chain. - Verify. The simulation is deterministic: the same seed (and race card) always produces the same race, tick for tick, on any machine. So anyone can re-run it and compare.
Verify a race yourself
The easy way: the Verify button
- Open a settled race from Stats or My bets (the on-chain race page).
- Find the Verifiable, commit / reveal panel. It lists the commit hash, the lock slot hash, the revealed salt and the seed.
- Press Verify. Your browser recomputes
sha256(salt || race_id)and checks it equals the commit hash, then recomputessha256(salt || lock_slot_hash)and checks it equals the seed. Finally the site asks the simulation service to re-run the race for the on-chain seed and race card, and compares the winner and result hash with what the chain recorded. Each check shows pass, fail or unavailable.
The check goes through the site's /api/verify endpoint, which forwards to the simulation service. That service is run by us, so the button is a convenience. The fully independent check is the next one.
The independent way
- Read the race account from Solana devnet with any tool you like (explorer, script or the program's published IDL) and copy its
seed,card,winnerand sport. - Re-compute
sha256(server_salt ‖ lock_slot_hash)yourself and confirm it equals the stored seed. - Run the open-source simulation (the
sim/package, entry pointfly_sim/race_runner.py) locally with that seed and card. You get the same trace, winner and result hash.
What is guaranteed
- The commitment comes first. The salt is committed before betting opens and cannot be changed afterwards without failing the on-chain check.
- The seed cannot be known early. It depends on a slot hash that does not exist until after the lock slot, so the outcome is not computable while bets are open.
- Nobody picks the lock moment. The lock slot is fixed at creation and locking is permissionless.
- The race is reproducible. Every race and every spike in every brain is recomputed identically from the seed, so a result that does not match the simulation can be demonstrated by anyone.
- A stuck race refunds, publicly. An admin cannot cancel a race after it locks. If a locked race is not settled within a timeout, anyone can expire it into full refunds, and the reason is recorded on-chain.
- The pool maths is on-chain. Payouts, the 6% rake and refunds are enforced by the program, which has a solvency test: winners plus rake can never exceed what the race vault holds.
What is not
| Limit | In plain terms |
|---|---|
| Not a VRF | The slot hash is not cryptographically unpredictable randomness, and the slot's leader has some narrow influence over what their slot's hash is. |
| Oracle posts the winner | The settlement oracle submits the winner and result hash. The program checks the salt and derives the seed, but it does not re-run the simulation on-chain, and there is no slashing for a wrong answer. A wrong result is detectable after the fact, not prevented. |
| Choosing not to settle | An operator who dislikes an outcome could decline to settle and let the race expire into refunds. Nothing can force an oracle to settle. The choice is public, permissionless to execute and permanently tagged on-chain, so a pattern would be visible. |
| Operator betting | The rule that the operator does not bet on races it commits salts for is an operational rule, not an enforced one. During the beta, clearly labelled house bots do place small test-USDC bets to keep pools full. |
| Not audited | The program has had an internal security review only. It has not been externally audited. |
| Devnet, test money | All of this is on Solana devnet with test USDC. It is a pre-launch demo, not a production system. |
If you want the full threat model, the project's design note on commit-reveal (ADR-001) and security review document every attack considered, including the ones above, and what was done about each.
When a race gets stuck
If settlement fails (the simulation or the network is down), the race is not left hanging. After the settle timeout (120 seconds by default) anyone can call the expire instruction and every bettor is refunded in full, with no rake. The race page shows “expired” instead of a winner, and your bet panel offers Claim refund. See Refunds.