From 03cdffb34d3a09be3b99c6e8cd5fb84feb85f9fd Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 21 Jul 2026 15:38:17 +0200 Subject: [PATCH] Update CLAUDE.md for the admin dashboard, Docker deploy and DB-only config Ran through the init skill's checklist against the current codebase: test count (49 -> 54), a new "Admin dashboard and test UI" architecture section (the two static SPAs, their endpoints, and the deliberate non-linking between them), two new non-obvious domain decisions (admin privkey export is intentional not a bug; who pays an RBF fee bump), a pointer to docs/ for the human-facing guides, and updated Known gaps (deployment gap resolved and removed; admin-token blast radius and the Docker auto-restart risk called out; user-facing history gap reworded now that the admin side has one). MVP business parameters reworded to distinguish what's admin-configurable (bet amount, min amount) from what's genuinely hardcoded (70/30 split, 1-conf threshold). Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1129054..2b6665b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,12 +8,14 @@ The user communicates in Italian in chat — reply to them in Italian. Everythin ## 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. +All 10 build-order stages from `/home/davide/.claude/plans/scalable-mixing-sloth.md` are code-complete and unit-tested (54 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. Beyond the original 10 stages: a Docker + Caddy deployment (see below), a full admin dashboard (`/admin`), and a static test UI for the user-facing flow (`/`). 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). +Human-facing guides live in [docs/](docs/) (Italian, per explicit request — an exception to this file's English-only rule below): [setup.md](docs/setup.md), [running-the-server.md](docs/running-the-server.md), [guida-utente.md](docs/guida-utente.md), [guida-admin.md](docs/guida-admin.md). + ## Commands ```bash @@ -61,7 +63,7 @@ Known risk: `docker-compose.yml` sets `restart: unless-stopped` on `app`, so a c - **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**: every business/round parameter (fee address, bet amount, round duration, round cooldown, minimum amount, network fee rate, RBF timeout) lives in the `round_config` DB table (single row, `app/rounds/config.py`) and is only editable live via the admin dashboard (`/admin`) or its API — no env var involved at all, no redeploy or restart needed. Defaults for a brand-new instance are hardcoded column defaults on the `RoundConfig` model (`app/db/models.py`), not `app/config.py`. Secrets and infra wiring (master key, JWT secret, Electrum host, admin token, database URL) stay env-var-driven in `.env` since those genuinely need a restart. -- **Round cooldown**: gap after a round closes before the next one opens, so players have time to see the outcome (default 30s). Not in the original flowchart; added afterwards as an explicit design decision. +- **Round cooldown**: `round_cooldown_seconds` — gap after a round closes before the next one opens, so players have time to see the outcome (default 30s). Not in the original flowchart; added afterwards as an explicit design decision. ## PLM network parameters @@ -78,10 +80,10 @@ Mainnet: ## 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**. +- Bet cost per round: **10 PLM** by default, admin-configurable (`RoundConfig.bet_amount_sats`) — not a fixed constant. +- Prize split: **70% winner / 30% fees**, hardcoded in `rounds/scheduler.py` (`winner_share = pool_amount_sats * 70 // 100`) — unlike bet amount, this ratio is not in `RoundConfig` and would need a code change, not an admin-panel edit. +- Minimum withdrawal amount: **1 PLM** by default, admin-configurable (`RoundConfig.min_amount_sats`) — a business-friendly floor, above the network's technical dust limit. Deposits have no server-side minimum check. +- Confirmations required for all tx types (deposit, bet, payout, withdrawal): **1**, hardcoded in `tx/confirmation.py` — not configurable, per the design decision below. ## What is PLM Lottery @@ -99,6 +101,15 @@ The flow is organized into 5 phases, each a subgraph in [flowchart.mmd](flowchar 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. +## Admin dashboard and test UI + +Two static single-page apps, served directly by FastAPI (`app/main.py` mounts `app/static/` and adds a dedicated `GET /admin` route) — no build step, no framework: + +- **`/` (`app/static/index.html`)**: the end-user test UI. Register/login, then a menu-driven dashboard (Deposito with a QR code of the address via `GET /qr/{address}`, Bet, Prelievo) with a persistent round-status card (`GET /rounds/current`: id/status/timer/participant count/jackpot) above the menu. +- **`/admin` (`app/static/admin.html`)**: gated by a token screen (not a real login — just checks `X-Admin-Token` against `ADMIN_TOKEN` from `.env`), then a navbar-driven dashboard with five sections, each backed by its own `/admin/*` endpoint (`app/api/routes/admin.py`): Parametri (`RoundConfig` CRUD), Utenti (list + per-user WIF privkey export, audit-logged), Round (history), Transazioni pendenti (in-flight RBF candidates), Audit log. **`/admin` is deliberately not linked from `/`** in either direction — reachable only by knowing the URL. + +Both pages talk to the same JSON API everything else uses; there's no separate "admin API" vs "user API" boundary beyond the `require_admin` dependency. + ## 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: @@ -107,6 +118,8 @@ These choices were made explicitly during design (not derivable from reading a s - 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. +- The admin panel can export any user's raw WIF private key (`GET /admin/users/{id}/privkey`, `app/wallet/hd.py:derive_user_wif`). This is intentional, not a vulnerability to fix: the server already holds the master key everything derives from (custodial by design, see above), so this only exposes through the API something an operator could already do via a script. Every access is written to `audit_log` (`admin_privkey_accessed`) — don't remove that logging when touching this endpoint. +- RBF fee bumps are paid by whoever's change output the tx pays back to — the user for bets/withdrawals, the pool for payouts — never by the fixed counterparty amount (recipient/winner/fee-address outputs are untouched; only the sender's own change shrinks). See `bump_fee` in `app/tx/broadcast.py`. ## Known gaps / TODO @@ -116,8 +129,8 @@ Not blockers for reading the code, but must be addressed before this is producti - **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 user-facing history endpoints** (list my own bets / withdrawals / past rounds) — a user still only has `/users/me` (balance). The admin side now has this (`/admin/rounds`, `/admin/pending-transactions`, `/admin/audit-log`), but there's no equivalent scoped to "my own history" for a logged-in user. +- **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 (the `audit_log` table records *what* changed, not which operator did it). This token now gates a lot more than config (user list, private key export, round/audit history), so its blast radius if leaked is correspondingly larger. +- **No rate limiting / abuse protection** on any endpoint (register, bet, withdrawal, admin). - 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. +- **`docker-compose.yml`'s `restart: unless-stopped`** on the app container means a crash mid-round auto-restarts straight into the scheduler-resume gap above — see the Deployment section.