Decentralized peer-to-peer chess in the browser — the fediverse of chess. Play with anyone over the web without a central game server, or nearby over BLE with no internet. Same open wire protocol over WebRTC (online) and BLE (local); build your own client and interoperate. Matchmaking uses public WebTorrent trackers for online play; moves and chat go directly between peers.
Version: 0.3.0 — see CHANGELOG for release history.
- Why FediChess?
- Features
- How it works
- Tech stack
- Prerequisites
- Installation & quick start
- Configuration
- Playing and testing
- Deployment
- Deployment checks (CI)
- Project structure
- Documentation
- Architecture at a glance
- Security and privacy
- FAQ
- About this repository
- Transports
- Building other clients
- Ranking and federation
- Contributing
- License
- No central server. Games run between browsers. No company owns your games or ELO; everything can stay on your device.
- Open protocol. The wire protocol is documented so anyone can build a compatible client (web, mobile, CLI) and play with this app and others.
- Self-hostable and federated. Use default public trackers or run your own. Point your instance at community trackers to grow the pool. Optional ranking API allows voluntary, user-consent leaderboards.
- Censorship-resistant. As long as peers can reach trackers and each other (or a TURN server), games work. No single point of control.
- P2P multiplayer — No game server. Peers discover each other via WebTorrent trackers and connect over WebRTC. Moves and chat go directly between players.
- Lobby + ELO matchmaking — Default ELO range ±200; optional “Show all peers” to challenge anyone in the lobby. Set a username (e.g.
HKTITAN) visible to others. - Full game flow — 5 min + 30s increment clocks, in-game chat, resign, draw offer/accept. Spectators can join and watch; only the two players send moves.
- PGN export & analysis — Copy FEN/PGN from the result dialog. One-click link to analyze on Lichess (or paste into Chess.com).
- Account backup — Export progress (ELO, game history, name) to a file and restore on another device. Backups are signed (password-protected HMAC) so they cannot be edited for fair play. Use Backup account from the lobby or home.
- Open wire protocol — Protocol spec: rooms, action types (≤12 bytes), JSON payloads. Any client that implements it can interoperate.
- Self-hosted trackers — Override
NEXT_PUBLIC_P2P_TRACKERSto use your own or community WebTorrent trackers. Same protocol across instances. - Optional STUN/TURN — Configure NAT traversal so connections work behind strict firewalls or symmetric NAT (see Configuration).
- Static export — Next.js builds to static files (
out/). No Node server required in production; host on Vercel, Netlify, or any static host.
- Classic and Neon themes — Toggle board/UI themes. Mobile-responsive layout, 44px touch targets, safe-area padding.
- Local AI — Play vs a simple built-in engine (random legal moves) from the home page. For strong engine play, you can integrate e.g. Stockfish.wasm in a Web Worker.
- Discovery — Your browser connects to one or more WebTorrent-compatible WSS trackers and joins a lobby room (e.g.
p2p-chess-global) with a shared app ID (p2p-chess-v1). - Lobby — The app sends periodic heartbeats (ELO, username) and receives others’ heartbeats. You see a list of peers; you can challenge one (and choose your color). They accept or decline.
- Game room — On accept, both clients join a game room
p2p-chess-{gameId}over the same P2P layer. Each move and game event is broadcast as a shared event log; board state is derived by replaying the log so spectators and late joiners see the same position. - WebRTC — After discovery, peers connect directly (or via TURN if configured). All game data (moves, chat, sync) flows over WebRTC data channels — no server in the middle.
Dual transport: The same protocol also runs over BLE (Web Bluetooth) for nearby play — see Transports and Protocol (BLE). Architecture has diagrams, data flow, and stability (disconnect, refresh).
| Layer | Technology | Notes |
|---|---|---|
| UI | Next.js 15, React 19, TailwindCSS | App Router, static export; responsive, touch-friendly. |
| P2P | Trystero (torrent), WebTorrent trackers, WebRTC; BLE (Web Bluetooth) | Online: discovery/signaling via trackers, data over WebRTC. Nearby: BLE GATT (see lib/ble-transport.ts). Shared Room interface in lib/transport-types.ts. |
| Game | chess.js, react-chessboard | Rules, FEN, move validation; board component. |
| State | Zustand | User, lobby, game, UI state; peers tagged by transport (webrtc/ble). |
| Persistence | IndexedDB (idb-keyval) | ELO, peak ELO, game history. Optional localStorage for name and preferences. |
- Package name:
fedichess(npm/repo). Product name: FediChess. Version: 0.3.0.
- Node.js 18+ (or 20+ recommended)
- npm or pnpm
- Modern browser with WebRTC support (Chrome, Firefox, Safari, Edge)
git clone https://github.com/HKTITAN/fedichess.git
cd fedichess
npm installnpm run devOpen http://localhost:3000. Use Play Random to join the lobby, or Copy Link to share the lobby URL with a friend.
| Command | Description |
|---|---|
npm run dev |
Start Next.js dev server (default port 3000). |
npm run build |
Production build; outputs static export to out/. |
npm run start |
Serve the built app (after npm run build). |
npm run lint |
Run ESLint. |
All configuration is via environment variables. For Next.js, use NEXT_PUBLIC_* for values needed in the browser.
| Variable | Description | Default |
|---|---|---|
NEXT_PUBLIC_P2P_TRACKERS |
Comma-separated WSS tracker URLs. Override to use your own or extra trackers. | wss://tracker.webtorrent.dev, wss://tracker.openwebtorrent.com, wss://tracker.btorrent.xyz |
NEXT_PUBLIC_STUN_URL |
STUN server for NAT traversal (e.g. stun:stun.l.google.com:19302). |
(browser default) |
NEXT_PUBLIC_TURN_URL |
TURN server URL (optional). | — |
NEXT_PUBLIC_TURN_USERNAME |
TURN username (if required). | — |
NEXT_PUBLIC_TURN_CREDENTIAL |
TURN credential (if required). | — |
Create a .env.local in the project root (do not commit secrets). Example:
# Optional: use your own trackers
NEXT_PUBLIC_P2P_TRACKERS=wss://your-tracker.example.com,wss://another.example.com
# Optional: STUN/TURN for strict NATs
NEXT_PUBLIC_STUN_URL=stun:stun.l.google.com:19302
# NEXT_PUBLIC_TURN_URL=turn:turn.example.com:3478
# NEXT_PUBLIC_TURN_USERNAME=user
# NEXT_PUBLIC_TURN_CREDENTIAL=pass- Tab 1: Open the app, click Play Random (or go to
/lobby). - Tab 2: Open the app, click Play Random.
- Both join the same lobby. Optionally set usernames. From Tab 1, click Challenge next to Tab 2’s peer.
- In Tab 2, click Accept. Both tabs navigate to the game; play as usual.
- In the lobby, click Copy Link and send the URL (e.g. via chat or email).
- The other person opens the link in their browser and joins the same lobby.
- Challenge and accept as above.
- Lobby: Click Connect via BLE (Chrome or Edge; HTTPS or localhost). Select a FediChess BLE device when the picker appears.
- The other device appears as a Nearby peer. Challenge and accept as usual; the game runs over the same BLE connection.
- Note: The browser can only act as a central (client). The other side must advertise the FediChess GATT service (e.g. a native app or a separate BLE peripheral). Two browser tabs cannot connect to each other via BLE alone.
Click Local AI on the home page to play vs a simple engine (random legal moves). No P2P involved.
FediChess builds to a static export (out/). No Node server is required in production.
The repo is set up for one-click and Git-based deploys:
| File | Purpose |
|---|---|
package.json |
engines.node: >=18.17.0; scripts: build = next build, dev = next dev. |
.nvmrc |
Node 20 (used by many platforms and locally with nvm use). |
vercel.json |
Framework: nextjs (Vercel runs next build and serves the static export). |
netlify.toml |
Build: npm run build, publish: out, NODE_VERSION: 20. |
render.yaml |
Blueprint: static site, buildCommand: npm ci && npm run build, staticPublishPath: out. |
Other hosts: use build command npm run build (or npm ci && npm run build) and publish directory out. Optional env: NEXT_PUBLIC_P2P_TRACKERS, NEXT_PUBLIC_STUN_URL, etc. (see Configuration).
- Push the repo to GitHub.
- In Vercel, import the repository.
- Deploy. The default Next.js preset works; build command
next buildproduces the static export. - (Optional) Add environment variables under Project Settings → Environment Variables (e.g.
NEXT_PUBLIC_P2P_TRACKERS).
- Run
npm run buildand upload the contents of theout/directory to any static host (Netlify, Cloudflare Pages, GitHub Pages, etc.). - Ensure the host supports client-side routing (SPA fallback to
index.htmlfor/lobby,/game, etc.) if you use paths other than/.
The manifest uses the favicon as a fallback. For a better "Add to home screen" experience, add icon-192.png and icon-512.png to public/ and update the icons array in public/manifest.webmanifest.
The repo runs GitHub Actions CI (lint + build) on every push and pull request to the default branch. You can require this check to pass before Vercel promotes a deployment to Production, so only passing builds go live.
- Workflow: .github/workflows/ci.yml — installs dependencies, runs
npm run lint, thennpm run build. - Enable in Vercel: Project → Settings → Git → Deployment Checks → require the CI (or "Build and lint") check before deploying. See documentation/deployment-checks.md for step-by-step instructions and troubleshooting.
| Path | Purpose |
|---|---|
app/ |
Next.js App Router: page.tsx (home), lobby/page.tsx, game/page.tsx, local/page.tsx, layout.tsx, globals.css. |
components/ |
React components: game/ (board, chat, timer, move history), landing/hero.tsx, ui/ (button, card, dialog, input), theme-provider.tsx, error-boundary.tsx. |
lib/ |
Core logic: chess-engine.ts (rules, FEN), p2p.ts (Trystero + BLE helpers), ble-transport.ts (BLE GATT room), transport-types.ts (Room interface), store.ts, elo.ts, pgn.ts, constants.ts, clipboard.ts. |
types/ |
TypeScript: web-bluetooth.d.ts (Web Bluetooth API for BLE). |
documentation/ |
Index, architecture, protocol, SDK guide, ranking API. |
public/ |
Static assets and manifest.webmanifest. |
Full “where to find what” map: documentation/README.md.
| Document | Description |
|---|---|
| documentation/README.md | Index — quick links and map of the codebase. |
| Architecture | High-level design, transports (WebRTC + BLE), diagrams, data flow, state, discovery/NAT, stability (disconnect, refresh), security. |
| Protocol | Wire protocol — transports (WebRTC, BLE), rooms, action types (≤12 bytes), JSON payloads, lobby and game flows. Required for building other clients. |
| SDK guide | How to build FediChess clients in other languages (JS/TS, Python, Go, Rust) and interoperate with this app. |
| Ranking API | Optional voluntary ranking service: submit results, leaderboard. User consent and configurable. |
| CHANGELOG | Version history and release notes (current: 0.3.0). |
| Releases | How to cut a release; tag-triggered GitHub Releases with notes from CHANGELOG. |
| Deployment checks | Require CI to pass before production deploys (Vercel). |
All of the above live in the documentation/ folder; documentation/README.md is the index.
FediChess has no central game server. All gameplay (moves, chat, sync) happens directly between peers. The app uses two transports that share the same wire protocol (rooms, action names, JSON payloads):
- WebRTC (online) — Browsers connect to public WebTorrent-compatible WSS trackers to discover each other, then form peer-to-peer WebRTC data channels. The trackers are used only for discovery and signaling; once connected, game data flows only between the two players (and any spectators). Lobby room:
p2p-chess-global; each game gets a roomp2p-chess-{gameId}. - BLE (nearby) — For local play without internet, the same protocol runs over Web Bluetooth (GATT). One device acts as a peripheral advertising the FediChess GATT service; the browser connects as central. Messages are length-prefixed
actionName+ JSON, same payloads as WebRTC.
Lobby and game UI are transport-agnostic: they use a single Room interface (makeAction, getPeers, onPeerJoin, onPeerLeave, leave) implemented by both Trystero (WebRTC) and the BLE layer. State (user, lobby, game) lives in Zustand; persistence (ELO, game history) is IndexedDB and optional account backup (HMAC-signed export). For full diagrams, data flow, and edge cases (disconnect, refresh, NAT), see Architecture.
| Transport | Discovery | Data | Scope | Use case |
|---|---|---|---|---|
| WebRTC | WebTorrent trackers (WSS), Trystero | WebRTC data channels | Many peers per room; lobby + game rooms | Online play, spectators |
| BLE | Web Bluetooth device picker (FediChess GATT service) | GATT characteristic (length-prefixed actionName + JSON) |
1:1 per connection | Nearby play, no internet |
Same protocol: rooms, action names, JSON payloads. Only the wire and discovery differ. Peers discovered over WebRTC are challenged over WebRTC; peers discovered over BLE are challenged over BLE. The game page uses the same Room interface for both.
- No server-side game logic. Moves and chat are sent peer-to-peer. We do not log or store game content. ELO and game history are stored only in your browser (IndexedDB) and, if you use it, in your own backup file.
- Identity. There is no account system. You choose a display name (e.g. a username); it is not verified. Anyone who can join the same lobby room can see that name and your ELO (from heartbeats). For strong authentication you would need a separate mechanism (e.g. signed credentials) not provided by this app.
- Trust. Peers can send arbitrary JSON. The app validates FEN and game events and ignores invalid data. Only the two players designated via
rolemessages may send moves and game events; spectators cannot. - Trackers. Default trackers are public WebTorrent WSS servers. They see room IDs and peer presence (for signaling); they do not see move or chat content. To reduce exposure you can run your own tracker and set
NEXT_PUBLIC_P2P_TRACKERS. - Account backup. Exports are signed with HMAC (key derived from your password) so tampering is detectable. Backups are intended for personal restore; do not share backup files if they contain data you care about.
The wire protocol is the source of truth. Any client that implements the same rooms, app ID, action names, and JSON payloads can discover peers, send challenges, and play games with this web app and other compatible clients.
- JavaScript/TypeScript: This repo is the reference. WebRTC: Use Trystero (torrent strategy), same trackers and app ID. BLE: Implement the FediChess GATT service (see protocol doc for UUIDs and message format) or use
lib/ble-transport.tsas reference. See SDK guide. - Python, Rust, C++: Official SDKs and examples live in sdks/; they use a shared Node bridge and the same protocol.
- Other languages: Use the bridge from a client (see sdks/) or a WebRTC + WebTorrent library; protocol and payload shapes are in protocol.md.
Community clients that implement the protocol can be listed in this README (open an issue or PR).
Do I need an account?
No. You set a display name in the app; it is stored locally and sent in lobby heartbeats. There is no login or password for the game server (there is no game server). Account backup is optional and password-protected for your own restore.
Why can’t I see any peers in the lobby?
You and others must be in the same lobby room (default: p2p-chess-global) and use the same app ID and trackers. If you changed NEXT_PUBLIC_P2P_TRACKERS, you’ll only see peers using the same trackers. Firewall or NAT can block WebRTC; try enabling STUN/TURN (see Configuration).
Does BLE work between two browsers?
No. The browser is always the central (client). The other side must be a peripheral advertising the FediChess GATT service (e.g. a native app or a BLE board). Two browser tabs cannot connect to each other via BLE.
Where is my ELO stored?
Locally in your browser (IndexedDB). Optional “Backup account” exports it to a file; you can restore on another device. Optional ranking API can submit results to a third-party leaderboard with your consent.
Can I self-host?
Yes. Build with npm run build and serve the out/ directory. You can point the app at your own WebTorrent trackers via NEXT_PUBLIC_P2P_TRACKERS. There is no backend to host; it’s a static site.
How do I add a new SDK (e.g. Go)?
Implement a client that spawns the Node bridge and speaks the same stdio JSON-lines protocol, or implement WebRTC + tracker discovery in your language. See sdks/ and SDK guide.
- Local ELO — Computed and stored on device; updated after each game. No server required.
- Voluntary ranking — The optional ranking API lets clients submit game results (with user consent) to a central or federated service for a global leaderboard. Anyone can run a compatible ranking server.
- Federation — Using the same protocol and trackers across instances, communities can share a player pool; optional ranking services can aggregate across instances.
Contributions are welcome: bug reports, feature ideas, code, and documentation.
- Issues: Open a GitHub Issue to report bugs or suggest features.
- Pull requests: Open a PR with your changes. Keep PRs focused; run
npm run lintbefore submitting. - Documentation: Improvements to documentation/ and this README are welcome.
- Protocol: The protocol is the source of truth. Client implementations in other languages that follow it can be proposed for listing as community SDKs.
See CONTRIBUTING.md for a short guide. By contributing, you agree that your contributions will be licensed under the MIT License.
MIT. See LICENSE.