ZKdle
Play today

Documentation

How ZKdle works

A five-letter word game where the answer is sealed on Solana before you guess, and every clue comes with a zero-knowledge proof that it was computed against that seal. Here are the exact rules and numbers, with nothing rounded off.

Sealing & proofs

Seal, prove, reveal

Seal. Before anyone plays, the server picks the word and a random 248-bit salt and computes Poseidon(word, salt). Only that hash, the seal, is written on-chain: in the day account for the Daily, and in each player’s own card for bounty and duel seats.

Prove. For every guess the server returns the colours plus a Groth16 proof (about 256 bytes) that those colours are what the sealed word really gives. The Solana program checks the proof against the stored seal before it records the row, so a wrong colour can’t be recorded.

Reveal. When the day or round closes, the word and salt are published. Anyone can hash them again and confirm they match the seal. The play page has a “Check the seal” button that does it in your browser.

The Daily

Daily board
  • One word for everyone, six tries, free, and no wallet needed. Sealing a run on-chain costs only Solana fees and rent for your card and streak accounts.
  • Clues are proven and checked in your browser. Connect a wallet to “seal my run” on-chain and keep your streak in your own streak account (a PDA).
  • A new word every 24 h (00:00 UTC). Yesterday’s word and salt are published for re-checking.
  • Streak repair: burn 500 $PROOF to bridge exactly one missed day.

Bounty rounds

Bounty round page
Round length1 hour; the next round opens as the previous one closes
Seats12 per round during early access (the program refuses more), one seat per wallet per round
EntryThe $PROOF worth 0.01 SOL, fixed once when the round opens and stored in the round account
Per entry10% burned · 5% treasury · 85% into the round’s escrow (the pot)
Paid placesTop 30% of seats, rounded up (12 seats → 4 paid)
RankingFewer guesses, then shorter own time
Last callNo new seats in the final 2 minutes; a warning appears when fewer than 10 minutes are left

Fairness: no first-mover advantage

Round timeline with own clocks
  • Your own clock. Time is measured from the moment you sit down until your last guess. Joining at minute 41 is no worse than joining at minute 2.
  • Your own word. Every seat gets a different word, derived from a seed that was committed (hashed on-chain) before the round opened. A player who finishes early can’t leak your answer.
  • Solving first doesn’t end it. The round only ends early when every seat is taken and every board is finished. Otherwise it runs the full hour.
  • Leave yourself time. Seats close 2 minutes before the end, and the game warns you if fewer than 10 minutes are left. Boards still open at the close count as unsolved.
  • Rationed proofs. Only the server can make proofs, so it issues six per seat and no more.

Entry, burn & pot

Entry split

Price. When a round opens, the server reads $PROOF’s on-chain reserves: the pump.fun bonding curve before graduation, then the pool’s two reserve accounts (PumpSwap or Raydium). It takes the median of several samples over about 5 minutes, so a single pumped block can’t move the entry. The resulting $PROOF amount is clamped to configured min/max values and written into the round account. It stays fixed for that round even if the price moves. If no price is available (for example, right after launch), a fallback of 1,000 $PROOF is used. The round page shows the entry in $PROOF and its ≈SOL value at open.

At entry, in the same transaction as your seat: 10% is burned with an SPL BurnChecked (total supply goes down), 5% goes to the treasury’s token account, and 85% moves into a token account owned by the round’s program address. Nobody holds a key to that escrow; only the settle instruction can move it.

Payouts & refunds

Payout curve

Winners are the top 30% of seats, rounded up. Shares fall linearly: with k winners the weights are k, k−1, … 1. First place also gets any rounding dust. An exact tie (same guesses and same own time, to the second) goes to the earlier seat.

Example (2,000 $PROOF entry)Pot1st2nd3rd4th
4 seats → 2 winners6,8004,5332,267——
10 seats → 3 winners17,0008,5005,6672,833—
12 seats → 4 winners20,4008,1606,1204,0802,040

10 winners (later phases, 33 seats, pot 56,100): 10,200 · 9,180 · 8,160 · 7,140 · 6,120 · 5,100 · 4,080 · 3,060 · 2,040 · 1,020.

No solver → refund. If nobody solves, each seat gets its escrowed 85% back. Only the 10% burn and the 5% fee are spent. If fewer seats solve than there are paid places, only the solvers are paid.

Settlement is one instruction that anyone can call once the round is over. Afterwards each seat’s card account is closed and its rent goes back to the player.

Duels

