aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark
diff options
context:
space:
mode:
Diffstat (limited to 'src/ark')
-rw-r--r--src/ark/manager.js26
1 files changed, 24 insertions, 2 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index c0e972b..15714b7 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -210,6 +210,10 @@ export class ArkManager {
this.state.serverPubkey = this.info.serverPubkey;
this._save();
}
+ // Older builds kept server-rejected dust in the spendable balance.
+ let expiryChanged = false;
+ for (const action of this.state.actions) expiryChanged = this._recordExpiryRejection(action) || expiryChanged;
+ if (expiryChanged) this._save();
return this;
}
@@ -337,7 +341,22 @@ export class ArkManager {
// the exception — they have no unilateral exit to protect and the coinos ASP
// keeps honoring them off-chain (mirrors validateVtxo's carve-out).
_expired(v, tip) {
- return !!tip && !!v.expiryHeight && v.amountSat >= 330 && v.expiryHeight <= tip;
+ return !!v.expiryRejected || (!!tip && !!v.expiryHeight && v.amountSat >= 330 && v.expiryHeight <= tip);
+ }
+
+ // The dust exemption is not a promise that every old coin will be
+ // accepted. A cosign rejection names the exact expired input; remember
+ // that verdict without discarding the other inputs in its atomic package.
+ _recordExpiryRejection(action) {
+ if (action.step !== 'failed') return false;
+ const m = /vtxo ([0-9a-f]{64}:\d+) expired at height (\d+) \(tip = (\d+)\)/i.exec(action.error || '');
+ if (!m) return false;
+ const inputs = [...(action.inputIds || []), ...this._sendParts(action).map(p => p.inputId)];
+ const v = inputs.includes(m[1]) && this._vtxo(m[1]);
+ if (!v || v.expiryHeight !== Number(m[2]) || v.expiryRejected) return false;
+ v.expiryRejected = true;
+ this._tipH = Math.max(this._tipH || 0, Number(m[3]));
+ return true;
}
balance() {
@@ -810,6 +829,7 @@ export class ArkManager {
this.state.actions.push(action);
this._save();
await this._driveSend(action);
+ if (action.step !== 'done') throw new Error(action.error || 'Ark payment did not complete');
return action.id;
}
@@ -853,7 +873,8 @@ export class ArkManager {
if (spent) this.reconcile().catch(() => {});
action.step = 'failed';
action.error = e.message;
- this._movement({ type: 'send', amountSat: action.amountSat, status: 'failed', detail: e.message });
+ this._recordExpiryRejection(action);
+ this._movement({ type: 'send', amountSat: action.amountSat, status: 'failed', detail: e.message, to: action.destAddress });
this._save();
return;
}
@@ -1105,6 +1126,7 @@ export class ArkManager {
if (spent) this.reconcile().catch(() => {});
action.step = 'failed';
action.error = e.message;
+ this._recordExpiryRejection(action);
this._movement({ type: 'ln-send', amountSat: action.amountSat, status: 'failed', detail: e.message });
this._save();
return;