Remove redundant zap receipt fetching — NIP-85 provides zap stats
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.
This commit is contained in:
@@ -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 (
|
||||
<ZapDialog target={target}>
|
||||
<div className={`flex items-center gap-1 ${className}`}>
|
||||
<Zap className="h-4 w-4" />
|
||||
<span className="text-xs">
|
||||
{showLoading ? (
|
||||
'...'
|
||||
) : showCount && totalSats > 0 ? (
|
||||
`${totalSats.toLocaleString()}`
|
||||
) : (
|
||||
'Zap'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</ZapDialog>
|
||||
);
|
||||
}
|
||||
+14
-110
@@ -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<string | null>(null);
|
||||
@@ -40,96 +38,6 @@ export function useZaps(
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { data: zapEvents, ...query } = useQuery<NostrEvent[], Error>({
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user