A Go Fish card game implementation using Paima Engine (Effectstream) with EVM for lobbies and Midnight for game logic.
Run the complete development environment with orchestrator:
# First time setup
bun install
# Build EVM contracts
bun run build:evm
# Start all services (backend + frontend + blockchain)
bun run devThis will launch:
- Frontend: http://localhost:3000
- API: http://localhost:9996
- Explorer: http://localhost:10590
- Blockchain: http://localhost:8545
For development with Midnight blockchain integration, you can run the infrastructure and dev server separately:
# Terminal 1: Start Midnight infrastructure (node, indexer, proof server, contract deployment)
EFFECTSTREAM_STDOUT=true bun run --filter @go-fish/node midnight:setup
# Terminal 2: Start the dev server with batcher mode (after infra is ready)
USE_TYPESCRIPT_CONTRACT=false EFFECTSTREAM_STDOUT=true USE_BATCHER_MODE=true SKIP_MIDNIGHT_INFRA=true bun run devThe SKIP_MIDNIGHT_INFRA=true flag tells the dev server to skip launching Midnight infrastructure since it's already running from the first command.
For quick frontend-only development:
bun run frontend:devVisit http://localhost:3000
/packages/
├── frontend/ # Web UI (Three.js 3D game scene, HTML overlays, TypeScript, Vite)
├── client/
│ ├── node/ # Paima engine node (state machine, APIs)
│ ├── batcher/ # Transaction batching service
│ └── database/ # PGLite queries and schema (pgtyped)
└── shared/
├── contracts/
│ ├── evm/ # Hardhat contracts for lobbies & stats (EVM)
│ └── midnight/ # Midnight contracts for game logic (stub)
├── data-types/ # Shared types and config
└── simulation/ # Game simulation logic
This project follows a hybrid blockchain architecture:
- Frontend: Three.js 3D game scene with Balatro-style post-processing, HTML overlays for lobby/menu screens
- EVM Contracts: Handle lobbies, player stats, matchmaking (Hardhat)
- Midnight Contracts: Handle private game state and logic
- Paima Node: Processes blockchain transactions and maintains game state
- Batcher: Batches user transactions to reduce on-chain costs
- Database: PGLite (in-memory/WASM PostgreSQL) for game state storage
- Simulation: Shared deterministic game logic (frontend + backend)
- 🃏 Classic Go Fish card game mechanics
- 🎮 Multiplayer lobbies (2-6 players)
- 🏆 Player statistics and leaderboards (EVM)
- 🔐 Private game state using Midnight
- 📊 Match history and rankings
Go Fish is a classic card matching game:
- Setup: Each player is dealt 5-7 cards
- Turn: On your turn, ask any player for a specific rank (e.g., "Do you have any 7s?")
- Match: If they have it, they give you all cards of that rank. You get another turn.
- Go Fish: If they don't have it, they say "Go Fish!" and you draw a card
- Books: When you collect all 4 cards of a rank, it forms a "book" (1 point)
- Win: The player with the most books when the deck runs out wins!
# Frontend Development
bun run frontend:dev # Start dev server
bun run frontend:build # Production build
# Backend
bun run dev # Start full stack via orchestrator
bun run mainnet # Start in mainnet mode
# Testing
bun run test # Node API unit tests
bun run test:e2e # E2E game-round test
# Smart Contracts (EVM)
cd packages/shared/contracts/evm
bun run build # Compile contracts
npx hardhat test # Run contract tests
# Root Level
bun run build:evm # Compile EVM contracts
bun run build:midnight # Compile Midnight contracts
bun run frontend:dev # Start frontend from root
bun run frontend:build # Build frontend from rootUSE_TYPESCRIPT_CONTRACT=true- Use local TypeScript-compiled Midnight contract (for testing without Midnight infrastructure)SKIP_EVM_LAUNCH=true- Skip launching Hardhat node and EVM deployment (use when Hardhat is already running externally)SKIP_MIDNIGHT_INFRA=true- Skip launching Midnight infrastructure (node, indexer, proof-server)SKIP_PGLITE=true- Skip launching PGLite database (for shared infrastructure environments)USE_BATCHER_MODE=true- Run frontend in batcher mode (no Lace wallet needed)DEPLOY_MIDNIGHT_CONTRACT=true- Deploy Midnight contract at startup (takes ~6 minutes)EFFECTSTREAM_STDOUT=true- Log all output to stdout instead of tmux
These are set in a .env file in packages/frontend/ or passed inline when running the dev server. Vite exposes them to the frontend via import.meta.env.
| Variable | Default | Description |
|---|---|---|
VITE_USE_LEGACY_GAME_UI |
"false" |
Set to "true" to use the old DOM-based game screen instead of the Three.js 3D scene. Lobby, wallet, and setup screens are unaffected. |
VITE_BATCHER_MODE_ENABLED |
"false" |
Set to "true" to enable batcher mode (transactions go through Paima batcher, no Lace wallet needed). |
VITE_BATCHER_URL |
http://localhost:3334 |
URL of the Midnight batcher service. |
VITE_INDEXER_HTTP_URL |
http://127.0.0.1:8089/api/v1/graphql |
Midnight indexer HTTP/GraphQL endpoint. |
VITE_INDEXER_WS_URL |
ws://127.0.0.1:8089/api/v1/graphql/ws |
Midnight indexer WebSocket endpoint. |
VITE_PAIMA_L2_CONTRACT_ADDRESS |
0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 |
Paima L2 contract address on the EVM chain. |
VITE_EVM_RPC_URL |
http://localhost:8545 |
EVM JSON-RPC endpoint (Hardhat by default). |
VITE_PROOF_SERVER_URL |
(unset) | URL of an external Midnight proof server (e.g. http://localhost:6300). When set, the frontend delegates ZK proof generation to this HTTP server instead of running proofs locally in the browser via WASM. Leave unset (the default) for in-browser WASM proving, which requires no separate proof server — the recommended mode for production since the Lace wallet does not ship with a built-in proof server. |
# Start with existing Hardhat instance
SKIP_EVM_LAUNCH=true USE_TYPESCRIPT_CONTRACT=true EFFECTSTREAM_STDOUT=true bun run devPGLite database tables for the Go Fish game:
- lobbies - Active game lobbies with player counts
- lobby_players - Players in each lobby with ready status
- games - Game instances with status (planned for Midnight integration)
- effectstream.accounts - Account management (Paima Effectstream)
- effectstream.addresses - Address-to-account mapping (Paima Effectstream)
All migrations are in packages/client/database/src/mod.ts
Queries are defined in SQL files and auto-generated using pgtyped:
- packages/client/database/src/lobby-queries.sql
- Run
pgtyped -c pgtyped.config.jsonto regenerate TypeScript query files
Blockchain commands (Paima concise grammar format).
Go Fish is a fixed 2-player game with auto-start on the second join, so the EVM grammar is deliberately minimal — just three commands:
Lobby Management (EVM):
createdLobby|playerName|lobbyName— Create a new lobby. Host is added as the first player.joinedLobby|playerName|lobbyID— Second player joins. The state machine flips the lobby toin_progressin the same transition.closedLobby|lobbyID— Host cancels an open lobby while still alone. Deletes the lobby row.
Game Actions:
Game-round logic (asking for cards, drawing, booking) runs on the Midnight
contract as ZK circuits. See packages/shared/contracts/midnight/. None of
those actions touch the EVM grammar.
See packages/shared/data-types/src/grammar.ts
- Backend: Bun, TypeScript, Paima Engine
- Frontend: Vite, TypeScript, Three.js, postprocessing, gsap
- Blockchain (EVM): Hardhat, Solidity (lobbies & stats)
- Blockchain (Midnight): Midnight contracts (game logic - stub)
- State Management: Paima State Machine (PaimaSTM)
MIT