diff options
| author | Adam Soltys <[email protected]> | 2026-11-14 23:02:39 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-11-14 23:02:39 -0700 |
| commit | 31bdd26474436d80204bd51a8d75c674bfe762b1 (patch) | |
| tree | 8cf87c221034ea83d5dd1b42146b13dbb7fa5410 /tools/noffer-test.js | |
| parent | 1424cc46382266a51123a8425424f3c8cb943e8e (diff) | |
| download | coinosv3-31bdd26474436d80204bd51a8d75c674bfe762b1.tar.gz coinosv3-31bdd26474436d80204bd51a8d75c674bfe762b1.zip | |
A zap to you gets one receipt, not one per device. Helen's 88-sat zap on Adam's comment (2026-11-15 05:33) had two identical kind 9735 receipts a minute apart; Adam guessed "probably my phone and laptop". It was: the zaps waiting for their receipt live in the synced wallet state and every device holds the offer key that signs them, so each device published one when it saw the payment complete. coinos's notifications fold receipts by invoice (two rows were two zaps), other clients may not. Now a device skips a receipt it already sent (zapDone, synced, last 100) or that the relays already carry (one query by the offer key and recipient for that bolt11), and drops it from the pending list either way. noffer-test: the receipt goes out once however often the pass runs, and a second device holding the same pending zap finds it on the relays and stays quiet; nwc, notif-app-zap and features-smoke pass
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
Diffstat (limited to 'tools/noffer-test.js')
| -rw-r--r-- | tools/noffer-test.js | 22 |
1 files changed, 21 insertions, 1 deletions
diff --git a/tools/noffer-test.js b/tools/noffer-test.js index 152ef0b..5886f84 100644 --- a/tools/noffer-test.js +++ b/tools/noffer-test.js @@ -31,8 +31,9 @@ const published = []; const nwcTransport = { subscribe: (relays, filter, on) => { subHandlers.push({ filter, on }); return () => {}; }, publish: async (relays, evt) => { published.push(evt); return true; }, - query: async () => [], + query: async (relays, filter) => queryImpl(relays, filter), }; +let queryImpl = async () => []; const wallet = { watchOnly: false, loadFeatureState: (n, fb) => JSON.parse(store['fs:' + n] || 'null') || fb, @@ -95,5 +96,24 @@ await offerSub.on(reqEv); await new Promise((r) => setTimeout(r, 60)); check('replayed request ignored', published.filter((e) => e.kind === 21001).length === before); +console.log('\n[the receipt, from two devices]'); +// the zap is paid: this device publishes its receipt once +hooks.arkMovements = () => [{ type: 'ln-receive', status: 'complete', invoice: 'lnbc_mock_21', preimage: 'ee'.repeat(32) }]; +const receipts = () => published.filter((e) => e.kind === 9735); +queryImpl = async (relays, filter) => published.filter((e) => (filter.kinds || []).includes(e.kind) && (!filter.authors || filter.authors.includes(e.pubkey))); +await f.nwcPublishZapReceipts(); +check('the paid zap gets its receipt', receipts().length === 1 && receipts()[0].pubkey === offer.pubkey); +await f.nwcPublishZapReceipts(); +check('…once, however often the pass runs', receipts().length === 1); +// another device: the synced state still lists it pending (it synced before +// this device's pass), and the relays have the receipt already +const st3 = JSON.parse(store['fs:nwc']); +st3.zapPending = [{ zap: JSON.stringify(zapReq), invoice: 'lnbc_mock_21', ts: Date.now() }]; +st3.zapDone = []; +store['fs:nwc'] = JSON.stringify(st3); +await f.nwcPublishZapReceipts(); +check('a second device finds it on the relays and stays quiet', receipts().length === 1, receipts().length + ' receipts'); +check('…and drops it from the pending list', (JSON.parse(store['fs:nwc']).zapPending || []).length === 0); + console.log(fails ? `\n❌ ${fails} failure(s)` : '\n✅ offer service behaves'); process.exit(fails ? 1 : 0); |
