Skip to content

Session Keys

A full game is several transactions (create/join, commit, reveal). Session keys reduce that to one wallet approval per session: the wallet delegates signing to an ephemeral key, and the ephemeral key plays the game. Both chains implement the same capability with chain-native shapes. The wallet always remains the on-chain player and payout recipient — the session key is an authorized signer, never the owner of funds.

Solana: session PDA + *_session instructions

Section titled “Solana: session PDA + *_session instructions”

The player signs create_player_session once, authorizing an ephemeral session keypair. The session is recorded in a PDA and is valid for 24 hours (SESSION_DURATION_SECONDS = 86_400).

  • The session key can sign any of the *_session-suffixed instruction variants: deposit_stake_session, create_game_session, join_game_session, commit_guess_session, reveal_guess_session. There is no per-instruction permission bitmask — which instructions are session-callable is defined by which *_session handlers exist in the program.
  • Expiry is enforced on-chain: after 24 hours every *_session instruction rejects the session key as a signer at the Anchor account-validation layer. No manual revocation is needed for natural rotation.
  • Early termination: close_player_session (the wallet closes it) or close_session_by_delegate (the session key voluntarily closes its own session — useful for an agent process cleaning up on shutdown).

Game creation is session-signed, unconditionally. P1’s frontend create path is create_game_session: the transaction carries the matchmaker’s cosignature (the matchup-commitment forgery guard — see Commit-Reveal Lifecycle) and is partial-signed and submitted by the player’s session key, with no second wallet popup. P2 never enters the create path — they receive game_ready and join_game session-signed.

In the browser, the session keypair lives in sessionStorage with a 24-hour TTL matching the on-chain expiry.

EVM: openSession delegate + escrowed stake

Section titled “EVM: openSession delegate + escrowed stake”

The EVM contracts record one session per wallet: sessions(wallet) → (sessionKey, expiry). The whole setup is one popup:

  1. The wallet signs a single openSessionAndDeposit(sessionKey, expiry, topUp) call. In that one transaction the contract registers the ephemeral session key as the wallet’s on-chain delegate, forwards gas (topUp) to the session key’s address, and escrows the stake to the wallet’s withdrawable ledger. The stake never sits on the session key — a lost or expired session key can cost at most leftover gas dust, never the stake.
  2. The gas-only session key then submits createGame/joinGame (which debit the wallet’s escrow, msg.value == 0), commitGuess, revealGuess, and withdrawFor as the wallet, with zero further popups. The contract’s _actsFor check (sessionKey matches and block.timestamp < expiry) authorizes each call.
  3. Payouts go to the wallet, not the session key: on the V4 contracts winnings are pushed to the wallet at resolve (see EVM & Cross-Chain); any remaining escrow is swept with withdrawFor.

Sessions last 24 hours, matching the sessionStorage TTL of the key itself. revokeSession ends one early.

SolanaEVM (Base / Ethereum)
Authorizationcreate_player_session → session PDAopenSessionAndDepositsessions(wallet) record
Popups per gameone (session create; deposit rides the same session)one (registers delegate + gas + stake escrow in a single tx)
What the session key signs*_session instruction variantsall game calls, as the wallet, via _actsFor
Stake custodyplayer’s stake escrow PDAcontract escrow (withdrawable[wallet]) — never the session key
Expiry24h, enforced at Anchor account validation24h, enforced by expiry in _actsFor
Revocationclose_player_session / close_session_by_delegaterevokeSession
Key storage (browser)sessionStorage, 24h TTLsessionStorage, 24h TTL