diff --git a/src/app/apps/page.tsx b/src/app/apps/page.tsx index c849bc20..7b9d9905 100644 --- a/src/app/apps/page.tsx +++ b/src/app/apps/page.tsx @@ -20,6 +20,8 @@ export const metadata: Metadata = pageMetadata({ export const revalidate = 300; +const WINDOWS = ["24h", "7d", "30d"] as const; + export default async function AppsHubPage() { const [evmData, solanaData, solPrice] = await Promise.all([ fetchEVMRevenue(), @@ -43,30 +45,39 @@ export default async function AppsHubPage() { const bscChain = evmRow?.chains["bsc"]; const baseChain = evmRow?.chains["base"]; + // EVM fees are always 24h — we don't have multi-window EVM data. const ethFees = ethChain ? ethChain.stable24h + (ethChain.native?.usd ?? 0) : null; const bscFees = bscChain ? bscChain.stable24h + (bscChain.native?.usd ?? 0) : null; const baseFees = baseChain ? baseChain.stable24h + (baseChain.native?.usd ?? 0) : null; + const evmTotal = (ethFees ?? 0) + (bscFees ?? 0) + (baseFees ?? 0); + + const windows: UnifiedAppRow["windows"] = {}; - let solanaFees: number | null = null; - if (solRow && solPrice !== null) { - const window24h = solRow.windows["24h"]; - if (window24h) { - solanaFees = (window24h.txCount * window24h.avgPlatformFeeLamports) / 1e9 * solPrice; + for (const w of WINDOWS) { + let solanaFees: number | null = null; + if (solRow && solPrice !== null) { + const wData = solRow.windows[w]; + if (wData) { + solanaFees = (wData.txCount * wData.avgPlatformFeeLamports) / 1e9 * solPrice; + } } + windows[w] = { + solana: solanaFees, + ethereum: ethFees, + bsc: bscFees, + base: baseFees, + total: (solanaFees ?? 0) + evmTotal, + }; } - const total24h = - (solanaFees ?? 0) + (ethFees ?? 0) + (bscFees ?? 0) + (baseFees ?? 0); - return { meta, - fees: { solana: solanaFees, ethereum: ethFees, bsc: bscFees, base: baseFees }, + windows, stableOnly: { ethereum: ethChain?.coverage === "stable-only", bsc: bscChain?.coverage === "stable-only", base: baseChain?.coverage === "stable-only", }, - total24h, }; }); @@ -101,7 +112,7 @@ export default async function AppsHubPage() {

Solana fees = txCount × avgPlatformFeeLamports / 1e9 × SOL price. - EVM fees = stable (USDC/USDT) + native where traceable.{" "} + EVM fees = stable (USDC/USDT) + native where traceable, always 24h.{" "} Detailed execution metrics → diff --git a/src/components/trading-apps-leaderboard.tsx b/src/components/trading-apps-leaderboard.tsx index ea80b9cf..d15ccc37 100644 --- a/src/components/trading-apps-leaderboard.tsx +++ b/src/components/trading-apps-leaderboard.tsx @@ -1,23 +1,27 @@ "use client"; import Image from "next/image"; +import Link from "next/link"; import { useState } from "react"; import { logoPath } from "@/lib/logo-manifest"; import type { AppMeta } from "@/lib/trading-apps-config"; +type FeeWindow = { + solana: number | null; + ethereum: number | null; + bsc: number | null; + base: number | null; + total: number; +}; + export type UnifiedAppRow = { meta: AppMeta; - fees: { - solana: number | null; - ethereum: number | null; - bsc: number | null; - base: number | null; - }; + windows: Record; stableOnly: { ethereum: boolean; bsc: boolean; base: boolean }; - total24h: number; }; type TabKey = "all" | "meme-bot" | "telegram-bot"; +type WindowKey = "24h" | "7d" | "30d"; const TABS: { key: TabKey; label: string }[] = [ { key: "all", label: "All" }, @@ -25,6 +29,12 @@ const TABS: { key: TabKey; label: string }[] = [ { key: "telegram-bot", label: "Telegram Bots" }, ]; +const WINDOWS: { key: WindowKey; label: string }[] = [ + { key: "24h", label: "24h" }, + { key: "7d", label: "7d" }, + { key: "30d", label: "30d" }, +]; + const CATEGORY_BADGE: Record = { "meme-bot": "bg-orange-500/10 text-orange-400 border border-orange-500/20", "telegram-bot": "bg-blue-500/10 text-blue-400 border border-blue-500/20", @@ -79,9 +89,12 @@ export function TradingAppsLeaderboard({ updatedAt: string | null; }) { const [tab, setTab] = useState("all"); + const [window, setWindow] = useState("24h"); const filtered = tab === "all" ? rows : rows.filter((r) => r.meta.category === tab); - const sorted = [...filtered].sort((a, b) => b.total24h - a.total24h); + const sorted = [...filtered].sort( + (a, b) => (b.windows[window]?.total ?? 0) - (a.windows[window]?.total ?? 0) + ); const hasStableOnly = sorted.some( (r) => r.stableOnly.ethereum || r.stableOnly.bsc || r.stableOnly.base @@ -106,11 +119,29 @@ export function TradingAppsLeaderboard({ ))} - {updatedAt && ( - - Updated {new Date(updatedAt).toLocaleString()} - - )} +

+
+ {WINDOWS.map((w) => ( + + ))} +
+ {updatedAt && ( + + {new Date(updatedAt).toLocaleString()} + + )} +
@@ -120,16 +151,23 @@ export function TradingAppsLeaderboard({ # App Category - Solana + + + Solana + + Ethereum BSC Base - Total 24h + + Total {window} + {sorted.map((row, i) => { - const { meta, fees, stableOnly } = row; + const { meta, stableOnly } = row; + const fees = row.windows[window] ?? { solana: null, ethereum: null, bsc: null, base: null, total: 0 }; const logo = meta.logoKey ? logoPath(meta.logoKey) : null; return ( @@ -141,23 +179,37 @@ export function TradingAppsLeaderboard({ {i + 1} - - {logo && ( - {meta.name} +
+ + {logo && ( + {meta.name} + )} + {meta.name} + + {meta.benchUrl && ( + + + )} - {meta.name} - +
@@ -169,7 +221,7 @@ export function TradingAppsLeaderboard({ - {row.total24h > 0 ? fmtUSD(row.total24h) : } + {fees.total > 0 ? fmtUSD(fees.total) : } ); @@ -181,6 +233,12 @@ export function TradingAppsLeaderboard({ {hasStableOnly && (

° USDC only (no native trace on Base). + {window !== "24h" && " EVM fees are always 24h — only Solana has multi-window data."} +

+ )} + {!hasStableOnly && window !== "24h" && ( +

+ EVM fees (ETH/BSC/Base) are always 24h — only Solana has multi-window data.

)}
diff --git a/src/lib/trading-apps-config.ts b/src/lib/trading-apps-config.ts index 65d07c44..8195d67e 100644 --- a/src/lib/trading-apps-config.ts +++ b/src/lib/trading-apps-config.ts @@ -10,13 +10,13 @@ export type AppMeta = { }; export const TRADING_APPS: AppMeta[] = [ - { id: "pump.fun", name: "pump.fun", category: "meme-bot", logoKey: "pump-fun", productUrl: "https://pump.fun", benchUrl: null, evmKey: "pumpfun", solanaKey: "pump.fun" }, - { id: "fomo", name: "FOMO", category: "meme-bot", logoKey: "fomo", productUrl: "https://fomo.fund", benchUrl: null, evmKey: null, solanaKey: "fomo" }, - { id: "bullx", name: "BullX", category: "meme-bot", logoKey: "bullx", productUrl: "https://bullx.io", benchUrl: null, evmKey: null, solanaKey: "bullx" }, - { id: "photon", name: "Photon", category: "meme-bot", logoKey: "photon", productUrl: "https://photon-sol.tinyastro.io", benchUrl: null, evmKey: null, solanaKey: "photon" }, - { id: "gmgn", name: "GMGN", category: "telegram-bot", logoKey: "gmgn", productUrl: "https://gmgn.ai", benchUrl: null, evmKey: "gmgn", solanaKey: "gmgn" }, - { id: "axiom", name: "Axiom", category: "telegram-bot", logoKey: "axiom", productUrl: "https://axiom.trade", benchUrl: null, evmKey: "axiom", solanaKey: "axiom" }, - { id: "maestro", name: "Maestro", category: "telegram-bot", logoKey: "maestro", productUrl: "https://maestro.bots.gg", benchUrl: null, evmKey: "maestro", solanaKey: null }, - { id: "banana-gun", name: "Banana Gun", category: "telegram-bot", logoKey: "banana-gun", productUrl: "https://t.me/BananaGunSniper_bot", benchUrl: null, evmKey: "banana-gun", solanaKey: null }, - { id: "trojan", name: "Trojan", category: "telegram-bot", logoKey: "trojan", productUrl: "https://trojan.bot", benchUrl: null, evmKey: null, solanaKey: "trojan" }, + { id: "pump.fun", name: "pump.fun", category: "meme-bot", logoKey: "pump-fun", productUrl: "https://pump.fun", benchUrl: "/apps/exec", evmKey: "pumpfun", solanaKey: "pump.fun" }, + { id: "fomo", name: "FOMO", category: "meme-bot", logoKey: "fomo", productUrl: "https://fomo.fund", benchUrl: "/apps/exec", evmKey: null, solanaKey: "fomo" }, + { id: "bullx", name: "BullX", category: "meme-bot", logoKey: "bullx", productUrl: "https://bullx.io", benchUrl: "/apps/exec", evmKey: null, solanaKey: "bullx" }, + { id: "photon", name: "Photon", category: "meme-bot", logoKey: "photon", productUrl: "https://photon-sol.tinyastro.io", benchUrl: "/apps/exec", evmKey: null, solanaKey: "photon" }, + { id: "gmgn", name: "GMGN", category: "telegram-bot", logoKey: "gmgn", productUrl: "https://gmgn.ai", benchUrl: "/apps/exec", evmKey: "gmgn", solanaKey: "gmgn" }, + { id: "axiom", name: "Axiom", category: "telegram-bot", logoKey: "axiom", productUrl: "https://axiom.trade", benchUrl: "/apps/exec", evmKey: "axiom", solanaKey: "axiom" }, + { id: "maestro", name: "Maestro", category: "telegram-bot", logoKey: "maestro", productUrl: "https://maestro.bots.gg", benchUrl: "/apps/exec", evmKey: "maestro", solanaKey: null }, + { id: "banana-gun", name: "Banana Gun", category: "telegram-bot", logoKey: "banana-gun", productUrl: "https://t.me/BananaGunSniper_bot", benchUrl: "/apps/exec", evmKey: "banana-gun", solanaKey: null }, + { id: "trojan", name: "Trojan", category: "telegram-bot", logoKey: "trojan", productUrl: "https://trojan.bot", benchUrl: "/apps/exec", evmKey: null, solanaKey: "trojan" }, ];