aboutsummaryrefslogtreecommitdiffstats
path: root/src/nwc-respond.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-14 08:53:35 -0700
committerAdam Soltys <[email protected]>2026-09-14 08:53:35 -0700
commit6394de43d4e596aaeddd2b262bb7a595679d8fbc (patch)
tree391f79322f788955985ad149d02e60111aa6ea42 /src/nwc-respond.js
parent4131e5ee24c4891caffdd8f4ca903ee973d104f7 (diff)
downloadcoinosv3-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 'src/nwc-respond.js')
-rw-r--r--src/nwc-respond.js13
1 files changed, 13 insertions, 0 deletions
diff --git a/src/nwc-respond.js b/src/nwc-respond.js
index 532b13e..9fdfcff 100644
--- a/src/nwc-respond.js
+++ b/src/nwc-respond.js
@@ -293,6 +293,19 @@ export async function respondFromBg(data, {
}
break;
}
+ if (a && a.step === 'failed' && /already in progress/i.test(a.error || '')) {
+ // Another device of this wallet started this same payment first. Its
+ // outcome is ours: wait on it, and only speak if the payer stays quiet.
+ log('a sibling device is paying this invoice — waiting on its outcome');
+ const out = await mgr.lnOutcome(dec.paymentHash, Math.max(3000, deadline - Date.now()));
+ if (out.status === 'failed') return failQuietly(a.error);
+ if (out.status !== 'success') { log('sibling payment still pending — leaving it to them'); return true; }
+ await new Promise((r) => setTimeout(r, errGraceMs));
+ if (!(await answered())) {
+ await publish({ result_type: 'pay_invoice', result: { preimage: out.preimage, fees_paid: 0 } });
+ }
+ return true;
+ }
if (!a || a.step === 'failed') {
return failQuietly((a && a.error) || 'payment failed');
}