From 51e66727d946dc8833d3cbbd00b315db0636082b Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Tue, 29 Sep 2026 18:02:57 +0700 Subject: [PATCH] feat(mints): surface MintRadar's community ratings, reliability and audit data The directory we already fetch returns review ratings, reliability, uptime and liveness-audit stats that the client was throwing away (it only typed ~12 of ~35 fields). Now: - MintDirectoryEntry types the community fields (rating/weighted rating/count, reliabilityScore, uptime 24h+7d, audit volume + recent errors, serverLocation, units, mint/melt methods, pubkey, discoveredAt, lastError). - Row shows the rating (e.g. 4.6 (30)) plus reliability and 24h uptime. - Detail sheet shows rating, reliability, uptime, location, latency, NUTs, units, version, plus a liveness-audit line and an honest note that written reviews live on MintRadar (linked), not in the directory API. Written comments are genuinely not in MintRadar's /api/mints/known (probed a few plausible review endpoints - all 404), so the next step for in-app comments would be Nostr-native mint reviews. --- src/hooks/useMintDirectory.ts | 28 ++++++++++++ src/pages/MintsPage.tsx | 84 ++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/hooks/useMintDirectory.ts b/src/hooks/useMintDirectory.ts index 509b40c..c321361 100644 --- a/src/hooks/useMintDirectory.ts +++ b/src/hooks/useMintDirectory.ts @@ -31,6 +31,34 @@ export interface MintDirectoryEntry { nutCount?: number; tosUrl?: string; lastOnlineAt?: string; + + // ── Community signal MintRadar also returns (previously unused) ──────────── + /** Average community rating, 0–5. */ + reviewAvgRating?: number; + /** How many community ratings back the average. */ + reviewCount?: number; + /** Rating after MintRadar's own weighting. */ + reviewWeightedRating?: number; + /** 0–100 composite reliability score. */ + reliabilityScore?: number; + uptimePct24h?: number; + uptimePct7d?: number; + /** Liveness audit volume — tokens actually minted/melted through it. */ + auditNMints?: number; + auditNMelts?: number; + auditNErrors?: number; + auditRecentErrors?: number; + auditRecentTotal?: number; + auditCheckedAt?: string; + serverLocation?: string; + units?: string[]; + mintMethods?: Array<{ unit?: string; method?: string; min_amount?: number; max_amount?: number }>; + meltMethods?: Array<{ unit?: string; method?: string; min_amount?: number; max_amount?: number }>; + /** The mint's own Nostr announcement key, when it publishes one. */ + pubkey?: string; + nostrAnnouncedAt?: string; + discoveredAt?: string; + lastError?: string | null; } const SITE_ORIGIN = 'https://skate-spots.org'; diff --git a/src/pages/MintsPage.tsx b/src/pages/MintsPage.tsx index 25f70f8..dfdf49a 100644 --- a/src/pages/MintsPage.tsx +++ b/src/pages/MintsPage.tsx @@ -12,7 +12,7 @@ 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, ChevronRight, Copy, Info } from 'lucide-react'; +import { Search, Coins, ExternalLink, Plus, Loader2, Check, ShieldAlert, Activity, ChevronRight, Copy, Info, Star } from 'lucide-react'; function hostOf(url: string): string { try { @@ -112,6 +112,27 @@ function MintRow({ )} + + {/* Community signal — ratings + reliability from MintRadar. */} + {((typeof mint.reviewAvgRating === 'number' && (mint.reviewCount ?? 0) > 0) || + typeof mint.reliabilityScore === 'number' || + typeof mint.uptimePct24h === 'number') && ( +
+ {typeof mint.reviewAvgRating === 'number' && (mint.reviewCount ?? 0) > 0 && ( + + + {mint.reviewAvgRating.toFixed(1)} + ({mint.reviewCount}) + + )} + {typeof mint.reliabilityScore === 'number' && ( + reliability {mint.reliabilityScore}/100 + )} + {typeof mint.uptimePct24h === 'number' && ( + {Math.round(mint.uptimePct24h)}% up · 24h + )} +
+ )}
@@ -358,6 +379,28 @@ export default function MintsPage() {
{[ + { + label: 'Community rating', + value: + typeof selected.reviewAvgRating === 'number' && (selected.reviewCount ?? 0) > 0 + ? `${selected.reviewAvgRating.toFixed(1)} / 5 · ${selected.reviewCount}` + : '—', + }, + { + label: 'Reliability', + value: + typeof selected.reliabilityScore === 'number' + ? `${selected.reliabilityScore} / 100` + : '—', + }, + { + label: 'Uptime 24h / 7d', + value: + typeof selected.uptimePct24h === 'number' && typeof selected.uptimePct7d === 'number' + ? `${Math.round(selected.uptimePct24h)}% / ${Math.round(selected.uptimePct7d)}%` + : '—', + }, + { label: 'Location', value: selected.serverLocation ?? '—' }, { label: 'Latency', value: @@ -369,8 +412,11 @@ export default function MintsPage() { label: 'NUTs supported', value: typeof selected.nutCount === 'number' ? String(selected.nutCount) : '—', }, + { + label: 'Units', + value: selected.units?.length ? selected.units.join(', ') : '—', + }, { label: 'Version', value: selected.version ?? '—' }, - { label: 'Last online', value: selected.lastOnlineAt ?? '—' }, ].map((f) => (
+
+

+ + + {typeof selected.reviewAvgRating === 'number' && (selected.reviewCount ?? 0) > 0 + ? `Rated ${selected.reviewAvgRating.toFixed(1)}/5 by ${selected.reviewCount} people on MintRadar.` + : 'No community rating yet on MintRadar.'}{' '} + Written reviews aren't part of the directory —{' '} + + open MintRadar + {' '} + to read or leave one. + +

+ {typeof selected.auditNMints === 'number' && ( +

+ + + Liveness audit: {selected.auditNMints.toLocaleString()} mints and{' '} + {(selected.auditNMelts ?? 0).toLocaleString()} melts tested + {selected.auditRecentTotal + ? ` · ${selected.auditRecentErrors ?? 0}/${selected.auditRecentTotal} recent errors` + : ''} + . + +

+ )} +
+
{addedUrls.has(selected.url.toLowerCase()) ? (