From 7721a68f6202db562e68226f06319304bb180970 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 28 Aug 2026 19:30:11 -0700 Subject: No exit leaves without fare money: starting one now requires Savings to cover the estimated hop fees (named in the refusal), and a mid-flight stall says exactly how many sats the next step needs and that it resumes on its own Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi --- src/features/ark.js | 9 ++++++++- src/i18n.js | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/features/ark.js b/src/features/ark.js index 14ac9c1..81e3351 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -1597,6 +1597,13 @@ export function arkFeature(ctx) { const mgr = await connectArk(); const spendables = mgr.vtxos().filter((v) => v.state === 'spendable'); if (!spendables.length) throw new Error(t('arkNotConnected')); + // Fees are paid hop by hop from Savings as the exit unrolls — an exit + // that can't fund its hops just stalls at step zero. Refuse to start + // one Savings can't carry, with the number on the table. + const feeEst = estimateExitFeeSat(mgr); + if ((wallet.spendable || 0) < feeEst) { + throw new Error(t('arkExitFeeShort', { need: fmtAmount(feeEst), have: fmtAmount(wallet.spendable || 0) })); + } for (const v of spendables) mgr.startExit(v.id); toast(t('arkExitStarted', { n: spendables.length })); driveExits(mgr).catch(() => {}); @@ -1650,7 +1657,7 @@ export function arkFeature(ctx) { mgr._save(); return; } catch { - const e = new Error(t('arkExitNoFeeCoin')); + const e = new Error(t('arkExitNoFeeCoin', { need: fmtAmount(Math.max(294, feeSat)) })); e.actionable = true; throw e; } diff --git a/src/i18n.js b/src/i18n.js index 234c622..4eeca92 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -525,7 +525,8 @@ const STR = { arkOffboardHistory: 'Moved to Savings', arkExitBtn: 'Move to Savings without the server', arkExitStarted: 'Unilateral exit started for {n} coin(s) — broadcasting the exit chain.', - arkExitNoFeeCoin: 'This needs a little in Savings to pay the mining fees.', + arkExitNoFeeCoin: 'Paused: the next step needs about {need} sats in Savings for its mining fee. Top up Savings and it resumes by itself.', + arkExitFeeShort: 'Savings can’t cover the exit’s mining fees (about {need} sats needed, {have} available). Top up Savings first.', preimageLabel: 'Preimage', copyPreimage: 'Copy preimage', arkExitSummary: 'Moving {n} sats to Savings on-chain — about {eta} until it lands.', -- cgit v1.2.3