aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-30 10:28:23 -0700
committerAdam Soltys <[email protected]>2026-07-30 10:38:43 -0700
commitfd1617ea66e01557e71debc7e45ca08cfe01bf70 (patch)
tree8c3600a86d60dc7731130397c98e818970d63990 /routes
parentf5a5b1ff856efcd2849275a301daabb80ef76638 (diff)
downloadcoinos-server-fd1617ea66e01557e71debc7e45ca08cfe01bf70.tar.gz
coinos-server-fd1617ea66e01557e71debc7e45ca08cfe01bf70.zip
mint/lightning: refuse caller-supplied invoices, tiered fees, log hygiene
- invoices: reject caller-supplied bolt11/bolt12; the server always mints its own. Closes the invoice-registration path behind the shared-node credit and payment-hijack vectors. - lightning: skip mint-owned invoices (lbl* label) in the paid-invoice listener. - payments: lightning free-tier fee credit, withheld for flagged high-throughput (ln:nofree) accounts which pay an elevated rate; the failed-send reversal restores credit at the same rate. freezeCheck made crash-resilient. Lightning and bolt12 sends stay pending until the preimage settles. - payments (route): validate payment-request / invoice input types. - logging: collapse repeated xpay failure lines into one. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat (limited to 'routes')
-rw-r--r--routes/payments.ts14
1 files changed, 11 insertions, 3 deletions
diff --git a/routes/payments.ts b/routes/payments.ts
index 4239a409..9adc10ee 100644
--- a/routes/payments.ts
+++ b/routes/payments.ts
@@ -5,7 +5,7 @@ import { archive, db, g, gf, s, sa } from "$lib/db";
import { generate, getUserOffer } from "$lib/invoices";
import { replay } from "$lib/lightning";
import ln from "$lib/ln";
-import { err, l, warn } from "$lib/logging";
+import { err, l, shortError, warn } from "$lib/logging";
import mqtt from "$lib/mqtt";
import {
build,
@@ -45,6 +45,14 @@ export default {
const balance = await g(`balance:${user.id}`);
try {
+ // Reject non-string payreq/hash up front. A client sending a number/object
+ // here otherwise reaches a decoder that calls `.startsWith` on it and throws
+ // an opaque "n.startsWith is not a function" (logged bare via the catch
+ // below). A malformed *string* is fine — it fails later with a clear error.
+ if (payreq != null && typeof payreq !== "string")
+ fail("Invalid payment request");
+ if (hash != null && typeof hash !== "string") fail("Invalid invoice");
+
if (typeof amount !== "undefined") {
amount = Number.parseInt(amount);
if (amount < 0 || amount > SATS || Number.isNaN(amount))
@@ -93,7 +101,7 @@ export default {
res.send(p);
} catch (e) {
warn(user.username, "payment failed", amount, balance, hash, payreq);
- err(e.message);
+ err(shortError(e.message));
bail(res, e.message);
}
},
@@ -578,7 +586,7 @@ export default {
res.send(p);
} catch (e) {
- warn(user.username, "payment failed", e.message);
+ warn(user.username, "payment failed", shortError(e.message));
res.code(500).send(e.message);
}
},