aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/nwc.js15
1 files changed, 15 insertions, 0 deletions
diff --git a/src/features/nwc.js b/src/features/nwc.js
index 9768740..741b0a4 100644
--- a/src/features/nwc.js
+++ b/src/features/nwc.js
@@ -627,12 +627,23 @@ export function nwcFeature(ctx) {
if (!o || !pend.length) return;
const moves = hook('arkMovements') || [];
const keep = [];
+ // The pending list rides the synced wallet state, and every device holds
+ // the offer key: phone and laptop each published the same receipt a
+ // minute apart (Helen's 88-sat zap, Adam 2026-11-14). A receipt already
+ // out — sent from here, or found on the relays — isn't sent again.
+ const done = new Set(st.zapDone || []);
+ const sent = [];
for (const z of pend) {
if (Date.now() - z.ts > 86400_000) continue; // expired unpaid
+ if (done.has(z.invoice)) continue;
const paid = moves.find((m) => m.type === 'ln-receive' && m.status === 'complete' && m.invoice === z.invoice);
if (!paid) { keep.push(z); continue; }
try {
const zapReq = JSON.parse(z.zap);
+ const offerPk = getPublicKey(hex.decode(o.sk));
+ const to = (zapReq.tags.find((t2) => t2[0] === 'p') || [])[1];
+ const out = await query(NWC_RELAYS, { kinds: [9735], authors: [offerPk], ...(to ? { '#p': [to] } : {}), since: Math.floor(z.ts / 1000) - 120 }, 2500).catch(() => []);
+ if ((out || []).some((r) => (r.tags || []).some((t2) => t2[0] === 'bolt11' && t2[1] === z.invoice))) { sent.push(z.invoice); continue; }
const tags = [
...zapReq.tags.filter((t2) => t2[0] === 'p' || t2[0] === 'e'),
['P', zapReq.pubkey],
@@ -644,10 +655,12 @@ export function nwcFeature(ctx) {
const relayTag = zapReq.tags.find((t2) => t2[0] === 'relays');
const relays = [...new Set([...(relayTag ? relayTag.slice(1) : []), ...NWC_RELAYS])].slice(0, 8);
await publish(relays, receipt);
+ sent.push(z.invoice);
} catch (e) { console.warn('nwc: zap receipt failed', e.message); }
}
const st2 = load();
st2.zapPending = keep;
+ st2.zapDone = [...new Set([...(st2.zapDone || []), ...sent])].slice(-100);
save(st2);
}
@@ -1106,6 +1119,8 @@ export function nwcFeature(ctx) {
id: 'nwc',
nwcOfferPubkey() { return (hook('arkReady') && !wallet.watchOnly) ? offerKeys().pk : null; },
nwcOfferString() { return hook('arkReady') && !wallet.watchOnly ? offerString() : null; },
+ // tests: the receipt pass on demand (normally the watchdog's and the reconcile's)
+ nwcPublishZapReceipts() { return publishZapReceipts(); },
init() { listen(); startWatchdog(); refreshRegistration(); reconcileBg(); ensureBackground(false); serveWake(); },
stop() { stop(); if (watchdog) { clearInterval(watchdog); watchdog = null; } },
nostrSettingsCards() { return [nwcCard()]; },