Two-player chess on the Sui blockchain, with SUI betting and on-chain move validation. Built with Move smart contracts and a React/TypeScript frontend. Developed with the help of Claude Code.
https://dist-psi-one-77.vercel.app — deployed on Sui testnet.
Note: Sui testnet is periodically reset, which wipes all published packages. When this happens the live demo link will temporarily break until the contracts are republished. If the app shows a "dependent package not found" error, that's why.
- Fully on-chain chess rules — legal moves, check, checkmate, stalemate, castling, en passant, and pawn promotion are all validated by the Move smart contracts. The chain is authoritative; the frontend never validates moves locally.
- Lobby matchmaking — players advertise open games, others browse and join from any machine. No need to share game IDs or addresses.
- SUI betting — players wager SUI on the outcome. Winner takes both bets; draws return each bet. Bets can be asymmetric.
- Private games — alternatively, specify an opponent address directly for a private game.
- Resume from lobby — active games appear in "Your Games" so players can leave and return without losing context.
- Visual move feedback — pending moves highlight in green, rejected moves in red with a ghost piece showing the attempted destination.
- Smart error messages — illegal moves show human-readable errors like "This piece cannot move to the destination square".
┌──────────────────────┐ ┌────────────────────────┐ ┌──────────────────┐
│ React frontend │ │ Sui blockchain │ │ Slush wallet │
│ (Vercel-hosted) │◀─────▶│ (Move contracts) │◀─────▶│ (signs txns) │
└──────────────────────┘ gRPC └────────────────────────┘ └──────────────────┘
▲ ▲
│ │
▼ │
getObject() polls │
for Game + Lobby │
state every 3s │
│
All game state lives on-chain:
- Board (64-square vector)
- Bets (Balance<SUI>)
- Lobby (open + active games)
- Move contracts (
move/) are the single source of truth: board state, bets, move validation, game lifecycle, and the matchmaking lobby all live on-chain. - Frontend (
frontend/) is a static React app built with Vite. It uses@mysten/dapp-kitfor wallet connection (viaConnectModalanduseSignAndExecuteTransaction) andSuiGrpcClientfrom@mysten/sui/grpcfor reading state and executing transactions. No JSON-RPC (deprecated) is used for chain interactions. - Integration scripts (
scripts/) publish contracts and run end-to-end tests on devnet/testnet.
sui-chess/
├── move/
│ ├── sources/
│ │ ├── chess.move # Game module: create/join/move/resign/draw + Lobby
│ │ ├── chess_rules.move # Legal-move validation, check/checkmate detection
│ │ └── chess_board.move # Board representation and mechanical move application
│ ├── tests/
│ │ ├── chess_tests.move # Game lifecycle, lobby, betting (20 tests)
│ │ ├── chess_rules_tests.move # Move validation, check/mate (60 tests)
│ │ └── chess_board_tests.move # Board operations (20 tests)
│ └── Move.toml
│
├── frontend/
│ ├── src/
│ │ ├── main.tsx # Providers (QueryClient, SuiClient, Wallet)
│ │ ├── App.tsx # Routes between lobby and active game
│ │ ├── config.ts # Reads VITE_NETWORK, VITE_PACKAGE_ID, VITE_LOBBY_ID
│ │ ├── suiClient.ts # Shared SuiGrpcClient instance
│ │ ├── components/
│ │ │ ├── GameLobby.tsx # Open games list + create/join/cancel
│ │ │ ├── ChessGame.tsx # Active game view (board + controls)
│ │ │ ├── ChessBoard.tsx # 8×8 grid with click-to-move
│ │ │ └── PromotionPicker.tsx # Pawn promotion piece selector
│ │ ├── hooks/
│ │ │ ├── useLobby.ts # Polls Lobby object for open/active games
│ │ │ ├── useGame.ts # Polls a single Game object
│ │ │ └── useGameActions.ts # Transaction builders for all game actions
│ │ └── lib/
│ │ └── boardParser.ts # Parses on-chain Board into renderable grid
│ ├── .env.example
│ └── package.json
│
└── scripts/
├── setup-accounts.sh # Creates test accounts, requests faucet (devnet)
├── publish.sh # Publishes contracts + creates lobby, writes .env files
└── test-game.sh # End-to-end scenarios on the active network
-
Clone the repo and install frontend dependencies:
cd frontend npm install -
Create test accounts and fund them (devnet):
./scripts/setup-accounts.sh devnet
For testnet, pass
testnetinstead and request tokens at https://faucet.sui.io for the addresses printed by the script. -
Publish the contracts and create the lobby:
./scripts/publish.sh devnet
Pass
testnetinstead for testnet. This publishes the package, creates the sharedLobbyobject, and writesPACKAGE_ID+LOBBY_IDto bothscripts/.envandfrontend/.env— the values correspond to whichever network you published to.
cd move
sui move test -e devnetThe -e devnet flag is needed because Move.toml uses an empty chain ID for devnet (to avoid hardcoding the ID that changes on resets). If your CLI is switched to testnet or mainnet, you can omit it.
chess_rules_tests(60) — per-piece move validation, check/checkmate/stalemate detection, castling, en passant, pawn promotionchess_tests(20) — game lifecycle, lobby, betting distribution, resignation, drawchess_board_tests(20) — board construction, piece placement, apply_move mechanics
./scripts/test-game.shThe script runs against whichever network the Sui CLI is currently switched to (sui client active-env), using the PACKAGE_ID and LOBBY_ID from scripts/.env — which point at whatever network you last ran publish.sh for. To test on a different network, switch the CLI and republish:
sui client switch --env testnet
./scripts/publish.sh testnet
./scripts/test-game.shIt runs three scenarios:
- Create a game via the lobby, join, make one move
- Play scholar's mate and verify checkmate + bet payout
- Create a game and resign, verify winner gets both bets
Run the app locally:
cd frontend
npm run devOpen the printed URL in a browser with a Sui wallet extension (Slush). Make sure your wallet is on the same network as VITE_NETWORK in frontend/.env.
To play a full game you need two wallet accounts — one creates an open game, the other joins it via the lobby. The accounts can be in different browser profiles or on different machines; both clients see the board update via 3-second polling.
Deploy to a specific network with:
./scripts/publish.sh testnet # or devnet, mainnetThe script:
- Removes stale publication files
- Publishes the package to the specified network
- Calls
create_lobbyto mint the shared Lobby object - Writes the resulting
PACKAGE_IDandLOBBY_IDtoscripts/.envandfrontend/.env
cd frontend
npm run build
npx vercel deploy --prod distThe build bakes in VITE_NETWORK, VITE_PACKAGE_ID, and VITE_LOBBY_ID from frontend/.env. Vercel gives you a public URL that anyone with a Sui wallet can use.
Checkmate detection is the most expensive operation in chess because it requires brute-forcing every legal move for every piece. Initial naive implementation cost ~110M MIST (~0.11 SUI) per checkmate move. After three optimization passes it's down to ~1M MIST — roughly a 100× reduction:
- Cached king positions — avoid scanning all 64 squares to find the king during check detection.
- Piece lists by color — iterate ~16 pieces instead of 64 squares when enumerating legal moves.
- Smart candidate generation — per piece type, generate only reachable squares (knight: 8, king: 10, sliders: walk rays) instead of trying all 64 destinations.
All other game actions (create, join, move, resign, draw) cost ~1M MIST each.
MIT