aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/manager.js6
-rw-r--r--src/features/ark.js32
-rw-r--r--src/i18n.js1
3 files changed, 32 insertions, 7 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index e9f79b0..9731dd6 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -200,7 +200,11 @@ export class ArkManager {
async _esplora(path, init) {
const hosts = (this._esploraHosts ||= [
this.esploraUrl,
- ...(this.network === 'mainnet' ? ['https://blockstream.info/api', 'https://mempool.space/api'] : []),
+ // last resort: our own registrar relays chain calls — the one host a
+ // wallet that can reach coinos at all provably reaches
+ ...(this.network === 'mainnet'
+ ? ['https://blockstream.info/api', 'https://mempool.space/api', 'https://names.coinos.io/esplora']
+ : []),
].filter((h, i, a) => h && a.indexOf(h) === i));
const start = this._esploraIdx || 0;
let lastErr;
diff --git a/src/features/ark.js b/src/features/ark.js
index ba566ee..829cba9 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -728,12 +728,15 @@ export function arkFeature(ctx) {
const dec = maybeLnInvoice(invoice);
if (!dec || !arkAvailable() || wallet.watchOnly) return false;
const s = arkStateNow();
- const covers = (state, sats) =>
- !!s && (s.vtxos || []).some((v) => v.state === state && v.amountSat >= sats);
+ // 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
+ // tester holding 204+29 sats pasted a ~220-sat invoice and nothing
+ // happened at all. Fees are the quote's business, not this gate's.
+ const sum = (state) =>
+ (s ? (s.vtxos || []) : []).filter((v) => v.state === state).reduce((n, v) => n + v.amountSat, 0);
// pending funds count for the take-over decision: an in-flight payment or
- // revocation frees them in seconds, and the Boltz fallback can't do small
- // amounts at all — better to wait for ark than bounce off Boltz's minimum
- if (!covers('spendable', dec.amountSat || 1) && !covers('pending', dec.amountSat || 1)) return false;
+ // revocation frees them in seconds
+ if (sum('spendable') < (dec.amountSat || 1) && sum('pending') < (dec.amountSat || 1)) return false;
ui.arkLnPay = { invoice, meta: meta || null, amountSat: dec.amountSat, amount: '', feeSat: null, status: 'quote' };
ui.sendError = '';
render();
@@ -2835,7 +2838,24 @@ export function arkFeature(ctx) {
return true;
}
}
- if (maybeLnInvoice(inv)) return startArkLnPay(inv);
+ if (maybeLnInvoice(inv)) {
+ if (startArkLnPay(inv)) return true;
+ if (arkAvailable() && !wallet.watchOnly) {
+ // An invoice Spending can't cover used to fall through SILENTLY —
+ // the bolt11 landed in the address field looking like a dead
+ // paste. Own it: offer to board the shortfall from Savings when
+ // there is one, else say plainly what's short.
+ const need = (maybeLnInvoice(inv) || {}).amountSat || 0;
+ if (need && wallet.spendable > 1000 && ctx.hook('arkOfferBoard', need)) return true;
+ ui.sendError = t('arkLnExceedsSpending', {
+ need: fmtAmount(need) + ' ' + unitLabel(),
+ have: fmtAmount(arkBalance()?.spendableSat || 0) + ' ' + unitLabel(),
+ });
+ render();
+ return true;
+ }
+ return false;
+ }
const pk = npubToHex(text);
if (!pk || !arkAvailable() || !wallet.nostrFetch) return false;
startNpubPay(pk, String(text).trim());
diff --git a/src/i18n.js b/src/i18n.js
index f9f8567..fc23c19 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -852,6 +852,7 @@ const STR = {
arkSendTitle: 'Send',
arkSendHint: 'Ark address — sent instantly off-chain, no mining fee.',
arkOwnAddrBoard: "That's your own Spending address — moving from Savings instead.",
+ arkLnExceedsSpending: 'That invoice asks for {need} but Spending holds {have}.',
arkPayFromSpending: 'Ark payments come from your Spending balance — switch to Spending to send this.',
arkPayTo: 'To',
arkNoFee: 'none — instant off-chain send',