<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib, branch regtest-fulcrum</title>
<subtitle>coinos server — legacy coinos.io backend (Lightning, on-chain, Liquid)</subtitle>
<id>https://git.coinos.io/coinos-server.git/atom?h=regtest-fulcrum</id>
<link rel='self' href='https://git.coinos.io/coinos-server.git/atom?h=regtest-fulcrum'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/'/>
<updated>2026-06-18T14:37:04Z</updated>
<entry>
<title>invoices: don't re-list an invoice id on update (dedupe :invoices)</title>
<updated>2026-06-18T14:37:04Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-18T14:37:04Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=2d2f9856b31ac98665f008a00550efa224761f9a'/>
<id>urn:sha1:2d2f9856b31ac98665f008a00550efa224761f9a</id>
<content type='text'>
generate() unconditionally lPush'd the invoice id to `${aid}:invoices`,
but it's also called from the update() handler (tip/webhook changes, and
repeated bolt12 offer creation) re-using the existing id — so each update
re-appended the same id and the user's :invoices accumulated duplicate
entries, rendering the same offer/invoice multiple times (kaliyi's OCEAN
offer showed 2x, another 6x). Only push when the id isn't already in the
list (LPOS guard).

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>lnurl: SSRF-guard user-supplied URL fetches + validate zap events</title>
<updated>2026-06-16T06:35:50Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-16T06:35:50Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=497277ef7b48c86aa2f44b613e0a35a0c3b0c238'/>
<id>urn:sha1:497277ef7b48c86aa2f44b613e0a35a0c3b0c238</id>
<content type='text'>
The /lnurl/proxy, /decode and /encode handlers fetched an arbitrary
user-supplied URL via got() and returned the body — an unauthenticated
SSRF reaching internal services. Route those fetches through a new
safeGot() that rejects hosts resolving to loopback/private/link-local/
CGNAT/metadata ranges, re-validates on each redirect hop, restricts the
scheme to http(s), and caps timeout + response size. Also validate the
NIP-57 zap request (kind 9734 + signature) before storing it, instead of
trusting the client-supplied event.

These endpoints can't be removed — they back lightning-address/LNURL
parsing and resolution in the wallet UI.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Push broadcast txs directly to public explorers</title>
<updated>2026-06-15T20:01:41Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-15T20:01:41Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=35df18174ab61c6d4a72bf8f1157584adb14bc4f'/>
<id>urn:sha1:35df18174ab61c6d4a72bf8f1157584adb14bc4f</id>
<content type='text'>
After broadcasting an on-chain send via bitcoind, also POST the raw tx to
mempool.space and blockstream (fire-and-forget, non-blocking) so those
explorers see it immediately instead of waiting ~8s for p2p propagation.
Skipped on regtest/testnet.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>payments: guard /replay double-credit race with SET NX idempotency</title>
<updated>2026-06-13T21:21:44Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-13T21:21:44Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=ca28c19fe26d3f81ae62d2d3f0dc8b175849a9a8'/>
<id>urn:sha1:ca28c19fe26d3f81ae62d2d3f0dc8b175849a9a8</id>
<content type='text'>
getPayment() pre-check in the lightning listener/replay isn't atomic, so
concurrent re-credits of the same settlement each ran incrBy. Claim each
lightning/bolt12 settlement with an atomic SET NX on credited:&lt;ref&gt;
(ref = preimage) and bail if already claimed. Also disable the /replay
route that triggered it.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>payments: freeze matches uid as well as username (rename-proof)</title>
<updated>2026-06-13T21:21:39Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-13T21:21:39Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=43427afe5b0b16d887163bd788360ac29105d69c'/>
<id>urn:sha1:43427afe5b0b16d887163bd788360ac29105d69c</id>
<content type='text'>
The blacklist (send freeze) only matched the current username, so a
compromised account could shake the freeze by renaming itself. Match on
either the lowercased username OR the immutable uid; add the uid to the
`blacklist` set to freeze durably.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>lightning: watchdog only recycles on real backlog, not normal idle</title>
<updated>2026-06-07T04:12:17Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-07T04:12:17Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=7b71c96b5ed9728c63e8fcbb7b2a8bb7155ec7af'/>
<id>urn:sha1:7b71c96b5ed9728c63e8fcbb7b2a8bb7155ec7af</id>
<content type='text'>
The zombie-listener watchdog (bd1ece38) treated "waitanyinvoice blocked &gt; 5min
while cl is alive" as a stall — but that's NORMAL during quiet periods, so it
false-fired and needlessly recycled the cl listen socket every few minutes
(~26x in 6h on 2026-06-07). Receives still worked, but it was churn + noise and
would mask a genuine zombie.

Fix: a blocked long-poll is only a zombie if cl actually has a paid invoice the
listener isn't picking up. When blocked past the threshold, probe cl on the MAIN
socket with waitanyinvoice(pay_index, 2s server-side timeout): if it returns an
invoice there's a real backlog the wedged listener missed -&gt; recycle; if it
times out (CLN code 904 = nothing waiting) the listener is just idle and healthy
-&gt; leave it alone. Only recycle on a confirmed backlog.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwc: fix pay_invoice reply reliability, rate-limit drops, notifications</title>
<updated>2026-06-06T16:10:16Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-06T16:10:16Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=8cb1882111c079cfac9870054d9917fa5f94de76'/>
<id>urn:sha1:8cb1882111c079cfac9870054d9917fa5f94de76</id>
<content type='text'>
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 -&gt; 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) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>payments: clarify deposit log (pending vs confirmed) to stop phantom double-receive</title>
<updated>2026-06-05T06:38:32Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-05T06:38:32Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=c4aba2cfd76eeaa0a177a4a92ca4539f5bf61e52'/>
<id>urn:sha1:c4aba2cfd76eeaa0a177a4a92ca4539f5bf61e52</id>
<content type='text'>
completePayment logs once when an on-chain deposit is first seen (pending) and
again from /confirm when it confirms, both as bare "received" — so a single
liquid/bitcoin deposit appeared twice in the logs and looked like a double
credit. It never was (the /confirm p.confirmed guard and the seen set prevent
double-crediting); purely misleading log noise. Now logs "receiving (pending)"
on first sight and "received" on confirmation. Instant lightning receives are
confirmed=true so they still log a single "received".

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwc: pay_invoice honors request amount for amountless bolt11</title>
<updated>2026-06-05T01:40:42Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-05T01:40:42Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=ce9616f62d0ac682c1e92956ef4cb48f053df27d'/>
<id>urn:sha1:ce9616f62d0ac682c1e92956ef4cb48f053df27d</id>
<content type='text'>
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) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwc: fix make_invoice timestamps (ms -&gt; seconds) breaking NWC clients</title>
<updated>2026-06-05T01:19:25Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-05T01:19:25Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=0154bb060fad7650cd8b825a30a5d88e7018bf8d'/>
<id>urn:sha1:0154bb060fad7650cd8b825a30a5d88e7018bf8d</id>
<content type='text'>
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) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
