diff options
| author | Adam Soltys <[email protected]> | 2026-09-14 08:53:35 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-14 08:53:35 -0700 |
| commit | 6394de43d4e596aaeddd2b262bb7a595679d8fbc (patch) | |
| tree | 391f79322f788955985ad149d02e60111aa6ea42 /tools/nwc-test.js | |
| parent | 4131e5ee24c4891caffdd8f4ca903ee973d104f7 (diff) | |
| download | coinosv3-6394de43d4e596aaeddd2b262bb7a595679d8fbc.tar.gz coinosv3-6394de43d4e596aaeddd2b262bb7a595679d8fbc.zip | |
A zap answered by two tabs no longer reports as failed.
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
Diffstat (limited to 'tools/nwc-test.js')
| -rw-r--r-- | tools/nwc-test.js | 28 |
1 files changed, 27 insertions, 1 deletions
diff --git a/tools/nwc-test.js b/tools/nwc-test.js index 379df6c..5bb08e2 100644 --- a/tools/nwc-test.js +++ b/tools/nwc-test.js @@ -13,12 +13,14 @@ const store = {}; globalThis.localStorage = { getItem:k=>k in store?store[k]:null, setItem:(k,v)=>{store[k]=v;}, removeItem:k=>{delete store[k];} }; let subHandler = null; +let priorReplies = []; const published = []; // fake relay transport: capture the subscription handler and any replies const nwcTransport = { subscribe: (relays, filter, on) => { subHandler = on; return () => { subHandler = null; }; }, publish: async (relays, evt) => { published.push(evt); return true; }, - query: async () => [], // no other device has answered anything in tests + query: async () => priorReplies, // what other devices have answered (none, unless a test says so) + errGraceMs: 20, // the sibling-first hold, shortened for the harness }; let paid = []; const wallet = { @@ -107,6 +109,30 @@ check('second payment succeeds', !!r?.result?.preimage, JSON.stringify(r?.error check('spend accumulates across payments', spent === 52, String(spent)); // 2 × (25 + 1 fee) from the mock seam } +console.log('\n[sibling payment in flight]'); +{ + // The ASP refuses a second HTLC for an invoice another device is paying. + // This device must relay THAT payment's outcome, not report a failure. + const payer = hooks.arkPayInvoice; + hooks.arkPayInvoice = async () => { throw new Error('grpc bark_server.ArkService/RequestLightningPayHtlcCosign: status 3: error making payment: bad user input: payment already in progress for this invoice'); }; + hooks.arkLnOutcome = async () => ({ status: 'success', preimage: '12'.repeat(32) }); + r = await request('pay_invoice', { invoice: INV21 }); + check('sibling success relayed with its preimage', r?.result?.preimage === '12'.repeat(32), JSON.stringify(r)); + const spent = JSON.parse(store['fs:nwc']).conns.find((x) => x.id === 'test1').spentToday; + check('relayed payment not counted as our spend', spent === 52, String(spent)); + // ...and stays quiet when the payer's own reply is already on the relay + priorReplies = [{ id: 'x' }]; + const n = published.length; + r = await request('pay_invoice', { invoice: INV21 }); + check('silent when the payer already replied', published.length === n); + priorReplies = []; + hooks.arkLnOutcome = async () => ({ status: 'failed' }); + r = await request('pay_invoice', { invoice: INV21 }); + check('sibling failure surfaces as an error', /already in progress/.test(r?.error?.message || ''), JSON.stringify(r)); + hooks.arkPayInvoice = payer; + delete hooks.arkLnOutcome; +} + console.log('\n[nip04 fallback]'); r = await request('get_balance', {}, 'nip04'); check('nip04 request answered', r?.result?.balance === 42000*1000); |
