| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
| |
Port of master be896868. scripts/audit-missing-history.ts flags users
whose payment history has gaps vs their recorded balance. scripts/
ln-liquid-arb.ts surfaces lightning->liquid rebalancing opportunities.
Note: written against the master stack (Redis balances); on staging
balances live in TigerBeetle and may need the balance source adjusted.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Port of master 183a3cb0 + 3db24424. scripts/analytics.ts streams live db
(SCAN, never KEYS) and reports a trailing window: MAU/WAU, new users,
custody total + top holders, network volume by type, most-active/highest-
volume users, bolt12 recipients; --csv flag for month-over-month trends.
scripts/analytics-geo.ts reports top countries from the request log.
lib/register.ts stamps `created` and stores `country` on new users so
reports get signup timing/geography from the db directly.
Note: these scripts were written against the master stack; on staging
balances live in TigerBeetle, so custody/holder figures may need the
balance source adjusted from the Redis balance: keys.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Port of master 2548f757, adapted from Express to this stack's Hono
handlers (c.get("user"), c.req.json(), setCookie, bail) and TigerBeetle
balances (getBalance/getPending instead of the Redis balance:/pending:
keys). POST /delete (auth) wipes every key tied to the calling user:
user records, payments/invoices, sub-accounts, NWC apps, nostr follow
graph + nip5. Gated by typing the username and refuses while
balance+pending > 10k sats so funds can't be destroyed by accident.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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:<ref> (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 <[email protected]>
|
| |
|
|
|
|
|
|
| |
Guard refreshArkWallet against running when config.ark.arkPrivateKey is
unset, and add a comment explaining the ensureListenerAlive watchdog's
zombie-socket recovery.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Port of master bd1ece3 to staging. When cl restarts (e.g. autoheal after a
healthcheck timeout), the app's waitanyinvoice long-poll can hang on the now-dead
listen socket: listenerActive stays true forever, nothing restarts it, and
incoming lightning payments silently stop crediting until a manual app restart.
The per-call RPC timeout can't catch this — waitanyinvoice is meant to block.
ensureListenerAlive (run every 120s) now also detects a stalled listener — active
but blocked in waitanyinvoice for >5min — probes cl via the main socket (getinfo),
and only if cl is actually alive treats the listen socket as a zombie: bumps a
listenerEpoch, calls lnListen.reset() (new: drops the proxy socket so the next
call reconnects), and re-arms. The epoch guard makes the wedged invocation bow out
when its waitanyinvoice finally rejects, so we never run two listeners or
double-credit.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
| |
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) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two related defenses around the CLN unix socket connection:
- Global net.Socket.prototype.connect patch: when Bun's
net.createConnection is called on a non-existent unix socket, the
ENOENT it throws bypasses all JS error handlers and crashes the
process. existsSync-check the path first; if missing, emit a normal
'error' event so the caller can handle it gracefully.
- lightningProxy: existsSync gate before constructing LightningClient,
plus a client.on("error") listener that clears the cached client on
socket-died errors instead of letting the unhandled event take down
the process.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
When the underlying payment record is missing, removing the id from the
user's payments list was destructive — a transiently unavailable record
(arc fallback miss, database hiccup) would cause us to permanently drop
the pointer. Leave the orphan in place so a back-fill from an RDB backup
can restore the record under the same id later.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
- fail loudly when an NWC request references an app whose user record no
longer exists (migrated/deleted account); previously crashed inside
handle() on `user.id`
- filter `revertedDuplicate` payments out of list_transactions so
clients never see the doubled records that were later reverted
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The bolt11 description_hash is computed from the metadata JSON, which
was being constructed with the sender's `comment` substituted into
text/plain. Wallets verify description_hash against the metadata they
received in the lnurl-pay response, so injecting the comment into the
hash made the verification fail — the invoice would settle but
description_hash verification would reject it as inconsistent.
Keep metadata canonical ("Paying user@host") so the description_hash
the wallet receives matches what they verify against. The comment is
still preserved as the human-readable memo on the invoice record.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
Tor users access via .onion through a forwarding layer that puts the
real hostname in x-forwarded-host while the literal host header is the
internal target. Without checking the forwarded header first, captcha
prompted for .onion visitors who should have been exempt.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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:<txid>:<vout>` / `payment:<hash>`
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) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Unauthenticated arbitrary db.set abuse vector. Exploited on the master
deployment to inflate user balances and override safety controls in real
time, leading to a hot-wallet drain. Route and handler both fully
removed; if a webhook-by-short-channel-id mapping is ever needed again
it must be rebuilt with auth + SCID format validation + webhook URL
validation + per-user ownership check.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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) => 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) <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
- 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) <[email protected]>
|
| | |
|
| |
|
|
|
|
|
| |
App and other containers need root access for unix sockets and file permissions.
All derived images already override USER as needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| |
|
|
| |
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
| |
- Validate account ownership (aid) before allowing ark send/sync/vault
operations and in debit/sendOnchain as defense-in-depth
- Harden PIN check to reject missing/empty pin when user has one set
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Rewrite setup.sh: auto-installs Docker, downloads regtest snapshot,
pulls images, starts all services. Idempotent, works on fresh Ubuntu/macOS.
- Create coinos-base image (196MB) shared by all containers: bitcoin,
elements, lightning, strfry, kvrocks, electrs, tigerbeetle, chopsticks.
Total pull ~490MB, down from ~4.3GB.
- Move all images to ghcr.io/coinos/* namespace.
- Drop nutshell (mint/wallet), postgres, mon from default dev compose.
- Comment out ark stack (optional), guard ark.ts against empty config.
- Add missing sampledata: lightningc, strfry, kvrocks, valkey configs.
- Strip strfry binary (145MB debug symbols removed).
- Use single CLN version (v25.12.1) for all 3 lightning nodes.
- Switch valkey to alpine variant (110MB → 41MB).
- Update config.ts.sample with correct service URLs.
- Regtest snapshot hosted on GitHub Releases (~2MB).
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
| |
Adds timeout to exec helper and catches setup failures so tests skip
instead of hanging for 60s when Docker Lightning nodes aren't reachable.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|