<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib/payments.ts, branch staging</title>
<subtitle>coinos server — legacy coinos.io backend (Lightning, on-chain, Liquid)</subtitle>
<id>https://git.coinos.io/coinos-server.git/atom?h=staging</id>
<link rel='self' href='https://git.coinos.io/coinos-server.git/atom?h=staging'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/'/>
<updated>2026-06-17T14:47:47Z</updated>
<entry>
<title>finalize: don't crash on non-integer fee delta (tbRefund NaN)</title>
<updated>2026-06-17T14:47:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-14T19:34:12Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=51847c5e05ad70e936fd374e01e62e0e4b5e3c72'/>
<id>urn:sha1:51847c5e05ad70e936fd374e01e62e0e4b5e3c72</id>
<content type='text'>
Port of master 8aba0bc2, adapted to this stack's tbRefund. When xpay's
response shape is unexpected (or a zero-amount invoice's decoded
amount_msat is undefined) the computed p.fee becomes NaN, and the refund
delta crashes the balance write, leaving finalize partial (user loses up
to maxfee). Validate the computed fee is a finite integer; if not, keep
the originally-reserved maxfee (zero refund, no balance corruption) and
warn. Same guard on the refund delta before tbRefund.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>sendLightning/sendKeysend: instrument exit paths, tighten LEAKED DEBIT guard</title>
<updated>2026-06-17T14:47:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-14T19:34:04Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=e55165307096093d769893475a7481c781457f94'/>
<id>urn:sha1:e55165307096093d769893475a7481c781457f94</id>
<content type='text'>
Port of master 51107ed4 + fe767002 + cb0f1962 (combined) to the staging
stack. Track an outcome string through every exit path, handle the
xpay failed_parts branch explicitly (verify with listpays then finalize/
reverse/leave-pending), and dump abnormal xpay/keysend response shapes.
Leak guard re-reads the db record before firing: if ref is persisted the
payment is settled regardless of which path threw (finalize may set ref
then throw on the later tbRefund), so only a genuinely missing ref logs
"LEAKED DEBIT". Uses warn (no warnThrottled on this stack) and keeps the
existing retry_for: 20.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>withdraw-lock: refuse external sends when /locks/&lt;type&gt;.locked is present</title>
<updated>2026-06-17T14:47:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-14T19:33:54Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=1dc51829c39422495e289ba11f8668352abdba41'/>
<id>urn:sha1:1dc51829c39422495e289ba11f8668352abdba41</id>
<content type='text'>
Port of master 9142b4db. Out-of-band emergency stop: nobal.sh (or a human)
creates a sentinel file on the host, bind-mounted at /locks in the
container; debit() checks for the file on every external send and refuses
with "External withdrawals temporarily disabled". /locks/ALL.locked is a
global override. Lives outside the db so a db-write vector can't disable
it. Internal sends are exempt.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>payments: freeze matches uid as well as username (rename-proof)</title>
<updated>2026-06-17T14:47:46Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-14T19:33:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=85849ede6fb6bd9151703ebb8cf1b2b2df385b99'/>
<id>urn:sha1:85849ede6fb6bd9151703ebb8cf1b2b2df385b99</id>
<content type='text'>
Port of master 43427afe. 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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>payments: guard /replay double-credit race with SET NX idempotency</title>
<updated>2026-06-17T14:47:45Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-14T19:33:29Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=17a4461d8497f4f0f89871bb80604d449487a4c3'/>
<id>urn:sha1:17a4461d8497f4f0f89871bb80604d449487a4c3</id>
<content type='text'>
Port of master ca28c19f to the staging stack. The getPayment() pre-check
in the lightning listener/replay isn't atomic, so concurrent re-credits of
the same settlement each ran the credit path. Claim each lightning/bolt12
settlement with an atomic SET NX on credited:&lt;ref&gt; (ref = preimage) and
bail if already claimed. Guard sits before tbCredit and the pointer writes
so a duplicate bails cleanly. Also disable the /replay route that triggered
it.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>getAddressType: surface lq downtime as actionable error</title>
<updated>2026-05-18T06:51:52Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-18T06:51:52Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=4dd5c51fa238fefb1c06cc1da1b1b77344139213'/>
<id>urn:sha1:4dd5c51fa238fefb1c06cc1da1b1b77344139213</id>
<content type='text'>
When lq.getAddressInfo throws on a connection error (ECONNREFUSED /
ETIMEDOUT / "Unable to connect"), the generic catch was reporting
"unrecognized address" — misleading for both users (who think their
address is wrong) and operators (who can't tell user error from
infrastructure problems in the logs). Map connection errors to a
specific "liquid temporarily unavailable" message instead.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>mirror payment records to arc + atomic /confirm lock to prevent double-credits</title>
<updated>2026-05-18T06:46:58Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-18T06:46:58Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=dfd31ad83d24e780523f4e76d04a76aba1fa8be1'/>
<id>urn:sha1:dfd31ad83d24e780523f4e76d04a76aba1fa8be1</id>
<content type='text'>
Apr 29 double-credit prevention, ported from master 0e368dab and adapted
to staging's hono/tigerbeetle structure. Two parallel callers of /confirm
(walletnotify, catchUp, bulk sweeps) could each read p.confirmed=false
and each run tbConfirm — doubling the pending→balance transfer.
tbConfirm itself isn't idempotent on its own, so the race materializes
as a real double-credit even with TB.

