<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib/nwc.ts, branch master</title>
<subtitle>coinos server — legacy coinos.io backend (Lightning, on-chain, Liquid)</subtitle>
<id>https://git.coinos.io/coinos-server.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinos-server.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/'/>
<updated>2026-10-07T04:12:19Z</updated>
<entry>
<title>payments: lowercase payreq before looking up our own invoices</title>
<updated>2026-10-07T04:12:19Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-07T04:12:19Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=f53e70ed98eb723479b4276c5b621b599fe570a0'/>
<id>urn:sha1:f53e70ed98eb723479b4276c5b621b599fe570a0</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>ecash: redeem tokens by melting to the user's own invoice</title>
<updated>2026-09-16T11:53:22Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-16T11:53:22Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=c72fa2a5ccf1cc04073f9ff12606491971dadd36'/>
<id>urn:sha1:c72fa2a5ccf1cc04073f9ff12606491971dadd36</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>security: drop the admin endpoint surface and land pending hardening</title>
<updated>2026-09-14T03:44:48Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T03:44:48Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=d8a361bdcfaa8cc5b39d9675e74b0e1d2e42be2c'/>
<id>urn:sha1:d8a361bdcfaa8cc5b39d9675e74b0e1d2e42be2c</id>
<content type='text'>
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) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_0112TGi9GGNWqji1XQ5jZRzc
</content>
</entry>
<entry>
<title>nwc: enforce per-connection spend budgets + flag-gated incident containment</title>
<updated>2026-08-22T15:08:04Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-22T15:08:04Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=0bb7ab45344068ed8ea3cc77ad9f7c2a038bb45f'/>
<id>urn:sha1:0bb7ab45344068ed8ea3cc77ad9f7c2a038bb45f</id>
<content type='text'>
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:&lt;pubkey&gt;, which
let an authenticated user overwrite another connection's config, and validate
budget/fee/renewal inputs.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XiwgmQKwKnRxUmVDd4xSf7
</content>
</entry>
<entry>
<title>nwc: reply UNAUTHORIZED on unknown connection instead of returning silently</title>
<updated>2026-08-18T05:32:03Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-18T05:32:03Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=f385ae87f7b65ef2e2cc7caaedc78170c6c99392'/>
<id>urn:sha1:f385ae87f7b65ef2e2cc7caaedc78170c6c99392</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwc: budget keysend, atomic dedup, serialize spends (S4/S6/S8)</title>
<updated>2026-08-08T06:29:26Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-08T05:59:54Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=a95a0dfd27980b01dbfe723d8be1ca4535b7d96a'/>
<id>urn:sha1:a95a0dfd27980b01dbfe723d8be1ca4535b7d96a</id>
<content type='text'>
- 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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwc: republish 13194 info events unconditionally every 2 minutes</title>
<updated>2026-08-04T16:33:27Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-04T16:33:27Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=103001c49da84f9cd6b6d8c6eb682550442dc8e5'/>
<id>urn:sha1:103001c49da84f9cd6b6d8c6eb682550442dc8e5</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Dpqu6wizYe1c1g4k5Gd8JK
</content>
</entry>
<entry>
<title>nwc: conform pay/receive to the BIP-321 methods spec (nwc#2)</title>
<updated>2026-08-01T00:59:22Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-31T22:58:57Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=974c9cae7e11ffafdc571af0f9c47d896a901131'/>
<id>urn:sha1:974c9cae7e11ffafdc571af0f9c47d896a901131</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>bolt12 payer proofs: disclose zap fields, lightningd v26.06.2 in sample</title>
<updated>2026-07-30T15:57:17Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-30T15:57:17Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=f5a5b1ff856efcd2849275a301daabb80ef76638'/>
<id>urn:sha1:f5a5b1ff856efcd2849275a301daabb80ef76638</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>bolt12 zaps: pay offers, NWC pay/receive, per-user offers, NIP-44</title>
<updated>2026-07-30T15:51:36Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-30T15:51:36Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=add3ad93ff37f1daaedab5788f00663be299555c'/>
<id>urn:sha1:add3ad93ff37f1daaedab5788f00663be299555c</id>
<content type='text'>
- 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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
