import { useCallback, useEffect, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { useNWC } from '@/hooks/useNWCContext'; import { useToast } from '@/hooks/useToast'; import { getSatoshisFromBolt11 } from '@/lib/bolt11'; import { cn } from '@/lib/utils'; import { ArrowDownToLine, ArrowUpFromLine, Check, Copy, Loader2, RefreshCw, Zap, } from 'lucide-react'; /** * Lightning wallet over Nostr Wallet Connect (NIP-47). * * The Wallet page was Cashu-only and showed "App only" on web, so anyone who * connected an NWC wallet (Alby, Coinos, …) saw no balance, send or receive. * This card surfaces the connected wallet: balance, make-invoice (receive) and * pay-invoice (send). It renders nothing when no NWC connection is active. */ export function NwcWalletCard() { const { getActiveConnection, getBalance, makeInvoice, sendPayment } = useNWC(); const { toast } = useToast(); const connection = getActiveConnection(); const [balance, setBalance] = useState(null); const [loadingBalance, setLoadingBalance] = useState(false); const [receiveAmount, setReceiveAmount] = useState(''); const [creating, setCreating] = useState(false); const [invoice, setInvoice] = useState(null); const [qr, setQr] = useState(null); const [copied, setCopied] = useState(false); const [payInput, setPayInput] = useState(''); const [paying, setPaying] = useState(false); const refreshBalance = useCallback(async () => { if (!connection) return; setLoadingBalance(true); try { setBalance(await getBalance(connection)); } catch (e) { toast({ title: 'Could not read balance', description: e instanceof Error ? e.message : 'Your wallet did not respond.', variant: 'destructive', }); } finally { setLoadingBalance(false); } }, [connection, getBalance, toast]); useEffect(() => { void refreshBalance(); }, [refreshBalance]); const handleCreateInvoice = async () => { if (!connection) return; const sats = Number(receiveAmount); if (!Number.isFinite(sats) || sats <= 0) { toast({ title: 'Enter an amount in sats', variant: 'destructive' }); return; } setCreating(true); try { const inv = await makeInvoice(connection, sats, 'SkateSpots'); setInvoice(inv); const { default: QRCode } = await import('qrcode'); setQr(await QRCode.toDataURL(inv.toUpperCase(), { width: 256, margin: 1 })); } catch (e) { toast({ title: 'Could not create invoice', description: e instanceof Error ? e.message : 'Try again in a moment.', variant: 'destructive', }); } finally { setCreating(false); } }; const handlePay = async () => { if (!connection) return; const inv = payInput.trim(); if (!inv) return; setPaying(true); try { await sendPayment(connection, inv); const sats = (() => { try { return getSatoshisFromBolt11(inv); } catch { return 0; } })(); toast({ title: 'Payment sent', description: sats > 0 ? `Paid ${sats.toLocaleString()} sats.` : 'Paid.' }); setPayInput(''); void refreshBalance(); } catch (e) { toast({ title: 'Payment failed', description: e instanceof Error ? e.message : 'The wallet rejected the payment.', variant: 'destructive', }); } finally { setPaying(false); } }; const copyInvoice = async () => { if (!invoice) return; try { await navigator.clipboard.writeText(invoice); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { /* ignore */ } }; if (!connection) return null; const paySats = (() => { try { return payInput.trim() ? getSatoshisFromBolt11(payInput.trim()) : 0; } catch { return 0; } })(); return ( Lightning Wallet

{connection.alias || 'NWC wallet'} · NIP-47

{/* Balance */}

Balance

{loadingBalance && balance === null ? '…' : balance === null ? '—' : balance.toLocaleString()} sats

{/* Receive */}

Receive

setReceiveAmount(e.target.value)} inputMode="numeric" placeholder="Amount in sats" className="h-11 border-gray-700 bg-gray-900 text-white" />
{invoice && (
{qr && Lightning invoice QR code}

{invoice}

)}
{/* Send */}

Send