diff options
| author | Adam Soltys <[email protected]> | 2026-10-21 06:15:30 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-29 16:40:36 -0700 |
| commit | 5cafd3dad520ea620deee8519c9d26eeeaa258f6 (patch) | |
| tree | 16589c7374a23be9daa8f9d3f68fadf7b7343bae /lib/payments.ts | |
| parent | bdeda0fa35a0c0d1a59621f95143e09ce8def8b9 (diff) | |
| download | coinos-server-5cafd3dad520ea620deee8519c9d26eeeaa258f6.tar.gz coinos-server-5cafd3dad520ea620deee8519c9d26eeeaa258f6.zip | |
payments: refuse a lightning send over the balance before quoting its route
A retrying client against an empty account cost an askrene query per
attempt. debit() stays the authoritative check. Route-quote failures are
also rate-limited in the log per user.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NfM8zA4DdKvW1316z4zCaD
Diffstat (limited to 'lib/payments.ts')
| -rw-r--r-- | lib/payments.ts | 9 |
1 files changed, 8 insertions, 1 deletions
diff --git a/lib/payments.ts b/lib/payments.ts index 09cf2cf6..20025cd2 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -1011,6 +1011,11 @@ export const sendLightning = async ({ const amt = amount_msat ? Math.round(amount_msat / 1000) : amount; + // Bots retrying sends against an empty account would otherwise cost an + // askrene query each. debit() below is still the authoritative check. + if (amt > (Number.parseInt(await g(`balance:${user.id}`)) || 0)) + fail(t(user).insufficientFunds); + // Ask askrene up front what routing this will cost — the same query xpay // runs — so the maxfee we reserve is the real fee, not a guess. A caller // that names its own cap keeps it (finalize refunds whatever xpay doesn't @@ -1022,7 +1027,9 @@ export const sendLightning = async ({ try { ({ fee: quoted } = await quoteRoutingFee({ pr, amount: amt })); } catch (e: any) { - warn("route quote failed", pr.substr(-8), shortError(e?.message)); + const n = quiet(`quotefail:${user.id}`, 5 * 60 * 1000); + if (n !== null) + warn("route quote failed", user.username, pr.substr(-8), shortError(e?.message), suppressed(n)); } const explicit = Number.parseInt(fee); |
