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
- Player X creates a game by deploying a game cell with their stake
- Player X shares the game link with a friend
- Player O opens the link, connects wallet, and joins by matching the stake
- Players take turns clicking cells -each move is a signed CKB transaction
- Winner claims the prize (80/20 split) or draw resets the board for another round
- 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-reactpackage 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.
- Go to grid3-ckb (dot) vercel (dot) app
- Connect your wallet (JoyID or any CCC-supported wallet)
- Create a game and set your stake
- Share the game link with a friend
- Play!
Feedback, issues, and PRs are welcome on GitHub
Script Code Hash: 0x4245528f6287893443bb2a160757f32c4aba0669772c2575955abbff298dc59d
Network: CKB Testnet