aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/ark.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-01 23:16:52 -0700
committerAdam Soltys <[email protected]>2026-08-01 23:16:52 -0700
commit3dab66c5d3d63f19444e7fe14017fdfeb9a8e27d (patch)
tree45d7f2074b31190ee01d0f7b644fe631086d5271 /src/features/ark.js
parentbabac497c1f44892b646b30be4d3ad035b461386 (diff)
downloadcoinosv3-3dab66c5d3d63f19444e7fe14017fdfeb9a8e27d.tar.gz
coinosv3-3dab66c5d3d63f19444e7fe14017fdfeb9a8e27d.zip
Speak about money, not rails: Spending and Saving
The balance card now leads with the total and names the two places money lives — Spending (instant) and Saving (on-chain) — instead of an on-chain headline with an 'Ark balance' line hanging off it. A 'Move money' button under the balance opens one screen that moves funds either way, replacing a board form buried inside the Ark receive pane. Strings throughout drop Ark and Lightning: boarding becomes 'Move to Spending', exiting becomes 'Move to Saving', the receive tabs read 'Instant' and 'Saving address', and How it works explains two balances and the escape hatch without naming a protocol. The rails keep their names in the code, where they belong. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/features/ark.js')
-rw-r--r--src/features/ark.js113
1 files changed, 70 insertions, 43 deletions
diff --git a/src/features/ark.js b/src/features/ark.js
index 0981918..baee9a4 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -843,7 +843,7 @@ export function arkFeature(ctx) {
render();
};
const head = [
- h('h3', { class: 'row gap6', style: 'align-items:center' }, h('span', { html: ARK_MARK(18) }), 'Ark'),
+ h('h3', { class: 'row gap6', style: 'align-items:center' }, t('arkTitle')),
h('p', { class: 'small muted', style: 'margin:0' }, t('arkDesc')),
h('select', { onChange: (e) => applyProvider(e.target.value) },
presets.map((p) => h('option', { value: p.id, selected: p.id === id }, p.label))),
@@ -1338,6 +1338,60 @@ export function arkFeature(ctx) {
ui.arkBusy = null; render();
}
+ // Moving money between the two balances, in plain terms. This is the only
+ // place the two rails meet, and it never names them: Saving is on-chain,
+ // Spending is instant, and the wallet handles the rest.
+ function boardForm() {
+ const minBoard = (ark && ark.info && ark.info.minBoardAmountSat) || 0;
+ const canBoard = wallet.spendable >= minBoard;
+ return h('div', { class: 'col', style: 'width:100%;gap:8px' },
+ h('div', { class: 'input-group' },
+ h('input', {
+ type: 'number', inputmode: 'numeric', min: '0',
+ placeholder: t('arkBoardPlaceholder', { n: minBoard.toLocaleString() }),
+ value: ui.arkBoardAmt || '',
+ onInput: (e) => { ui.arkBoardAmt = e.target.value; render(); },
+ }),
+ h('button', { class: 'btn-primary', disabled: !!ui.arkBusy || !canBoard, onClick: doArkBoard },
+ ui.arkBusy === 'board' ? h('span', { class: 'spinner sm' }) : t('arkBoardBtn'))),
+ (() => {
+ const sats = parseInt((ui.arkBoardAmt || '').trim(), 10);
+ if (!sats || sats < minBoard || !ark) return null;
+ const fee = boardFee(sats, ark.info.boardFees);
+ return h('div', { class: 'small muted', style: 'text-align:center' },
+ t('arkBoardFeeNote', { fee: fmtAmount(fee), net: fmtAmount(sats - fee) }));
+ })(),
+ canBoard
+ ? h('div', { class: 'small faint', style: 'text-align:center' },
+ t('arkBoardAvailable', { n: fmtAmount(wallet.spendable) + ' ' + unitLabel() }))
+ : h('div', { class: 'small faint', style: 'text-align:center' },
+ t('arkBoardNoFunds', { n: minBoard.toLocaleString() })));
+ }
+
+ function movePage() {
+ const b = arkBalance();
+ const back = () => { ui.arkMovePage = false; ui.arkError = ''; render(); };
+ if (!ark) connectArk().catch(() => {});
+ return h('div', { class: 'col', style: 'gap:16px;padding:16px;max-width:460px;margin:0 auto;width:100%' },
+ h('div', { class: 'row between' },
+ h('h3', { style: 'margin:0' }, t('moveTitle')),
+ h('span', { class: 'linklike small', onClick: back }, t('back'))),
+ ui.arkError ? h('div', { class: 'notice err' }, ui.arkError) : null,
+ h('div', { class: 'card col', style: 'gap:10px' },
+ h('h4', { style: 'margin:0' }, t('moveToSpending')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('moveToSpendingDesc')),
+ ark ? boardForm() : h('div', { class: 'row gap6', style: 'align-items:center' },
+ h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('arkConnecting')))),
+ h('div', { class: 'card col', style: 'gap:10px' },
+ h('h4', { style: 'margin:0' }, t('moveToSaving')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('moveToSavingDesc')),
+ h('div', { class: 'small faint' }, t('moveAvailable', { n: fmtAmount(b ? b.spendableSat : 0) + ' ' + unitLabel() })),
+ h('button', {
+ class: 'btn-block', disabled: !b || !b.spendableSat,
+ onClick: () => { ui.arkMovePage = false; ui.arkExitPage = true; ui.arkError = ''; render(); },
+ }, t('moveToSavingBtn'))));
+ }
+
// The Ark receive pane: connect-on-demand, then address + board form.
function arkReceivePane(seg) {
if (true) {
@@ -1354,8 +1408,6 @@ export function arkFeature(ctx) {
);
}
const arkAddr = ark.address();
- const minBoard = ark.info.minBoardAmountSat || 0;
- const canBoard = wallet.spendable >= minBoard;
return h(
'div',
{ class: 'card col', style: 'align-items:center;gap:14px' },
@@ -1364,30 +1416,6 @@ export function arkFeature(ctx) {
h('div', { html: qrSvg(arkAddr) }),
h('div', { class: 'addr-box break', style: 'width:100%;font-size:11px' }, arkAddr),
copyBtn(arkAddr, t('copyAddress')),
- // Board: fund your Ark balance from this wallet's on-chain coins.
- h('div', { class: 'col', style: 'width:100%;gap:8px;border-top:1px solid var(--border,rgba(128,128,128,.2));padding-top:14px' },
- h('div', { class: 'small muted', style: 'text-align:center' }, t('arkBoardIntro')),
- h('div', { class: 'input-group' },
- h('input', {
- type: 'number', inputmode: 'numeric', min: '0',
- placeholder: t('arkBoardPlaceholder', { n: minBoard.toLocaleString() }),
- value: ui.arkBoardAmt || '',
- onInput: (e) => { ui.arkBoardAmt = e.target.value; render(); },
- }),
- h('button', { class: 'btn-sm', disabled: !!ui.arkBusy || !canBoard, onClick: doArkBoard },
- ui.arkBusy === 'board' ? h('span', { class: 'spinner sm' }) : t('arkBoardBtn'))),
- // show the server's board fee BEFORE the money moves, not after
- (() => {
- const sats = parseInt((ui.arkBoardAmt || '').trim(), 10);
- if (!sats || sats < minBoard) return null;
- const fee = boardFee(sats, ark.info.boardFees);
- return h('div', { class: 'small muted', style: 'text-align:center' },
- t('arkBoardFeeNote', { fee: fmtAmount(fee), net: fmtAmount(sats - fee) }));
- })(),
- canBoard
- ? h('div', { class: 'small faint', style: 'text-align:center' }, t('arkBoardAvailable', { n: fmtAmount(wallet.spendable) + ' ' + unitLabel() }))
- : h('div', { class: 'small faint', style: 'text-align:center' }, t('arkBoardNoFunds', { n: minBoard.toLocaleString() })),
- ui.arkError ? h('div', { class: 'notice err' }, ui.arkError) : null),
// Lightning receive straight into the ark balance (ASP-native swap)
arkLnReceiveSection()
);
@@ -1533,7 +1561,7 @@ export function arkFeature(ctx) {
id: 'ark',
init() { initArk(); },
stop() { stopArk(); },
- screenView() { return ui.arkExitPage ? arkExitPage() : null; },
+ screenView() { return ui.arkMovePage ? movePage() : ui.arkExitPage ? arkExitPage() : null; },
receiveModes() {
if (!arkAvailable()) return [];
return [{ id: 'ark', label: t('receiveArkTab'), icon: ARK_MARK(18), render: (seg) => arkReceivePane(seg) }];
@@ -1645,24 +1673,23 @@ export function arkFeature(ctx) {
ui.arkMoveDetail = null;
return null;
},
+ // The spendable off-chain balance IS the wallet's "Spending" — the balance
+ // card shows it by name, so this feature only reports the number.
+ // "Move money" under the balance — the one door between the two balances.
+ balanceActions() {
+ if (wallet.watchOnly || !arkAvailable()) return [];
+ return [{ label: t('moveMoney'), onClick: () => { ui.arkMovePage = true; ui.arkError = ''; render(); } }];
+ },
+ spendingSat() {
+ const b = arkBalance();
+ return b ? b.spendableSat + b.pendingSat : 0;
+ },
balanceLines() {
const b = arkBalance();
if (!b) return [];
- const lines = [];
- if (b.spendableSat + b.pendingSat > 0) {
- // "Ark balance … Exit" — the exit link opens the offboard/exit page.
- // Watch-only wallets can't move funds, so they just show the balance.
- const label = wallet.watchOnly
- ? t('arkBalance')
- : h('span', { class: 'row gap6', style: 'align-items:center' },
- t('arkBalance'),
- h('span', { class: 'linklike', style: 'font-size:12px', onClick: () => { ui.arkExitPage = true; ui.arkError = ''; render(); } }, t('arkExitLink')));
- lines.push({ label, sat: b.spendableSat + b.pendingSat });
- }
- // A board in flight: the sats already left the on-chain balance, so show
- // where they went instead of having them silently disappear.
- if (b.boardingSat > 0) lines.push({ label: t('arkBoarding'), sat: b.boardingSat });
- return lines;
+ // Money mid-move: it has left Saving but hasn't landed in Spending, so
+ // say where it went rather than letting it vanish for a confirmation.
+ return b.boardingSat > 0 ? [{ label: t('movingLabel'), sat: b.boardingSat }] : [];
},
decorateTxRow(tx) {
const s = arkStateNow();