# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview H2H (Head to Head) is a peer-to-peer betting platform MVP where users can create, accept, and settle wagers directly with other users. The platform demonstrates core betting workflows with escrow, real-time updates, and a marketplace. ## Tech Stack - **Backend**: FastAPI (Python 3.11+) with async SQLAlchemy ORM - **Database**: SQLite (via aiosqlite) - designed for easy migration to PostgreSQL - **Frontend**: React 18+ with Vite, TypeScript, TailwindCSS - **Authentication**: JWT tokens with refresh mechanism - **Real-time**: WebSockets for live updates - **State Management**: Zustand for client state, React Query for server state ## Commands ### Backend Development ```bash # Setup cd backend python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt # Initialize database python seed_data.py # Run development server uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # Run with Docker docker-compose up backend ``` ### Frontend Development ```bash # Setup cd frontend npm install # Run development server npm run dev # Build for production npm run build # Type checking npm run type-check # If configured # Run with Docker docker-compose up frontend ``` ### Full Stack ```bash # Start both services docker-compose up --build # Stop all services docker-compose down # Reset database (when needed) docker-compose down -v ``` ## Architecture ### Core Data Flow 1. **Bet Creation Flow**: - User creates bet → Validates funds → Bet enters marketplace (status: OPEN) - No funds locked until bet is accepted 2. **Bet Matching Flow**: - User accepts bet → Validates opponent funds → Locks funds in escrow for BOTH parties → Updates bet status to MATCHED - Both creator and acceptor have funds locked in wallet.escrow 3. **Settlement Flow**: - Winner submits claim → Loser confirms OR disputes → Funds distributed or flagged for review - On confirm: escrow released, winner receives stake from both sides - On dispute: bet marked DISPUTED for manual resolution ### Database Architecture **Critical relationship pattern**: The Bet model uses multiple foreign keys to User: - `creator_id` → User who created the bet - `opponent_id` → User who accepted the bet (nullable until matched) - `winner_id` → User who won (nullable until settled) When querying bets with relationships, use `selectinload()` or `joinedload()` to prevent N+1 queries: ```python query = select(Bet).options( selectinload(Bet.creator), selectinload(Bet.opponent) ) ``` ### Async SQLAlchemy Patterns All database operations are async. Key patterns: **Transaction safety for critical operations**: ```python async with db.begin_nested(): # Creates savepoint bet = await db.get(Bet, bet_id, with_for_update=True) # Row lock wallet.balance -= amount wallet.escrow += amount # All changes committed together or rolled back ``` **CRUD operations must use async methods**: - `await db.execute(query)` not `db.execute(query)` - `await db.commit()` not `db.commit()` - `await db.refresh(obj)` not `db.refresh(obj)` ### Wallet & Escrow System The wallet has two balance fields: - `balance`: Available funds the user can use - `escrow`: Locked funds in active bets **Critical invariant**: `balance + escrow` should always equal total funds. When accepting a bet: 1. Deduct from `balance` 2. Add to `escrow` 3. Create ESCROW_LOCK transaction record When settling: 1. Deduct from loser's `escrow` 2. Add to winner's `balance` 3. Deduct from winner's `escrow` (their original stake) 4. Add to winner's `balance` (their original stake returned) ### WebSocket Events WebSocket connections authenticated via query param: `ws://host/api/v1/ws?token={jwt}` Event types broadcast to relevant users: - `bet_created`: New bet in marketplace - `bet_accepted`: Bet matched with opponent - `bet_settled`: Bet result confirmed - `wallet_updated`: Balance or escrow changed - `notification`: General user notifications ## Key Constraints ### Bet State Transitions Valid state transitions: - OPEN → MATCHED (via accept) - OPEN → CANCELLED (creator cancels before match) - MATCHED → IN_PROGRESS (event starts) - IN_PROGRESS → PENDING_RESULT (event ends, awaiting settlement) - PENDING_RESULT → COMPLETED (settlement confirmed) - PENDING_RESULT → DISPUTED (settlement disputed) Invalid: Cannot cancel MATCHED bets, cannot settle OPEN bets ### Validation Rules - Stake amount: Must be > 0, ≤ $10,000 - User cannot accept their own bet - User must have sufficient balance to accept bet - Only creator can modify/cancel OPEN bets - Only bet participants can settle - Odds must be > 0 ## Environment Configuration Backend requires (see `backend/.env.example`): - `DATABASE_URL`: SQLite path (default: `sqlite+aiosqlite:///./data/h2h.db`) - `JWT_SECRET`: Secret for signing tokens - `JWT_ALGORITHM`: HS256 - `ACCESS_TOKEN_EXPIRE_MINUTES`: Token TTL Frontend requires (see `frontend/.env.example`): - `VITE_API_URL`: Backend API URL (default: `http://localhost:8000`) - `VITE_WS_URL`: WebSocket URL (default: `ws://localhost:8000`) ## Migration to Production Database The codebase is designed for easy migration from SQLite to PostgreSQL: 1. Update `DATABASE_URL` to PostgreSQL connection string: ```python DATABASE_URL = "postgresql+asyncpg://user:pass@host:5432/h2h" ``` 2. Update `requirements.txt`: - Remove: `aiosqlite` - Add: `asyncpg` 3. Run Alembic migrations against new database 4. No code changes needed - SQLAlchemy ORM abstracts database differences ## Testing Data Run `python seed_data.py` to create test users: - alice@example.com / password123 - bob@example.com / password123 - charlie@example.com / password123 Each starts with $1000 balance and sample bets in marketplace. ## MVP Scope **In scope**: User auth, virtual wallet, bet creation/acceptance, basic settlement, WebSocket updates, responsive UI **Out of scope** (future): Real payments, KYC/AML, blockchain, odds feeds, admin panel, email notifications, social features