From 39ac517610fef1feda5a3f4eea25433061c50ac4 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Mon, 7 Sep 2026 20:15:10 +0700 Subject: [PATCH] Fix Cashu zap: pay in one click + honor custom-amount balance guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of the wallet/zap flow surfaced a real money-path bug in ZapDialog's Cashu branch: • One-click Cashu zap never worked. The Cashu payment branch only ran `if (... && invoice)`, but no invoice exists on the first click — so selecting "Cashu" and tapping Zap silently fell through to the Lightning flow (generating an invoice / paying via NWC-WebLN) and the user's Cashu balance was never used. Now the Cashu path generates the recipient's amount-verified zap invoice and melts it from the Cashu wallet in a single click. To support this cleanly, useZaps now exposes `generateZapInvoice()` — builds + signs the NIP-57 request, fetches the LNURL invoice, and runs the same `assertInvoiceAmount` security check the Lightning rail uses — WITHOUT auto-paying. The Lightning `zap()` path was refactored to use it too, so both rails share one verified-invoice codepath. • Custom-amount balance guard bypassed. The insufficient-balance warning and the disabled Send state only checked `typeof amount === 'number'`, but the custom-amount input stores a string — so a typed amount over the Cashu balance slipped past both. Both now coerce to a number first. • Smarter defaults: when the dialog opens and the user has Cashu funds but no connected Lightning wallet, Cashu is pre-selected and the "connect a wallet" setup nag is skipped (they can already pay). • Removed a dead `useLightningOperations` import. Also audited mobile layout (Layout.tsx — mature, safe-area-aware, no changes needed) and ZapCrewDialog (uses the separate useCrewZap splitter, no Cashu option, no bug). Bumps to 1.2.21 (Android versionCode 10221 auto-derived). Co-authored-by: shakespeare.diy --- package.json | 2 +- src/components/ZapDialog.tsx | 133 +++++++++++++++++++++--------- src/hooks/useZaps.ts | 151 +++++++++++++++++------------------ 3 files changed, 172 insertions(+), 114 deletions(-) diff --git a/package.json b/package.json index ba99631..fb353cd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.20", + "version": "1.2.21", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/ZapDialog.tsx b/src/components/ZapDialog.tsx index b0198e3..6dbbc3d 100644 --- a/src/components/ZapDialog.tsx +++ b/src/components/ZapDialog.tsx @@ -32,7 +32,6 @@ import { useZaps } from '@/hooks/useZaps'; import { useWallet } from '@/hooks/useWallet'; import { useIsMobile } from '@/hooks/useIsMobile'; import { useCashu } from '@/hooks/wallet/useCashu'; -import { useLightningOperations } from '@/hooks/wallet/useLightningOperations'; import { WalletModal } from '@/components/WalletModal'; import type { Event } from 'nostr-tools'; // qrcode imported lazily in async function to avoid canvas crash at module load @@ -363,27 +362,40 @@ const ZapContent = forwardRef(({ />
- {/* Show insufficient balance warning for Cashu */} - {paymentMethod === 'cashu' && cashuBalance !== undefined && ( - typeof amount === 'number' && amount > cashuBalance ? ( + {/* Show insufficient balance warning for Cashu. Coerce amount to a + number first — the custom-amount input stores it as a string, so a + plain `typeof === 'number'` check would let an over-balance custom + amount slip past both this warning and the disabled state. */} + {(() => { + const numericAmount = + typeof amount === 'string' ? parseInt(amount, 10) : amount; + const overBalance = + paymentMethod === 'cashu' && + cashuBalance !== undefined && + Number.isFinite(numericAmount) && + numericAmount > cashuBalance; + return overBalance ? (

- ⚠️ Insufficient Cashu balance. You need {(amount - cashuBalance).toLocaleString()} more sats. + ⚠️ Insufficient Cashu balance. You need {(numericAmount - cashuBalance).toLocaleString()} more sats. Switch to Lightning or top up your Cashu wallet.

- ) : null - )} - + ) : null; + })()} +