Skip to main content

Architecture

This document explains the architecture and core concepts of the Chia Gaming system. Understanding these concepts is essential for developers building games or integrating with the platform.

System Overview​

The Chia Gaming system enables trustless two-player games using state channels on the Chia blockchain. Players lock funds into a shared on-chain coin, play the game entirely off-chain (fast, free, private), and settle the result back on-chain when done.

The system consists of:

  • Player app (hosted): Static HTML/JS/CSS/WASM on any web server. Players open it in a browser. The local demo includes a simulator option for play without real XCH.
  • Player app (Electron desktop): The same React + WASM bundle, packaged as a native installer (desktop/, tools/build-electron.sh). Origin is chiagaming://app. The desktop shell hides the simulator, allowlists hub origins, and does not replace the hub.
  • Hub: Matchmaking UI and WebSocket relay. Provides a player list and challenges, and ferries game messages between peers. Hubs are third-party code; anyone can run one. Required for both hosted and desktop players.
  • Chia wallet (live play): Connected via WalletConnect (Link Wallet). The player app reads chain state and submits transactions through the wallet (for example chia_getCoinRecordsByNames, chia_pushTransactions). A full node on the same machine is not required for players; the wallet handles chain interaction. A Cloud Wallet connection path is in progress but not complete; use WalletConnect for live play today.
  • Simulator (development, hosted/web build only): Optional local service (chia-gaming-sim, port 5800: HTTP /health and WebSocket /ws) used instead of WalletConnect when testing without real XCH. Configured in front-end/src/settings.ts. Not shown in the Electron app.

State Channels​

A state channel is a mechanism that allows two parties to transact off-chain while retaining on-chain security guarantees. The general flow is:

  1. Open: Both players fund a shared on-chain coin (the "channel coin")
  2. Play: Game moves happen off-chain, with each state update signed by both parties
  3. Close: The final state is settled on-chain, distributing funds according to the game outcome

The key insight is that either party can unilaterally close the channel at any time by posting the latest agreed-upon state to the blockchain. This means neither player needs to trust the other; if one party disappears or misbehaves, the other can always recover their funds.

Coin Hierarchy​

The on-chain state is represented by a hierarchy of coins (simplified; see OVERVIEW.md in the chia-gaming repository for the full tree including per-player funding coins and the launcher):

Funding coins (one per player) → launcher → Channel Coin
→ Unroll Coin → reward coins and/or Game Coins (referee puzzle)
  • Channel Coin: Holds the channel funds after the handshake. Controlled by a 2-of-2 aggregate key; off-chain play updates signed unroll commitments without moving the channel coin until shutdown or dispute.
  • Unroll Coin: Represents the latest mutually-agreed state with a sequence number. A higher sequence number preempts a lower one during dispute resolution.
  • Game coins: Created when a game is forced on-chain. Each is governed by a referee puzzle (Chialisp) for that game type; on-chain moves are validated against that puzzle if a dispute is forced.

The Potato Protocol​

The "potato" is a conceptual token that alternates between players, determining whose turn it is to act. The protocol ensures:

  • Only one player can propose a state update at a time
  • Each state update increments a sequence number
  • Both players sign each state transition
  • The latest signed state is always available for on-chain settlement

The name comes from "hot potato": you hold it when it's your turn, and pass it when you've made your move. Each potato pass is a batch of game actions plus half-signatures over the new unroll commitment. See OVERVIEW.md in the chia-gaming repository for the batch protocol, handshake (messages A–D), and handler phases.

Referee Pattern​

Each game type implements a referee: a Chialisp puzzle that can validate game moves on-chain. During normal play, the referee is never invoked because both players agree on the game state. However, if a dispute arises:

  1. Either player posts the game state on-chain
  2. The referee puzzle validates each subsequent move
  3. After a timeout or game completion, the referee distributes funds

The referee ensures that even if the off-chain communication breaks down, the game can always be completed fairly, on-chain (albeit more slowly and at transaction cost).

Game packages​

Each game is a package under games/<key>/ with CLVM rules (clsp/), a TypeScript/React UI (ui/), and optional Rust tests. Packages register in games/registry.json. Off-chain handlers and on-chain validators are Chialisp programs inside the package; the Rust host (src/session_phases/, src/channel_state/, src/referee/) runs the channel, potato, and referee without game-specific presentation logic.

To add a game, follow GAME_WRITING_GUIDE.md. Handler calling conventions are in HANDLER_GUIDE.md and clsp/handler_api.md.

Connection Types​

Per the connectivity model in the chia-gaming repository (CONNECTIVITY.md), the blockchain itself is not a connection; it is the ground truth. What you connect to in the player app are three operational axes plus session state:

AxisPurposeHow it is reached
WalletSign spends, read balances and coin recordsWalletConnect (live). Simulator in the hosted/web build only; hidden in Electron. Cloud Wallet integration is not ready yet
HubMatchmaking UI (iframe) and message relayWebSocket to the hub you joined (desktop: origin must be allowlisted)
PeerOpponent game trafficRelayed over the hub's WebSocket
SessionIn-progress channel obligationLocal state + on-chain coins; not a socket

Session Rollover​

Durable session state is stored in IndexedDB (one complete session record). localStorage holds only small preferences (for example hub URL and network). A reload should restore the session; the player app treats reload like a dropped remote connection and reconnects the wallet and hub. In Electron that storage lives under chiagaming://app, not a website origin.

