import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions'; import { getZapProviderPubkey } from '@/lib/lightningAddress'; import { PRO_LIGHTNING_ADDRESS } from '@/lib/sasquatchPro'; import { isFeaturedActive } from '@/lib/types'; export type FeaturedStatus = 'none' | 'claimed' | 'verified'; /** * Verify a "featured"/Pro listing against NIP-57 zap receipts. * * SECURITY: a receipt only counts as `verified` when it is AUTHORED by the * platform's Lightning provider pubkey. Previously any kind-9735 with matching * `#a`/`#p` tags was accepted — but anyone can publish such an event themselves, * so a scammer could self-mint a "Verified payment" badge. Requiring the * provider as author makes the receipt unforgeable. * * Layered / best-effort: * - `verified`: a provider-authored kind-9735 zap receipt paying the platform * and referencing this entity's `a` coordinate was found. Tamper-proof. * - `claimed`: the event carries an unexpired `featured` tag but no verifiable * provider receipt could be found. Self-attested — shown, but NOT verified. * - `none`: not featured (or the featured window has expired). */ export function useFeaturedVerification( entityCoord: string | undefined, featuredUntil: number | undefined, ) { const { nostr } = useNostr(); const claimed = isFeaturedActive(featuredUntil); return useQuery({ queryKey: ['featured-verification', entityCoord, featuredUntil], queryFn: async (c) => { // Not featured at all. if (!claimed || !entityCoord) return 'none'; const signal = AbortSignal.any([c.signal, AbortSignal.timeout(6000)]); try { // Only a receipt AUTHORED by the platform's Lightning provider is // trustworthy. Resolve it; if unavailable we can't verify → claimed. const providerPubkey = await getZapProviderPubkey(PRO_LIGHTNING_ADDRESS).catch(() => null); if (!providerPubkey) return 'claimed'; // Provider-authored zap receipts paying the platform that reference this // entity. `authors` is the critical anti-forgery constraint. const receipts = await nostr.query( [ { kinds: [9735], authors: [providerPubkey], '#a': [entityCoord], '#p': PLATFORM_OWNER_PUBKEYS, limit: 20, }, ], { signal }, ); // A provider-authored matching receipt authorizes featured status. if (receipts.length > 0) return 'verified'; } catch { // Relay error — fall through to claimed. } // Featured tag present but unverifiable → self-attested. return 'claimed'; }, enabled: claimed && !!entityCoord, staleTime: 5 * 60 * 1000, // 5 min gcTime: 30 * 60 * 1000, retry: 1, }); }