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 /src/ark | |
| 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 'src/ark')
| -rw-r--r-- | src/ark/manager.js | 23 |
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; } |