The hub connection auto-reconnects with backoff after transient outages (CONNECTIVITY.md). Peer traffic rides on the hub WebSocket: if the hub is down, the peer is down. Transient hub or peer loss degrades the session (yellow “Peer pings look stuck”) rather than automatically going on-chain; the player can reconnect or choose Go On Chain. Clearing site data (hosted) or app data (desktop) loses the local session; the on-chain obligation remains until shutdown or timeout. Wallet disconnect stalls signing until the wallet is reconnected.

A live session binds to one wallet account (provider scope). Reconnecting that same account resumes work. Connecting a different account is treated as a mismatch: the app does not apply funding or cleanup to the wrong wallet.

Hub Availability​

The hub is only required for:

  • Initial matchmaking (player list and challenges)
  • Relaying messages between peers during gameplay

It is not required for on-chain settlement. If the hub disappears permanently, players can still close the channel on-chain using their locally stored state.

WalletConnect Integration​

The player app communicates with the Chia wallet via WalletConnect using the chia namespace. The following methods are used:

MethodPurpose
chia_getWalletsList available wallets
chia_getWalletBalanceCheck available balance
chia_getNextAddressGet a receive address
chia_getHeightInfoGet current blockchain height
chia_selectCoinsSelect coins for channel funding
chia_createOfferForIdsCreate offers (channel open and optional fee spend)
chia_cancelOfferCancel an offer when the protocol needs to withdraw it
chia_pushTransactionsPush signed transactions to the mempool
chia_createNewRemoteWalletCreate a remote wallet for tracking channel coins
chia_registerRemoteCoinsRegister channel coins for observation
chia_getCoinRecordsByNamesLook up specific coin records
chia_getPuzzleAndSolutionGet puzzle/solution for spent coins
chia_getFullNodePeerCountWallet peer-count check

Channel open (handshake)​

Opening a channel still lands as one on-chain funding transaction, but each wallet goes through several WalletConnect steps during the A–D handshake (for example chia_selectCoins, chia_createOfferForIds for that player’s funding share and for an optional fee offer, and chia_pushTransactions). Handshake D is the receiver’s acceptance; each player locally combines the C and D halves, validates the result, and submits the assembled spend. Approve each request in the Chia wallet; a missing approval can make the handshake look stuck even though only one transaction is submitted on chain.

Live WalletConnect play can attach a transaction fee from the player app Wallet tab (mojos or XCH). The default is 100,000,000 mojos (0.0001 XCH). You can set 0 for a free transaction. Chia’s mempool treats a fee below 100,000,000 mojos as effectively zero (front-end/src/constants/fees.ts); a nonzero fee in that range can be rejected instead of admitted as free. A configured fee is a validate-only chia_createOfferForIds offer, converted in WASM and aggregated into the protocol bundle. If that offer cannot be signed, the protocol spend is still submitted without a fee and the UI warns. Simulator sessions do not use this fee path.

Security Model​

The security of the system rests on several guarantees:

  1. Unilateral close: Either player can always close the channel on-chain
  2. Latest state wins: Higher sequence numbers always supersede lower ones
  3. Timeout protection: If one player disappears during an on-chain dispute, the other can claim funds after a timeout
  4. Hub is a relay, not a co-signer: The hub ferries matchmaking and game messages; it does not hold channel keys or settle balances. Players still rely on signed off-chain state and on-chain puzzles for security.
  5. Wallet isolation: The player app never has access to private keys; live signing happens in the connected wallet (WalletConnect today)

Frontend Architecture​

The frontend is one player bundle plus a separately deployed hub. The player bundle is either hosted as static files or packaged in Electron; both load the hub iframe.

Player App​

The hosted form of the player bundle (static files in a browser):

  • Fully static (HTML/JS/CSS/WASM): no server-side logic
  • Contains the WASM game engine compiled from Rust
  • Handles WalletConnect integration (Link Wallet) for live play
  • Persists durable session state in IndexedDB
  • Loads the hub's matchmaking UI in an iframe (session credential via origin-restricted postMessage, not in the iframe URL)
  • Local/web builds can use the simulator
  • Cloud Wallet support is under development and not ready for use

Player App (Electron desktop)​

  • Same front-end/ bundle as the hosted app, inside a sandboxed Electron shell (desktop/)
  • Custom scheme chiagaming://app (not file://), so storage and WASM behave like the hosted origin
  • Simulator button is hidden; use Link Wallet (WalletConnect) for live play
  • Still talks to a hosted hub over the network; the shell allowlists hub origins (config.json hubOrigins, plus hubs entered in the picker)
  • Does not include the hub service. Build with tools/build-electron.sh, or download desktop installers from 0.4.0-beta.1. The macOS and Windows installers in that beta are unsigned. See desktop/README.md.

Hub (Matchmaking + Relay)​

  • Express + WebSocket service
  • Serves the hub UI (player list, challenges, optional different buy-ins per player)
  • Relays game messages between connected peers
  • Loaded inside an iframe within the player app (hosted or Electron)
  • Must be on a different origin from the player app (security boundary). The player app passes a hub session credential with origin-restricted postMessage, not in the iframe URL.

This separation ensures that the hub (which is third-party code) cannot access the player app's WalletConnect session or game state.