aboutsummaryrefslogtreecommitdiffstats
path: root/lib/nwc.ts
Commit message (Collapse)AuthorAgeFilesLines
* payments: lowercase payreq before looking up our own invoicesAdam Soltys8 days1-1/+7
| | | | | | | | | An uppercase bolt11 for a coinos invoice missed the getInvoice lookup, so it was paid over lightning to our own node instead of internally. The listener then saw the sender's payment pointer for that bolt11 and skipped crediting the recipient. Normalize in POST /payments and nwc pay_invoice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ecash: redeem tokens by melting to the user's own invoiceAdam Soltys2026-09-161-39/+40
| | | | | | | | | | | Redeeming ecash is now a plain NUT-05 melt into a Lightning invoice owned by the redeeming user, credited by the regular lightning listener. Drop the house proof pool and its lock, the /mint and /melt routes, and the special-casing of a mint account in NWC and payment routing. Tokens are decoded against the mint's keyset list so short v1 ids resolve, and an empty keyset list is never cached. Co-Authored-By: Claude Fable 5.1 <[email protected]>
* security: drop the admin endpoint surface and land pending hardeningAdam Soltys2026-09-131-3/+22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Removes the admin-gated endpoints and the account flags they set, plus a batch of hardening that had accumulated uncommitted in the tree. These were deployed and verified together, so they land as one change. admin surface: - drop POST /hidepay, POST /unlimit and GET /users, their handlers, and the now-unused admin preValidation export - `unlimited` was only ever written, never read; `hidepay` only appeared in output projections. Both flags go, along with their whitelist, public-fields and type entries. request log: - redactBody now recurses. It only masked top-level keys, so a body that nests credentials one level down wrote them to the log in plaintext, and pin fields were not on the list at all. payments: - sendOnchain/build gate `aid` through requireAccount, so a caller can only spend from an account it owns - sendOnchain signs only a tx we composed, matched against a short-lived per-account build receipt - raise xpay retry_for to 60s; route-finding preferred a drained zero-fee hop and burned the whole budget on it. check()'s reversal window keeps its 2x margin. ecash / invoices / webhooks: - resolve a token's mint through the IP-pinning fetcher instead of plain fetch, closing an unauthenticated SSRF path into the internal network - reject non-positive amounts before the balance write - /sign requires the caller to own the address, rather than signing under any address the hot wallet controls - webhook delivery no longer disables TLS verification; the body carries the merchant's shared secret registration: - refuse a username the v3 registrar holds unless the caller's own nostr key is the holder (lib/names.ts) liquid: - honour the deposit kill switch at invoice generation and at credit elements image: - pin 23.3.4 and verify the release tarball checksum Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_0112TGi9GGNWqji1XQ5jZRzc
* nwc: enforce per-connection spend budgets + flag-gated incident containmentAdam Soltys2026-08-221-16/+156
| | | | | | | | | | | | | | | | | | | | | | | | | Thread the remaining allowance through every spend path (pay_invoice, pay, pay_keysend, internal transfer) as maxTotal, and have debit() assert the spend stays within it after it reloads the invoice and captures the tip — so a delegated NWC credential can no longer exceed its configured budget, and a mutated tip can't slip a small-budget check and drain via the internal path. checkBudget now returns the remaining allowance alongside any error; a blank budget is treated as deliberately unlimited and a positive budget requires a known renewal period. Add operator-controlled containment for a suspected credential disclosure, all gated behind Redis flags with no hardcoded scope: nwc:disabled fail every method closed nwc:credential-cutoff retire connections created before an epoch nwc:quarantined block an individual disclosed connection nwc:guard:dormant-keysend quarantine the first keysend on a long-idle app Fix an ownership check that read the bare pubkey instead of app:<pubkey>, which let an authenticated user overwrite another connection's config, and validate budget/fee/renewal inputs. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01XiwgmQKwKnRxUmVDd4xSf7
* nwc: reply UNAUTHORIZED on unknown connection instead of returning silentlyAdam Soltys2026-08-171-2/+13
| | | | | | | | A NIP-47 request whose pubkey/user no longer exists (deleted app or migrated user) was silently dropped, leaving the client spinning. Send an UNAUTHORIZED error reply so the client can surface it. Still not logged (not a server fault). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* nwc: budget keysend, atomic dedup, serialize spends (S4/S6/S8)Adam Soltys2026-08-071-12/+66
| | | | | | | | | | | | | | | - pay_keysend never called checkBudget and never recorded to ${pubkey}:payments, so a budget-limited connection string was a full-drain credential. Budget-check it, honor max_fee, and record the debit so subsequent budgeted calls count it (S4). - Replace the non-atomic zScore-then-(unawaited)-zAdd dedup with an awaited SET NX claim, so two concurrent deliveries of the same signed event can't both reach handle() and pay twice; also reject future-dated created_at beyond a small skew (S6). - Serialize budget-affecting methods per app with an async mutex so a concurrent request can't read a stale spent-total and overspend (S8). Co-Authored-By: Claude Opus 4.8 <[email protected]>
* nwc: republish 13194 info events unconditionally every 2 minutesAdam Soltys2026-08-041-53/+18
| | | | | | | | | | | | | The stored replaceable 13194 info event was intermittently disappearing from the relay, leaving NWC clients (Alby etc.) with "no info event" until the next hourly existence-check happened to republish it. Drop the check-then-publish dance and just republish both server info events every 2 minutes — a replaceable event overwrites in place, so it's cheap and self-heals immediately. Silence the per-publish success log since it now fires on every interval. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01Dpqu6wizYe1c1g4k5Gd8JK
* nwc: conform pay/receive to the BIP-321 methods spec (nwc#2)Adam Soltys2026-07-311-15/+116
| | | | | | | | | | | | | | | | | | | | Tightens our implementation of the BIP-321-based NWC methods proposed in https://github.com/nostr-wallet-connect/nwc/pull/2 — the successor to pay_invoice/make_invoice that clients are asked to transition to for bolt12 zaps in https://github.com/nostr-protocol/nips/pull/2421: - reject cross-network payment instructions before paying (UNSUPPORTED_NETWORK) instead of failing after the debit - reject unknown req- BIP-321 params, including req-pop since we can't open proof-of-payment callbacks - reject a request amount that conflicts with the instruction amount - payer_note is delivered or rejected, never dropped: bolt11+note is rejected, internal settlements hand the note over as the payment memo - receive mints a per-request bolt12 offer carrying the requested description/amount instead of reusing the standing amountless offer - get_info reports the node's actual network instead of "mainnet" Co-Authored-By: Claude Fable 5 <[email protected]>
* bolt12 payer proofs: disclose zap fields, lightningd v26.06.2 in sampleAdam Soltys2026-07-301-10/+17
| | | | | | | | | | | createproof's default proof omits invreq_payer_note, and passing include replaces the default disclosure set — so for zap payments request both invreq_payer_note and invoice_amount, which NIP-177 verification requires. Verified end to end on v26.06.2: NWC pay over nip44 returns a proof that decodes valid with note + amount disclosed and preimage matching the payment hash. Co-Authored-By: Claude Fable 5 <[email protected]>
* bolt12 zaps: pay offers, NWC pay/receive, per-user offers, NIP-44Adam Soltys2026-07-301-53/+303
| | | | | | | | | | | | | | | | - sendLightning fetches an invoice from lno1 offers (payer_note passthrough for NIP-177 zap intents) and pays it through the existing bolt12 path - NWC: new nwc#2 pay/receive methods (BIP-321 URIs, payer proofs stubbed until CLN 26.06 createproof), nip44_v2 alongside nip04 with per-request scheme detection, dual 23196/23197 notifications, 13194 refreshed when its content or encryption tag goes stale - standing per-user bolt12 offer via GET /offer; incoming bolt12 payments record invreq_payer_note as memo - parse/fetchinvoice routes handle offers; zap display parses kind 9736 receipts (amount tag + embedded intent) and drops a call to a sync() helper that never existed Co-Authored-By: Claude Fable 5 <[email protected]>
* nwc: confirm 13194 info-event publish so it cannot be silently droppedAdam Soltys2026-06-281-3/+47
| | | | | | | | | | | | | | | | publishInfo sent the kind-13194 info event over the long-lived NWC socket fire-and-forget. The nostr lib Relay.send is async (await wait_connected; ws.send) but was called without await, and success was logged immediately. A send that rejected (e.g. ws in CLOSING state during a strfry reconnect) was silently dropped while the log claimed success, leaving NWC clients with "no info event" until a manual republish. Publish over a fresh short-lived socket and wait for the relay OK, retrying up to 3x with honest logging (warn on unconfirmed, err on give-up). ensureInfos receive-based detection is unchanged. Verified on prod: deleted both info events, restarted, both republished within seconds. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: fix pay_invoice reply reliability, rate-limit drops, notificationsAdam Soltys2026-06-061-36/+81
| | | | | | | | | | | | | | | | | | | | | | | | | Three issues reported by an NWC client integrator (refs #80): (a) pay_invoice sync reply: the handler polled listpays for ~20s then returned a misleading "Payment timed out" error on slow routes even though the payment settled — client never got the preimage. sendLightning already runs xpay to completion and finalize() sets p.ref = preimage, so return that directly; keep a longer listpays loop only as a fallback, and on still-in-flight return an explicit "check with lookup_invoice" rather than implying failure. (b) rate limiting: raised per-pubkey cap 5/min -> 60/min (normal usage — a balance check + payment + a few lookup_invoice reconciles — trivially exceeded 5), and over-limit requests now get a NIP-47 RATE_LIMITED error reply instead of a silent drop that looked exactly like a lost reply. Also: every request path now always sends a 23195 reply (success, error, or handler-returned- nothing) so a client never hangs waiting. (c) notifications: payment_received/payment_sent are advertised in the info event but app.notify defaulted to false unless the client sent notify:"true" (only ~1% of apps had it on), so they never fired. Default notify ON; only disable when explicitly "false". (Existing apps still false — backfill TBD.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: pay_invoice honors request amount for amountless bolt11Adam Soltys2026-06-041-2/+14
| | | | | | | | | | | | | | | | pay_invoice derived the amount solely from the decoded invoice, so an amountless bolt11 yielded amount = round(undefined/1000) = NaN and sendLightning rejected it with "Invalid amount". NIP-47 allows the client to specify the amount (msat) in the request for exactly this case (AlbyHub supports it; Alby Go and others hit the error). Fall back to the request's `amount` field when the invoice carries none; invoice amount still takes precedence when present, and a still-missing amount returns a clear error instead of NaN. sendLightning/sendInternal already handle amountless invoices given a valid amount — the bug was only the handler dropping it. Fixes #83 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: fix make_invoice timestamps (ms -> seconds) breaking NWC clientsAdam Soltys2026-06-041-5/+8
| | | | | | | | | | | | | | | | | | generate() stores `created` in milliseconds (Date.now()), but the NWC make_invoice response returned it directly as created_at and computed expires_at = created_at + expiry. NWC timestamps are Unix SECONDS (NIP-47), so clients parsed a ~1.78e12 expires_at that overflowed their date types — e.g. ptcpay's .NET DateTimeOffset, whose max is 253402300799 (issue #84: "creating lightning invoices via NWC fails ... Valid values are between -62135596800 and 253402300799"). Convert created to seconds before use. The other NWC handlers were already correct (lookup_invoice reads seconds straight from cln; list_transactions divides p.created by 1000). Only make_invoice leaked milliseconds. Fixes #84 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: publish 13194 info event only when missing, recheck hourlyAdam Soltys2026-06-021-26/+65
| | | | | | | | | | | | | | | | | | NWC clients (Alby Go, Lightning Piggies) fetch the kind 13194 info event (capability advertisement) before they'll talk to the wallet; without it they report "no info event" and show an unknown balance. The events went missing after strfry restarted on 2026-05-29 and stayed gone for days, because the app only published them once in the relay's on-open handler and nothing re-asserted them. 13194 is a replaceable event meant to persist, so instead of blindly republishing, query the relay on connect and publish only the server pubkeys whose info event is actually missing (ensureInfo). Re-check hourly so a relay restart that leaves our socket intact also self-heals. Interval is cleared on close/reconnect alongside the heartbeat to avoid leaks. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* nwc: handle stale client state cleanly and skip reverted-duplicate recordsAdam Soltys2026-05-171-1/+5
| | | | | | | | | | | | | | | | | | Three small NWC robustness fixes that surfaced from the Apr 29 double-credit cleanup: - fail loudly when an NWC request references a pubkey whose user record no longer exists (migrated/deleted account); previously crashed inside handle() on `user.id` - swallow `pubkey not found` and `user not found` in the per-event catch so deleted-app/migrated-user spam doesn't fill the error log — these are client-side staleness, not server faults - use gf() instead of g() when fetching payment records (consistent with the rest of the codebase: arc fallback for wiped main-db pointers) - filter `revertedDuplicate` payments out of list_transactions so clients never see the doubled records that were later reverted Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* nwc heartbeatAdam Soltys2026-03-291-8/+21
|
* stop republishing primal eventsAdam Soltys2026-03-181-1/+14
|
* adjust ln minfeeregtest-dataAdam Soltys2026-03-151-9/+1
|
* consult archive for older paymentsAdam Soltys2026-01-301-4/+8
|
* relay reconnect logicAdam Soltys2026-01-301-31/+45
|
* purge old nwc eventsAdam Soltys2026-01-281-2/+12
|
* quiet nwc failuresAdam Soltys2026-01-211-8/+8
|
* loggingAdam Soltys2026-01-211-8/+8
|
* Fix incorrect extratlvs format in pay_keysendEric P2025-11-091-1/+11
|
* rm logAdam Soltys2025-10-011-3/+0
|
* rotate server keyAdam Soltys2025-09-291-5/+36
|
* catch errors in pay_invoiceAdam Soltys2025-09-291-19/+23
|
* add missing make_invoice fields (fixes #78)Adam Soltys2025-07-261-2/+8
|
* put metadata in payment memoAdam Soltys2025-04-131-1/+2
|
* rm logAdam Soltys2025-03-081-1/+0
|
* fix get_infoAdam Soltys2025-03-081-1/+1
|
* nwc date comparison in secondsAdam Soltys2025-03-071-4/+3
|
* nwc max feeAdam Soltys2025-03-051-1/+2
|
* fix payment_hash lookupAdam Soltys2025-03-051-4/+7
|
* get payment_hash if possibleAdam Soltys2025-03-051-2/+2
|
* add importsAdam Soltys2025-03-051-0/+2
|
* add blockhash to get info responseAdam Soltys2025-03-051-7/+24
|
* record payment_hashAdam Soltys2025-03-051-5/+15
|
* handle no budgetAdam Soltys2025-02-201-1/+1
|
* check createdAdam Soltys2025-02-191-3/+4
|
* return quota exceeded errorAdam Soltys2025-02-171-1/+1
|
* fix nwc periodsAdam Soltys2025-02-181-7/+11
|
* nwc app prefixAdam Soltys2025-02-131-2/+2
|
* add uid to default appAdam Soltys2025-02-131-1/+2
|
* catch nwc errorsAdam Soltys2025-02-121-8/+8
|
* catch payment errorsAdam Soltys2025-02-121-7/+10
|
* nwc refactorAdam Soltys2025-02-121-9/+10
|
* nwc error loggingAdam Soltys2025-02-121-6/+6
|
* nwc notificationsAdam Soltys2025-02-111-11/+15
|