import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import { useCurrentUser } from './useCurrentUser'; import { onlyVerified } from '@/lib/verifyEvents'; /** * Hook to fetch trust scores for multiple pubkeys in batch. * More efficient than calling useTrustScore multiple times. * * @param pubkeys - Array of pubkeys to calculate trust scores for * @returns Map of pubkey to trust score (0-100) */ export function useBatchTrustScores(pubkeys: string[]) { const { nostr } = useNostr(); const { user } = useCurrentUser(); return useQuery({ queryKey: ['batch-trust-scores', pubkeys.sort().join(','), user?.pubkey], queryFn: async (c) => { const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); const scores = new Map(); try { // Get user's follow list (kind 3) const sourcePubkey = user?.pubkey || '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21'; const followListEvents = onlyVerified(await nostr.query( [{ kinds: [3], authors: [sourcePubkey], limit: 1 }], { signal } )); const followList = followListEvents[0]; if (!followList) { // No follow list (e.g. logged-out or new account) — default all to 0. pubkeys.forEach((pk) => scores.set(pk, 0)); return scores; } // Extract followed pubkeys const following = new Set( followList.tags .filter(([tag]) => tag === 'p') .map(([, pubkey]) => pubkey) ); // Process each pubkey for (const targetPubkey of pubkeys) { // Self score = 100 if (user?.pubkey && targetPubkey === user.pubkey) { scores.set(targetPubkey, 100); continue; } // Direct follow = 90 if (following.has(targetPubkey)) { scores.set(targetPubkey, 90); continue; } // Default to 0 (will check friend-of-friend and NIP-05 later) scores.set(targetPubkey, 0); } // Get friend-of-friend relationships (batch query) const uncheckedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0); if (uncheckedPubkeys.length > 0) { const friendsFollowLists = onlyVerified(await nostr.query( [{ kinds: [3], authors: Array.from(following), limit: 100 }], { signal } )); // Build a set of all friends-of-friends const friendsOfFriends = new Set(); for (const friendList of friendsFollowLists) { friendList.tags .filter(([tag]) => tag === 'p') .forEach(([, pubkey]) => friendsOfFriends.add(pubkey)); } // Check friend-of-friend = 60 for (const pubkey of uncheckedPubkeys) { if (friendsOfFriends.has(pubkey)) { scores.set(pubkey, 60); } } } // Check NIP-05 verification for remaining 0-score pubkeys const unverifiedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0); if (unverifiedPubkeys.length > 0) { const profileEvents = onlyVerified(await nostr.query( [{ kinds: [0], authors: unverifiedPubkeys }], { signal } )); for (const profileEvent of profileEvents) { try { const metadata = JSON.parse(profileEvent.content); if (metadata.nip05) { scores.set(profileEvent.pubkey, 30); } } catch { // Ignore JSON parse errors } } } return scores; } catch (error) { console.warn('[useBatchTrustScores] Error calculating batch trust scores:', error); // Return default scores of 0 pubkeys.forEach((pk) => scores.set(pk, 0)); return scores; } }, enabled: pubkeys.length > 0, staleTime: 3600000, // Cache for 1 hour gcTime: 86400000, // Keep in cache for 24 hours retry: 1, }); }