import type { DecodedInvoice } from '../shared'; import { useState } from 'react'; import { Loader2, Zap } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { formatTimestamp, satsToMsat, truncateMiddle } from '../format'; import { Amount } from '../Amount'; import { useWalletOperations } from '../useWalletData'; // Pay a BOLT11 invoice: decode first, confirm what you are about to pay, then pay. // // The two steps are deliberate. A BOLT11 string is opaque, so paying one straight from the paste box is // signing something you cannot read. Decoding is a free read — it does not need the wallet unlocked — and // it is the only chance to see the amount and the destination before the money moves. // // Amounts stay msat STRINGS end to end (see format.ts): the decoded amount is rendered as a string and a // zero-amount invoice's manual amount is converted with satsToMsat rather than a multiplication. type PayInvoiceDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; walletId: number }; export const PayInvoiceDialog = ({ open, onOpenChange, walletId }: PayInvoiceDialogProps) => { const { decode, pay } = useWalletOperations(walletId); const [bolt11, setBolt11] = useState(''); const [decoded, setDecoded] = useState(null); const [amountSats, setAmountSats] = useState(''); const [feeLimitSats, setFeeLimitSats] = useState(''); const close = () => { setBolt11(''); setDecoded(null); setAmountSats(''); setFeeLimitSats(''); onOpenChange(false); }; const runDecode = async (ev: React.FormEvent) => { ev.preventDefault(); const trimmed = bolt11.trim(); if (!trimmed) return; const result = await decode.mutateAsync(trimmed); setDecoded(result.decoded); }; // A zero-amount invoice carries no amountMsat, so the payer names the figure. const needsAmount = decoded != null && decoded.amountMsat == null; const manualSats = Number(amountSats); const feeLimit = Number(feeLimitSats); const canPay = decoded != null && (!needsAmount || (Number.isFinite(manualSats) && manualSats > 0)) && !pay.isPending; const runPay = async () => { if (!decoded || !canPay) return; await pay.mutateAsync({ bolt11: decoded.bolt11, amountMsat: needsAmount ? satsToMsat(manualSats) : undefined, feeLimitMsat: Number.isFinite(feeLimit) && feeLimit > 0 ? satsToMsat(feeLimit) : undefined, }); close(); }; return ( (next ? onOpenChange(true) : close())}> Pay an invoice {decoded ? 'Check this before you pay — a lightning payment cannot be reversed.' : 'Paste a BOLT11 invoice.'} {decoded ? (
{decoded.amountMsat == null ? ( payer chooses ) : ( )} {decoded.description || —} {truncateMiddle(decoded.destination, 12, 8)} {formatTimestamp(decoded.timestamp + decoded.expiry)}
{needsAmount && (
setAmountSats(ev.target.value)} className="w-40" />
)}
setFeeLimitSats(ev.target.value)} className="w-40" />
) : (
setBolt11(ev.target.value)} className="font-mono text-xs" />
)}
); }; const Row = ({ label, children }: { label: string; children: React.ReactNode }) => (
{label}
{children}
);