From 5d7b53f562224c4ec6937ea8617997060420734d Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:52:38 +0200 Subject: [PATCH] feat: /trading-apps comparison table with all 5 metrics per platform --- src/app/trading-apps/page.tsx | 486 ++++++++++++++++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 src/app/trading-apps/page.tsx diff --git a/src/app/trading-apps/page.tsx b/src/app/trading-apps/page.tsx new file mode 100644 index 00000000..ecc9c02d --- /dev/null +++ b/src/app/trading-apps/page.tsx @@ -0,0 +1,486 @@ +import Link from "next/link"; +import { ProviderLogo } from "@/components/provider-logo"; +import { readSnapshot } from "@/lib/snapshot"; +import type { SnapshotPayload } from "@/lib/snapshot"; +import { pageMetadata } from "@/lib/page-metadata"; +import { safeJsonLd, buildBreadcrumbJsonLd } from "@/lib/jsonld"; +import { SITE } from "@/data/site"; + +const DESCRIPTION = + "Live benchmarks for Solana trading platforms and Telegram bots — volume, fees, execution quality, unique traders, and app store ratings."; + +export const metadata: import("next").Metadata = pageMetadata({ + path: "/trading-apps", + title: "Best Solana Trading Apps 2026, ranked by benchmark", + description: DESCRIPTION, +}); + +export const revalidate = 60; + +const BENCH_SLUGS = [ + "solana-trading-platform-wars", + "solana-dex-volume", + "solana-unique-traders", + "solana-avg-trade-size", + "solana-launchpad-wars", + "memecoin-platforms", + "trading-app-execution", + "app-store-ratings", +] as const; + +const PLATFORMS = [ + { slug: "pump-fun", name: "pump.fun" }, + { slug: "gmgn", name: "GMGN" }, + { slug: "axiom", name: "Axiom" }, + { slug: "fomo", name: "FOMO" }, + { slug: "trojan", name: "Trojan" }, + { slug: "photon", name: "Photon" }, + { slug: "maestro", name: "Maestro" }, + { slug: "banana-gun", name: "Banana Gun" }, + { slug: "bullx", name: "BullX" }, +] as const; + +const COLUMNS = [ + { + key: "volume" as const, + label: "24h Volume", + bench: "solana-trading-platform-wars", + fmt: fmtUSD, + tip: "Attributed 24h trading volume", + higherBetter: true, + }, + { + key: "traders" as const, + label: "Unique Traders", + bench: "solana-unique-traders", + fmt: fmtCount, + tip: "Unique swap transactions in 24h", + higherBetter: true, + }, + { + key: "tradeSize" as const, + label: "Avg Trade", + bench: "solana-avg-trade-size", + fmt: fmtUSD, + tip: "Average swap size in USD", + higherBetter: false, + }, + { + key: "feeRate" as const, + label: "Fee Rate", + bench: "memecoin-platforms", + fmt: fmtPct, + tip: "Protocol fee revenue ÷ volume (take rate)", + higherBetter: false, + }, + { + key: "rating" as const, + label: "App Rating", + bench: "app-store-ratings", + fmt: fmtRating, + tip: "Apple App Store rating (out of 5)", + higherBetter: true, + }, +] as const; + +type ColKey = (typeof COLUMNS)[number]["key"]; + +function indexBySlug(snap: SnapshotPayload | null): Record { + const out: Record = {}; + for (const r of snap?.results ?? []) { + out[r.slug] = r.ms.mean; + } + return out; +} + +function fmtUSD(v: number | null): string { + if (v === null) return "—"; + if (v >= 1_000_000_000) return `$${(v / 1_000_000_000).toFixed(1)}B`; + if (v >= 1_000_000) return `$${(v / 1_000_000).toFixed(0)}M`; + if (v >= 1_000) return `$${(v / 1_000).toFixed(0)}K`; + return `$${v.toFixed(0)}`; +} + +function fmtCount(v: number | null): string { + if (v === null) return "—"; + if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`; + if (v >= 1_000) return `${(v / 1_000).toFixed(0)}K`; + return v.toFixed(0); +} + +function fmtPct(v: number | null): string { + if (v === null) return "—"; + return `${v.toFixed(2)}%`; +} + +function fmtRating(v: number | null): string { + if (v === null) return "—"; + return `${v.toFixed(1)} / 5`; +} + +const GROUPS = [ + { + label: "Volume & activity", + items: [ + { slug: "solana-trading-platform-wars", title: "Trading platform volume" }, + { slug: "solana-dex-volume", title: "DEX volume & protocol revenue" }, + { slug: "solana-unique-traders", title: "Unique traders" }, + { slug: "solana-avg-trade-size", title: "Average trade size" }, + ], + }, + { + label: "Launchpads", + items: [{ slug: "solana-launchpad-wars", title: "Launchpad volume" }], + }, + { + label: "Fees & execution", + items: [ + { slug: "memecoin-platforms", title: "Platform fee rates" }, + { slug: "trading-app-execution", title: "Execution quality" }, + ], + }, + { + label: "App store", + items: [{ slug: "app-store-ratings", title: "iOS app store ratings" }], + }, +] as const; + +export default async function TradingAppsHubPage() { + const [volumeSnap, tradersSnap, tradeSizeSnap, feeSnap, ratingsSnap] = + await Promise.all([ + readSnapshot("solana-trading-platform-wars"), + readSnapshot("solana-unique-traders"), + readSnapshot("solana-avg-trade-size"), + readSnapshot("memecoin-platforms"), + readSnapshot("app-store-ratings"), + ]); + + const volIdx = indexBySlug(volumeSnap); + const tradersIdx = indexBySlug(tradersSnap); + const tradeSizeIdx = indexBySlug(tradeSizeSnap); + const feeIdx = indexBySlug(feeSnap); + const ratingIdx = indexBySlug(ratingsSnap); + + type Row = { + slug: string; + name: string; + volume: number | null; + traders: number | null; + tradeSize: number | null; + feeRate: number | null; + rating: number | null; + }; + + const matrix: Row[] = PLATFORMS.map((p) => ({ + slug: p.slug, + name: p.name, + volume: volIdx[p.slug] ?? null, + traders: tradersIdx[p.slug] ?? null, + tradeSize: tradeSizeIdx[p.slug] ?? null, + feeRate: feeIdx[p.slug] ?? null, + rating: ratingIdx[p.slug] ?? null, + })).sort((a, b) => (b.volume ?? -1) - (a.volume ?? -1)); + + // Find the best value per column (for highlighting) + function best(key: ColKey, higherBetter: boolean): number | null { + const vals = matrix.map((r) => r[key]).filter((v): v is number => v !== null); + if (!vals.length) return null; + return higherBetter ? Math.max(...vals) : Math.min(...vals); + } + + const bests: Partial> = {}; + for (const col of COLUMNS) { + bests[col.key] = best(col.key, col.higherBetter); + } + + const topVolume = volumeSnap?.results[0]; + const topRating = ratingsSnap?.results.find((r) => + PLATFORMS.some((p) => p.slug === r.slug) + ); + + const breadcrumbLd = { + "@context": "https://schema.org", + ...buildBreadcrumbJsonLd([ + { name: "Home", item: SITE.url }, + { name: "Trading Apps", item: `${SITE.url}/trading-apps` }, + ]), + }; + + const itemListLd = { + "@context": "https://schema.org", + "@type": "ItemList", + name: "Trading app benchmarks by OpenChainBench", + description: DESCRIPTION, + numberOfItems: BENCH_SLUGS.length, + itemListElement: BENCH_SLUGS.map((slug, i) => ({ + "@type": "ListItem", + position: i + 1, + name: slug, + url: `${SITE.url}/benchmarks/${slug}`, + })), + }; + + return ( +
+