# Handoff — Ark / bridge / NWC work Working note, not project docs. Delete when it's stale. Persistent memory for all of this lives in `~/.claude/projects/-home-adam-coinosv3/memory/` — start by reading `MEMORY.md`, then `hal-nwc`, `coinos-ark-mainnet`, `ark-swap-bridge`, `coinos-ark-asp`, `ark-third-party-htlc`. --- ## 1. ~~THE ONE UNRESOLVED BUG~~ — **RESOLVED 2026-08-01** **Root cause:** nostr-tools ≥2.23 changed `SimplePool.subscribeMany` to take a SINGLE filter object instead of an array. Our `[filter]` wrapping serialized as `["REQ",id,[{...}]]` — invalid NIP-01 — and every relay answered `["CLOSED","sub:N","ERROR: bad req: provided filter is not an object"]`, then silently dropped the sub. Neither guard nor decrypt was ever reached; there was simply no live subscription. The CDP probe's own harness had the same bug, which is why it "received frames" but could never observe a 23195. Fixed in `src/nostr.js` (`subscribeOn` + `NostrSync.subscribeEvents` — device sync's live subscription was broken too) and `nwcpush/server.js` (deployed to cs, restarted, resubscribed OK). Verified end-to-end: a real nip04 `get_balance` published from the stored connection's client key got back `{"result_type":"get_balance","result":{"balance":20000000}}` over public relays. Instrumentation (guard logging + sub lifecycle gens) is still in `nwc.js` — strip once Amethyst is confirmed. **Remaining human step:** paste the existing connection string into Amethyst again — it should now show a balance. **LAYER 3 (2026-08-01 evening — the zap):** NWC now round-trips; the zap `pay_invoice` failed for stacked reasons, all found: (a) the swap bridge defaults OFF in hal (`BRIDGE_DEFAULT.mainnet='off'`) so pays went to the ASP, whose CLN routing budget = 90% of the ASP's 1-sat min fee = 0 sats → unroutable; (b) once enabled, the bridge needed its bearer token (cs `~/arkswap/config-main.json` `tokens[0]`, now set in the laptop wallet) and (c) its state dir `cs:~/arkswap/data-main` had been deleted → dangling mount, 500s (recreated + container restarted); (d) with all that fixed the 21-sat zap STILL fails: the recipient is a private node behind Megalithic.me and is offline — even manual `xpay` with 10-sat maxfee dies on temporary_channel_failure everywhere. Not our infra. Refund machinery worked. ALSO fixed: pool ping+reconnect (silent socket death used to kill sync + NWC subs until reload — that's why a phone-created connection stayed invisible to the open laptop tab). RESOLVED SAME EVENING (Adam approved): ASP min_fee_sat 1→3 (captaind-main restarted, healthy); bridge default ON for mainnet (commit 0344733) and bridge opened — tokens cleared in `cs:~/arkswap/config-main.json`. Still open: phone PWA needs two loads to get new builds. **LAYER 2 (found after the filter fix — Amethyst still spun):** relay.coinos.io's `write-policy.pl` (strfry `sf` on desk, `~/coinos-server/data/strfry/`, gitignored) restricted kinds 13194/23195 to coinos server pubkeys, so every reply hal published there was rejected — and Amethyst listens for replies ONLY on the first URI relay, which is relay.coinos.io. The rejections also strike-counted the laptop IP into a temp-ban. Fixed 2026-08-01: NWC kinds opened to all pubkeys on the relay (rate limits intact), `publishOn` now logs refused publishes, 13194 goes out once per session per connection. Amethyst-shaped probe (coinos-only, #e+#p filter) round-trips get_balance green on the deployed build.
Original investigation notes (superseded) **Symptom:** pasting a hal NWC connection string into Amethyst shows a spinner forever. No balance, no error. **This is narrowed, not mysterious. Do not re-derive the following — all of it was verified against the live laptop tab over Chrome DevTools Protocol:** - hal DOES subscribe correctly, on all three NWC relays. Captured from its own socket: `["REQ","sub:1",[{"kinds":[23194],"#p":[""],"since":}]]` - The stored connection's keypairs are self-consistent: `getPublicKey(secret) === clientPk` and `getPublicKey(serviceSk) === servicePk`. So the string Amethyst holds is valid. - hal's sockets **did receive 23194 frames** (6 — one per relay) in one run. - No 23195 reply is ever produced. - The decrypt path is now instrumented (logs + Settings card + replies with an INTERNAL error in both schemes). A fresh probe produced **no log, no UI error, no reply** — so execution isn't reaching decrypt. **Therefore the drop is in one of the silent guards before decrypt**, in `onRequest` in `src/features/nwc.js`: ```js if (handled.has(ev.id)) return; // replay set if (nowSec() - ev.created_at > MAX_AGE_SEC) return; // 120s age check if (ev.pubkey !== c.clientPk) return; // wrong client ``` …or `onRequest` isn't being invoked at all. **Second suspect, untested:** `subscribeOn` reuses the shared `pool` in `src/nostr.js`, and `listen()` is called from three places — feature `init()`, `createConn()`, and the sync-snapshot `load()`. Each call does `stop()` then re-subscribes. If a later `listen()` closes the subscription whose callback would have fired, frames keep arriving on the socket with nothing handling them. That fits every observation. **Next step (one cycle should name it):** log at the TOP of `onRequest` — every event id seen and which guard returns — plus subscription lifecycle (each `listen()`/`stop()` and how many subs are live). Then re-run the probe below. ### The probe (copy-paste, needs Chrome with `--remote-debugging-port=9222` and a hal tab open) ```js // bun run this from ~/coinosv3 import puppeteer fro
# This file should be named lnd.conf and reside at /home/user/.lnd.testa/lnd.conf

