Init.
This commit is contained in:
216
CLAUDE.md
Normal file
216
CLAUDE.md
Normal file
@ -0,0 +1,216 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user