aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-15 14:51:34 -0700
committerAdam Soltys <[email protected]>2026-08-15 14:51:34 -0700
commit3bd43675f1f0d64f31b6c9d95a2ec771a20d34b5 (patch)
tree67c8ec77a4530b4c46f8f4d0494f4634ff325175 /src
parent643046319fa9a393374fc2348381d9505fe143eb (diff)
downloadcoinosv3-3bd43675f1f0d64f31b6c9d95a2ec771a20d34b5.tar.gz
coinosv3-3bd43675f1f0d64f31b6c9d95a2ec771a20d34b5.zip
The balance card's switch becomes Manage accounts, the header chip retires, Move money is gone — and pasting an on-chain address in a Spending wallet just exits ark to it
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
Diffstat (limited to 'src')
-rw-r--r--src/app.js33
-rw-r--r--src/features/ark.js48
-rw-r--r--src/i18n.js3
3 files changed, 50 insertions, 34 deletions
diff --git a/src/app.js b/src/app.js
index 8c3b05b..25cf508 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1741,8 +1741,6 @@ function brandHeader(withLock) {
messagesBtn(),
settingsBtn(),
lockBtn(),
- h('button', { class: 'btn-sm', onClick: () => { ui.screen = 'accounts'; render(); } },
- acc ? viewLabel(acc, accountSel()) : t('accounts')),
avatarMenu())
: null
);
@@ -2796,28 +2794,12 @@ function balanceCard() {
// target — so the switch is a real button that names where it goes.
// Before Spending exists, its spot offers to set it up — the one
// doorway into the optional spending-account flow.
- kindLocked
- ? (sibling
- ? h('button', {
- class: 'balance-switch',
- onClick: () => switchToView(sibling.id, opp),
- },
- h('span', { html: SWAP_ICON }),
- h('span', { class: 'bsw-label' }, viewLabel(sibling, opp)))
- : null)
- : hasSpending
- ? h('button', {
- class: 'balance-switch',
- onClick: () => setAccountSel(isSpending ? 'savings' : 'spending', isSpending ? 'left' : 'right'),
- },
- h('span', { html: SWAP_ICON }),
- h('span', { class: 'bsw-label' }, isSpending ? t('savingLabel') : t('spendingLabel')))
- : featureHook('arkReady') && !wallet.watchOnly
- ? h('button', {
- class: 'balance-switch',
- onClick: () => { ui.onbError = ''; ui.onb = { step: 'spend' }; render(); },
- }, h('span', { class: 'bsw-label' }, t('spendSetup')))
- : null),
+ h('button', {
+ class: 'balance-switch',
+ onClick: () => { ui.screen = 'accounts'; render(); },
+ },
+ h('span', { html: SWAP_ICON }),
+ h('span', { class: 'bsw-label' }, t('manageAccounts')))),
h('div', { class: 'amt', style: firstLoad ? 'opacity:.3' : '' },
firstLoad ? h('span', { class: 'spinner sm', style: 'margin-right:8px' }) : null,
animatedAmount('bal:' + sel, isSpending ? spending : saving), ' ', unitTag('unit')),
@@ -2841,7 +2823,8 @@ function balanceCard() {
// pointless (and confusing) while there's only one.
...(hasSpending ? featureAll('balanceActions') : []).map((a2) =>
h('button', { class: 'btn-sm', style: 'margin-top:10px', onClick: a2.onClick }, a2.label)),
- kindLocked && isSpending && !hasSpending && featureHook('arkReady') && !wallet.watchOnly
+ (ui.account === 'spending' || (kindLocked && acc0.kind === 'spending')) && !hasSpending
+ && featureHook('arkReady') && !wallet.watchOnly
? h('button', { class: 'btn-sm', style: 'margin-top:10px', onClick: () => { ui.onbError = ''; ui.onb = { step: 'spend' }; render(); } }, t('spendSetup'))
: null,
// A feature can unfold UI right on the card (ark's inline move panel).
diff --git a/src/features/ark.js b/src/features/ark.js
index 6ce95ca..dd71062 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -1725,7 +1725,32 @@ export function arkFeature(ctx) {
// vtxo via a self-send (regtest-verified: the mailbox delivers self-sends),
// then exits just that one. The number the user typed is the number that
// leaves Spending; fees come off what arrives in Savings.
- async function doArkOffboard(amountSat) {
+ // Send-to-on-chain from Spending: amount in, exit out — an offboard whose
+ // destination is whatever address was pasted.
+ function arkOffboardSendView() {
+ const o = ui.arkOffboardSend;
+ const spendable = arkBalance()?.spendableSat || 0;
+ return h('div', { class: 'card col', style: 'gap:12px' },
+ h('h3', {}, t('arkExitSendTitle')),
+ h('div', { class: 'small muted break' }, o.address),
+ h('div', { class: 'small faint' }, t('arkExitSendNote')),
+ h('div', { class: 'input-group' },
+ h('input', { type: 'number', min: '1', placeholder: String(spendable), value: o.amount,
+ onInput: (e) => { o.amount = e.target.value; } }),
+ h('button', { type: 'button', onClick: () => { o.amount = String(spendable); render(); } }, t('max'))),
+ ui.arkError ? h('div', { class: 'notice err' }, ui.arkError) : null,
+ h('div', { class: 'row gap6' },
+ h('button', { class: 'btn-ghost grow', onClick: () => { ui.arkOffboardSend = null; ui.arkError = ''; ui.send = blankSend(); render(); } }, t('back')),
+ ui.arkBusy === 'offboard'
+ ? h('button', { class: 'btn-primary grow', disabled: true }, h('span', { class: 'spinner sm' }))
+ : h('button', { class: 'btn-primary grow', onClick: () => {
+ const sats = parseInt(o.amount, 10) || spendable;
+ if (!sats || sats <= 0 || sats > spendable) { ui.arkError = t('enterValidAmtForN', { n: 1 }); render(); return; }
+ doArkOffboard(sats >= spendable ? 0 : sats, o.address);
+ } }, t('send'))));
+ }
+
+ async function doArkOffboard(amountSat, destAddress = null) {
ui.arkBusy = 'offboard'; ui.arkError = ''; render();
try {
const mgr = await connectArk();
@@ -1746,11 +1771,12 @@ export function arkFeature(ctx) {
}
ids = [target.id];
}
- const address = wallet.freshReceive().address;
+ const address = destAddress || wallet.freshReceive().address;
const spk = btc.OutScript.encode(btc.Address(wallet.netCfg.net).decode(address));
const action = await mgr.startOffboard(spk, address, ids);
ui.arkOffboardAmt = '';
ui.arkMoveOpen = false;
+ ui.arkOffboardSend = null;
ui.arkOffboarded = { txid: action.txid, netSat: action.netSat, feeSat: action.feeSat };
wallet.scan().catch(() => {}); // surface the incoming pending tx promptly
} catch (e) {
@@ -2092,6 +2118,7 @@ export function arkFeature(ctx) {
return false;
},
sendView() {
+ if (ui.arkOffboardSend) return arkOffboardSendView();
if (ui.arkLnPaid || ui.arkLnPay) return arkLnPayView();
if (ui.arkZapped || ui.arkZap) return arkZapView();
if (ui.arkSent || ui.arkSend) return arkSendReview();
@@ -2149,6 +2176,15 @@ export function arkFeature(ctx) {
// delegates back via the startArkLnPay hook).
matchSendText(text) {
const inv = (text || '').trim().replace(/^lightning:/i, '');
+ // In a Spending wallet an on-chain address means "exit ark to there":
+ // the offboard happens right in the send flow, no Move money ceremony.
+ if (ctx.getAccount() === 'spending' && wallet.isOnchainAddress(inv)
+ && arkAvailable() && (arkBalance()?.spendableSat || 0) > 0) {
+ ui.arkOffboardSend = { address: inv, amount: '' };
+ ui.sendError = '';
+ render();
+ return true;
+ }
if (maybeBolt11(inv)) return startArkLnPay(inv);
const pk = npubToHex(text);
if (!pk || !arkAvailable() || !wallet.nostrFetch) return false;
@@ -2182,13 +2218,7 @@ export function arkFeature(ctx) {
// 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: ui.arkMoveOpen ? t('hide') : t('moveMoney'),
- onClick: () => { ui.arkMoveOpen = !ui.arkMoveOpen; ui.arkError = ''; render(); },
- }];
- },
+ balanceActions() { return []; }, // Move money retired: exits ride the Send field now
balanceExtra() {
if (!ui.arkMoveOpen || wallet.watchOnly || !arkAvailable()) return null;
return movePanel();
diff --git a/src/i18n.js b/src/i18n.js
index 50d7c0f..f9e8d6d 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -670,6 +670,9 @@ const STR = {
settingsNotifications: 'Notifications',
searchUsers: 'Search',
spendingName: 'Spending',
+ manageAccounts: 'Manage accounts',
+ arkExitSendTitle: 'Send on-chain',
+ arkExitSendNote: 'Leaves your Spending balance and arrives on-chain — network fees come off what arrives.',
savingsName: 'Savings',
renameWallet: 'Rename',
addWalletSeed: 'Seed phrase',