<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib/lightning.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-09-23T14:31:46Z</updated>
<entry>
<title>lightning: don't wedge the listener on an already-credited preimage</title>
<updated>2026-09-23T14:31:46Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-23T14:31:46Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=bd54987e1f2d708f7e9c5e6e903bcd3c4502b495'/>
<id>urn:sha1:bd54987e1f2d708f7e9c5e6e903bcd3c4502b495</id>
<content type='text'>
Skip settlements whose preimage was already credited instead of retrying
until the process exits, and reject previously used preimages when they
are queued.

Co-Authored-By: Claude Opus 5.5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>ln listener: skip a settlement whose account is gone before crediting</title>
<updated>2026-09-05T07:36:32Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-05T07:36:32Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=287fdb2b14b48d769e43a223c5c33049ff470880'/>
<id>urn:sha1:287fdb2b14b48d769e43a223c5c33049ff470880</id>
<content type='text'>
credit() now returns without writing for a missing user, but the
listener's persistence check after credit() then threw and kept the
cursor stuck. Check the account up front and skip like an unknown
invoice, recording the preimage in `missing`.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
</content>
</entry>
<entry>
<title>ln: bound receive-listener wait, phase-aware watchdog, re-enable health check</title>
<updated>2026-08-22T15:08:13Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-22T15:08:13Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=658724acea184ba4f15ddc70fe9f1cf8946ff51d'/>
<id>urn:sha1:658724acea184ba4f15ddc70fe9f1cf8946ff51d</id>
<content type='text'>
Give waitanyinvoice an explicit 30s server-side timeout so a dead/half-open
socket can no longer hold the listener indefinitely; CLN's 904 timeout during a
quiet period is treated as a clean re-arm without counting as a failure. Split
the listener into waiting vs processing phases so the watchdog never resets a
healthy socket merely because a ledger write is slow, and never starts a second
concurrent processor — a stalled processing phase is surfaced loudly instead.
Process the settlement and confirm the durable payment pointer BEFORE advancing
pay_index, so a store load/restart between the cursor write and credit() cannot
drop a received payment. Expose listener status and add a backlog probe to the
health check, and re-enable startHealthCheck().

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XiwgmQKwKnRxUmVDd4xSf7
</content>
</entry>
<entry>
<title>ln: tear down dead cl clients fully + recover invoice listener on socket death</title>
<updated>2026-08-16T06:16:28Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-16T06:16:28Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=7c6805decc508b009673e1a5651b2a86ebdef717'/>
<id>urn:sha1:7c6805decc508b009673e1a5651b2a86ebdef717</id>
<content type='text'>
Replace bare `client = null` with dropClient(): stop the clightning-client
library's internal reconnect loop (clear reconnectTimeout, neuter reconnect,
destroy the socket, drop listeners). A dead client was otherwise orphaned and
spun forever retrying `connect ENOENT`, spamming logs and leaking a socket+timer
on every cl restart. Guard the error handler on `c === client` so a stale
client's late error can't drop a newer, healthy one.

Add lnListen.onDrop so the invoice listener recycles immediately when its
long-poll socket dies (bump epoch, re-arm) instead of waiting up to 5 minutes for
the stall watchdog. Stop counting LightningUnavailableError toward the listener's
process.exit escalation, so a cl restart no longer makes the app self-exit — it
retries until cl returns.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>zaps: stop spurious "failed to handle zap" warnings on non-zap invoices</title>
<updated>2026-07-31T04:07:48Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-31T04:07:48Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=60f9c91b61ce069fb472369eb6c16bf1a984a3d6'/>
<id>urn:sha1:60f9c91b61ce069fb472369eb6c16bf1a984a3d6</id>
<content type='text'>
The zap dispatch parsed the invoice description and, when it wasn't JSON,
suppressed the error only if the message contained "Unexpected" — V8's parse-
error wording. On Bun the message is "Unable to parse JSON string", so every
paid invoice with a non-JSON memo logged a warning. Parse-then-check instead:
a non-JSON description is simply not a zap and is skipped silently; warn only
when an actual kind-9734 zap request fails to process.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>mint/lightning: refuse caller-supplied invoices, tiered fees, log hygiene</title>
<updated>2026-07-30T17:38:43Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-07-30T17:28:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=fd1617ea66e01557e71debc7e45ca08cfe01bf70'/>
<id>urn:sha1:fd1617ea66e01557e71debc7e45ca08cfe01bf70</id>
<content type='text'>
- invoices: reject caller-supplied bolt11/bolt12; the server always mints its
  own. Closes the invoice-registration path behind the shared-node credit and
  payment-hijack vectors.
- lightning: skip mint-owned invoices (lbl* label) in the paid-invoice listener.
- payments: lightning free-tier fee credit, withheld for flagged high-throughput
  (ln:nofree) accounts which pay an elevated rate; the failed-send reversal
  restores credit at the same rate. freezeCheck made crash-resilient. Lightning
  and bolt12 sends stay pending until the preimage settles.
- payments (route): validate payment-request / invoice input types.
- logging: collapse repeated xpay failure lines into one.

Co-Authored-By: Claude Opus 4.8 &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>
<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>ln/lightning: watchdog recycles zombie receive-listener socket</title>
<updated>2026-06-05T01:17:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-05T01:17:47Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=bd1ece38e42751575ac2e03a5c795b5b109653ac'/>
<id>urn:sha1:bd1ece38e42751575ac2e03a5c795b5b109653ac</id>
<content type='text'>
On 2026-06-04 cl restarted (autoheal, after its 15s healthcheck timed out),
and the app's waitanyinvoice long-poll hung on the now-dead listen socket.
listenerActive stayed true forever, so ensureListenerAlive never restarted
it, and incoming lightning payments stopped crediting for ~2h until a manual
app restart. The per-call RPC timeout in ln.ts can't catch this: waitanyinvoice
is intentionally in NO_TIMEOUT (it's meant to block).

Fix: ensureListenerAlive (runs every 120s) now also detects a stalled listener
— active but blocked in waitanyinvoice for &gt;5min. It probes cl via the main
ln socket (getinfo); only if cl is actually alive does it treat the listen
socket as a zombie, call lnListen.reset() (new: drops the proxy's socket so the
next call reconnects), and re-arm. A listenerEpoch guard makes the wedged
invocation bow out when its waitanyinvoice finally rejects, so we never run two
listeners concurrently and never double-credit. Verified with a state-machine
test covering normal receive, quiet-but-healthy (left alone), stall+cl-alive
(recycled), stall+cl-down (left alone), and the no-double-listener race.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>lnurl proxy</title>
<updated>2026-03-28T06:41:08Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-03-28T06:41:08Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=ef06f2eb48581c201e62fc2cde27b106e898e54d'/>
<id>urn:sha1:ef06f2eb48581c201e62fc2cde27b106e898e54d</id>
<content type='text'>
</content>
</entry>
</feed>
