summaryrefslogtreecommitdiffstats
path: root/src/ark
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/ark
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/ark')
-rw-r--r--src/ark/manager.js23
1 files changed, 22 insertions, 1 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 4dd39e4..5775d65 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -957,6 +957,22 @@ export class ArkManager {
_expiryMargin() { return this.network === 'regtest' ? 12 : 144; }
+ // The settled outcome of a payment by hash, polled until the ASP's node
+ // decides or the wait runs out. For a payment another device of this
+ // wallet started (the server refuses a second HTLC for the same invoice)
+ // this is the only way to learn whether it paid.
+ async lnOutcome(paymentHash, timeoutMs = 60000) {
+ const deadline = Date.now() + timeoutMs;
+ for (;;) {
+ const res = await checkLightningPayment(this.arkUrl, hex.decode(paymentHash), false).catch(() => null);
+ if (res && res.status === 'success' && res.preimage
+ && hex.encode(sha256(hex.decode(res.preimage))) === paymentHash) return res;
+ if (res && res.status === 'failed') return res;
+ if (Date.now() >= deadline) return { status: 'pending' };
+ await new Promise((r) => setTimeout(r, 1500));
+ }
+ }
+
lnAction(id) {
return this.state.actions.find((a) => a.id === id && a.type.startsWith('ln-'));
}
@@ -1140,7 +1156,12 @@ export class ArkManager {
action.step = 'failed';
action.error = e.message;
this._recordExpiryRejection(action);
- this._movement({ type: 'ln-send', amountSat: action.amountSat, status: 'failed', detail: e.message });
+ // "already in progress" means another device of this wallet is
+ // paying this very invoice — that payment's row is the history,
+ // a failed row beside it would read as a lost zap.
+ if (!/already in progress/i.test(e.message)) {
+ this._movement({ type: 'ln-send', amountSat: action.amountSat, status: 'failed', detail: e.message });
+ }
this._save();
return;
}