Castle Black is an open-source, real-time chess platform built for speed, simplicity, and trustless competition on the Stellar network. It merges zero-friction web play with authoritative server arbitration and Soroban smart contract escrow wagers.
Most Web3 gaming experiences introduce friction directly into the gameplay loop:
- Forcing wallet installation before seeing the board.
- Requiring blockchain signatures and gas fees for individual moves.
- Introducing latency and transaction waiting times into an instantaneous game.
- Zero-Friction Casual Play: No account, no wallet, and no setup required. Two players simply share a 6-character room code (e.g.
KN7X9P) to play lightning-fast, real-time chess. - Optional Trustless Escrow: Stellar and Soroban are used for match wagers:
- Both players deposit equal testnet XLM/tokens into a Soroban smart contract.
- The authoritative game arbiter cryptographically verifies the match outcome (checkmate, timeout, resignation).
- The arbiter triggers the Soroban contract to release 100% of the pot directly to the winner's wallet.
- Why Stellar?
- Sub-5 second finality: Instant escrow payouts the moment checkmate lands.
- Near-zero transaction fees: Low friction for micro-stakes and casual matches.
- Soroban Rust safety: Formally verifiable state transitions with native asset contract (SAC) support.
Castle Black is structured as a high-cohesion, low-coupling monorepo:
castle-black/
├── web/ # Next.js 15+ App Router, Tailwind CSS, react-chessboard, Freighter API
├── server/ # Express + Socket.io authoritative game arbiter & Stellar SDK service
└── contracts/ # Minimal Soroban Rust 2-player escrow contract with arbiter release
flowchart TD
subgraph Client["Web Client (/web)"]
UI[Next.js 15 UI / react-chessboard]
SocketClient[Singleton Socket.io Client]
Wallet[Stellar Freighter API]
end
subgraph Server["Arbiter Server (/server)"]
Express[Express HTTP Server]
SocketServer[Socket.io Gateway]
Engine[chess.js Authoritative Validator]
Rooms[In-Memory Room Manager]
Arbiter[Stellar SDK Arbiter Signer]
end
subgraph Blockchain["Stellar Network (/contracts)"]
Soroban[Soroban Escrow Contract]
Token[Stellar Asset Contract / XLM]
end
%% Move Loop
UI -->|1. make_move| SocketClient
SocketClient -->|2. Event: make_move| SocketServer
SocketServer -->|3. Validate legality & clocks| Engine
Engine -->|4. move_applied| SocketServer
SocketServer -->|5. Broadcast FEN & clocks| SocketClient
SocketClient -->|6. Update Board| UI
%% Optional Wager Escrow Flow
Wallet -.->|Deposit stake| Soroban
Engine -->|Checkmate / Flag-fall detected| Arbiter
Arbiter -->|Signed resolve_game tx| Soroban
Soroban -->|Release pot| Token
| Module | Core Technologies | Primary Responsibility |
|---|---|---|
/web |
Next.js 15, React 19, Tailwind CSS, react-chessboard, @stellar/freighter-api |
Zero-login match lobby, reactive board UI, synchronized dual clocks, Freighter wallet integration. |
/server |
Node.js, Express, Socket.io, TypeScript, chess.js, @stellar/stellar-sdk |
Authoritative move validation, in-memory room management, millisecond clock ticking, testnet keypair arbiter resolution. |
/contracts |
Rust, Soroban SDK 22.0.0, WebAssembly |
Multi-player escrow contract locking matching stakes and releasing pots exclusively upon verified arbiter authorization. |
- Node.js:
v20.0.0or higher (tested on Node 24) - Rust:
1.80+withwasm32-unknown-unknowntarget (for contract compilation) - Git
git clone https://github.com/Stanley471/castle-black.git
cd castle-blackcd server
npm install
npm run devThe server will start on port
4000. If noARBITER_SECRET_KEYis provided in.env, it automatically boots with a transient simulation keypair so casual chess matches work out of the box with zero configuration!
In a new terminal window:
cd web
npm install
npm run devOpen http://localhost:3000 in your browser. Click "Create Match & Get Code" to generate an instant 6-character room code and play!
In a new terminal window:
cd contracts
cargo testThe escrow contract (contracts/src/lib.rs) is written in #![no_std] Rust using soroban-sdk = "22.0.0".
Run cargo test inside /contracts:
running 4 tests
test test::test_cannot_play_self - should panic ... ok
test test::test_cancel_game_lifecycle ... ok
test test::test_full_escrow_lifecycle ... ok
test test::test_invalid_winner - should panic ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test_full_escrow_lifecycle:- Creator locks 100 tokens.
- Opponent locks 100 tokens.
- Contract holds 200 tokens in escrow.
- Arbiter authorizes payout to White upon verified victory.
- White receives 200 tokens (100% of the pot); contract balance returns to 0.
test_cancel_game_lifecycle:- Creator locks 50 tokens.
- Creator cancels match before an opponent joins.
- Contract refunds 100% of the deposit back to creator; status transitions to
Cancelled.
test_cannot_play_self:- Verifies that creator cannot join their own game as opponent.
test_invalid_winner:- Verifies that attempting to release the pot to a third-party non-participant reverts with
InvalidWinner.
- Verifies that attempting to release the pot to a third-party non-participant reverts with
-
Install the Stellar CLI:
cargo install --locked stellar-cli --features opt
-
Configure and Fund Testnet Keypair:
stellar keys generate arbiter --network testnet stellar keys fund arbiter --network testnet
-
Build the Optimized WebAssembly Binary:
cd contracts cargo build --target wasm32-unknown-unknown --release -
Deploy Contract:
stellar contract deploy \ --wasm target/wasm32-unknown-unknown/release/castle_black_escrow.wasm \ --source arbiter \ --network testnet
-
Link to Server: Copy the returned Contract ID into
/server/.env:ESCROW_CONTRACT_ID=CA...YOUR_CONTRACT_ID