summaryrefslogtreecommitdiffstats
path: root/src/features/zaps.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-31 08:04:33 -0700
committerAdam Soltys <[email protected]>2026-07-31 08:04:33 -0700
commitad281e832ed9e36b777f03ed54e799df7d70ffc9 (patch)
tree2e7ba6df785e6b8f74266c56a7ca2006cbb9579b /src/features/zaps.js
parent72e407bf6d7d09923d15461be82685017a0887be (diff)
downloadcoinosv3-ad281e832ed9e36b777f03ed54e799df7d70ffc9.tar.gz
coinosv3-ad281e832ed9e36b777f03ed54e799df7d70ffc9.zip
Fix send-form UX around lightning pays; harden ln state machines
- a rejected cosign (gRPC INVALID_ARGUMENT) returns the input vtxo to spendable and fails the action instead of leaving it pending forever — the stuck-pending state that pushed small zaps onto Boltz's 25k minimum - ark ln-pay now takes over when funds are settling (retrying quote) rather than falling through to Boltz - typing a destination clears stale send errors (also cleared on tab switch); paste path likewise - typed lightning addresses no longer auto-advance mid-keystroke (the matcher fired at `[email protected]` before `.com`, yanking the form away): the send form offers an explicit "Zap <addr>" button; pasted/scanned addresses still advance immediately - ln-recv: HTLC vtxos anchored to a not-yet-confirmed pool round park and retry instead of throwing; the requested HTLC expiry is anchored on first prepare so retries can't inflate it past the server's grant Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/features/zaps.js')
-rw-r--r--src/features/zaps.js34
1 files changed, 21 insertions, 13 deletions
diff --git a/src/features/zaps.js b/src/features/zaps.js
index 36afb12..2667921 100644
--- a/src/features/zaps.js
+++ b/src/features/zaps.js
@@ -21,13 +21,6 @@ export function zapsFeature(ctx) {
// Lightning payment has to be possible for a zap to make sense.
const canPay = () => !wallet.watchOnly && !!hook('canLnPay');
- // A lightning address (name@domain) matches its regex mid-typing — at
- // `[email protected]` before the user finishes `.com` — so we debounce those; npub and
- // lnurl carry a bech32 checksum and only match when complete, so they fire
- // immediately.
- let resolveTimer = null;
- const clearResolve = () => { if (resolveTimer) { clearTimeout(resolveTimer); resolveTimer = null; } };
-
const shortNpub = (npub) => (npub && npub.length > 16 ? npub.slice(0, 12) + '…' + npub.slice(-6) : npub);
// Kick off resolution of a parsed target. `display` is what we show as the
@@ -136,18 +129,33 @@ export function zapsFeature(ctx) {
return {
id: 'zaps',
// A lightning address / lnurl (always), or an npub that Ark didn't claim.
- matchSendText(text) {
- if (!canPay() || !ui.send || ui.send.recipients.length !== 1) { clearResolve(); return false; }
+ matchSendText(text, typed) {
+ if (!canPay() || !ui.send || ui.send.recipients.length !== 1) return false;
const target = parseZapTarget(text);
- if (!target) { clearResolve(); return false; }
+ if (!target) return false;
// npub with no nostr seam to look up a profile → can't resolve here.
if (target.kind === 'npub' && !wallet.nostrProfile) return false;
+ // A TYPED lightning address matches mid-keystroke (`[email protected]` before the
+ // user finishes `.com`), so never auto-advance while typing — the send
+ // form offers a zap button via sendFormNote instead. Pasted/scanned
+ // text is complete and advances immediately; npub/lnurl carry a bech32
+ // checksum and only ever match when complete.
+ if (typed && target.kind === 'lnaddr') return false;
const display = target.kind === 'npub' ? shortNpub(npubOf(target.pk) || text.trim()) : (target.address || text.trim());
- clearResolve();
- if (target.kind === 'lnaddr') resolveTimer = setTimeout(() => { resolveTimer = null; begin(target, display); }, 600);
- else begin(target, display);
+ begin(target, display);
return true;
},
+ // The typed-lightning-address affordance on the send form.
+ sendFormNote(a) {
+ if (!canPay() || !ui.send || ui.send.recipients.length !== 1) return null;
+ const target = parseZapTarget(a);
+ if (!target || target.kind === 'npub') return null;
+ const display = target.address || String(a || '').trim();
+ return h('button', {
+ type: 'button', class: 'btn-primary btn-block',
+ onClick: () => begin(target, display),
+ }, '⚡ ' + t('lnZapOffer', { addr: display }));
+ },
// True when an npub can be zapped over Lightning from this build/wallet —
// ark's "no Ark address" screen checks this before offering the fallback.
canLnZap() { return canPay() && !!wallet.nostrProfile; },