Grid3 — Fully On-Chain Tic Tac Toe on CKB

Hey everyone! I’m sharing a project I built as part of my CKB learning journey -Grid3, a fully on-chain Tic Tac Toe game where every move is a real CKB transaction.

Play it live: grid3-ckb (dot) vercel (dot) app
Source code: github.com/truthixify/grid3

What is Grid3?

Grid3 is a two-player, stake-based Tic Tac Toe game that runs entirely on CKB. Players stake CKB to create or join a game. The winner takes 80% of the pool, 20% goes to the protocol fee. If the game ends in a draw, the board resets and players keep playing until someone wins.

No server holds game state. No backend decides who wins. Everything is validated by a CKB script running on-chain.

How It Works

The Script

A single Rust script compiled to RISC-V serves dual roles -both as the lock script and type script on game cells:

  • As type script: Validates all game state transitions -create, join, move, finish, reset (draw), forfeit, and cancel
  • As lock script: Controls who can spend the game cell -during WAITING state anyone can join, during ACTIVE state only the two players can make moves

The game state is packed into 84 bytes of cell data:

Field Size Description
game_state 1 byte WAITING, ACTIVE, FINISHED, CANCELLED
board 9 bytes 3x3 grid (0=empty, 1=X, 2=O)
current_turn 1 byte Whose turn it is
player_x_lock 32 bytes Player X’s lock script hash
player_o_lock 32 bytes Player O’s lock script hash
stake_amount 8 bytes CKB staked per player
winner 1 byte Game result

The Game Flow

  1. Player X creates a game by deploying a game cell with their stake
  2. Player X shares the game link with a friend
  3. Player O opens the link, connects wallet, and joins by matching the stake
  4. Players take turns clicking cells -each move is a signed CKB transaction
  5. Winner claims the prize (80/20 split) or draw resets the board for another round
  6. Either player can forfeit (abandon) at any time -opponent wins by default

State Transitions

CREATE → WAITING → JOIN → ACTIVE → MOVE → MOVE → ... → FINISH (winner)
                                    ↓                     ↑
                                    → RESET (draw) ───────┘
                                    → FORFEIT (abandon)
                   → CANCEL (creator cancels before anyone joins)

Tech Stack

Layer Technology
Script Rust → RISC-V (ckb-std, compiled for CKB-VM)
Frontend Next.js 16 + TypeScript + Tailwind CSS
Wallet Connection CCC (@ckb-ccc/connector-react)
Database Supabase (leaderboard, match history)
Network CKB Testnet (Pudge)
Deployment Vercel (frontend), offckb (script)

Key Technical Challenges

Shared state cells: In CKB, both players need to spend the game cell to make moves. Solved by making the game script serve as both lock and type -the lock allows either player to spend during ACTIVE state, while the type script validates the game logic.

Cell migration tracking: Every move consumes the old cell and creates a new one with a different OutPoint. The frontend polls the CKB indexer and tracks cells by playerXLock (which stays constant throughout the game) rather than by OutPoint.

Indexer transition handling: During block processing, the CKB indexer can briefly show both the old and new cells. Added a forward-progress guard that only accepts state transitions that move the game forward -never backward.

Self-join prevention: The script rejects transactions where player_o_lock == player_x_lock, enforced both on-chain and in the frontend.

What I Learned

This project was my deep dive into CKB development. Some takeaways:

  • The cell model is fundamentally different from account-based chains. Every state change means consuming and recreating cells. It took a while to internalize this but once it clicks, it’s elegant.
  • Type scripts as state machines map perfectly to game logic. Each valid move is just a valid state transition.
  • CCC SDK makes wallet integration straightforward. The @ckb-ccc/connector-react package handles JoyID and other wallets with minimal setup.
  • offckb with Type ID deployment is great for iterating -upgrade the script without changing the code hash.

Try It Out

The game is live on CKB testnet. You’ll need testnet CKB from the faucet (faucet dot nervos dot org) to play.

  1. Go to grid3-ckb (dot) vercel (dot) app
  2. Connect your wallet (JoyID or any CCC-supported wallet)
  3. Create a game and set your stake
  4. Share the game link with a friend
  5. Play!

Feedback, issues, and PRs are welcome on GitHub

Script Code Hash: 0x4245528f6287893443bb2a160757f32c4aba0669772c2575955abbff298dc59d
Network: CKB Testnet

9 Likes