diff --git a/CLAUDE.md b/CLAUDE.md index 2b6665b..2678efc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,7 +62,7 @@ Known risk: `docker-compose.yml` sets `restart: unless-stopped` on `app`, so a c - **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**: 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. +- **Operational config**: every business/round parameter (fee address, bet amount, round duration, round cooldown, draw animation duration, 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**: `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 @@ -96,7 +96,7 @@ The flow is organized into 5 phases, each a subgraph in [flowchart.mmd](flowchar - **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. +- **DRAW (Periodic draw)**: configurable timer (default 10 minutes). Once a round leaves `open` (closing/drawing/paying_out), **no new bets are accepted** for it — `place_bet` checks `status == "open"` and a new round can't open until the current one is fully `closed` (see round cooldown below). 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. The frontend shows a "drawing" animation on every user's dashboard for at least `draw_animation_seconds` (admin-configurable, default 20s) once the round starts closing — purely cosmetic, decoupled from the real (and much longer, ~block-time) wait for `winner_user_id` to actually be set; see `GET /rounds/current`'s `winner_user_id`/`winner_amount_sats` and `app/static/index.html`'s reveal logic. - **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. diff --git a/docs/guida-admin.md b/docs/guida-admin.md index b9b4be0..eec478e 100644 --- a/docs/guida-admin.md +++ b/docs/guida-admin.md @@ -40,6 +40,7 @@ business — quelli si toccano solo da qui. | **Bet amount (PLM)** | Il costo fisso d'ingresso per round. | | **Durata round (secondi)** | Quanto resta aperto un round prima di chiudersi ed estrarre il vincitore. | | **Pausa tra un round e il successivo (secondi)** | Cooldown dopo la chiusura di un round, prima che il successivo si apra — dà tempo ai giocatori di vedere l'esito. | +| **Durata animazione estrazione (secondi)** | Tempo minimo per cui la dashboard di ogni utente mostra l'animazione "Estrazione in corso" dopo la chiusura del round, prima di rivelare il vincitore. È solo un minimo: l'estrazione reale aspetta un blocco confermato (~2 minuti in media), quindi l'animazione può durare più a lungo di questo valore, mai meno. | | **Importo minimo deposito/prelievo (PLM)** | Soglia minima per un prelievo (i depositi non hanno un controllo minimo lato server, solo un floor consigliato). | | **Fee rate di rete (sat/vB)** | Fee per byte usata per costruire bet, payout e prelievi. | | **Timeout prima del fee-bump RBF (secondi)** | Dopo quanto tempo senza conferma una transazione viene ritrasmessa con fee più alta. | @@ -49,7 +50,8 @@ solo nella chiamata API — il backend lavora sempre in sats. Su un'istanza nuova (mai avviata), questi campi partono con dei default hardcoded nel codice (`RoundConfig` in `app/db/models.py`: bet 10 PLM, round -10 minuti, cooldown 30s, minimo 1 PLM, fee 1 sat/vB, RBF timeout 900s) — vanno +10 minuti, cooldown 30s, animazione estrazione 20s, minimo 1 PLM, fee 1 +sat/vB, RBF timeout 900s) — vanno comunque rivisti e confermati dal pannello prima del primo utilizzo reale. **Chi paga il fee-bump RBF?** Quando una bet, un payout o un prelievo resta diff --git a/docs/guida-utente.md b/docs/guida-utente.md index 02ecbff..105552d 100644 --- a/docs/guida-utente.md +++ b/docs/guida-utente.md @@ -32,6 +32,26 @@ Dopo l'accesso vedi, in ordine: 30% in fee) 3. **Menu di navigazione** con tre sezioni: +### Estrazione del vincitore + +Quando il round chiude, **le bet non sono più accettate** e la card del +round mostra un'animazione ("Estrazione del vincitore in corso…") al posto +del timer — la stessa cosa compare nella dashboard di ogni giocatore, non +solo la tua. L'animazione resta visibile per almeno un tempo minimo +configurabile dall'admin (default 20s), ma può durare più a lungo: il +vincitore viene scelto usando l'hash del primo blocco confermato dopo la +chiusura, quindi il tempo reale dipende dalla rete (mediamente ~2 minuti, +il block time di PLM). + +Appena il vincitore è determinato, l'animazione lascia spazio a un +messaggio: +- **"🎉 Hai vinto! +N PLM"** se sei tu il vincitore — l'importo ti verrà + accreditato non appena la transazione di payout viene confermata (il + round successivo non si apre finché questo non accade) +- **"Non hai vinto questa volta."** altrimenti + +Il messaggio resta visibile fino all'apertura del round successivo. + ### Deposito - Il tuo **saldo interno** (accreditato dopo 1 conferma di rete) con bottone