aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-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();