Duel flow
Duel page
  • Two players, equal SOL stakes (duels stay in SOL), and a different sealed word each.
  • Post the link on X and it unfolds into a “Take the duel” Blink.
  • Fewer guesses wins; a tie goes to the shorter own time. The winner takes both stakes minus the 5% fee.
  • Stake between 0.005 and 100 SOL.
  • Settles as soon as both boards are finished; open for up to 24 h. If nobody accepts, or neither side solves, both stakes come back in full with no fee.

$PROOF

  • Bounty entries: 10% of each entry is burned.
  • Streak repair: burn 500 $PROOF to bridge one missed day.
  • Holder frame: wallets holding at least 100,000 $PROOF get a gold frame and a holder badge on their share card.
  • Treasury: 5% of each entry goes to a public treasury token account, linked on the home page.

Trust model & known limits

Enforced by the program

  • Every recorded clue matches the seal (Groth16 verified on-chain).
  • Burn, fee, escrow, payout order, the 30% cut and refunds are all computed on-chain.
  • Fee capped at 20% in code (set to 5%) and only payable to the configured treasury. Burn capped at 50% (set to 10%).
  • Round seeds are committed before entry; the reveal must hash to the commitment.

Still trusting us

  • The server knows the answers (it has to, to build proofs). A dishonest operator could leak them.
  • Seats are co-signed by the server, which issues six proofs per seat.
  • Words and seeds are random on our side: the commitment stops us from changing them, not from choosing them.
  • Phase-2 trusted setup: three team contributions plus a public random beacon. Public contributions come next (see Security).
  • The entry price comes from a server-side TWAP of on-chain reserves, within configured clamps.

Security & trust

Security and trust model

What we harden on the server

  • Fresh secrets. Every day gets a new random salt; every round and duel a new 32-byte seed, committed on-chain before anyone enters. Seat words are derived from the seed, so revealing it lets anyone recheck every seat.
  • Encrypted at rest. The state file holding live answers, salts and seeds is AES-256-GCM encrypted with ZK_SECRET_KEY. Outside the local test network the server refuses to start without it.
  • No leaks before reveal. No endpoint, share card or error returns a live answer, salt or seed. Unexpected errors are reduced to server error (id), and log lines are scrubbed. An end-to-end test greps every public response for the live secrets.
  • Rate limits and replays. Per-IP and per-wallet limits on guesses and seats. Six proofs per seat, no repeated guesses, and nothing after the round ends or is revealed.
  • Pot cap. ZK_MAX_POT_SOL_EQUIV (default 5 SOL equivalent) caps what any one round can hold. New seats are refused past it.
  • Separate keys. The host key only co-signs and opens/reveals. It can't move escrow anywhere except to the payout the program computes. The treasury is just an address, and its key is never on the server.

Host key rotation

The program reads the host from its config account. set_admin (instruction 10) swaps it and needs signatures from both the current and the new key, so a typo can't lock the config. Procedure: generate the new key offline → sign set_admin(old, new) with both → check the config account → restart the server with the new ZK_HOST_KEY → retire the old key. Open rounds keep settling, because every instruction checks the config, not a hard-coded key.

Trusted setup

Phase 1 is the public Perpetual Powers of Tau (80 contributions, power 10). Phase 2 for our circuit had three independent contributions plus a final beacon: the blockhash of Solana mainnet slot 458,674,048, committed to before the first contribution. The transcript and the snarkjs zkey verify output are in docs/ceremony/. To be clear, all three phase-2 contributions were made by the ZKdle team. We'll open a public round so anyone can add theirs.

Residual risks, plainly

  • The operator sees live answers. The server has to know a word to prove clues about it. ZK stops us from swapping the answer or forging a hint; it doesn't stop us from knowing.
  • A server breach could leak live answers for rounds in progress. The pot cap and the 12-seat beta bound what that is worth.
  • Daily proofs aren't bound to a wallet, so a Daily clue can be replayed by someone else. The Daily is free; round and duel seats each have their own seal.
  • Next: sealing inside a TEE or with threshold keys, so no single operator holds a live word.

Early access

Capacity roadmap

Bounty rounds start at 12 players per round while we test in public. That is what a single settle transaction pays out comfortably today. Capacity goes up step by step as the upgrades ship: Phase 2 (~100 per round) brings an incremental on-chain leaderboard, so settlement no longer needs every seat in one transaction, plus dedicated prover workers. Phase 3 (200+) runs rounds in parallel. No dates promised; each step ships once it has been tested on devnet, and the rules above don’t change.