- routes/payments.ts: SET NX EX 60 lock around the confirmation block
  keyed by `confirmlock:${txid}:${vout}`. Concurrent callers short-circuit
  cleanly. Released on success and on every early-return.

- lib/payments.ts credit() + routes/payments.ts /confirm: mirror payment
  + invoice records and the `payment:&lt;txid&gt;:&lt;vout&gt;` / `payment:&lt;hash&gt;`
  pointer keys into arc as soon as they're written. gf() will then find
  the prior credit via arc fallback even if the main-db copy is wiped
  by a future data-loss event.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>sendLightning / sendKeysend: only reverse when CLN reports the payment failed</title>
<updated>2026-05-18T06:38:53Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-05-18T06:38:53Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=6992af9bc03822dea71b59604f03d34a91511264'/>
<id>urn:sha1:6992af9bc03822dea71b59604f03d34a91511264</id>
<content type='text'>
The catch blocks for ln.xpay and ln.keysend reversed the user's debit
whenever the RPC call threw, regardless of the actual payment state in
CLN. If the RPC errored transiently while the payment was still pending,
the user's balance got refunded — and then the lightning payment could
still complete, leaving the user with both the refund and the outgoing
sats.

Both call sites now mirror the safe check() logic:

  const failed = !pays.length || pays.every((p) =&gt; p.status === "failed");
  if (completed) finalize ...
  else if (failed) reverse ...
  // else (pending) leave the debit; check() reconciles when CLN settles.

If listpays itself errors, log via warn() and skip the reverse — the
debit stays in the pending set and check() will retry.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: blocked address protection, LNURL proxy, replay timestamps</title>
<updated>2026-03-28T06:58:56Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-03-28T06:58:51Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=a574e774318a37bfc1e5c638a3d83e3fb9c180d2'/>
<id>urn:sha1:a574e774318a37bfc1e5c638a3d83e3fb9c180d2</id>
<content type='text'>
- Add lib/changeid.ts for in-process account rekeying
- Block sends to addresses in redis blocked_addresses set, auto-rekey on attempt
- Add created param to credit() for accurate replay timestamps
- Add paid_at to replay() for correct payment dates
- Add /lnurl/proxy endpoint for server-side LNURL callback fetching

Co-Authored-By: Claude Opus 4.6 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>verify ark amounts</title>
<updated>2026-03-22T15:41:12Z</updated>
<author>
<name>Adam Soltys</name>
<email>asoltys@gmail.com</email>
</author>
<published>2026-03-22T15:41:08Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=af568c46cb3da198ea8614d21616bac3951f3262'/>
<id>urn:sha1:af568c46cb3da198ea8614d21616bac3951f3262</id>
<content type='text'>
</content>
</entry>
</feed>
