From b46be71072ae1f5b1ef3b035aba9d9ffbec55fe8 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:23:55 +0200 Subject: [PATCH] feat: products-style bench rankings on /perp/[slug] pages --- src/app/perp/[slug]/page.tsx | 159 ++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 23 deletions(-) diff --git a/src/app/perp/[slug]/page.tsx b/src/app/perp/[slug]/page.tsx index e0f2b2b1..102abfe0 100644 --- a/src/app/perp/[slug]/page.tsx +++ b/src/app/perp/[slug]/page.tsx @@ -4,27 +4,36 @@ import Link from "next/link"; import { ArrowLeft, ArrowUpRight, ExternalLink } from "lucide-react"; import { SITE } from "@/data/site"; import { PERP_VENUES } from "@/lib/perp-stats"; -import { - PERP_VENUE_META, - benchRowsForVenue, -} from "@/lib/perp-venue-context"; +import { PERP_VENUE_META } from "@/lib/perp-venue-context"; import { fetchPerpVenueKpis } from "@/lib/perp-venue-data"; import { fetchPerpCohort } from "@/lib/perp-stats"; import { fetchPerpVenueExternalStats } from "@/lib/perp-venue-external"; import { loadBenchFromBlob } from "@/lib/bench-blob"; +import { fmtUnit } from "@/lib/format"; import { PerpVenueKpiStrip } from "@/components/perp-venue-kpi-strip"; -import { PerpVenueBenchCards } from "@/components/perp-venue-bench-cards"; import { PerpBarChart } from "@/components/perp-bar-chart"; import { logoPath } from "@/lib/logo-manifest"; import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld"; +import type { Benchmark } from "@/types/benchmark"; export const dynamic = "force-dynamic"; export const maxDuration = 60; type Params = { slug: string }; +const LIVE_PERP_BENCH_SLUGS = [ + "perp-fees", + "perp-funding", + "perp-active-markets", + "perp-execution-quality", + "perp-mark-price-lag", + "perp-funding-stability", + "perp-protocol-longevity", + "perp-cost-slope", + "perp-volume-share", +] as const; + function resolveVenue(slug: string) { - // "gmx" product slug → "gmx-v2" cohort slug const cohortSlug = slug === "gmx" ? "gmx-v2" : slug; const seed = PERP_VENUES.find((v) => v.slug === cohortSlug); const meta = PERP_VENUE_META[cohortSlug]; @@ -32,6 +41,44 @@ function resolveVenue(slug: string) { return { seed, meta, cohortSlug, productSlug: slug }; } +type BenchRanking = { + benchSlug: string; + bench: Benchmark; + rank: number; + totalRanked: number; + p50: number; + hasData: boolean; +}; + +function buildRankings( + blobs: (Benchmark | null)[], + cohortSlug: string, +): BenchRanking[] { + return LIVE_PERP_BENCH_SLUGS.flatMap((slug, i) => { + const bench = blobs[i]; + if (!bench) return []; + const live = bench.results.filter( + (r) => r.availability !== "unavailable" && !r.unresponsive, + ); + const sorted = [...live].sort((a, b) => + bench.higherIsBetter ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50, + ); + const idx = sorted.findIndex((r) => r.slug === cohortSlug); + if (idx < 0) return []; + const result = sorted[idx]; + return [ + { + benchSlug: slug, + bench, + rank: idx + 1, + totalRanked: sorted.length, + p50: result.ms.p50, + hasData: result.ms.p50 > 0, + }, + ]; + }).sort((a, b) => a.rank - b.rank); +} + export async function generateMetadata({ params, }: { @@ -91,19 +138,21 @@ export default async function PerpVenuePage({ const { seed, meta, cohortSlug } = v; - const [cohort, kpis, ext, feesAtSizeBench] = await Promise.all([ + const [, kpis, ext, ...benchBlobs] = await Promise.all([ fetchPerpCohort(), fetchPerpVenueKpis(cohortSlug), fetchPerpVenueExternalStats(cohortSlug), - loadBenchFromBlob("perp-fees-at-size"), + ...LIVE_PERP_BENCH_SLUGS.map((s) => loadBenchFromBlob(s)), ]); - const venueRow = cohort?.venues.find((r) => r.slug === cohortSlug); - const benchRows = - cohort && venueRow - ? benchRowsForVenue(cohort, venueRow, feesAtSizeBench) - : []; - const measured = benchRows.filter((r) => r.value !== null && r.rank !== null); + const rankings = buildRankings(benchBlobs as (Benchmark | null)[], cohortSlug); + + const lastMeasured = rankings.reduce((acc, a) => { + const t = a.bench.lastRunAt; + if (!t) return acc; + if (!acc || new Date(t) > new Date(acc)) return t; + return acc; + }, null); const externalHost = (() => { try { @@ -303,16 +352,80 @@ export default async function PerpVenuePage({ )} - {/* OCB Rankings */} - {measured.length > 0 && ( + {/* Live benchmark results */} + {rankings.length > 0 && (
-

- OpenChainBench rankings -

- + {lastMeasured && (() => { + const d = new Date(lastMeasured); + return ( +

+ Last measured{" "} + +

+ ); + })()} +

+ Live benchmark results +

+
    + {rankings.map((a) => { + const value = a.hasData ? fmtUnit(a.p50, a.bench.unit) : null; + return ( +
  1. + + + {a.hasData ? ( + <> + #{a.rank} + + of {a.totalRanked} + + + ) : ( + + awaiting + + )} + +
    +

    + Trading +

    +

    + {a.bench.title} +

    +

    {a.bench.metric}

    +
    +
    + {value ? ( + <> +

    {value}

    +

    + p50 · 24h +

    + + ) : ( +

    + data warming up +

    + )} +
    + +
  2. + ); + })} +
)}