COMMITCLASH
$RPS
DOSSIER.SOL

COMMITCLASH

> WHITEPAPER v0.1

Fully on-chain Rock-Paper-Scissors on Solana. Commit-reveal moves. FIFO matchmaking. Session-key auto-reveal. Real burns. Zero house.

REV 2026-05-03 · DEPLOYED · DEVNET · AUDIT-PATCHED
> SECTION_00

ABSTRACT

COMMITCLASH is a Solana program that lets two players stake $RPS tokens against each other in a single round of Rock-Paper-Scissors. Moves are sealed using a keccak256 commit-reveal scheme so neither player can see the other's choice before locking in their own. Matches are paired via a first-in-first-out queue, settle atomically on-chain, and pay out 85% to the winner. 7.5% of each pot is burned via a real spl_token::burn CPI — decreasing $RPS supply on-chain — and 7.5% accrues to the protocol treasury. There is no house, no oracle, no admin override on game outcomes, and no off-chain matchmaker.

> SECTION_01

VISION

On-chain games today fall into two camps: slow and trustworthy (Ethereum, full settlement on every move, $20+ in fees), or fast and custodial (Web2 wrappers that take wallets but route trades through their own backends). COMMITCLASH chooses neither.

Solana lets us run a full PvP game with cryptographic move secrecy, atomic settlement, and sub-cent fees — entirely on-chain. The design uses a session-key delegation pattern so the user signs one transaction per game while still preserving the security guarantees of two-phase commit-reveal. The result is a game that plays as fast as a Web2 product and settles as cleanly as a smart contract.

DESIGN_PRINCIPLES
  • Custody zero. Tokens never leave the player's wallet except into a program-owned vault, and only when entering a match.
  • Move secrecy. Cryptographically guaranteed via commitment hash. Front-running impossible.
  • No house edge. 85% of every pot returns to a player. The remaining 15% is a flat protocol fee.
  • Real deflation. Half of the protocol fee burns from total supply, on-chain, every game.
  • One-click UX. Session-key delegation keeps the wallet popup count at one per match.
> SECTION_02

PROTOCOL ARCHITECTURE

The COMMITCLASH program is an Anchor 0.31.1 Solana program. State is split across seven account types, all program-derived:

STATE // ACCOUNTS
PDASEEDSPURPOSE
Config["config"]Admin, mint, treasury, reveal timeout. Singleton.
GlobalStats["stats"]Lifetime aggregates: rounds, burned, treasury, volume.
Pool["pool", id]Per-tier pool: entry amount, queue head/tail, next match id.
PoolStats["pool_stats", id]Per-pool counters.
QueueEntry["entry", id, idx]One pending player + commitment + session key.
Match["match", id, mid]Paired game state; commitments, reveals, pot, slot_matched.
PlayerStats["player", wallet]Cross-pool: wins, losses, ties, streak, volume.

State is mutated by fifteen instructions across two parallel surfaces: eight on the $RPS-token side (initialize, initialize_pool, join_solo, join_and_match, reveal, resolve_timeout, cancel_queue_entry, admin_update_config) and seven mirrored on the SOL parallel world for native-SOL pools (initialize_sol_config, initialize_sol_pool, join_sol_solo, join_sol_and_match, reveal_sol, resolve_timeout_sol, cancel_sol_queue_entry). Both surfaces share the same commit-reveal logic, fee splits, and PlayerStats counters.

> SECTION_03

MOVES

Moves are encoded as a single byte: 0x01 ROCK, 0x02 PAPER, 0x03 SCISSORS. The standard cycle applies: Rock crushes Scissors, Paper covers Rock, Scissors cuts Paper. Any other byte rejects with InvalidMove.

Each player generates a fresh 32-byte random nonce per match and computes:

COMMITMENT
commitment = keccak256(
move_byte || nonce || player_pubkey
)

Including the player's pubkey in the preimage prevents commitment replay across players. Including the nonce prevents brute-forcing a 1-of-3 move from the hash alone (3 candidates × 2^256 hashes is computationally infeasible).

> SECTION_04

MATCHMAKING

Each pool maintains a single FIFO queue with strict mutual exclusion: at most one queue entry exists at a time. Two instructions implement the queue contract:

QUEUE_INSTRUCTIONS
  • join_solo — required when queue_tail == queue_head. Stakes the entry amount, registers commitment + session key, increments tail.
  • join_and_match — required when queue_tail > queue_head. Stakes the entry amount, consumes the queued entry, creates a Match account with both commitments + session keys, increments head.
