aboutsummaryrefslogtreecommitdiffstats
path: root/lib/payments.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-21 06:15:30 -0700
committerAdam Soltys <[email protected]>2026-10-29 16:40:36 -0700
commit5cafd3dad520ea620deee8519c9d26eeeaa258f6 (patch)
tree16589c7374a23be9daa8f9d3f68fadf7b7343bae /lib/payments.ts
parentbdeda0fa35a0c0d1a59621f95143e09ce8def8b9 (diff)
downloadcoinos-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.ts9
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);