import "./send-zap.css"; import { type ReactNode, useEffect, useState } from "react"; import { LNURL } from "@snort/shared"; import { EventPublisher, NostrEvent } from "@snort/system"; import { secp256k1 } from "@noble/curves/secp256k1"; import { bytesToHex } from "@noble/curves/abstract/utils"; import { FormattedMessage, FormattedNumber } from "react-intl"; import { formatSats } from "../number"; import { Icon } from "./icon"; import QrCode from "./qr-code"; import { useLogin } from "@/hooks/login"; import Copy from "./copy"; import { defaultRelays } from "@/const"; import { useRates } from "@/hooks/rates"; import { DefaultButton } from "./buttons"; import Modal from "./modal"; import Pill from "./pill"; export interface LNURLLike { get name(): string; get maxCommentLength(): number; get canZap(): boolean; getInvoice(amountInSats: number, comment?: string, zap?: NostrEvent): Promise<{ pr?: string }>; } export interface SendZapsProps { lnurl: string | LNURLLike; pubkey?: string; aTag?: string; eTag?: string; targetName?: string; onFinish: () => void; button?: ReactNode; } export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: SendZapsProps) { const satsAmounts = [ 21, 69, 121, 420, 1_000, 2_100, 4_200, 10_000, 21_000, 42_000, 69_000, 100_000, 210_000, 500_000, 1_000_000, ]; const usdAmounts = [0.05, 0.5, 2, 5, 10, 50, 100, 200]; const [isFiat, setIsFiat] = useState(false); const [svc, setSvc] = useState(); const [amount, setAmount] = useState(satsAmounts[0]); const [comment, setComment] = useState(""); const [invoice, setInvoice] = useState(""); const login = useLogin(); const rate = useRates("BTCUSD"); const relays = Object.keys(defaultRelays); const name = targetName ?? svc?.name; async function loadService(lnurl: string) { const s = new LNURL(lnurl); await s.load(); setSvc(s); } const usdRate = rate.time ? rate.ask : 26_000; useEffect(() => { if (!svc) { if (typeof lnurl === "string") { loadService(lnurl).catch(console.warn); } else { setSvc(lnurl); } } }, [lnurl]); async function send() { if (!svc) return; let pub = login?.publisher(); let isAnon = false; if (!pub) { pub = EventPublisher.privateKey(bytesToHex(secp256k1.utils.randomPrivateKey())); isAnon = true; } const amountInSats = isFiat ? Math.floor((amount / usdRate) * 1e8) : amount; let zap: NostrEvent | undefined; if (pubkey) { zap = await pub.zap(amountInSats * 1000, pubkey, relays, undefined, comment, eb => { if (aTag) { eb.tag(["a", aTag]); } if (eTag) { eb.tag(["e", eTag]); } if (isAnon) { eb.tag(["anon", ""]); } return eb; }); } const invoice = await svc.getInvoice(amountInSats, comment, zap); if (!invoice.pr) return; if (window.webln) { await window.webln.enable(); try { await window.webln.sendPayment(invoice.pr); onFinish(); } catch (error) { setInvoice(invoice.pr); } } else { setInvoice(invoice.pr); } } function input() { if (invoice) return; return ( <>
{ setIsFiat(false); setAmount(satsAmounts[0]); }}> SATS { setIsFiat(true); setAmount(usdAmounts[0]); }}> USD
{isFiat && ( <>   , }} /> )}
{(isFiat ? usdAmounts : satsAmounts).map(a => ( setAmount(a)}> {isFiat ? `$${a.toLocaleString()}` : formatSats(a)} ))}
{svc && (svc.maxCommentLength > 0 || svc.canZap) && (