| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|