Frontend reads pool state to choose the correct instruction. Race conditions resolve cleanly: a stale call fails with QueueEmpty or QueueNotEmpty and the client retries with the other.
> SECTION_05

SESSION KEYS

A naive commit-reveal protocol requires two wallet signatures per match: one to commit, one to reveal. COMMITCLASH compresses this to a single popup using a delegated signing key.

SESSION_KEY_FLOW
  1. On Play, the frontend generates a fresh ephemeral Ed25519 keypair locally. The public key is the session_key; the secret stays in browser memory + localStorage, never touching a server.
  2. The user signs ONE transaction (join_solo or join_and_match) which registers the session pubkey on-chain alongside their commitment.
  3. When the match is created, the frontend silently builds and submits the reveal transaction signed by the session key. No second wallet popup.
  4. The on-chain handler accepts a reveal signed by either the player's main wallet OR the registered session key for that match's side.
The session key has zero fund authority. Its only on-chain power is calling reveal for its specific match. Compromise of the session key cannot drain wallet funds.
> SECTION_06

ECONOMICS

Each pool defines a fixed entry amount that is locked when a player joins. The pot equals 2× the entry. Distribution depends on outcome:

POT_DISTRIBUTION
OUTCOMEWINNERLOSERBURNTREASURY
WIN85.0%0%7.5%7.5%
TIE42.5%42.5%7.5%7.5%
FORFEIT85.0%0%7.5%7.5%
DUAL TIMEOUT*refundrefund0%0%
* Safety fallback only — never triggered in normal play. Session-key auto-reveal makes this case effectively impossible. The branch exists so funds can never be stranded.

Streak rules: a win increments your current_streak and updates best_streak if higher. A loss resets to zero. A tie freezes the streak — neither increment nor reset. The frontend triggers a celebration animation when current_streak crosses 3, 5, or 10.

> SECTION_07

$RPS TOKENOMICS

MINT // SUPPLY
TOTAL SUPPLY1,000,000,000 $RPS
DECIMALS6
LAUNCHPump.fun bonding curve
MINT AUTHORITYNone (burn-only deflation)
FREEZE AUTHORITYNone
DEFLATION7.5% of every pot, on-chain

$RPS launches via pump.fun with the standard 1B token supply. No mint authority means new $RPS can never be created post-launch. The protocol's only economic mechanic is deflation via game burn: every resolved match permanently destroys 7.5% of its pot via a real spl_token::burn CPI. The mint's supply field decreases on every game and is visible on Solscan, Birdeye, Dexscreener, and Jupiter.

No insider allocation. No vested team supply. No private sale. Every token enters circulation through the pump.fun curve. The protocol takes a 7.5% fee per game which accrues to the treasury and is used for development, marketing, and (when SOL pools are added) buybacks.

Pool entry amounts are fixed and immutable. The first pool is POOL_30K with a 30,000 $RPS entry — approximately $2 USD at the pump.fun graduation market cap of ~$69k. As $RPS price changes, new pool tiers are launched (10k, 100k, 300k, 1M, etc.) by the admin. Existing pool entry amounts never change.

> SECTION_08

SECURITY

The COMMITCLASH program holds player stakes in escrow during a match. Several invariants are enforced on-chain:

INVARIANTS
  • Move secrecy. Reveal verifies keccak256(move ‖ nonce ‖ pubkey) == commitment. A wrong nonce or move rejects with InvalidReveal.
  • Account binding. Vault, treasury, mint, and player token accounts are all PDA-bound or address-pinned via Anchor constraints. Substitution attempts revert.
  • Session-key uniqueness. All four key slots (player_a, session_a, player_b, session_b) must be pairwise distinct. Enforced at join time. Prevents reveal-classifier ambiguity attacks.
  • Liveness. Permissionless cancel_queue_entry after timeout means a stuck queue can always be unblocked. Permissionless resolve_timeout after timeout means a stuck match always eventually settles.
  • Deterministic distribution. All outcomes use exact integer math. Pool entry amounts must be divisible by 20 so 7.5% / 42.5% / 85% splits leave zero dust.
  • Real burn. The 7.5% burn invokes spl_token::burn from the Vault PDA's authority — no mint-level burn authority required, no off-chain dependency.
> SECTION_09

AUDIT

The program was reviewed by an independent code reviewer pre-launch. Findings:

