SkateSpots/src/hooks/useSetTrustProvider.ts
SkateSpots 3b06e90d15 feat(trust): let the viewer choose the trust provider (NIP-85 kind 10040)
Settings -> Trust is now functional, not read-only. useSetTrustProvider
publishes a kind-10040 designation for a custom provider (npub or 64-hex +
wss relay, the five 30382:<type> rows) and can clear it to fall back to the
community provider. The card validates input, prompts the signer, and
invalidates the provider + assertion queries so scores refresh. Docs
updated.
2026-10-09 17:55:00 +07:00

74 lines
2.6 KiB
TypeScript

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { nip19 } from 'nostr-tools';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import { NIP85_PROVIDER_LIST_KIND, NIP85_USER_ASSERTION_KIND, type TrustProvider } from '@/lib/nip85';
/** The assertion types we designate (the normative kind-30382 set). */
const ASSERTION_TYPES = ['rank', 'followers', 'reporters', 'muters', 'hops'] as const;
/** Accept an npub or a 64-char hex pubkey; return hex or null. */
export function normalizeProviderPubkey(input: string): string | null {
const value = input.trim();
if (/^[0-9a-f]{64}$/i.test(value)) return value.toLowerCase();
if (value.startsWith('npub1')) {
try {
const decoded = nip19.decode(value);
if (decoded.type === 'npub') return decoded.data;
} catch {
return null;
}
}
return null;
}
/** A relay hint must be a websocket URL. */
export function normalizeProviderRelay(input: string): string | null {
const value = input.trim();
if (/^wss?:\/\/.+/i.test(value)) return value;
return null;
}
/**
* Publish (or clear) the viewer's NIP-85 kind-10040 provider designation.
* Passing `null` clears it, so the app falls back to the community provider.
* This is a signed action, so it prompts the signer like any other publish.
*/
export function useSetTrustProvider() {
const { mutateAsync: publish } = useNostrPublish();
const { user } = useCurrentUser();
const { toast } = useToast();
const { t } = useTranslation();
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (provider: TrustProvider | null) => {
if (!user) throw new Error(t('trustUi.loggedOutProvider'));
const relay = provider?.relay;
const tags: string[][] =
provider && relay
? ASSERTION_TYPES.map((type) => [`${NIP85_USER_ASSERTION_KIND}:${type}`, provider.pubkey, relay])
: [];
await publish({ kind: NIP85_PROVIDER_LIST_KIND, content: '', tags });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['trust-provider', user?.pubkey] });
queryClient.invalidateQueries({ queryKey: ['trust-assertions-batch', user?.pubkey ?? ''] });
toast({ title: t('trustUi.providerSaved') });
},
onError: (error) => {
toast({
title: t('trustUi.publishFailed'),
description: error instanceof Error ? error.message : undefined,
variant: 'destructive',
});
},
});
return mutation;
}