diff options
| author | Adam Soltys <[email protected]> | 2026-06-18 11:15:27 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-18 11:15:27 -0700 |
| commit | d2762384156e3310ca08d7c295dea0e3856f8c9d (patch) | |
| tree | a25c56a6af40fff8374768a70a9fe0ba983a3619 | |
| parent | 3a60e3a49a031c8efea067b3c142b086bb6f5847 (diff) | |
| download | coinos-server-d2762384156e3310ca08d7c295dea0e3856f8c9d.tar.gz coinos-server-d2762384156e3310ca08d7c295dea0e3856f8c9d.zip | |
payments: normalize bolt11 to lowercase so failed uppercase sends get reconciled
BOLT11 is case-insensitive, but an uppercase-pasted invoice was stored as
the payment hash and the pending-set entry verbatim. check()'s
startsWith("ln") test is case-sensitive, so it treated such pending
entries as non-lightning and evicted them without reversing — a failed
send would never be auto-refunded.
- sendLightning: lowercase the invoice up front so the stored hash, the
pending entry, and xpay all use the same form.
- check(): lowercase before the startsWith("ln") test (defensive; also
rescues any already-stored uppercase entries).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | lib/payments.ts | 13 |
1 files changed, 12 insertions, 1 deletions
diff --git a/lib/payments.ts b/lib/payments.ts index 16eb7213..c715af47 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -697,6 +697,14 @@ export const sendLightning = async ({ }) => { let p; + // BOLT11 is case-insensitive, but downstream code is not: the stored payment + // hash, the `pending` reconciler entry, and check()'s startsWith("ln") test + // all assume lowercase. An uppercase-pasted invoice gets stored uppercase, + // then check() treats its pending entry as non-lightning and evicts it + // without reversing — a failed send that never gets auto-refunded. Normalize + // once, up front, so every downstream use sees the same lowercase form. + pr = pr.replace(/\s/g, "").toLowerCase(); + if (typeof amount !== "undefined") { amount = Number.parseInt(amount); if (amount < 0 || amount > SATS || Number.isNaN(amount)) { @@ -1072,7 +1080,10 @@ export const check = async () => { const payments = await db.sMembers("pending"); for (const pr of payments) { - if (!pr.startsWith("ln")) { + // Case-insensitive: an uppercase-pasted invoice ("LNBC…") is still a + // lightning payment. Matching case-sensitively here used to evict such + // entries without reversing, stranding failed sends with no auto-refund. + if (!pr.toLowerCase().startsWith("ln")) { await db.sRem("pending", pr); continue; } |
