Files
h2h-prototype/CLAUDE.md
2026-01-02 10:43:20 -06:00

6.1 KiB

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

# 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

# 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

# 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:

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:

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:

    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:

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