Tie the withdrawal minimum to the bet amount instead of a separate field

RoundConfig.min_amount_sats was an independently-configurable floor that
could drift out of sync with bet_amount_sats for no real reason (deposits
never had a server-side minimum anyway). Drop the field and enforce
amount_sats >= config.bet_amount_sats directly in request_withdrawal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 19:25:15 +02:00
co-authored by Claude Sonnet 5
parent 6c51a81f0e
commit 7b3555f8eb
8 changed files with 44 additions and 23 deletions
-3
View File
@@ -27,7 +27,6 @@ _CONFIG_FIELDS = (
"bet_amount_sats",
"round_duration_seconds",
"round_cooldown_seconds",
"min_amount_sats",
"fee_rate_sat_vb",
"rbf_timeout_seconds",
"draw_animation_seconds",
@@ -40,7 +39,6 @@ class RoundConfigResponse(BaseModel):
bet_amount_sats: int
round_duration_seconds: int
round_cooldown_seconds: int
min_amount_sats: int
fee_rate_sat_vb: int
rbf_timeout_seconds: int
draw_animation_seconds: int
@@ -52,7 +50,6 @@ class RoundConfigUpdate(BaseModel):
bet_amount_sats: int | None = None
round_duration_seconds: int | None = None
round_cooldown_seconds: int | None = None
min_amount_sats: int | None = None
fee_rate_sat_vb: int | None = None
rbf_timeout_seconds: int | None = None
draw_animation_seconds: int | None = None
+1 -2
View File
@@ -75,7 +75,7 @@ class RoundConfig(Base):
"""Single-row operational config, DB-backed so it's editable without a redeploy.
Everything business/round-related lives here (round timing, bet amount, fee
rate, RBF timeout, minimum amount) so an operator can tune it live. Secrets
rate, RBF timeout) so an operator can tune it live. Secrets
and infra wiring (master key, JWT secret, Electrum host, admin token,
database URL) deliberately stay env-var-driven — those require a restart
anyway and aren't safe to hot-swap."""
@@ -92,7 +92,6 @@ class RoundConfig(Base):
# NOT gate the actual draw, which still waits for a real confirmed block for
# its entropy (rounds/scheduler.py) — that can take longer than this value.
draw_animation_seconds: Mapped[int] = mapped_column(default=20)
min_amount_sats: Mapped[int] = mapped_column(BigInteger, default=100_000_000)
fee_rate_sat_vb: Mapped[int] = mapped_column(default=1)
rbf_timeout_seconds: Mapped[int] = mapped_column(default=900)
# Maintenance switch: when true, the round currently in progress still runs to
-5
View File
@@ -250,8 +250,6 @@
<input id="admin-fee-address" class="mono" placeholder="plm1q...">
<label for="admin-bet-amount">Bet amount (PLM)</label>
<input id="admin-bet-amount" inputmode="decimal" placeholder="es. 10">
<label for="admin-min-amount">Importo minimo deposito/prelievo (PLM)</label>
<input id="admin-min-amount" inputmode="decimal" placeholder="es. 1">
</div>
<div>
<label for="admin-round-duration">Durata round (secondi)</label>
@@ -479,7 +477,6 @@ async function adminLoadConfig() {
document.getElementById('admin-round-duration').value = data.round_duration_seconds;
document.getElementById('admin-round-cooldown').value = data.round_cooldown_seconds;
document.getElementById('admin-draw-animation').value = data.draw_animation_seconds;
document.getElementById('admin-min-amount').value = data.min_amount_sats / SATS_PER_PLM;
document.getElementById('admin-fee-rate').value = data.fee_rate_sat_vb;
document.getElementById('admin-rbf-timeout').value = data.rbf_timeout_seconds;
renderMaintenanceState(data.paused);
@@ -533,14 +530,12 @@ async function adminSave() {
const btn = document.getElementById('save-btn');
const feeAddress = document.getElementById('admin-fee-address').value;
const betAmountPlm = parseFloat(document.getElementById('admin-bet-amount').value);
const minAmountPlm = parseFloat(document.getElementById('admin-min-amount').value);
const body = {
fee_address: feeAddress,
bet_amount_sats: Math.round(betAmountPlm * SATS_PER_PLM),
round_duration_seconds: parseInt(document.getElementById('admin-round-duration').value, 10),
round_cooldown_seconds: parseInt(document.getElementById('admin-round-cooldown').value, 10),
draw_animation_seconds: parseInt(document.getElementById('admin-draw-animation').value, 10),
min_amount_sats: Math.round(minAmountPlm * SATS_PER_PLM),
fee_rate_sat_vb: parseInt(document.getElementById('admin-fee-rate').value, 10),
rbf_timeout_seconds: parseInt(document.getElementById('admin-rbf-timeout').value, 10),
};
+2 -2
View File
@@ -19,8 +19,8 @@ async def request_withdrawal(
session: AsyncSession, client: ElectrumClient, user: User, external_address: str, amount_sats: int
) -> Withdrawal:
config = await get_round_config(session)
if amount_sats < config.min_amount_sats:
raise WithdrawalError(f"amount below the minimum of {config.min_amount_sats} sats")
if amount_sats < config.bet_amount_sats:
raise WithdrawalError(f"amount below the minimum of {config.bet_amount_sats} sats")
unspent = (
await session.scalars(