<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib/payments.ts, 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-15T20:01:41Z</updated>
<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>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>debit: exempt internal sends from server liquidity limit</title>
<updated>2026-06-01T06:27:59Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-01T06:27:59Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=7841b2d61830f13260e46d24ce69f24b2dbcea29'/>
<id>urn:sha1:7841b2d61830f13260e46d24ce69f24b2dbcea29</id>
<content type='text'>
freezeCheck only populates ${type}:limit for external asset types
(lightning/fund/ecash/bitcoin/liquid/bolt12), so internal:limit stays 0
forever. The atomic limit check added in 609fcebe applied to all types,
so amount &gt; 0 blocked every internal coinos-to-coinos payment with
"serverLimit 0". Internal sends are pure ledger moves that never touch
an external wallet — there's no hot-wallet liquidity to reserve — so
exempt them, matching the existing soft-freeze and withdraw-lock gates.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>sendLightning / finalize: drop chatty per-send INFO logs</title>
<updated>2026-05-25T19:59:00Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-25T19:59:00Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=2ea66724f331d4f24a0ca83a91ef90c27ed07353'/>
<id>urn:sha1:2ea66724f331d4f24a0ca83a91ef90c27ed07353</id>
<content type='text'>
Each successful lightning send was emitting 5 INFO lines:
  "&lt;user&gt; sent lightning &lt;amount&gt;"          (debit, KEEP)
  "paying lightning invoice &lt;pr&gt; &lt;amt&gt; &lt;fee&gt;"  (drop — redundant)
  "payment completed &lt;id&gt; &lt;preimage&gt;"       (drop — implicit from absence of warns)
  "refunding fee &lt;maxfee&gt; &lt;fee&gt; &lt;delta&gt; &lt;ref&gt;"  (drop — the warn for non-integer
                                                 delta already covers anomalies)
  "sendLightning outcome &lt;id&gt; = finalized"  (drop for "finalized" only —
                                              keep for the recovery-via-fallback
                                              variants since those are diagnostic)

Net: only the "&lt;user&gt; sent lightning &lt;amount&gt;" line fires per healthy send,
matching the other asset types' style. Anything that needed a fallback path
or threw still surfaces via the existing warn/err lines.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>sendLightning: stop warning on healthy multi-path payments</title>
<updated>2026-05-25T19:56:16Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-25T19:56:16Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=caebad1dd85188f4315751c6ce77f92d7a1588c2'/>
<id>urn:sha1:caebad1dd85188f4315751c6ce77f92d7a1588c2</id>
<content type='text'>
Each xpay with failed_parts &gt; 0 that ultimately settled (multi-path
retry — common, not concerning) was firing 3 WARN lines:
  "xpay returned (abnormal)"
  "xpay returned failed_parts without throwing"
  "xpay failed_parts but listpays complete — finalizing"

These are all the SAME successful payment. The presence of failed_parts
just means CLN tried multiple paths before one succeeded. Adam was
seeing noise.

Now:
- Only warn on actually-missing preimage (xpay silently failed).
- Drop the unconditional "without throwing" warn — the failed_parts
  branch handles the recovery silently in the success case.
- Drop the "listpays complete — finalizing" warn — that's the success
  path, not a concern.
- KEEP warns for the genuinely interesting cases: listpays says failed
  (we're about to reverse), listpays says pending (debit left for
  check() to reconcile), or finalize itself throws.

Net effect: a healthy multi-path payment now emits only the standard
"sendLightning outcome &lt;id&gt; = finalized-via-listpays-after-failed_parts"
info line instead of 3 WARN lines.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>finalize: don't crash on non-integer fee delta (db.incrBy NaN)</title>
<updated>2026-05-25T16:22:26Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-25T16:22:26Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=8aba0bc2e3b5d09b3783b87eb0a29088b40796e4'/>
<id>urn:sha1:8aba0bc2e3b5d09b3783b87eb0a29088b40796e4</id>
<content type='text'>
Triggered by a recurring "finalize threw despite no failed_parts: ERR
value is not an integer or out of range" warning. Root cause: when xpay's
response shape is unexpected (or invoice is zero-amount and decoded
amount_msat is undefined), the computed p.fee becomes NaN. The
subsequent db.incrBy(balance:&lt;uid&gt;, maxfee - NaN) throws because Redis
can't increment by NaN.

Symptom: leak guard fires "ref set in db despite throw" — payment is
actually fine (p.ref persisted before the throw) but finalize never
completes the fee refund. User loses up to maxfee sats (was already
debited maxfee, gets nothing back).

Fix: validate the computed fee is a finite integer; if not, keep the
originally-reserved maxfee (zero refund, no balance corruption) and warn
loudly. Same guard on the refund delta before db.incrBy.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>sendLightning: silence success-case xpay log, re-read db before leak guard</title>
<updated>2026-05-21T14:35:42Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-21T14:35:42Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=cb0f1962238534ce0fa2261373a018bf34a868f5'/>
<id>urn:sha1:cb0f1962238534ce0fa2261373a018bf34a868f5</id>
<content type='text'>
Two cleanups based on a day of production data:

1. Only log the xpay response shape when something is abnormal
   (failed_parts &gt; 0 or missing preimage). The success case (200+/day) was
   pure noise — answers Adam's "do we still need these xpay warnings".

2. Re-read the payment record from db before the leak guard fires. Several
   "LEAKED DEBIT" lines today were finalize() throwing on a late line
   (likely db.incrBy for the fee refund) AFTER db.set had already
   persisted p.ref. The payment is functionally settled in those cases —
   only flag as a real leak if ref is genuinely missing from the db record.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>tighten LEAKED DEBIT guard: don't false-alarm on successful-via-fallback paths</title>
<updated>2026-05-21T05:29:35Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-21T05:29:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=fe767002dd59f000d54fd09c9d617d69b9a5c894'/>
<id>urn:sha1:fe767002dd59f000d54fd09c9d617d69b9a5c894</id>
<content type='text'>
The first deployment surfaced two LEAKED DEBIT lines that were actually
clean finalizes via the new failed_parts listpays-fallback path — exactly
the path that recovers payments that would have been stuck under the old
code (joho33's case). The leak guard's `outcome !== "finalized"` check
was too narrow: any path that finalizes the payment, regardless of how
it got there, is a success.

Bucket outcomes explicitly:
- finalizedOutcomes — clean successes (finalize ran, ref set)
- safeNonFinalized — reverse ran, OR debit left in pending for check()
  to reconcile (these aren't leaks)
- anything else — actually stranded (finalize-threw-*, unknown) → err

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
