From 1d9cac6ca92187718eafbd567562875d25fa772b Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 4 Jul 2026 19:29:08 -0700 Subject: Ark: recover from stale vtxo state Match captaind's actual 'not spendable' error so a crashed or foreign spend fails the action instead of retrying forever, and reconcile locally-spendable vtxos against the server (GetVtxoStatus + ownership attestation) when an ark address lands in the send form. boardFee now treats absent proto3 fee fields as zero (Second's signet boards free). Co-Authored-By: Claude Fable 5 --- src/features/ark.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src/features/ark.js') diff --git a/src/features/ark.js b/src/features/ark.js index 868192a..ca718ee 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -104,6 +104,17 @@ export function arkFeature(ctx) { return ark.balance(); } + // An ark address in the send form signals a send is coming: verify our + // spendable vtxos against the server now, so a stale one (same seed active + // elsewhere, restored state) is dropped before coin selection instead of + // failing at cosign time. Throttled — this fires from the render path. + let arkReconciledAt = 0; + function maybeReconcile() { + if (Date.now() - arkReconciledAt < 30_000) return; + arkReconciledAt = Date.now(); + connectArk().then((mgr) => mgr.reconcile()).catch(() => {}); + } + function arkSendReview() { if (ui.arkSent) { return h( @@ -408,7 +419,9 @@ export function arkFeature(ctx) { isSendDest(a) { return isArkAddress(a) && arkAvailable(); }, hideSendControls(a) { return isArkAddress(a); }, sendFormNote(a) { - return isArkAddress(a) ? h('div', { class: 'small faint' }, t('arkSendHint')) : null; + if (!isArkAddress(a)) return null; + maybeReconcile(); + return h('div', { class: 'small faint' }, t('arkSendHint')); }, interceptReview(s) { if (s.recipients.length === 1 && isArkAddress(s.recipients[0].address)) { -- cgit v1.2.3