Files
plm-lottery/CLAUDE.md
T
davideandClaude Sonnet 5 f783cfaf80 Update CLAUDE.md with real commands and known gaps
Records the MVP build as code-complete and unit-tested, documents the
real install/run/test commands now that the project is scaffolded, and
lists known gaps (scheduler restart resume, payout retry, RBF fallback,
missing history endpoints, deployment, admin auth, rate limiting) to
address before treating this as production-ready.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 10:27:12 +02:00

104 lines
9.6 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Language
The user communicates in Italian in chat — reply to them in Italian. Everything written to the repository (code, comments, commit messages, docs, this file) must be in English. Reasoning/thinking should also be done in English.
## Project status
All 10 build-order stages from `/home/davide/.claude/plans/scalable-mixing-sloth.md` are code-complete and unit-tested (49 tests green): project skeleton, DB schema + Alembic migrations, auth, HD wallet derivation, Electrum client, deposit detection, bet flow, round/draw engine, payout, withdrawal, RBF fee-bump, admin config + audit log.
Real-money verification on mainnet, done so far: registration + address derivation, deposit crediting (1-conf), a real 10 PLM bet (broadcast, confirmed, change credited back), and a full round cycle — close → draw (real block hash) → payout (70/30 split, exact sat math verified against the broadcast tx) → confirmation → round closed → next round auto-opened. Withdrawal and the RBF bump path are unit-tested but have never been exercised against a live broadcast. See "Known gaps" below before treating this as production-ready.
Before writing code, always read [flowchart.mmd](flowchart.mmd) in full: every node in the diagram corresponds to a behavior that must be implemented exactly as described, including the labels on the edges (conditions, retries, loops).
## Commands
```bash
source .venv/bin/activate # venv already created at .venv/
pip install -e ".[dev]" # install/update deps
alembic upgrade head # apply DB migrations
alembic revision --autogenerate -m "message" # generate a new migration after editing app/db/models.py
PYTHONPATH=. python scripts/generate_master_key.py # one-time: create+encrypt the server's master xprv (requires XPRV_ENCRYPTION_KEY in .env)
uvicorn app.main:app --reload --port 8123 # run the dev server
python -m pytest # run all tests
python -m pytest tests/unit/test_hd.py # run one test file
python -m pytest tests/unit/test_hd.py::test_derivation_is_deterministic # run a single test
```
`.env` (gitignored) holds real secrets for local dev; `.env.example` documents the required keys and how to generate them.
## Tech stack (MVP)
- **Backend language**: Python.
- **PLM node access**: Electrum protocol only (no full node/P2P). Bootstrap server for development: `santantonio.sytes.net:50002` (SSL).
- **Auth**: Argon2 password hashing + JWT sessions.
- **Secrets**: master xprv encrypted at rest with a symmetric scheme (AES-GCM/Fernet); the encryption key itself lives in an env var, never in the DB or in git.
- **Operational config** (fee/commission address, RBF fee-bump wallet, etc.): stored in a DB config table, not env vars — must be editable without a redeploy.
- **Round duration**: configurable via env var, default 10 minutes (not hardcoded).
## PLM network parameters
Source of truth: `PalladiumWallet` repo, [ChainProfiles.cs](../PalladiumWallet/src/Core/Chain/ChainProfiles.cs) and [PalladiumNetworks.cs](../PalladiumWallet/src/Core/Chain/PalladiumNetworks.cs) — always re-check that repo if a value is needed that isn't listed here, rather than guessing.
Mainnet:
- BIP44/84 coin type: `746` (i.e. HD path `m/84'/746'/0'/0/index`)
- Bech32 HRP: `plm`
- P2PKH address version byte: `55` (addresses start with `P`)
- P2SH address version byte: `5`
- WIF prefix: `0x80`
- Block time: 120s
- BIP32 extended key headers (Legacy/native-segwit `zprv`/`zpub` etc.): see `ExtKeyHeaders` in `ChainProfiles.cs`
## MVP business parameters
- Fixed bet cost: **10 PLM** per round.
- Prize split: 70% winner / 30% fees (fee address configurable in DB).
- Minimum deposit/withdrawal amount: **1 PLM** (business-friendly floor, above the network's technical dust limit).
- Confirmations required for all tx types (deposit, bet, payout, withdrawal): **1**.
## What is PLM Lottery
A periodic-round lottery system built on a Bitcoin-like coin (PLM, mainnet). Each user gets a dedicated P2WPKH address (server-side HD wallet); they deposit PLM to that address, place a fixed-cost bet to enter the current round, and when the round closes a winner is drawn who receives 70% of the prize pool (the remaining 30% goes to fees).
## Architecture (from the flowchart subgraphs)
The flow is organized into 5 phases, each a subgraph in [flowchart.mmd](flowchart.mmd):
- **REG (Registration)**: on signup the server derives a new P2WPKH address via BIP84 (`m/84'/coin'/0'/0/index`, one index per user) from a master xprv **encrypted at rest**. This address is permanent and serves as both the deposit address and the address that receives winnings and withdrawals.
- **DEP (Balance top-up)**: an ElectrumClient/SPV subscribes to the user's address scripthash. Internal balance (DB) is credited after **1 confirmation only** — the reorg risk at 1-conf is knowingly accepted in v1, with no rollback logic.
- **PLAY (Bet)**: fixed cost per round, **at most one active bet per user at a time** in v1. The server builds a PSBT user-address → pool-address for the fixed amount, with a **change output back to the same user address** (the user's balance must never exactly equal the bet amount). Fee minimized (~1 sat/vB), **deducted from the bet amount**. If the tx doesn't confirm within a timeout, fee-bump (RBF) and rebroadcast.
- **DRAW (Periodic draw)**: configurable timer (default 10 minutes). Round closing **waits for all already-broadcast bets to confirm** before proceeding (avoids losing bets at the round boundary). The **next round only opens once the previous round's payout tx is confirmed** — rounds never overlap in v1. v1 draw algorithm (deliberately simple, meant to be replaced later): wait for the first block confirmed after round closing, use its hash as seed, `index = seed mod participant_count` over the participant list ordered by **broadcast timestamp** (this is also the tie-break when two bets confirm in the same block). Every participant has **equal probability regardless of bet amount** (consistent with the fixed bet amount). The payout (70% winner / 30% fees) is signed with the pool address key; the **payout fee is deducted from the winner's 70%**, the 30% fee share stays intact. Same timeout → RBF → rebroadcast pattern here too.
- **WITHDRAW (Withdrawal)**: the only way to move funds out of the platform to an external address. PSBT user-address → external-address + change back to the user address, fee deducted from the withdrawn amount, same RBF retry pattern.
PLAY and WITHDRAW share a **per-user DB lock**: a user can never have a bet-build and a withdrawal-build in flight at the same time, since both would otherwise spend from the same UTXO set on the user's dedicated address.
## Non-obvious domain decisions
These choices were made explicitly during design (not derivable from reading a single file) and must be respected in any implementation:
- Private keys (xprv) are generated and held **server-side** — this is not a non-custodial system: the user never controls their own keys until they make an explicit withdrawal.
- The user's personal deposit address always doubles as the winnings-receiving address: there is no separate "winner address".
- 1 confirmation is the chosen threshold for all tx types (deposits, bets, payouts, withdrawals): don't introduce different thresholds (e.g. 3 or 6 confirmations) without an explicit decision.
- The draw algorithm (node R) is deliberately simple and should be treated as a replaceable/pluggable component, not the final design — don't architect around its current implementation.
## Known gaps / TODO
Not blockers for reading the code, but must be addressed before this is production-ready:
- **Scheduler doesn't resume mid-flight rounds after a restart.** `rounds/scheduler.py`'s `_tick()` only acts on rounds with `status == "open"`. If the process restarts while a round is `closing`/`drawing`/`paying_out`, it's permanently stuck — nothing re-enters `_wait_for_next_block` or retries `_trigger_payout`. Needs a startup routine that inspects in-progress rounds and resumes (or a periodic "unstick" check) before this can run unattended.
- **RBF bump only handles one case**: a single change output, paying back to the tx's own sender address, large enough to absorb the fee increase. No additional-input selection fallback — an exact-amount tx (no change) or a change output too small to absorb the bump raises `RbfError` and needs manual operator intervention. Documented in `tx/broadcast.py`.
- **Payout retry**: if `_trigger_payout` fails (e.g. insufficient pool UTXOs, Electrum disconnected), it just logs and returns — the round stays stuck in `paying_out` with no automatic retry.
- **Withdrawal and RBF bump have never been exercised against a live broadcast** — only deposit and bet flow are verified end-to-end with real PLM as of this commit.
- **No user-facing history endpoints** (list my bets / withdrawals / past rounds) — only `/users/me` (balance) exists.
- **No deployment setup**: no Dockerfile, process manager, or reconnect/supervision beyond the in-process asyncio tasks. Currently only run manually via `uvicorn` in a dev venv.
- **Admin auth is a single shared bearer token** (`ADMIN_TOKEN`, `X-Admin-Token` header) — no per-admin identity or audit trail of who changed config.
- **No rate limiting / abuse protection** on any endpoint (register, bet, withdrawal).
- No automated integration tests against a live Electrum connection — all live-network verification so far has been manual (ad hoc scripts + real mainnet transactions), not part of the `pytest` suite.