<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/tools/nwc-test.js, branch fdroid-v1.9</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.9</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.9'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-10-02T14:19:29Z</updated>
<entry>
<title>NWC: lookup_invoice, so BTCPay Server can connect</title>
<updated>2026-10-02T14:19:29Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-02T14:19:29Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=e8d82b8b23c271aa57134d762e14fd6dcbed9c7a'/>
<id>urn:sha1:e8d82b8b23c271aa57134d762e14fd6dcbed9c7a</id>
<content type='text'>
BTCPay's NWC connector requires get_info, make_invoice, lookup_invoice
and list_transactions; we never offered lookup_invoice, so it refused the
connection string.

- ArkManager.lnLookup answers lookup_invoice from wallet state (by hash or
  bolt11, either direction; pending/settled/expired/failed, preimage once
  claimed). A retry's several actions on one hash defer to the furthest.
- The open app pushes a pending receive along before answering, so a
  merchant polling sees it settle.
- The service worker now answers make_invoice and lookup_invoice too, and
  claims a paid invoice's HTLCs during the lookup: a BTCPay sale settles
  with the wallet closed. Its list_transactions reports receives as well.
- get_info and the 13194 info event list the new method.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
</content>
</entry>
<entry>
<title>A zap answers as fast as the route allows.</title>
<updated>2026-09-14T16:16:23Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T16:16:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=dd1489303f59ccef6b322142f94e08d750c06e79'/>
<id>urn:sha1:dd1489303f59ccef6b322142f94e08d750c06e79</id>
<content type='text'>
The wallet used to add two to four seconds of its own around a Lightning
send: a random pause plus a relay look-back before paying, and a
two-second poll for the result after. The notifier's claim now decides
who pays in one round trip, with the relay look-back kept only for
replays and for when the notifier gives no verdict; a phone answering
from behind another app waits a second so a desktop tab wins the claim
instead of paying at a throttled crawl. After the send, one held
CheckLightningPayment call brings the preimage the moment CLN has it —
for wallet-connect pays, the background worker, and the app's own Send.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>One device pays a zap, whichever of them heard it first.</title>
<updated>2026-09-14T16:06:02Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T16:06:02Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=671d2a96474f59ca9234c876ce6604d03de95727'/>
<id>urn:sha1:671d2a96474f59ca9234c876ce6604d03de95727</id>
<content type='text'>
Every awake device of a wallet hears a wallet-connect pay request within
the same second, and the per-tab lock only reaches one browser profile:
two profiles on one laptop each locked an HTLC for the same 21-sat zap.
The push notifier now hands each pay request to the first device that
claims it; a device that loses the claim stands down silently, and a
device that cannot reach the notifier answers as before so nobody goes
quiet over a hiccup. The worker path claims the same way.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>A zap answered by two tabs no longer reports as failed.</title>
<updated>2026-09-14T15:53:35Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T15:53:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6394de43d4e596aaeddd2b262bb7a595679d8fbc'/>
<id>urn:sha1:6394de43d4e596aaeddd2b262bb7a595679d8fbc</id>
<content type='text'>
Every open tab of the same wallet sees every wallet-connect request. Two
hidden tabs on one laptop both tried to pay the same zap: the ASP refused
the second with "payment already in progress", and that error reached
Amethyst half a second before the first tab's preimage. A Web Lock per
request now lets one tab (or the worker) answer while the rest stay
quiet, and a device that does meet "already in progress" waits for the
sibling payment's outcome — relaying its preimage only if the payer never
speaks — instead of shouting a failure over sats that went through. The
losing side also stops writing a failed row into history for it.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>Answering while closed becomes the default: a new connection asks once, and anywhere permission is already granted it arms itself — only an explicit Off is honored forever</title>
<updated>2026-08-14T06:22:17Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-14T06:22:17Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=3104c93cece2104457d672aef59da2327659de4b'/>
<id>urn:sha1:3104c93cece2104457d672aef59da2327659de4b</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
</content>
</entry>
<entry>
<title>Never let a losing device's error shout over a paid zap</title>
<updated>2026-08-02T00:49:50Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-02T00:49:50Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=4ca5dd0afa7848d100f0b99be7b53f5dcb2fd061'/>
<id>urn:sha1:4ca5dd0afa7848d100f0b99be7b53f5dcb2fd061</id>
<content type='text'>
Loose leader election for pay_invoice across a user's open tabs: before
handling, defer a beat and stay silent if any reply for the request
already exists on the relays; and before sending an ERROR reply,
re-check — a device that lost the race (mid-init, stale state) was
replying 'superseded' after another device's success, and clients
trusted the later error, marking paid zaps as failed.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Make NWC budgets accumulate and travel across devices</title>
<updated>2026-08-01T22:02:01Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-01T22:02:01Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=d554a13cf4560891010f50e117754825c1f1be7a'/>
<id>urn:sha1:d554a13cf4560891010f50e117754825c1f1be7a</id>
<content type='text'>
onRequest handled every request against the connection object captured
at listen() time, so each spend was recorded against a stale snapshot —
spentToday restarted from the old value on every payment and the daily
budget never actually bound. The connection is now re-resolved from
stored state per request, which also stops serving a connection revoked
on another device mid-session. recordSpend now publishes the sync
snapshot so the spend (and the payment's movement + vtxo change) reaches
the user's other devices. Test: two payments must accumulate to 52.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Sync NWC connections across devices</title>
<updated>2026-08-01T18:39:29Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-01T18:39:29Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0319e2fd392fc7e8569fbb6a88a6a1dd65e9d539'/>
<id>urn:sha1:0319e2fd392fc7e8569fbb6a88a6a1dd65e9d539</id>
<content type='text'>
A connection made on the phone existed only there, so only that device could
answer it — and if that device was asleep, a client just spun. Connections now
ride the encrypted device-sync snapshot, so whichever device is awake can
serve them. The snapshot is nip44-encrypted to a key derived from the seed,
the same channel that already carries ark vtxo bytes and gift secrets.

The merge is a union by id with two rules that matter:
- REVOCATION WINS, so a stale snapshot can never resurrect a revoked
  connection
- the higher same-day spend wins, so a stale device cannot hand a connection
  back its daily budget

Creating or revoking a connection publishes the snapshot immediately rather
than waiting for the next state change.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>NWC: own the relay set instead of borrowing the sync feature's</title>
<updated>2026-08-01T18:02:37Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-01T18:02:37Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=0519a79ac05ec983ae7ad8d4292f5cfa9fa321a9'/>
<id>urn:sha1:0519a79ac05ec983ae7ad8d4292f5cfa9fa321a9</id>
<content type='text'>
The connection URI advertised up to four relays (sync relays + profile
relays) while the subscription only ever listened on the sync relay, so a
client could publish its request somewhere hal wasn't and spin forever
waiting for a reply. Amethyst did exactly that.

NWC now has its own relay list used for all three things that must agree:
the URI, the subscription, and the replies. It no longer depends on device
sync being enabled, which would otherwise have made the whole wallet service
a silent no-op.

Also republish the kind 13194 capability event on every start — a client that
can't find it just spins, and a single publish at creation time is easy for a
relay to miss.

The relay transport is now injectable so the protocol can be exercised
without a live relay (tools/nwc-test.js).

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Add Nostr Wallet Connect (NIP-47) — hal as a wallet service</title>
<updated>2026-08-01T16:25:23Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-01T16:25:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=e09b76ea0ed13840426a56f031769f8d4154f34b'/>
<id>urn:sha1:e09b76ea0ed13840426a56f031769f8d4154f34b</id>
<content type='text'>
Other apps can connect over nostr and pay from this wallet without touching
its keys, within a per-payment and daily budget the user sets, revocable at
any time. hal holds no native Lightning balance, so every operation runs over
Ark: pay_invoice takes the swap bridge when configured and falls back to the
ASP's lightning-send, make_invoice mints on the ASP. NWC availability
therefore tracks Ark availability.

- kinds 13194/23194/23195, nip44_v2 and nip04, replying in whichever scheme
  the request used; every request gets a reply so clients never hang
- each connection gets its own service keypair, so revoking one can't affect
  the others and none is tied to the user's nostr identity
- budgets are checked BEFORE spending, not reconciled after
- requests are ignored unless signed by that connection's client key, and
  replayed/stale events (&gt;120s) can't re-trigger a payment
- Settings card to create, show (URI + QR) and revoke connections, and it
  says plainly that this only works while a tab is open

tools/nwc-test.js drives it as a real client: both encryption schemes, budget
refusal with a genuine over-limit invoice, unsupported-method errors, and a
request from an unauthorized key being ignored.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
