ManaMesh — Project Overview and Technology Choices Project Intent ManaMesh is an open-source, browser-based multiplayer platform for playing competitive card games such as Magic: The Gathering, One Piece Card Game, Lorcana, and others. It is inspired by existing play-testing tools like Untap.in, but designed from the ground up to be more performant, extensible, and resilient. The core intent of the project is to create a community-owned and community-operated digital card game ecosystem that:
Allows players to build decks, play matches, and share deck lists in a way that mirrors real-world competitive card game culture. Minimizes reliance on centralized servers by leveraging peer-to-peer networking and decentralized storage. Remains playable even if the original hosting service is discontinued, through offline/LAN support and open-source code. Enables community members to host their own matchmaking, signaling, or seeding servers without requiring a specific technology stack. Provides a freemium model where core gameplay is free, while premium features (e.g., cloud deck sync, ad-free experience) add value without gating essential play.
ManaMesh is built for card game enthusiasts and developers who value openness, decentralization, and long-term sustainability. Key Design Principles
Modularity – Game-specific rules (e.g., MTG tutors vs. One Piece top-deck peeks) are implemented as pluggable handlers. Decentralization – Gameplay networking, asset distribution, and deck storage use peer-to-peer and IPFS-based technologies. Security & Fairness – In-game deck state uses cryptographic commitments and mental poker techniques to prevent cheating while allowing open deck sharing outside of matches. Open Source First – The entire codebase is intended to be released under a permissive license (MIT or Apache 2.0) to encourage community contributions and self-hosting. Progressive Enhancement – The app works as a Progressive Web App (PWA) and can be packaged for desktop/mobile, supporting fully detached play.
Technology Choices Frontend & Game Engine
React + Vite – Fast development server with hot module replacement for rapid iteration. boardgame.io – Turn-based multiplayer game framework that handles state synchronization, moves, and phases. Chosen for its simplicity and excellent TypeScript support. Phaser 3 – Lightweight 2D rendering engine for card interactions, animations, drag-and-drop, and visual effects. TypeScript – Provides type safety across the entire stack, especially important for modular game rules and cryptographic operations.
Networking & Peer-to-Peer
libp2p (JavaScript implementation) – Core P2P networking layer supporting WebRTC data channels, mDNS (LAN discovery), and DHT (global peer discovery). Enables serverless matchmaking and gameplay in detached mode.
Decentralized Storage & Data Distribution
helia (JS IPFS implementation) – Browser-native IPFS node for adding, pinning, and retrieving content-addressed data. OrbitDB – Decentralized, peer-replicated database built on IPFS. Used for storing and sharing deck lists and community card data. WebTorrent – Optional hybrid torrenting for faster distribution of larger asset packages. IndexedDB – Local browser persistence for offline deck storage and caching of IPFS content.
Backend & Metadata
Node.js + Express – Minimal backend for optional centralized services (signaling, matchmaking, premium sync). MongoDB Atlas – Serves as a searchable directory of IPFS CIDs and magnet links. Stores user profiles, premium subscription data, and metadata for discoverability. Community servers can replace or fork this component.
Cryptography & Fair Play
elliptic – Elliptic curve operations used in mental poker protocols. circomlibjs / snarkyjs – Zero-knowledge proof support for verifiable deck operations (e.g., proving a search was performed correctly without revealing the deck).
Build & Development Tools
Yarn Workspaces – Monorepo management for frontend and backend packages. Vitest – Fast unit testing integrated with Vite. ESLint + Prettier – Code quality and formatting.
Why These Choices?
Goal Technology Choice Reason
Fast iteration Vite + React + TypeScript Instant HMR, excellent developer experience
Turn-based multiplayer logic boardgame.io Proven, simple, TypeScript-first framework
Card visuals & interaction Phaser 3 Lightweight, mature, great for 2D card games
Decentralized gameplay libp2p Browser-native P2P with WebRTC, mDNS, and DHT support
Decentralized asset storage helia + OrbitDB + IPFS Content-addressed, peer-seeded distribution; resilient to central failure
Searchable metadata MongoDB Atlas + Atlas Search Fast full-text search over CIDs; easy to self-host or replace
Fair play without full trust Mental poker, commitments, ZKPs Prevents cheating while allowing open deck sharing outside matches
Community ownership Open-source (MIT/Apache) + modular design Anyone can host servers, contribute games, or fork the project
Development Setup
- Node.js 20.x or later
- Yarn 4.x (Berry)
This repository uses git submodules for forked dependencies. Clone with:
git clone --recurse-submodules https://github.com/cyotee/manamesh.gitOr if you've already cloned:
git submodule update --init --recursiveThe vendor/ directory contains forked versions of boardgame.io dependencies:
| Package | Path | Description |
|---|---|---|
| boardgame.io | vendor/boardgame.io | Core game framework (forked for P2P transport) |
| @boardgame.io/p2p | vendor/boardgameIO-p2p | P2P transport layer |
Update submodules to latest:
git submodule update --remote --mergeMake changes to a submodule:
cd vendor/boardgame.io
# Make your changes
git commit -am "Your changes"
git push origin main
cd ../..
git add vendor/boardgame.io
git commit -m "Update boardgame.io submodule"Switch submodule to a different branch:
cd vendor/boardgame.io
git checkout feature-branch
cd ../..
git add vendor/boardgame.io
git commit -m "Switch boardgame.io to feature-branch"yarn install# Start frontend development server
yarn dev:frontend
# Run tests
yarn test
# Build all packages
yarn buildManaMesh uses a hybrid P2P transport system that automatically selects the best connection method. This eliminates dependency on centralized STUN servers while maintaining compatibility with various network environments.
The system tries transports in this order (first successful wins):
| Priority | Transport | Best For | STUN Required |
|---|---|---|---|
| 1 | LAN / Local Network | Same WiFi/network, LAN parties | No |
| 2 | Direct IP | VPN users, port-forwarded setups | No |
| 3 | Circuit Relay | NAT traversal via Protocol Labs nodes | No |
| 4 | Join Code | Fallback with copy/paste SDP exchange | Yes (Google STUN) |
Click the settings gear in the P2P Lobby to open the Transport Settings modal:
- Enable/Disable individual transports
- Force Transport - Select a specific transport for testing
- Verbose Logging - Enable detailed console logs for debugging
- Generate URL - Create shareable links with your transport config
Override transport settings via URL for testing or sharing:
# Force a specific transport
http://localhost:3000/?transport=relay # Force Circuit Relay only
http://localhost:3000/?transport=lan # Force LAN only
http://localhost:3000/?transport=joinCode # Force Join Code only
# Reset to defaults
http://localhost:3000/?transport=all # Enable all transports
# Enable specific transports
http://localhost:3000/?transport=lan,relay # Only LAN and Relay
# Enable verbose logging
http://localhost:3000/?verbose=true- Uses mDNS for automatic peer discovery on the same network
- No internet required - works completely offline
- Lowest latency for local multiplayer
- Manual IP:port exchange for custom setups
- Ideal for VPN connections or port-forwarded home servers
- Bypasses NAT issues when you control the network
- NAT traversal via Protocol Labs' decentralized relay network
- No STUN servers - uses libp2p circuit relay v2
- Works across most network configurations
- Two-way SDP offer/answer exchange via copy/paste
- Uses Google STUN servers for ICE candidate gathering
- Most compatible but requires external code sharing (Discord, etc.)
- Settings are automatically saved to
localStorage - URL parameters override localStorage for the current session
- Use "Reset to Defaults" to clear saved preferences
Connection fails on all transports:
- Check if both players have at least one common transport enabled
- Try forcing a specific transport to isolate the issue
- Enable verbose logging and check the browser console
LAN transport not working:
- Verify both devices are on the same network
- Some networks block mDNS - try Direct IP instead
Relay transport slow:
- Relay adds latency due to routing through third-party nodes
- If on same network, ensure LAN transport is enabled
Card assets (images, metadata) are loaded from IPFS with automatic caching for offline play. Assets are not embedded in the app bundle - they're fetched on-demand and cached locally.
┌─────────────────────────────────────────────────────────────┐
│ Asset Request │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. IndexedDB Cache (local) │
│ • Instant if cached │
│ • Works completely offline │
└─────────────────────────────────────────────────────────────┘
│ cache miss
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Helia (Browser IPFS Node) │
│ • Direct P2P fetch from IPFS network │
│ • Content-addressed by CID │
│ • Timeout: configurable (default 10s) │
└─────────────────────────────────────────────────────────────┘
│ fail/timeout
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. HTTP Gateways (fallback) │
│ • ipfs.io, dweb.link, cloudflare-ipfs.com │
│ • Tries multiple gateways in sequence │
│ • Most reliable but centralized │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Cache Result → IndexedDB │
│ (for future offline access) │
└─────────────────────────────────────────────────────────────┘
Card assets are organized into asset packs stored in assets/packs/:
assets/packs/standard-playing-cards/
├── manifest.json # Pack metadata, card list, CIDs
├── cards/ # Card images (PNG)
│ ├── ace-spades.png
│ ├── king-hearts.png
│ └── ...
└── README.md # Pack documentation
Each pack has a manifest that describes:
- Pack metadata (name, version, author)
- Card definitions (id, name, suit, rank)
- IPFS CIDs for each asset
| File | Purpose |
|---|---|
src/assets/ipfs-loader.ts |
Core loader with Helia + gateway fallback |
src/assets/config.ts |
Gateway URLs, timeouts, preferences |
src/assets/cache.ts |
IndexedDB caching layer |
src/assets/loader/ |
Asset pack manifest parsing |
Gateway and timeout settings in src/assets/config.ts:
// Default configuration
{
gateways: [
'https://ipfs.io/ipfs/',
'https://dweb.link/ipfs/',
'https://cloudflare-ipfs.com/ipfs/',
],
heliaInitTimeout: 5000, // Max time to initialize Helia
heliaFetchTimeout: 10000, // Max time for Helia fetch
gatewayTimeout: 15000, // Max time per gateway attempt
preferGateway: false, // Try Helia first by default
}Once assets are cached:
- No internet required - All cached assets load from IndexedDB
- Instant loading - No network latency for cached content
- Persistent - Cache survives browser restarts
To pre-cache assets for offline play, simply load them once while online. The app automatically caches everything it fetches.
- Create a manifest following the schema in
src/assets/loader/types.ts - Upload images to IPFS (via
ipfs addor a pinning service) - Add CIDs to your manifest
- Place the pack in
assets/packs/your-pack-name/
ManaMesh uses a pluggable game module system. Each game (War, Poker, MTG, etc.) is implemented as a module that provides:
- Card types and schemas
- Zone definitions (deck, hand, battlefield, etc.)
- Game logic and moves
- boardgame.io integration
| Module | Status | Description |
|---|---|---|
| War | Complete | Classic War card game - flip cards, higher wins |
| Poker | Ready | Texas Hold'em (planned) |
| MTG | Ready | Magic: The Gathering (planned) |
The War game module is the first complete implementation. To run its tests:
# Run War game tests
yarn workspace @manamesh/frontend test src/game/modules/war/game.test.ts
# Run with watch mode for development
yarn workspace @manamesh/frontend test src/game/modules/war/game.test.ts --watch
# Run all game module tests
yarn workspace @manamesh/frontend test src/game/modules/- Setup: A standard 52-card deck is shuffled and split evenly between two players (26 cards each)
- Gameplay: Each player flips their top card simultaneously
- Winning a Round: The player with the higher card wins both cards
- War: If cards match, each player places 3 cards face-down and 1 face-up. Higher face-up card wins all cards
- Victory: First player to collect all 52 cards wins
packages/frontend/src/game/modules/war/
├── types.ts # WarCard, WarState, zone definitions
├── game.ts # boardgame.io Game, moves, validation
├── index.ts # Module exports
└── game.test.ts # 56 tests covering full game flow
import { WarModule, WarGame } from './game/modules/war';
// Get the boardgame.io Game definition
const game = WarModule.getBoardgameIOGame();
// Create initial state
const state = WarModule.initialState({
numPlayers: 2,
playerIDs: ['0', '1'],
});
// Validate a move
const result = WarModule.validateMove(state, 'flipCard', '0');Future Vision ManaMesh aims to become a platform where the community collectively maintains card data, hosts game servers, and extends support for new games. By combining modern web technologies with decentralization primitives, we hope to create a lasting, player-owned alternative in the digital card game space.