externalip=66.186.251.69
alias=🚀 Cool Node Name 👁  
color=#FF3200
rpclisten=0.0.0.0:10001
restlisten=0.0.0.0:8003
listen=0.0.0.0:9736
datadir=/home/user/.lnd.testa/data
tlscertpath=/home/user/.lnd.testa/tls.cert
tlskeypath=/home/user/.lnd.testa/tls.key

[Bitcoin]
bitcoin.active=1
bitcoin.node=bitcoind
bitcoin.testnet=1

[bitcoind]
bitcoind.rpcpass=f-7mLj4qOocraqqeXzzfNSNaj912UsxtNapuQcHv7Kk=
bitcoind.rpcuser=user
bitcoind.zmqpubrawblock=tcp://127.0.0.1:18504
bitcoind.zmqpubrawtx=tcp://127.0.0.1:18505
wallet or bcln's outbound. Was going to the agent on desk (`~/scripts/solvency-monitor.ts`). Suggested alerts: rounds wallet under ~1M of 5M; bcln outbound low. - **bcln is shared** by boltz, the ark bridge, and now the ASP. A dedicated CLN is the clean fix if volume grows. - **Liquidity model** (verified in bark's source): ASP working capital ≈ total user balance under management, locked ~30 days per cycle. Boards and arkoor sends cost the ASP nothing; rounds and offboards front the full value. 5M sat comfortably supports ~3–4M of user balances. --- ## 5. TEST COMMANDS ``` bun tools/feature-registry-check.js # features ship (membership + order) bun tools/nwc-test.js # NWC protocol, budgets, sync merge bun tools/nwc-pouch-test.js # pouch key isolation bun tools/ark/manager-test.js # ark regression (needs regtest stack) bun tools/ark/ln-e2e-test.js # ark<->LN over the ASP bun tools/ark/bridge-e2e-test.js # trustless bridge, regtest bun tools/ark/htlc-bridge-test.js # third-party HTLC + attack rejections bun tools/ark/live-bridge-demo.js # live mutinynet demo (public infra) ``` Regtest stack: `docker start ark-postgres ark-electrs captaind cln-ark` (plain `docker start`, not compose — those services aren't in the compose file).