import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import type { NostrEvent } from '@nostrify/nostrify'; import { zapReceiptSats } from '@/hooks/useZapTotals'; export interface ZapLeaderboardTarget { /** `a` coordinate (`kind:pubkey:identifier`) or event id. */ key: string; sats: number; count: number; } export interface ZapLeaderboardSender { pubkey: string; sats: number; count: number; } export interface ZapLeaderboard { targets: ZapLeaderboardTarget[]; senders: ZapLeaderboardSender[]; } /** * Aggregate zap receipts into leaderboards. * * One query for kind-9735 receipts (bounded by `since` for time-ranged boards), * then two tallies: * - **targets** — total sats per zapped item (spot/shop/event coordinate). * - **senders** — total sats sent per zapper (taken from the embedded zap * request, since a receipt's own pubkey is the LNURL server, not the sender). */ export function useZapLeaderboard(since?: number) { const { nostr } = useNostr(); return useQuery({ queryKey: ['zap-leaderboard', since ?? 'all'], staleTime: 5 * 60 * 1000, queryFn: async (c): Promise => { const signal = AbortSignal.any([c.signal, AbortSignal.timeout(12000)]); const receipts: NostrEvent[] = await nostr.query( [{ kinds: [9735], ...(since ? { since } : {}), limit: 1000 }], { signal }, ); const targets = new Map(); const senders = new Map(); for (const zap of receipts) { const sats = zapReceiptSats(zap); const aTag = zap.tags.find(([t]) => t === 'a')?.[1]; const eTag = zap.tags.find(([t]) => t === 'e')?.[1]; const target = aTag ?? eTag; if (target) { const cur = targets.get(target) ?? { sats: 0, count: 0 }; cur.sats += sats; cur.count += 1; targets.set(target, cur); } const description = zap.tags.find(([t]) => t === 'description')?.[1]; if (description) { try { const sender = JSON.parse(description)?.pubkey as string | undefined; if (sender) { const cur = senders.get(sender) ?? { sats: 0, count: 0 }; cur.sats += sats; cur.count += 1; senders.set(sender, cur); } } catch { /* malformed zap request — skip */ } } } return { targets: [...targets.entries()] .map(([key, v]) => ({ key, ...v })) .sort((a, b) => b.sats - a.sats), senders: [...senders.entries()] .map(([pubkey, v]) => ({ pubkey, ...v })) .sort((a, b) => b.sats - a.sats), }; }, }); }