commit bae48c46dc46ae5ae0c8f05ecc62094bba381c05 Author: Davide Grilli Date: Mon Jul 20 21:22:35 2026 +0200 Initial commit diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1e3346e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# 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 + +This repository is at the **specification stage, not yet implemented**: it currently contains only [flowchart.mmd](flowchart.mmd), which is the source of truth for the project and describes the entire application flow. The tech stack is decided (see below) but no code, build system, or lint/test commands exist yet — once the project is scaffolded, this section must be updated with real commands (install, run, lint, test — including how to run a single test). + +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). + +## 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. diff --git a/flowchart.mmd b/flowchart.mmd new file mode 100644 index 0000000..7a48ff0 --- /dev/null +++ b/flowchart.mmd @@ -0,0 +1,55 @@ +flowchart TD + + subgraph REG["Registration"] + A["User registers: username + password"] --> B["Server derives a new P2WPKH address\n(BIP84, path m/84'/746'/0'/0/index)\nmaster xprv encrypted at rest"] + B --> C["Address linked to the user profile in the DB"] + end + + subgraph DEP["Balance top-up"] + C --> D["User sends PLM to their dedicated address"] + D --> E["ElectrumClient/SPV monitors the address\n(subscribe scripthash)"] + E --> F{"Tx confirmed\n(1 confirmation)?"} + F -- No --> E + F -- Yes --> G["User balance credited in the DB\n(balance = confirmed UTXOs on the address)"] + end + + subgraph PLAY["Bet"] + G --> H{"User confirms bet purchase?\n(fixed cost: 10 PLM per round,\nmax 1 active bet at a time,\nacquires per-user DB lock shared with WITHDRAW)"} + H -- No --> G + H -- "Yes (balance >= bet cost)" --> I["Server builds PSBT:\nuser address -> pool address\n(bet cost) + change -> user address\nfee ~1 sat/vB deducted from the bet amount"] + I --> J["Server signs with the user's derived key"] + J --> K["Broadcast tx to the network"] + K --> L{"Tx confirmed\n(1 confirmation)?"} + L -- "No (timeout)" --> K2["Fee bump (RBF) and rebroadcast"] + K2 --> K + L -- Yes --> M["User registered as a participant\nin the current round (with bet amount)"] + end + + subgraph DRAW["Periodic draw"] + N["Round timer: every X minutes (configurable, default 10)"] --> O{"Are there bets\nalready broadcast but not yet confirmed?"} + O -- Yes --> O + O -- No --> O2["Close current round"] + O2 --> P["List of round participants\n(user address + bet amount),\nordered by broadcast timestamp\n(tie-break for same-block confirmations)"] + P --> Q{"Are there participants?"} + Q -- No --> N + Q -- Yes --> R["Draw winner (simple v1 algorithm):\n1. wait for the first block confirmed after round closing\n2. seed = block hash (hex -> integer)\n3. index = seed mod participant_count\n4. winner = participants[index]\n(anyone can recompute and verify it;\nalgorithm replaceable in the future)"] + R --> S["Compute total round prize pool\n(sum of confirmed deposits to the pool address)"] + S --> T["70% of the prize pool - payout tx fee\n-> winner's deposit address"] + S --> U["30% of the prize pool (unchanged)\n-> fee address (configurable)"] + T --> V["Payout tx signed with\nthe pool address key"] + U --> V + V --> V2{"Tx confirmed\n(1 confirmation)?"} + V2 -- "No (timeout)" --> V3["Fee bump (RBF) and rebroadcast"] + V3 --> V2 + V2 -- Yes --> W["Log round\n(winner, amount, txid) for audit"] + W --> N + end + + subgraph WITHDRAW["Withdrawal (simple v1)"] + G --> X["User requests withdrawal:\nexternal address + amount <= balance\n(min 1 PLM, acquires per-user DB lock\nshared with PLAY)"] + X --> Y["Server builds and signs PSBT:\nuser address -> external address\n+ optional change -> user address\nfee deducted from the withdrawn amount"] + Y --> Z["Broadcast + wait for 1 confirmation\n(same RBF-on-timeout pattern)"] + Z --> G + end + + M --> N