FINDINGS_LOG
  • [H1]Adversarial session-key registration. Player B could set session_key_b equal to player A's wallet, locking A out of revealing with their main wallet and forcing forfeit. Patched: all four key slots must be pairwise distinct, enforced at join time. Validated and redeployed.
  • [M1]Treasury overwrite on admin update. admin_update_config previously rewrote the treasury account on every call, even when the caller only intended to update the timeout. Patched: treasury update now requires explicit update_treasury: bool flag.
  • [H-SOL-1]SolVault rent-exempt drain. The lamport-vault payout helper pay_lamports in the SOL parallel world deducted from the vault PDA without enforcing the rent-exempt minimum, allowing a sequence of payouts to drop the program-owned account below its rent floor and risk eventual purge. Patched: helper now reads Rent::minimum_balance(data_len) and rejects any transfer that would leave the vault below it. Validated and redeployed.
  • [INFO]Move secrecy, account substitution, replay, foreign stat tampering, queue griefing, CPI signer seeds, tie-streak invariance, dust math at 30k tier — all verified secure. 200-game stress test post-patch: 198/200 success rate, 2.64s median reveal time end-to-end.
> SECTION_10

ROADMAP

ROADMAP // TIMELINE
  • [DONE]Devnet program v0.1 — core game live, audited, patched, upgraded.
  • [DONE]Frontend — cyberpunk UI, wallet integration, session-key flow, live event feed, real-time PlayerStats, phase-driven SFX (waiting/win/loss), singleton-poller history with manual-reveal fallback, pre-launch preview-mode gate.
  • [DONE]SOL pools — full parallel instruction surface for native-SOL entry, audited (H-SOL-1 patched), deployed to devnet.
  • [NEXT]$RPS launch — pump.fun token deployment, mainnet program migration, first POOL_30K opens.
  • [Q3]Higher tiers — POOL_100K, POOL_1M opened as $RPS price stabilizes; treasury-funded $RPS buybacks from accumulated SOL fees.
  • [FUTURE]Tournament mode — bracket-style elimination across pools. Prize pools funded by entry percentages.
  • [FUTURE]Multi-sig treasury — Squads migration for treasury custody.
> SECTION_11

ADDRESSES

ON-CHAIN_REFERENCE
PROGRAMDymxJfPVGFD3BD1DWk6KeXaj7uPQhSFo2xXB3A8LuBFG
$RPS MINT (devnet)AyKZ2a5CRZX3sMihAQ6CbBJJPjoYqwL9dneaAS7GFGRL
TREASURY (devnet)8WzgAJPVNDBDQQ5Y1WyVAR7w7q9Y3EvSogZk1rDvhwJC
Mainnet addresses published here on $RPS launch.
> SECTION_12

FAQ

Why commit-reveal instead of just submitting a move?

A simple "submit your move" instruction would leak your move on-chain to anyone watching. The second player would always win. Commit-reveal seals your move cryptographically until both players have committed.

What's a session key and why do I sign only once?

When you click Play, the site generates a fresh single-use keypair in your browser. That key gets registered on-chain alongside your commitment. When you're matched, the site silently uses that key to submit your reveal — no second wallet popup. The session key has no fund authority; it can only sign one specific reveal.

What if my browser crashes mid-match?

You have 10 minutes to come back and reveal. Your localStorage holds the session key. If it's still there, the site auto-reveals as soon as you reload. If localStorage was wiped, a "Reveal Manually" button lets you reveal with your wallet. If you never reveal at all and your opponent did, they win by forfeit after 10 minutes. If neither of you reveals (effectively impossible — would require both players' clients to fail simultaneously), the program refunds both stakes after the timeout.

How do I know the burn is real?

The 7.5% burn calls the SPL Token program's burn instruction directly, which decreases the mint's supply field on-chain. Solscan, Birdeye, Dexscreener, and Jupiter all read this field — you'll see the supply curve drop in real time on every aggregator.

Can the admin steal my stake?

No. Admin authority only covers updating the reveal timeout and the treasury address. Admin cannot pause matches, redirect a vault, change pool entry amounts, or reverse a result. Outcomes are determined entirely by the on-chain verification of revealed moves against committed hashes.

What happens if my opponent never reveals?

After 10 minutes, anyone (including you) can call resolve_timeout on your match. If you revealed but they didn't, you win by forfeit and receive 85% of the pot. Standard 7.5% burn and 7.5% treasury fees still apply.

Is there an audit?

Pre-launch independent review across both surfaces found one HIGH-severity griefing attack on the $RPS surface, one MEDIUM-severity admin operational concern, and one HIGH-severity rent-exempt drain on the SOL parallel world. All three were patched and the program upgraded. A 200-game stress test on the patched build confirmed 198/200 success rate with a 2.64s median end-to-end reveal time. See SECTION 09 for details. A third-party audit by a security firm is on the roadmap before mainnet.

> READY TO PLAY?
▶ ENTER POOL_30K