Fairness & security
Smart contract
Stakes on OHKO are held by ohko_escrow, an Anchor program on Solana devnet. It escrows
both stakes, pays the winner, splits fees into its vaults, burns $OHKO, runs sit-and-go
tournaments and pays season prizes. The game server cannot move escrowed funds except through
the rules below, and every match can be refunded even if OHKO disappears.
Addresses
- Escrow program
DaKW9RdxAKPd8WDcXuG7j7hU8cccPXg7tzc795mXxh9S - $OHKO mint
BkhwZUpyJWSNks93vpJJGvgfVjPwaSeFokGc2k9xcCjw - Mock USDC mint
23Wz74M4x3RFXJYdtT5UMTLL3jjxBQNizqYQ1bFME3wW - Treasury vault
CZUVbMxMB4otqwfMKiFNpKe74gQobnAymhjkkh1Lc83x - Buyback vault
7ZVgdCGhLaHgKeoezX2ynCPE1tKLJrNHhbYC5Gtp9wJN - Season vault
21YaCJEeW1TLnVJca3CeZnPZhy5ZjzJZPXkAxmeVnNQ3 - Admin
4YpmgCxJ1jfMN1qboWWtCRdanxYusyASuv551cC3fiVH - Referee
9JUj5WyT9dwKdC5dNYtLxrByKy8ZbadHmCtb1bRamC7V - Keeper
HdfLmkoKNQpnsZbHD3x52Y3qQcQ68F3sY92SfvAYq4E4
The program's IDL ships with the OHKO SDK (packages/sdk), which also contains typed
instruction builders, account decoders and an exact copy of the fee math.
Accounts
All accounts are program-derived addresses (PDAs):
| Account | Seeds | Holds |
|---|---|---|
Config | ["config"] | Admin, referee and keeper keys, pause flag, rake per currency, fee split, discount tiers, stake limits per currency, deadlines, prize splits, the allowed mints and vaults |
PlayerProfile | ["profile", wallet] | The wallet, its referrer (set once) and when it was set, match statistics |
Match | ["match", id] | Creator, opponent, currency, stake, format, seed_commitment, state, deadlines, join slot and slot hash, rake applied, snapshotted referrers, result and battle_log_hash. SOL stakes are its lamports |
| Match escrow | ["escrow", match] | USDC or $OHKO stakes, in a token account owned by the match PDA |
Tournament | ["tournament", id] | Size, entry, currency, format, seats, bracket results, prize split and seed commitment. SOL entries are its lamports |
| Tournament escrow | ["tournament_escrow", tournament] | USDC or $OHKO entries |
Vault | ["vault", 0 / 1 / 2] | Treasury, buyback and season vaults (SOL as lamports, tokens in token accounts the vault owns) |
Season | ["season", id] | A published season payout: Merkle root, totals and which entries were paid |
Match states are Open (created, waiting for the opponent), Active (both stakes in), then closed by settlement, a draw, a cancel or a refund. Closed match accounts are deleted and their rent returns to the creator.
Instructions
| Instruction | Who | What it does |
|---|---|---|
initialize_config | Upgrade authority | Creates the config and the three vaults once |
update_config | Admin | Replaces economy parameters and the referee/keeper keys within the hard caps |
set_paused | Admin | Pauses new matches, joins and tournaments; never blocks settlement or refunds |
transfer_admin | Admin | Starts a two-step admin handover |
accept_admin | Pending admin | Completes the handover |
init_player_profile | Player | Creates the player's profile |
set_referrer | Player | Registers a referrer once; never the player itself |
create_match | Creator | Deposits the creator's stake with the server's seed commitment, optionally locked to one opponent |
join_match | Opponent | Deposits the opponent's stake, records the slot hash and makes the match Active |
cancel_match | Creator | Refunds an Open match before anyone joins |
settle_match | Referee | Pays the winner, splits the fee into the vaults, pays referrers, burns $OHKO rake, records battle_log_hash |
settle_draw | Referee | Refunds both players in full |
refund_expired | Anyone | Refunds an Open match after its join deadline, or both players of an Active match after its settle deadline |
create_tournament | Referee | Opens a sit-and-go table with a committed master seed |
join_tournament | Player | Takes a seat and deposits the entry; the last seat starts the tournament |
report_tournament_match | Referee | Records the winner of one bracket match with its log hash |
payout_tournament | Anyone | Pays the podium of a completed bracket and the fees |
refund_tournament | Anyone | Refunds every seat of a table that did not fill or missed its settle deadline |
withdraw_treasury | Admin | Withdraws revenue from the treasury vault |
withdraw_buyback | Keeper | Releases SOL or USDC from the buyback vault for a swap (never $OHKO) |
burn_from_vault | Keeper | Burns $OHKO held by the buyback vault |
publish_season | Admin | Publishes a season's Merkle root and totals (the vault must already hold them) |
distribute_season | Admin | Pays published season entries in batches, each proven against the root and paid once |
Events
Every state change emits an event, so indexers (OHKO's own fee ledger included) can rebuild the history from transactions alone:
| Event | Emitted when |
|---|---|
ConfigInitialized | The config is created |
ConfigUpdated | Parameters or keys change |
PausedSet | The pause flag changes |
AdminTransferStarted | An admin handover starts |
AdminTransferred | The new admin accepts |
ProfileCreated | A player profile is created |
ReferrerSet | A player registers a referrer |
MatchCreated | The creator deposits (includes the seed commitment) |
MatchJoined | The opponent deposits (includes the slot hash) |
MatchCancelled | The creator cancels |
MatchSettled | The referee settles (winner, payout, fee split, log hash) |
MatchDrawn | A draw refunds both players |
MatchRefunded | An expired match is refunded |
ReferralPaid | A referrer is paid during settlement |
ReferralRedirected | A referral too small for an empty wallet stays in the treasury |
OhkoBurned | $OHKO is burned (table fees, tournaments, buybacks) |
VaultWithdrawn | The admin or keeper withdraws from a vault |
TournamentCreated | A tournament table opens |
TournamentJoined | A player takes a seat |
TournamentStarted | The last seat is taken |
TournamentMatchReported | A bracket result is recorded |
TournamentPaidOut | The podium is paid |
TournamentEntryRefunded | One seat is refunded |
TournamentRefunded | Every seat of a table has been refunded |
SeasonPublished | A season's payout root is published |
SeasonPaid | A batch of season entries is paid |
Security model
- Checked math everywhere, with 128-bit intermediates for basis-point math. Rounding dust goes to the treasury (or is burned on $OHKO tables).
- Strict account checks: owners, mints, signers, PDA seeds and account relationships are verified before any handler runs. Payouts can only go to the match's players, the seated players or podium of a tournament, or the configured vaults.
- Hard caps: the rake is at most 10%, splits sum to 100%, discount tiers can only lower the rake, deadlines stay between one minute and 30 days.
- Pause never traps funds: it blocks new matches, joins and tournaments, never settlements, cancels or refunds.
- No minting: the program can burn $OHKO but never mint it.
- Front-running protection:
initialize_configis restricted to the upgrade authority, and a staked match is locked to the paired opponent's wallet. - Every program error has its own entry in OHKO's error catalog, with a plain-language explanation (see FAQ & troubleshooting).
The full threat model with every check per instruction is in
programs/ohko_escrow/SECURITY.md in the repository.
Trust assumptions
- The referee decides results. The program cannot run a battle, so the referee key (held by the game server) reports the winner. It can only pay one of the two players of the match, and only before the settle deadline; every settlement commits a log hash anyone can verify (see Provably fair). A compromised referee could misreport active matches until the key is rotated, but could never pay itself.
- The keeper handles buyback funds. It may withdraw SOL and USDC from the buyback vault (30% of fees since the last buyback) to swap them. Every withdrawal and burn is an on-chain event.
- The admin sets parameters within the caps and withdraws revenue. Mainnet requires a multisig admin.
- The upgrade authority can replace the program. Mainnet requires a multisig or timelock and a verifiable build.
- Holder discounts read current balances at create and join. Borrowing $OHKO to qualify costs nothing but lowers that match's fee by at most 1.5 points; it never risks player funds.
Refund guarantees
- Nobody joined: the creator can cancel at any time, and anyone can refund after the join deadline.
- The referee never settled: after the settle deadline anyone can call
refund_expired, and both players get their full stake back. The OHKO app shows a Claim refund button, and the server's own keeper sends it automatically. - A tournament never filled or stalled: after its deadline anyone can call
refund_tournamentand every seat is refunded. - These refunds work while the program is paused and even if OHKO's servers are gone: any Solana client can send them.