import { useMemo, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { useMintDirectory, type MintDirectoryEntry } from '@/hooks/useMintDirectory'; import { useCashu } from '@/hooks/wallet/useCashu'; import { useToast } from '@/hooks/useToast'; import { isNativePlatform } from '@/lib/platform'; import { cn } from '@/lib/utils'; import { Search, Coins, ExternalLink, Plus, Loader2, Check, ShieldAlert, Activity } from 'lucide-react'; function hostOf(url: string): string { try { return new URL(url).host; } catch { return url; } } function healthBadge(mint: MintDirectoryEntry) { if (mint.archived) { return { label: 'Archived', className: 'border-gray-600 bg-gray-800/60 text-gray-400' }; } if (mint.degraded) { return { label: 'Degraded', className: 'border-amber-500/40 bg-amber-500/10 text-amber-400' }; } if (mint.online === false) { return { label: 'Offline', className: 'border-red-500/40 bg-red-500/10 text-red-400' }; } return { label: 'Online', className: 'border-[#ccff00]/40 bg-[#ccff00]/10 text-[#ccff00]' }; } function MintRow({ mint, added, canAdd, adding, onAdd, }: { mint: MintDirectoryEntry; added: boolean; canAdd: boolean; adding: boolean; onAdd: () => void; }) { const health = healthBadge(mint); return (
{mint.iconUrl ? ( { (e.currentTarget as HTMLImageElement).style.display = 'none'; }} /> ) : (
)}

{mint.name || hostOf(mint.url)}

{health.label} {typeof mint.latencyMs === 'number' && mint.online && ( {mint.latencyMs} ms )}

{hostOf(mint.url)}

{mint.descriptionLong && (

{mint.descriptionLong}

)}
{typeof mint.nutCount === 'number' && {mint.nutCount} NUTs} {mint.version && {mint.version}} {mint.tosUrl && ( Terms )}
{added ? ( Added ) : canAdd ? ( ) : null}
); } export default function MintsPage() { const { data, isLoading, error } = useMintDirectory(); const { coco, mints, isInitialized, refreshMints } = useCashu(); const { toast } = useToast(); const [query, setQuery] = useState(''); const [onlineOnly, setOnlineOnly] = useState(false); const [addingUrl, setAddingUrl] = useState(null); useSeoMeta({ title: 'Cashu Mints — SkateSpots', description: 'Browse Cashu mints, check their health, and add one to your wallet.', }); const canAdd = isNativePlatform() && isInitialized && !!coco; const addedUrls = useMemo( () => new Set(mints.map((m) => m.url.toLowerCase())), [mints], ); const filtered = useMemo(() => { const list = data ?? []; const q = query.trim().toLowerCase(); return list .filter((m) => { if (onlineOnly && (m.archived || m.online === false)) return false; if (!q) return true; return ( m.url.toLowerCase().includes(q) || (m.name ?? '').toLowerCase().includes(q) ); }) .sort((a, b) => { const rank = (m: MintDirectoryEntry) => m.archived ? 3 : m.online === false ? 2 : m.degraded ? 1 : 0; const r = rank(a) - rank(b); if (r !== 0) return r; return (a.latencyMs ?? 9999) - (b.latencyMs ?? 9999); }); }, [data, query, onlineOnly]); const handleAdd = async (url: string) => { if (!coco) return; setAddingUrl(url); try { await coco.mint.addMint(url, { trusted: true }); await refreshMints(); toast({ title: 'Mint added', description: `${hostOf(url)} is now in your wallet.` }); } catch (e) { toast({ title: 'Could not add mint', description: e instanceof Error ? e.message : 'Something went wrong.', variant: 'destructive', }); } finally { setAddingUrl(null); } }; return (

Mints

Browse Cashu mints and their health. Cashu wallet operations run in the SkateSpots app.

setQuery(e.target.value)} placeholder="Search mints by name or URL…" className="h-11 border-gray-700 bg-gray-900 pl-10 text-white" />
{!isLoading && !error && (

{filtered.length} of {data?.length ?? 0} mints

)} {isLoading && (
{Array.from({ length: 6 }).map((_, i) => ( ))}
)} {error && !isLoading && (

{error instanceof Error ? error.message : 'Could not load the mint directory.'}

The directory is community-run and rate-limited — it may be briefly unavailable.

)} {!isLoading && !error && ( <> {!canAdd && (

Adding a mint happens in the SkateSpots app (Cashu is app-only).

)}
{filtered.map((mint) => ( handleAdd(mint.url)} /> ))}
{filtered.length === 0 && (

No mints match “{query}”.

)}

Health data from the community MintRadar project — unofficial and may be stale. Always review a mint's terms before trusting it with funds.

)}
); }