aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/ark.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-21 16:38:30 -0700
committerAdam Soltys <[email protected]>2026-09-21 16:38:30 -0700
commit05a98a9110f5e2e2476d9f9e948ee79be5effe73 (patch)
treecd69cd52efafe93b1fd5c68f2967fbab6eb0c5c0 /src/features/ark.js
parent9a29372359c0c998c51e5b82188202c779621fc8 (diff)
downloadcoinosv3-05a98a9110f5e2e2476d9f9e948ee79be5effe73.tar.gz
coinosv3-05a98a9110f5e2e2476d9f9e948ee79be5effe73.zip
Send: a Lightning payment while Savings is showing asks before it pays from Spending.
Every Lightning payment passes through the Ark flow, whichever balance is on screen, and the review's "from your Spending balance" line was easy to miss. With Savings selected the flow now stops at an ask first: switch to Spending and continue, move Savings into Spending first (the existing board panel — an on-chain step, so a fresh invoice afterwards), or cancel. Pasted invoices, Lightning addresses and LNURL all pass through; autopay (a trusted, budgeted code) never asks. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
Diffstat (limited to 'src/features/ark.js')
-rw-r--r--src/features/ark.js41
1 files changed, 41 insertions, 0 deletions
diff --git a/src/features/ark.js b/src/features/ark.js
index 9b3173e..ed494b0 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -923,9 +923,20 @@ export function arkFeature(ctx) {
// A bolt11 lands in Send (via the swaps feature's delegation hook, or
// directly when swaps is absent): take over when the ark balance can
// plausibly cover it, else decline so the Boltz path handles it.
+ // Lightning leaves Spending, whichever balance is on screen. With Savings
+ // showing, ask before going on — a person looking at one balance and
+ // charged from the other read it as the wrong wallet paying. Autopay
+ // (a trusted, budgeted code) never asks: nobody is looking.
function startArkLnPay(invoice, meta) {
const dec = maybeLnInvoice(invoice);
if (!dec || !arkAvailable() || wallet.watchOnly) return false;
+ if (ctx.getAccount() === 'savings' && !(meta && meta.autopay) && !ui.arkLnFromSavingsOk) {
+ ui.arkLnFromSavings = { invoice, meta: meta || null, amountSat: dec.amountSat };
+ ui.sendError = '';
+ render();
+ return true;
+ }
+ ui.arkLnFromSavingsOk = false;
const s = arkStateNow();
// Coverage by SUM: multi-input pays gather up to 24 coins, so judging by
// the single largest coin declined invoices the wallet could pay — a
@@ -1651,6 +1662,35 @@ export function arkFeature(ctx) {
})().catch((e) => { if (live()) { z.status = 'noark'; if (!bail(e.message)) ui.sendError = e.message; render(); } });
}
+ // The ask itself: switch and continue, move Savings over first, or not.
+ function arkLnFromSavingsView() {
+ const p = ui.arkLnFromSavings;
+ const have = arkBalance()?.spendableSat || 0;
+ const need = p.amountSat || 0;
+ const enough = !need || have >= need;
+ const proceed = () => {
+ ui.arkLnFromSavings = null;
+ ctx.setAccount('spending', 'right');
+ ui.arkLnFromSavingsOk = true; // this one call is the answer to the ask
+ if (startArkLnPay(p.invoice, p.meta)) return;
+ ui.arkLnFromSavingsOk = false;
+ // Spending can't carry it: the same offer the Spending side makes
+ if (need && wallet.spendable > 1000 && ctx.hook('arkOfferBoard', need)) return;
+ ui.sendError = t('arkLnExceedsSpending', { need: fmtAmount(need) + ' ' + unitLabel(), have: fmtAmount(have) + ' ' + unitLabel() });
+ render();
+ };
+ return h('div', { class: 'card col', style: 'gap:12px' },
+ h('h3', { style: 'margin:0' }, t('arkLnFromSavingsTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('arkLnFromSavingsBody', { have: fmtAmount(have) + ' ' + unitLabel() })),
+ need ? h('div', { class: 'row between' }, h('span', { class: 'small muted' }, t('lnPayAmount')), h('span', { class: 'small' }, fmtAmount(need) + ' ' + unitLabel())) : null,
+ !enough ? h('div', { class: 'notice info small' }, t('arkLnFromSavingsShort')) : null,
+ h('button', { class: 'btn-primary btn-block', onClick: proceed }, enough ? t('arkLnUseSpending') : t('arkLnMoveFirst')),
+ enough && wallet.spendable > 1000
+ ? h('button', { class: 'btn-block', onClick: () => { ui.arkLnFromSavings = null; ctx.hook('arkOfferBoard', need || 0); } }, t('arkLnMoveFirst'))
+ : null,
+ h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.arkLnFromSavings = null; ui.sendError = ''; ui.send = blankSend(); render(); } }, t('cancel')));
+ }
+
// shared by the ark and lightning zap flows (see zaps.js for the twin)
function zapSkeleton(pk, fallback) {
return h('div', { class: 'card col', style: 'gap:12px' },
@@ -3214,6 +3254,7 @@ export function arkFeature(ctx) {
return false;
},
sendView() {
+ if (ui.arkLnFromSavings) return arkLnFromSavingsView();
if (ui.arkOffboardSend) return arkOffboardSendView();
if (ui.arkLnPaid || ui.arkLnPay) return arkLnPayView();
if (ui.arkZapped || ui.arkZap) return arkZapView();