diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/ark.js | 51 | ||||
| -rw-r--r-- | src/i18n.js | 5 |
2 files changed, 42 insertions, 14 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index 0a41abc..953de5a 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -1571,6 +1571,22 @@ export function arkFeature(ctx) { .sort((a, b) => a.value - b.value)[0] || null; } + // What a unilateral exit will need from Savings, BEFORE it starts: every + // chain hop is a zero-fee tx bumped by a ~130 vB CPFP child, plus the + // final claim. Priced at today's feerate — the driver reprices per hop. + function estimateExitFeeSat(mgr) { + const feeRate = Math.max(1, (wallet.feeRates && wallet.feeRates.halfHourFee) || 2); + let total = 0; + for (const v of mgr.vtxos().filter((x) => x.state === 'spendable')) { + try { + for (const txi of signedExitTxs(mgr._decoded(v), mgr.serverPub)) + total += Math.ceil((txi.vsize + 130) * feeRate); + total += Math.ceil(150 * feeRate); // the claim tx + } catch {} + } + return total; + } + async function doArkExit() { ui.arkBusy = 'exit'; ui.arkError = ''; render(); try { @@ -1839,18 +1855,29 @@ export function arkFeature(ctx) { if (!sats || sats <= 0 || sats > spendable) { ui.arkError = t('enterValidAmtForN', { n: 1 }); render(); return; } doArkOffboard(sats >= spendable ? 0 : sats, o.address); } }, t('send'))), - // This send IS the cooperative exit. The trustless door stays visible — - // quiet while things work, promoted to a button the moment the server - // fails to cooperate (that being the whole point of a unilateral exit). - ui.arkError && ui.arkBusy !== 'offboard' - ? h('button', { - class: 'btn-block', - onClick: () => { ui.arkOffboardSend = null; ui.arkError = ''; ui.arkExitPage = true; render(); }, - }, t('arkUniOffer')) - : h('button', { - class: 'linklike small', style: 'align-self:center', - onClick: () => { ui.arkOffboardSend = null; ui.arkError = ''; ui.arkExitPage = true; render(); }, - }, t('arkUniOfferQuiet'))); + // This send IS the cooperative exit. The trustless fallback only + // appears when cooperation actually failed — and it states, before + // anything is confirmed, exactly what it does and what the mining + // fees from Savings will be. + (() => { + if (!ui.arkError || ui.arkBusy === 'offboard' || !ark) return null; + let feeEst = 0; + try { feeEst = estimateExitFeeSat(ark); } catch {} + const savings = wallet.spendable || 0; + const short = savings < feeEst; + return h('div', { class: 'col', style: 'gap:8px;border-top:1px solid var(--border,rgba(128,128,128,.2));padding-top:10px' }, + h('div', { class: 'small muted' }, t('arkUniFallbackBody', { fee: fmtAmount(feeEst) })), + short ? h('div', { class: 'small err' }, t('arkUniFallbackShort', { need: fmtAmount(feeEst), have: fmtAmount(savings) })) : null, + h('button', { + class: 'btn-block', disabled: !!ui.arkBusy || short, + onClick: () => { + ui.arkOffboardSend = null; ui.arkError = ''; ui.send = blankSend(); + doArkExit(); + ui.tab = 'history'; // the exit lives as a pending record there + render(); + }, + }, t('arkUniFallbackBtn', { fee: fmtAmount(feeEst) }))); + })()); } async function doArkOffboard(amountSat, destAddress = null) { diff --git a/src/i18n.js b/src/i18n.js index bec9592..e8bb52c 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -199,8 +199,9 @@ const STR = { onbBoardTitle: 'Top up Spending', onbBoardBody: 'Move some sats from Savings into Spending — payments there are instant, with no mining fees.', topUpFromSavings: 'Top up from Savings', - arkUniOffer: 'Exit without the server instead', - arkUniOfferQuiet: 'Server not cooperating? Unilateral exit', + arkUniFallbackBody: 'The server didn’t cooperate, but your money can still move without it: your whole Spending balance exits to Savings on-chain. It takes about a day, and roughly {fee} sats of mining fees will be paid from Savings. Funds land in Savings — send them on from there once it completes.', + arkUniFallbackShort: 'Savings holds {have} sats — about {need} sats are needed for the mining fees. Top up Savings first.', + arkUniFallbackBtn: 'Exit to Savings (≈ {fee} sats in fees)', msgPushOffer: 'Get notified when you receive payments and messages, even with coinos closed.', msgPushEnable: 'Enable notifications', msgPushOn: 'Notifications on', |
