aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/manager.js10
-rw-r--r--src/features/ark.js14
2 files changed, 21 insertions, 3 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index a2c69d8..cfb7436 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -990,16 +990,22 @@ export class ArkManager {
}
_settleLnPay(action, preimageHex) {
+ // The mailbox push and the status poll both land here, and their step
+ // checks happen BEFORE awaits — settle once, however many callers race
+ // (a hat purchase once wrote its history line twice this way).
+ if (action.step === 'done') return;
for (const id of action.htlcVtxoIds || [action.htlcVtxoId]) {
const htlc = this._vtxo(id);
if (htlc) htlc.state = 'spent';
}
action.preimage = preimageHex;
action.step = 'done';
- this._movement({
+ const dup = this.state.movements.some((m) =>
+ m.type === 'ln-send' && (m.paymentHash === action.paymentHash || (m.preimage && m.preimage === preimageHex)));
+ if (!dup) this._movement({
type: 'ln-send', amountSat: action.amountSat, status: 'complete',
detail: action.feeSat ? `fee ${action.feeSat} sat` : undefined,
- invoice: action.invoice, preimage: preimageHex,
+ invoice: action.invoice, preimage: preimageHex, paymentHash: action.paymentHash,
});
this._save();
}
diff --git a/src/features/ark.js b/src/features/ark.js
index 3e614c4..15b7687 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -175,7 +175,19 @@ export function mergeArkStates(a, b) {
return [...x, ...y.filter((i) => !ids.has(i.id))];
};
out.actions = unionById(a.actions, b.actions);
- out.movements = unionById(a.movements, b.movements).sort((m, n) => (m.ts || 0) - (n.ts || 0));
+ // Two devices (or the push and the poll, pre-guard) can each record the
+ // SAME lightning payment under different movement ids — same preimage is
+ // the same payment, keep the earliest telling of it.
+ const seenPay = new Set();
+ out.movements = unionById(a.movements, b.movements)
+ .sort((m, n) => (m.ts || 0) - (n.ts || 0))
+ .filter((m) => {
+ if (!m.type || !m.type.startsWith('ln-') || m.status !== 'complete') return true;
+ const key = m.type + ':' + (m.paymentHash || m.preimage || m.invoice || m.id);
+ if (seenPay.has(key)) return false;
+ seenPay.add(key);
+ return true;
+ });
const gifts = new Map((a.gifts || []).map((g) => [g.id, { ...g }]));
for (const rg of b.gifts || []) {
const lg = gifts.get(rg.id);