import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import type { NostrEvent } from '@nostrify/nostrify'; import { useCurrentUser } from './useCurrentUser'; import { onlyVerified } from '@/lib/verifyEvents'; import { HOUSE_TRUST_PROVIDER, NIP85_PROVIDER_LIST_KIND, NIP85_USER_ASSERTION_KIND, parseProviderDesignation, parseUserTrustAssertions, resolveProvider, type TrustProvider, type UserTrustAssertion, } from '@/lib/nip85'; const MAX_TARGETS = 500; /** * Read kind-30382 Trusted Assertions (NIP-85) for a batch of subjects, from the * provider the viewer designates via their own kind-10040 — falling back to the * configured house provider. Signatures are verified and the event author must * equal the resolved provider before a score is used. * * A missing assertion yields no entry: that means "nothing published", which is * NOT the same as distrust. Callers must treat absence as `unknown`. */ export function useTrustAssertions(pubkeys: string[]) { const { nostr } = useNostr(); const { user } = useCurrentUser(); const targets = [...pubkeys].sort().slice(0, MAX_TARGETS); return useQuery({ queryKey: ['trust-assertions', targets.join(','), user?.pubkey], queryFn: async (c): Promise> => { const out = new Map(); if (targets.length === 0) return out; const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); // 1. Resolve the provider: the viewer's own designation, else the house. let provider: TrustProvider = HOUSE_TRUST_PROVIDER; try { if (user?.pubkey) { const designationEvents = onlyVerified(await nostr.query( [{ kinds: [NIP85_PROVIDER_LIST_KIND], authors: [user.pubkey], limit: 1 }], { signal }, )); const designation = parseProviderDesignation(designationEvents[0]); provider = resolveProvider( designation, NIP85_USER_ASSERTION_KIND, 'rank', HOUSE_TRUST_PROVIDER, ); } } catch { // Designation lookup failed — the house provider is the fallback. } // 2. Fetch the assertions for these subjects from the resolved provider. const filter = { kinds: [NIP85_USER_ASSERTION_KIND], authors: [provider.pubkey], '#d': targets, limit: targets.length, }; try { let events: NostrEvent[]; if (provider.relay) { try { events = await nostr.relay(provider.relay).query([filter], { signal }); } catch { events = await nostr.query([filter], { signal }); } } else { events = await nostr.query([filter], { signal }); } return parseUserTrustAssertions(onlyVerified(events), provider.pubkey); } catch { // Provider unreachable — absence, not distrust. return out; } }, enabled: targets.length > 0, staleTime: 60 * 60 * 1000, // 1h gcTime: 24 * 60 * 60 * 1000, retry: 1, }); }