aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-21 10:26:38 -0700
committerAdam Soltys <[email protected]>2026-09-21 10:26:38 -0700
commit0761d901ed1f6468a5497e87efb2d21b1e222a02 (patch)
treeb0a1acb825556006ceb41fa867fff73ea80c60b2
parent9a03fafbb0a49c87fd9e6269f7db020caf62e0c6 (diff)
downloadcoinosv3-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
-rw-r--r--src/ark/manager.js29
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');