diff options
Diffstat (limited to 'src/features/nwc.js')
| -rw-r--r-- | src/features/nwc.js | 15 |
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()]; }, |
