From cbd767d62cfcbd8f058d7196165ff3da8a4ff2da Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 21 Feb 2026 22:29:29 -0600 Subject: [PATCH] =?UTF-8?q?Remove=20redundant=20zap=20receipt=20fetching?= =?UTF-8?q?=20=E2=80=94=20NIP-85=20provides=20zap=20stats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useZaps was fetching all kind 9735 events for every post to count zaps and sum sats. NIP-85 already provides these stats via useEventStats. Stripped useZaps down to payment flow only. Deleted unused ZapButton component. --- src/components/ZapButton.tsx | 59 ----------------- src/hooks/useZaps.ts | 124 ++++------------------------------- 2 files changed, 14 insertions(+), 169 deletions(-) delete mode 100644 src/components/ZapButton.tsx diff --git a/src/components/ZapButton.tsx b/src/components/ZapButton.tsx deleted file mode 100644 index 51bf19b2..00000000 --- a/src/components/ZapButton.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { ZapDialog } from '@/components/ZapDialog'; -import { useZaps } from '@/hooks/useZaps'; -import { useWallet } from '@/hooks/useWallet'; -import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { useAuthor } from '@/hooks/useAuthor'; -import { canZap } from '@/lib/canZap'; -import { Zap } from 'lucide-react'; -import type { Event } from 'nostr-tools'; - -interface ZapButtonProps { - target: Event; - className?: string; - showCount?: boolean; - zapData?: { count: number; totalSats: number; isLoading?: boolean }; -} - -export function ZapButton({ - target, - className = "text-xs ml-1", - showCount = true, - zapData: externalZapData -}: ZapButtonProps) { - const { user } = useCurrentUser(); - const { data: author } = useAuthor(target?.pubkey || ''); - const { webln, activeNWC } = useWallet(); - - // Only fetch data if not provided externally - const { totalSats: fetchedTotalSats, isLoading } = useZaps( - externalZapData ? [] : target ?? [], // Empty array prevents fetching if external data provided - webln, - activeNWC - ); - - // Don't show zap button if user is not logged in, is the author, or author has no lightning address - if (!user || !target || user.pubkey === target.pubkey || !canZap(author?.metadata)) { - return null; - } - - // Use external data if provided, otherwise use fetched data - const totalSats = externalZapData?.totalSats ?? fetchedTotalSats; - const showLoading = externalZapData?.isLoading || isLoading; - - return ( - -
- - - {showLoading ? ( - '...' - ) : showCount && totalSats > 0 ? ( - `${totalSats.toLocaleString()}` - ) : ( - 'Zap' - )} - -
-
- ); -} \ No newline at end of file diff --git a/src/hooks/useZaps.ts b/src/hooks/useZaps.ts index ec16d450..c9a309fb 100644 --- a/src/hooks/useZaps.ts +++ b/src/hooks/useZaps.ts @@ -1,4 +1,4 @@ -import { useState, useMemo, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useAppContext } from '@/hooks/useAppContext'; @@ -8,26 +8,24 @@ import type { NWCConnection } from '@/hooks/useNWC'; import { nip57 } from 'nostr-tools'; import type { Event } from 'nostr-tools'; import type { WebLNProvider } from '@webbtc/webln-types'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useNostr } from '@nostrify/react'; -import type { NostrEvent } from '@nostrify/nostrify'; +import { useQueryClient } from '@tanstack/react-query'; +/** + * Hook for sending zaps to an event author. + * Stats (zap count, total sats) come from NIP-85 via useEventStats — this hook + * only handles the payment flow. + */ export function useZaps( - target: Event | Event[], + target: Event, webln: WebLNProvider | null, _nwcConnection: NWCConnection | null, onZapSuccess?: () => void ) { - const { nostr } = useNostr(); const { toast } = useToast(); const { user } = useCurrentUser(); const { config } = useAppContext(); const queryClient = useQueryClient(); - - // Handle the case where an empty array is passed (from ZapButton when external data is provided) - const actualTarget = Array.isArray(target) ? (target.length > 0 ? target[0] : null) : target; - - const author = useAuthor(actualTarget?.pubkey); + const author = useAuthor(target?.pubkey); const { sendPayment, getActiveConnection } = useNWC(); const [isZapping, setIsZapping] = useState(false); const [invoice, setInvoice] = useState(null); @@ -40,96 +38,6 @@ export function useZaps( }; }, []); - const { data: zapEvents, ...query } = useQuery({ - queryKey: ['zaps', actualTarget?.id], - staleTime: 30000, // 30 seconds - refetchInterval: (query) => { - // Only refetch if the query is currently being observed (component is mounted) - return query.getObserversCount() > 0 ? 60000 : false; - }, - queryFn: async (c) => { - if (!actualTarget) return []; - - const signal = AbortSignal.any([c.signal, AbortSignal.timeout(5000)]); - - // Query for zap receipts for this specific event - if (actualTarget.kind >= 30000 && actualTarget.kind < 40000) { - // Addressable event - const identifier = actualTarget.tags.find((t) => t[0] === 'd')?.[1] || ''; - const events = await nostr.query([{ - kinds: [9735], - '#a': [`${actualTarget.kind}:${actualTarget.pubkey}:${identifier}`], - }], { signal }); - return events; - } else { - // Regular event - const events = await nostr.query([{ - kinds: [9735], - '#e': [actualTarget.id], - }], { signal }); - return events; - } - }, - enabled: !!actualTarget?.id, - }); - - // Process zap events into simple counts and totals - const { zapCount, totalSats, zaps } = useMemo(() => { - if (!zapEvents || !Array.isArray(zapEvents) || !actualTarget) { - return { zapCount: 0, totalSats: 0, zaps: [] }; - } - - let count = 0; - let sats = 0; - - zapEvents.forEach(zap => { - count++; - - // Try multiple methods to extract the amount: - - // Method 1: amount tag (from zap request, sometimes copied to receipt) - const amountTag = zap.tags.find(([name]) => name === 'amount')?.[1]; - if (amountTag) { - const millisats = parseInt(amountTag); - sats += Math.floor(millisats / 1000); - return; - } - - // Method 2: Extract from bolt11 invoice - const bolt11Tag = zap.tags.find(([name]) => name === 'bolt11')?.[1]; - if (bolt11Tag) { - try { - const invoiceSats = nip57.getSatoshisAmountFromBolt11(bolt11Tag); - sats += invoiceSats; - return; - } catch (error) { - console.warn('Failed to parse bolt11 amount:', error); - } - } - - // Method 3: Parse from description (zap request JSON) - const descriptionTag = zap.tags.find(([name]) => name === 'description')?.[1]; - if (descriptionTag) { - try { - const zapRequest = JSON.parse(descriptionTag); - const requestAmountTag = zapRequest.tags?.find(([name]: string[]) => name === 'amount')?.[1]; - if (requestAmountTag) { - const millisats = parseInt(requestAmountTag); - sats += Math.floor(millisats / 1000); - return; - } - } catch (error) { - console.warn('Failed to parse description JSON:', error); - } - } - - console.warn('Could not extract amount from zap receipt:', zap.id); - }); - - - return { zapCount: count, totalSats: sats, zaps: zapEvents }; - }, [zapEvents, actualTarget]); - const zap = async (amount: number, comment: string) => { if (amount <= 0) { return; @@ -148,7 +56,7 @@ export function useZaps( return; } - if (!actualTarget) { + if (!target) { toast({ title: 'Event not found', description: 'Could not find the event to zap.', @@ -195,14 +103,14 @@ export function useZaps( // Create zap request - use appropriate event format based on kind // For addressable events (30000-39999), pass the object to get 'a' tag // For all other events, pass the ID string to get 'e' tag - const event = (actualTarget.kind >= 30000 && actualTarget.kind < 40000) - ? actualTarget - : actualTarget.id; + const event = (target.kind >= 30000 && target.kind < 40000) + ? target + : target.id; const zapAmount = amount * 1000; // convert to millisats const zapRequest = nip57.makeZapRequest({ - profile: actualTarget.pubkey, + profile: target.pubkey, event: event, amount: zapAmount, relays: config.relayMetadata.relays.map(r => r.url), @@ -337,10 +245,6 @@ export function useZaps( }, []); return { - zaps, - zapCount, - totalSats, - ...query, zap, isZapping, invoice,