diff options
| author | Adam Soltys <[email protected]> | 2026-09-21 15:00:36 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-21 15:00:36 -0700 |
| commit | 6fbd868ee56ba370c6d31fd7104b85b6c74ee2aa (patch) | |
| tree | f341a798e4d0350c7fc6c21767420d8369b191dd | |
| parent | 5b3344d4987da57e2235b8c61e53b537db1a83ab (diff) | |
| download | coinosv3-6fbd868ee56ba370c6d31fd7104b85b6c74ee2aa.tar.gz coinosv3-6fbd868ee56ba370c6d31fd7104b85b6c74ee2aa.zip | |
Spending coins: Manage opens on the first tap.
The notice reads the persisted Ark state; the page read the live manager,
which is null for the first seconds after boot — so a tap before it had
connected found no coins and closed itself. The page now reads the same
state, dials the manager meanwhile, and keeps Renew and Exit now disabled
until it is there.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
| -rw-r--r-- | src/features/ark.js | 20 |
1 files changed, 13 insertions, 7 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index 486c7a8..bfd1f4b 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -2046,11 +2046,17 @@ export function arkFeature(ctx) { // its expiry and arkoor depth, what an uncooperative exit costs today, and // what a renewal costs and buys. function arkCoinsPage() { + // The notice that opens this page reads the persisted state; so must the + // page, or a tap before the manager has connected (the first seconds + // after boot) found no coins and closed itself — Manage needed two taps. + // The manager is dialled meanwhile; what needs it live waits for it. const mgr = ark; - const spend = ((mgr && mgr.state && mgr.state.vtxos) || []).filter((v) => v.state === 'spendable'); + const st = (mgr && mgr.state) || arkStateNow(); + const spend = ((st && st.vtxos) || []).filter((v) => v.state === 'spendable'); const tip = (mgr && mgr._tipH) || 0; const back = () => { ui.arkCoinsPage = null; ui.arkCoinsSel = null; render(); }; if (!spend.length) { ui.arkCoinsPage = null; return null; } // nothing left to manage + if (!mgr) connectArk().then(() => render()).catch(() => {}); const totalSat = spend.reduce((n, v) => n + v.amountSat, 0); let exitFee = 0; try { exitFee = estimateExitFeeSat(mgr); } catch {} const feeRate = Math.max(1, (wallet.feeRates && wallet.feeRates.halfHourFee) || 2); @@ -2071,15 +2077,15 @@ export function arkFeature(ctx) { const exitFeeOf = (v) => { try { let f = Math.ceil(150 * feeRate); - for (const vsize of exitTxVsizes(mgr._decoded(v), mgr.serverPub)) f += Math.ceil((vsize + 130) * feeRate); + if (mgr) for (const vsize of exitTxVsizes(mgr._decoded(v), mgr.serverPub)) f += Math.ceil((vsize + 130) * feeRate); return f; } catch { return null; } }; // The server prices a renewal by how much lifetime each coin still // carries (its ppm bracket) — waiting IS cheaper, and inside the final // bracket it's free, where the wallet renews on its own anyway. - const table = ((mgr.info || {}).refreshFees || {}).ppmExpiryTable || []; - const roundMins = Math.max(1, Math.round(((mgr.info || {}).roundIntervalSecs || 120) / 60)); + const table = (((mgr && mgr.info) || {}).refreshFees || {}).ppmExpiryTable || []; + const roundMins = Math.max(1, Math.round((((mgr && mgr.info) || {}).roundIntervalSecs || 120) / 60)); const roundEvery = roundMins === 1 ? t('arkCoinsRoundEveryOne') : t('arkCoinsRoundEvery', { n: roundMins }); const ppmFor = (blocks) => (table.filter((e) => e.thresholdBlocks <= blocks).pop()?.ppm) ?? 0; // A coin's SHARE of the renewal fee, fractional: the server totals the @@ -2118,7 +2124,7 @@ export function arkFeature(ctx) { const selCoins = spend.filter((v) => sel.has(v.id)); let selFee = 0; try { selFee = mgr.refreshFee(selCoins, tip); } catch {} const renew = () => { - if (!selCoins.length) return; + if (!selCoins.length || !mgr) return; // fire and let the round machinery carry it — rounds can be an hour out mgr.refresh(selCoins.map((v) => v.id)).catch((e) => toast(e.message)); toast(t('arkDepthRenewed')); @@ -2208,7 +2214,7 @@ export function arkFeature(ctx) { ...schedule.map((r) => h('div', { class: 'row between' }, h('span', { class: 'small muted' }, r.label), h('span', { class: 'small' + (r.free ? ' faint' : '') }, r.cost)))) : null, - h('button', { class: 'btn-primary btn-block', disabled: !!ui.arkBusy || !selCoins.length, onClick: renew }, + h('button', { class: 'btn-primary btn-block', disabled: !!ui.arkBusy || !selCoins.length || !mgr, onClick: renew }, !selCoins.length ? t('arkCoinsRenewNone') : selCoins.length < spend.length ? t('arkCoinsRenewSome', { n: selCoins.length, fee: selFee > 0 ? fmtSats(selFee) + ' sats' : t('arkDepthFree') }) @@ -2217,7 +2223,7 @@ export function arkFeature(ctx) { h('h4', { style: 'margin:0' }, t('arkCoinsExitTitle')), h('p', { class: 'small muted', style: 'margin:0' }, t('arkCoinsExitDesc', { fee: fmtSats(exitFee), after: fmtSats(afterFee) })), - h('button', { class: 'btn-ghost btn-block', disabled: !!ui.arkBusy || !spend.length, onClick: () => { ui.arkExitConfirm = true; render(); } }, t('arkCoinsExitBtn'))), + h('button', { class: 'btn-ghost btn-block', disabled: !!ui.arkBusy || !spend.length || !mgr, onClick: () => { ui.arkExitConfirm = true; render(); } }, t('arkCoinsExitBtn'))), h('button', { class: 'btn-ghost btn-block', onClick: back }, t('back'))); } |
