Skip to content

Commit-Reveal Lifecycle

Every game is a small on-chain state machine. Guesses are locked in as SHA-256 commitments before anything is revealed, and the matchup type itself is hidden behind a second commitment made by the matchmaker — neither player can change their guess based on privileged knowledge.

create_game (P1 + matchmaker cosign)

join_game (P2)

refund_pending (abandoned pairing)

first commit_guess

resolve_timeout — neither committed, both forfeit

second commit_guess

resolve_timeout — committer wins pot

second reveal_guess (payoffs distributed)

resolve_timeout — revealer wins pot

close_game (permissionless rent reclaim)

Created

Active

Refunded

Committing

TimedOut

Revealing

Resolved

The lifecycle in order:

  1. Match — the off-chain matchmaker pairs players and generates a hidden matchup commitment.
  2. Create — P1 calls create_game with the matchup commitment. The matchmaker co-signs to attest the commitment is legitimate (verified against GlobalConfig.matchmaker), preventing a player from substituting their own commitment. P1 pays gas; the matchmaker pays nothing.
  3. Join — P2 calls join_game; the game becomes Active and chat opens.
  4. Chat — anonymous conversation over the WebSocket relay (see Agent Protocol).
  5. Commit — each player submits a SHA-256 commitment of their guess. The on-chain commit order is recorded (first_committer, value 1 = P1 or 2 = P2) — it decides the tiebreak when both players guess correctly in a different-teams match.
  6. Reveal — the first revealer provides their guess preimage plus the matchup preimage; the second revealer provides their guess preimage only, and the game resolves per the payoff matrix.

The guess never exists on-chain (or on any server) in plaintext until reveal:

  1. Commit: the player generates 32 random bytes (preimage R). The last bit encodes the guess: R[31] & 10 = “same team”, 1 = “different team”. The player submits SHA-256(R) as the commitment.
  2. Reveal: the player submits R. The program verifies SHA-256(R) == commitment and extracts guess = R[31] & 1.

The matchup type (same team vs. different teams) uses the identical construction, committed by the matchmaker:

  1. Commit (at creation): the backend generates 32 random bytes R_matchup with R_matchup[31] & 1 encoding the matchup type, and SHA-256(R_matchup) is stored on the Game account as matchup_commitment. R_matchup stays server-side.
  2. Reveal (at first guess reveal): the first player to reveal also submits R_matchup (delivered to them via the reveal_data WebSocket message). The program verifies it against the stored commitment and extracts the matchup type — after both guesses are committed, so neither player can exploit the knowledge.

Timeout payoffs deliberately do not depend on the matchup type (both forfeit, or the sole committer/revealer wins), so a stalled game never needs the matchup reveal.

WindowAnchorElapsed outcome
Active timeout — ~1 hour (7,200 slots)game enters ActiveNeither committed: both stakes forfeit to the pool/treasury
Commit timeout — ~1 hour (7,200 slots)game enters ActiveOne committed: committer wins the full pot; non-committer slashed
Reveal timeout — ~2 hours (14,400 slots)both commits inOne revealed: revealer wins the full pot; non-revealer forfeited

The revealer receiving the full pot (not just a refund) is what removes timeout griefing — otherwise a losing player could refuse to reveal at zero cost while halving the winner’s payout.

InstructionCallerNotes
create_gameP1 + matchmaker cosignMatchmaker attests the matchup commitment; on the frontend this runs session-signed (see Session Keys)
join_gameP2
commit_guess / reveal_guessEach playerAlso available as *_session variants
resolve_timeoutAnyonePermissionless crank once a window elapses
refund_pendingPlayerRecovers a stake from an abandoned pairing (EVM mirror: cancelPending)
close_gameAnyonePermissionless; reclaims Solana rent after resolution (no EVM analogue)

The same lifecycle runs on the EVM contracts with per-phase timeout anchors (resolveTimeout) — see EVM & Cross-Chain for the differences that the chains force.