diff options
| author | Adam Soltys <[email protected]> | 2026-08-01 11:02:37 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-01 11:02:37 -0700 |
| commit | 0519a79ac05ec983ae7ad8d4292f5cfa9fa321a9 (patch) | |
| tree | 6939dd691379a5642d47c51c866dc98f846c24af /src/features/nwc.js | |
| parent | d8d2746f16d1bdcb71e1409a90871b65117d026c (diff) | |
| download | coinosv3-0519a79ac05ec983ae7ad8d4292f5cfa9fa321a9.tar.gz coinosv3-0519a79ac05ec983ae7ad8d4292f5cfa9fa321a9.zip | |
NWC: own the relay set instead of borrowing the sync feature's
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 <[email protected]>
Diffstat (limited to 'src/features/nwc.js')
| -rw-r--r-- | src/features/nwc.js | 29 |
1 files changed, 19 insertions, 10 deletions
diff --git a/src/features/nwc.js b/src/features/nwc.js index b582ed9..61bfc60 100644 --- a/src/features/nwc.js +++ b/src/features/nwc.js @@ -25,6 +25,7 @@ import { hex } from '@scure/base'; import { sha256 } from '@noble/hashes/sha256'; import { nip04, nip44, getPublicKey, finalizeEvent, generateSecretKey, + subscribeOn, publishOn, } from '../nostr.js'; import { t } from '../i18n.js'; import { qrSvg } from '../qr.js'; @@ -47,6 +48,9 @@ const errRes = (code, message) => ({ error: { code, message } }); export function nwcFeature(ctx) { const { h, ui, render, wallet, hook, fmtAmount, unitLabel, copyBtn, toast } = ctx; + // Relay transport, injectable so the protocol can be driven in tests + // without a live relay. + const { subscribe = subscribeOn, publish = publishOn } = ctx.nwcTransport || {}; // ---- persisted connections ------------------------------------------- // { id, name, secret (client sk hex), clientPk, servicePk, serviceSk, @@ -75,10 +79,11 @@ export function nwcFeature(ctx) { updateConn(c.id, { spentDate: today, spentToday: spent + sat, lastUsed: Date.now() }); } - function relays() { - const r = wallet.nostrRelays ? wallet.nostrRelays() : []; - return r.length ? r.slice(0, 4) : ['wss://relay.coinos.io']; - } + // The relays advertised in the URI MUST be the ones we listen on, or a + // client will publish its request somewhere we aren't and wait forever. + // Deliberately independent of the device-sync relay setting. + const NWC_RELAYS = ['wss://relay.coinos.io', 'wss://relay.damus.io', 'wss://nos.lol']; + const relays = () => NWC_RELAYS; // The string the user pastes into another app. function uriFor(c) { @@ -124,7 +129,7 @@ export function nwcFeature(ctx) { tags: [['p', c.clientPk], ['encryption', 'nip44_v2 nip04']], content: METHODS.join(' '), }, sk); - return wallet.nostrPublishSigned ? wallet.nostrPublishSigned(evt) : null; + return publish(NWC_RELAYS, evt); } const decrypt = (scheme, sk, pk, ct) => (scheme === 'nip44_v2' @@ -146,7 +151,7 @@ export function nwcFeature(ctx) { tags: [['p', ev.pubkey], ['e', ev.id], ['encryption', scheme]], content, }, sk); - await wallet.nostrPublishSigned(evt); + await publish(NWC_RELAYS, evt); } catch (e) { console.warn('nwc: could not reply', e.message); } @@ -262,15 +267,19 @@ export function nwcFeature(ctx) { } function listen() { stop(); - if (!wallet.nostrSubscribe || wallet.watchOnly) return; + if (wallet.watchOnly) return; const list = conns(); if (!list.length) return; for (const c of list) { - const un = wallet.nostrSubscribe( + unsubs.push(subscribe( + NWC_RELAYS, { kinds: [REQ_KIND], '#p': [c.servicePk], since: nowSec() - MAX_AGE_SEC }, (ev) => { onRequest(c, ev).catch(() => {}); }, - ); - unsubs.push(un); + )); + // Republish the capability event on every start: a client that can't + // find kind 13194 just spins, and a single publish at creation time can + // easily have been missed by a relay. + publishInfo(c).catch(() => {}); } } |
