diff options
| author | Adam Soltys <[email protected]> | 2026-09-21 10:26:38 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-21 10:26:38 -0700 |
| commit | 0761d901ed1f6468a5497e87efb2d21b1e222a02 (patch) | |
| tree | b0a1acb825556006ceb41fa867fff73ea80c60b2 /src/ark | |
| parent | 9a03fafbb0a49c87fd9e6269f7db020caf62e0c6 (diff) | |
| download | coinosv3-0761d901ed1f6468a5497e87efb2d21b1e222a02.tar.gz coinosv3-0761d901ed1f6468a5497e87efb2d21b1e222a02.zip | |
Ark: a Lightning invoice steps past a preimage index another device already spent.
The ASP answered "invoice has already been paid" for a point-of-sale
invoice: the receive counter on the phone was behind the one the laptop
had used. createLnInvoice now retries on that answer with the next index
(the counter only moves up; the key index is allocated once an invoice
exists), so the Receive tab, NWC make_invoice and the point of sale all
recover instead of failing.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
Diffstat (limited to 'src/ark')
| -rw-r--r-- | src/ark/manager.js | 29 |
1 files changed, 22 insertions, 7 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js index 1381095..3477638 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -1338,14 +1338,29 @@ export class ArkManager { if (this.info.maxUserInvoiceCltvDelta && minCltvDelta > this.info.maxUserInvoiceCltvDelta) { throw new Error('server max invoice CLTV delta is too low'); } - const idx = this.state.nextLnRecvIndex || 0; - this.state.nextLnRecvIndex = idx + 1; + // The preimage comes from a counter. Another device can have spent an + // index this one hasn't heard about yet (the merge keeps the higher + // count, but only once the state has travelled), and the server then + // refuses the hash as already paid. Step past any such index rather + // than fail the sale — the counter only ever moves up. + let idx, paymentHash, invoice; + for (let tries = 0; ; tries++) { + idx = this.state.nextLnRecvIndex || 0; + this.state.nextLnRecvIndex = idx + 1; + paymentHash = sha256(this._lnPreimage(idx)); + try { + invoice = await startLightningReceive(this.arkUrl, { + paymentHash, amountSat, minCltvDelta, + mailboxPubkey: this._mailboxKey().pubkey, description, + }); + break; + } catch (e) { + const used = /already been paid|already exists|already in use|duplicate/i.test(e?.message || ''); + if (!used || tries >= 50) throw e; + this._save(); // remember the skipped index even if the next try fails + } + } const keyIndex = this.state.nextKeyIndex++; - const paymentHash = sha256(this._lnPreimage(idx)); - const invoice = await startLightningReceive(this.arkUrl, { - paymentHash, amountSat, minCltvDelta, - mailboxPubkey: this._mailboxKey().pubkey, description, - }); const decInv = decodeBolt11(invoice); if (decInv.paymentHash !== hex.encode(paymentHash)) { throw new Error('server invoice payment hash mismatch'); |
