From 8e41abc4a65ef4eb2848365e993adb76e501cc69 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 02:50:18 +0200 Subject: [PATCH 1/5] fix: pump.fun +8 missing fee wallets, BullX inactive badge --- .../solana-exec/internal/platform/platforms.go | 10 ++++++++++ src/components/trading-apps-leaderboard.tsx | 15 +++++++++++---- src/lib/trading-apps-config.ts | 4 +++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/harnesses/solana-exec/internal/platform/platforms.go b/harnesses/solana-exec/internal/platform/platforms.go index 4fa44159..a1ec8b7a 100644 --- a/harnesses/solana-exec/internal/platform/platforms.go +++ b/harnesses/solana-exec/internal/platform/platforms.go @@ -5,8 +5,18 @@ package platform // paginates each independently and aggregates before sampling. // Sources: DefiLlama dimension-adapters, verified 2026-08. var FeeAccounts = map[string][]string{ + // 8 bonding-curve fee recipients (round-robin) + Mayhem wallet. + // Sources: DeFiLlama fees/pumpdotfun adapter (master, 2026-08). "pump.fun": { "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM", + "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV", + "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz", + "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX", + "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY", + "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz", + "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP", + "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ", + "GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS", // Mayhem mode }, "photon": { "AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH", diff --git a/src/components/trading-apps-leaderboard.tsx b/src/components/trading-apps-leaderboard.tsx index 13e561c2..8fd26aa9 100644 --- a/src/components/trading-apps-leaderboard.tsx +++ b/src/components/trading-apps-leaderboard.tsx @@ -92,9 +92,11 @@ export function TradingAppsLeaderboard({ const [window, setWindow] = useState("24h"); const filtered = tab === "all" ? rows : rows.filter((r) => r.meta.category === tab); - const sorted = [...filtered].sort( - (a, b) => (b.windows[window]?.total ?? 0) - (a.windows[window]?.total ?? 0) - ); + const sorted = [...filtered].sort((a, b) => { + if (a.meta.inactive && !b.meta.inactive) return 1; + if (!a.meta.inactive && b.meta.inactive) return -1; + return (b.windows[window]?.total ?? 0) - (a.windows[window]?.total ?? 0); + }); const hasStableOnly = sorted.some( (r) => r.stableOnly.ethereum || r.stableOnly.bsc || r.stableOnly.base @@ -173,7 +175,7 @@ export function TradingAppsLeaderboard({ return ( {i + 1} @@ -196,6 +198,11 @@ export function TradingAppsLeaderboard({ )} {meta.name} + {meta.inactive && ( + + Suspended + + )} {meta.benchUrl && ( Date: Wed, 12 Aug 2026 03:14:57 +0200 Subject: [PATCH 2/5] feat(alternatives): restore LedgerTable for content density, adjust CTA --- src/app/alternatives/[slug]/page.tsx | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/app/alternatives/[slug]/page.tsx b/src/app/alternatives/[slug]/page.tsx index bd3fb2b2..05dfd25f 100644 --- a/src/app/alternatives/[slug]/page.tsx +++ b/src/app/alternatives/[slug]/page.tsx @@ -22,6 +22,7 @@ import { isRegion } from "@/lib/brand"; import { PERP_VENUE_META, benchRowsForVenue } from "@/lib/perp-venue-context"; import { fetchPerpCohort } from "@/lib/perp-stats"; import { PerpVenueBenchCards } from "@/components/perp-venue-bench-cards"; +import { LedgerTable } from "@/components/ledger-table"; export const dynamic = "force-dynamic"; @@ -341,12 +342,17 @@ export default async function AlternativePage({ )} - {/* Latency / rate / USD benches: field stats + trend chart. - The full provider table lives on the canonical benchmark page — - linking there keeps this page focused on the alternatives angle - without duplicating the complete leaderboard. */} + {/* Latency / rate / USD benches: full ranked table + trend chart. + LedgerTable gives readers the complete ranked list on this page + (better search-intent match than a CTA-only pattern) while + /benchmarks/ remains canonical for the interactive bar chart, + distribution view, and per-chain drilldowns. */} {!isDraft && bench.unit !== "count" && ( <> +
+ +
+
)} - {/* CTA to the canonical benchmark page where the full ranked - leaderboard (all providers, p50/p90/p99, success rate, trend) - lives. Keeps the alternatives page focused while giving readers - a clear path to the complete data. */} + {/* Link to canonical benchmark for interactive views not present here: + ranked bar chart, distribution, per-chain drilldowns, chart export. */} {!isDraft && (

- Full leaderboard: {bench.results.length} providers ranked by{" "} - {bench.metric.toLowerCase()}, with p50/p90/p99, success rate and - 24h trend. + Interactive bar chart, distribution view, per-chain drilldowns and + chart export on the benchmark page.

- View full benchmark + Open benchmark
From d83760dfd0560ac25ff3e9e3a4c66748440b2382 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 03:59:25 +0200 Subject: [PATCH 3/5] feat: replace EVM harness with DeFiLlama, fix Solana sweep detection - defillama.ts: fetch fees from DeFiLlama for all 9 platforms (pump.fun, gmgn, axiom, maestro, banana-gun, photon, trojan, fomo, bullx) - apps/page.tsx: use DeFiLlama for revenue leaderboard, drop fetchEVMRevenue and fetchSolPrice, keep fetchExecLeaderboard for Solana exec metrics - exec-chain-tabs: hide EVM tabs when evmData is null - helius/client.go: populate SenderAccounts on EnhancedTx (accounts with negative SOL balance change) for sweep detection - collector/main.go: skip fee counting when a fee wallet appears as sender (internal sweep filter); increase maxPages 30->300 for busy wallets --- harnesses/solana-exec/cmd/collector/main.go | 21 +++++- .../solana-exec/internal/helius/client.go | 8 ++ src/app/apps/page.tsx | 66 ++++------------ src/components/exec-chain-tabs.tsx | 4 +- src/lib/defillama.ts | 75 +++++++++++++++++++ 5 files changed, 119 insertions(+), 55 deletions(-) create mode 100644 src/lib/defillama.ts diff --git a/harnesses/solana-exec/cmd/collector/main.go b/harnesses/solana-exec/cmd/collector/main.go index 496bb3e8..56cf537a 100644 --- a/harnesses/solana-exec/cmd/collector/main.go +++ b/harnesses/solana-exec/cmd/collector/main.go @@ -78,7 +78,7 @@ func collect(ctx context.Context, db *store.DB, h *helius.Client, plt string, fe } const sigLimit = 1000 - const maxPages = 30 // cap backfill at 30k sigs per account to avoid unbounded first-run pagination + const maxPages = 300 // cap backfill at 300k sigs per account (~20 days for busy wallets) var sigs []helius.SigEntry before := "" for page := 0; page < maxPages; page++ { @@ -191,10 +191,23 @@ func collect(ctx context.Context, db *store.DB, h *helius.Client, plt string, fe if feeToken == "" { // SOL-fee platform: detect via native SOL transfers. - for _, xfer := range tx.NativeTransfers { - if feeSet[xfer.ToUserAccount] { - platformFeeLamports += xfer.Amount + // Skip any tx where a fee wallet is also a sender — these are internal sweeps + // (e.g. Axiom consolidating 20 wallets) that would inflate the average fee. + hasFeeWalletSender := false + for _, sender := range tx.SenderAccounts { + if feeSet[sender] { + hasFeeWalletSender = true + break + } + } + if !hasFeeWalletSender { + for _, xfer := range tx.NativeTransfers { + if feeSet[xfer.ToUserAccount] { + platformFeeLamports += xfer.Amount + } } + } + for _, xfer := range tx.NativeTransfers { if platform.JitoTipAccounts[xfer.ToUserAccount] { jitoTipLamports += xfer.Amount isJito = true diff --git a/harnesses/solana-exec/internal/helius/client.go b/harnesses/solana-exec/internal/helius/client.go index d6ec7514..58b0743a 100644 --- a/harnesses/solana-exec/internal/helius/client.go +++ b/harnesses/solana-exec/internal/helius/client.go @@ -147,6 +147,9 @@ type EnhancedTx struct { // This is the authoritative price — use it directly, no division needed. // 0 = no priority fee set (base fee only). CUPriceDeclared int64 + // SenderAccounts is the set of accounts whose SOL balance decreased in this tx. + // Used by the collector to detect internal fee-wallet sweeps and skip them. + SenderAccounts []string } const b58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" @@ -360,7 +363,9 @@ func (c *Client) getTransaction(ctx context.Context, sig string) (EnhancedTx, er } // NativeTransfers: accounts whose SOL balance increased received SOL. + // SenderAccounts: accounts whose SOL balance decreased (used for sweep detection). var nativeTransfers []NativeTransfer + var senderAccounts []string for i, key := range accounts { if i >= len(m.PreBalances) || i >= len(m.PostBalances) { break @@ -368,6 +373,8 @@ func (c *Client) getTransaction(ctx context.Context, sig string) (EnhancedTx, er diff := m.PostBalances[i] - m.PreBalances[i] if diff > 0 { nativeTransfers = append(nativeTransfers, NativeTransfer{ToUserAccount: key.Pubkey, Amount: diff}) + } else if diff < 0 { + senderAccounts = append(senderAccounts, key.Pubkey) } } @@ -463,5 +470,6 @@ func (c *Client) getTransaction(ctx context.Context, sig string) (EnhancedTx, er ComputeUnitsConsumed: cuConsumed, CULimit: cuLimit, CUPriceDeclared: cuPriceDeclared, + SenderAccounts: senderAccounts, }, nil } diff --git a/src/app/apps/page.tsx b/src/app/apps/page.tsx index c1e78a43..e2cb44d8 100644 --- a/src/app/apps/page.tsx +++ b/src/app/apps/page.tsx @@ -2,12 +2,10 @@ import type { Metadata } from "next"; import { pageMetadata } from "@/lib/page-metadata"; import { safeJsonLd, buildBreadcrumbJsonLd } from "@/lib/jsonld"; import { SITE } from "@/data/site"; -import { fetchEVMRevenue } from "@/lib/evm-exec"; +import { fetchDeFiLlamaRevenue } from "@/lib/defillama"; import { fetchExecLeaderboard } from "@/lib/solana-exec"; -import { fetchSolPrice } from "@/lib/sol-price"; import { TRADING_APPS } from "@/lib/trading-apps-config"; import { TradingAppsLeaderboard, type UnifiedAppRow } from "@/components/trading-apps-leaderboard"; -import { RevenueSummary } from "@/components/revenue-summary"; import { ExecChainTabs } from "@/components/exec-chain-tabs"; import Link from "next/link"; @@ -25,65 +23,39 @@ export const revalidate = 300; const WINDOWS = ["24h", "7d", "30d"] as const; export default async function AppsHubPage() { - const [evmData, solanaData, solPrice] = await Promise.all([ - fetchEVMRevenue(), + const [dlData, solanaData] = await Promise.all([ + fetchDeFiLlamaRevenue(), fetchExecLeaderboard(), - fetchSolPrice(), ]); - const evmByPlatform = new Map( - (evmData?.platforms ?? []).map((p) => [p.platform, p]) - ); - - const solanaByPlatform = new Map( - (solanaData?.platforms ?? []).map((p) => [p.platform, p]) - ); - const rows: UnifiedAppRow[] = TRADING_APPS.map((meta) => { - const evmRow = meta.evmKey ? evmByPlatform.get(meta.evmKey) : undefined; - const solRow = meta.solanaKey ? solanaByPlatform.get(meta.solanaKey) : undefined; + const dl = dlData.get(meta.id); + const chain24h = dl?.chain24h ?? {}; - const ethChain = evmRow?.chains["ethereum"]; - 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 solanaFees = chain24h["solana"] ?? null; + const ethFees = chain24h["ethereum"] ?? null; + const bscFees = chain24h["bsc"] ?? null; + const baseFees = chain24h["base"] ?? null; const windows: UnifiedAppRow["windows"] = {}; - 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, + total: dl ? (w === "24h" ? dl.total24h : w === "7d" ? dl.total7d : dl.total30d) : 0, }; } return { meta, windows, - stableOnly: { - ethereum: ethChain?.coverage === "stable-only", - bsc: bscChain?.coverage === "stable-only", - base: baseChain?.coverage === "stable-only", - }, + stableOnly: { ethereum: false, bsc: false, base: false }, }; }); - const updatedAt = evmData?.updatedAt ?? solanaData?.updatedAt ?? null; + const updatedAt = solanaData?.updatedAt ?? null; const breadcrumb = { "@context": "https://schema.org", @@ -112,19 +84,13 @@ export default async function AppsHubPage() { -

- Solana fees = txCount × avgPlatformFeeLamports / 1e9 × SOL price. - EVM fees = stable (USDC/USDT) + native where traceable, always 24h. +

+ Revenue data from DeFiLlama. + Chain columns show 24h fees; 7d/30d totals update with window selection.

- {evmData && evmData.platforms.length > 0 && ( -
- -
- )} -
- +
diff --git a/src/components/exec-chain-tabs.tsx b/src/components/exec-chain-tabs.tsx index 5b571531..61a77371 100644 --- a/src/components/exec-chain-tabs.tsx +++ b/src/components/exec-chain-tabs.tsx @@ -109,10 +109,12 @@ export function ExecChainTabs({ }) { const [chain, setChain] = useState("solana"); + const visibleTabs = evmData ? TABS : TABS.filter((t) => t.key === "solana"); + return (
- {TABS.map((tab) => ( + {visibleTabs.map((tab) => (
-

- Revenue data from DeFiLlama. - Chain columns show 24h fees; 7d/30d totals update with window selection. +

+ Solana fees = txCount × avgPlatformFeeLamports / 1e9 × SOL price. + EVM fees = stable (USDC/USDT) + native where traceable, always 24h.

+ {evmData && evmData.platforms.length > 0 && ( +
+ +
+ )} +
- +
From 37f37632fd63076e648a66e026790c22af6c9189 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:39:38 +0200 Subject: [PATCH 5/5] feat: add FOMO relay fees via Dune + Base native ETH scaffold - dune.ts: fetch FOMO relay fees from query 8304081 (dune.tryfomo.fomo_relay_fees) adds 24h/7d/30d relay fees to FOMO Solana column (96% of FOMO revenue) - apps/page.tsx: include fomoRelay in window computation - etherscan.go: ChainIDBase='', BaseScan URL auto-selected when chainID empty - evm-exec: Base NativeEnabled=true (active when BASESCAN_API_KEY set) --- .../evm-exec/internal/platform/platforms.go | 2 +- .../evm-exec/internal/source/etherscan.go | 16 +++++--- src/app/apps/page.tsx | 12 +++++- src/lib/dune.ts | 39 +++++++++++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/lib/dune.ts diff --git a/harnesses/evm-exec/internal/platform/platforms.go b/harnesses/evm-exec/internal/platform/platforms.go index c6d8c2ab..8f2108c3 100644 --- a/harnesses/evm-exec/internal/platform/platforms.go +++ b/harnesses/evm-exec/internal/platform/platforms.go @@ -59,7 +59,7 @@ var PlatformConfig = map[string]map[string]EVMPlatform{ }, "base": { FeeCollector: "0x16388de42c5829fd0e88c8eb001ef43bfc93f177", - NativeEnabled: false, // requires BASESCAN_API_KEY (paid plan) + NativeEnabled: true, // via BaseScan API (gated on BASESCAN_API_KEY env) ERC20Tokens: []string{USDC_BASE}, BootstrapDays: 30, }, diff --git a/harnesses/evm-exec/internal/source/etherscan.go b/harnesses/evm-exec/internal/source/etherscan.go index dce32391..78843e10 100644 --- a/harnesses/evm-exec/internal/source/etherscan.go +++ b/harnesses/evm-exec/internal/source/etherscan.go @@ -11,11 +11,11 @@ import ( "time" ) -const etherscanV2 = "https://api.etherscan.io/v2/api" - const ( + etherscanV2 = "https://api.etherscan.io/v2/api" + basescanAPI = "https://api.basescan.org/api" ChainIDEthereum = "1" - ChainIDBase = "8453" + ChainIDBase = "" // BaseScan is single-chain; no chainid param needed ) // NativeTx is an ETH transfer (internal or normal) to a monitored address. @@ -154,8 +154,11 @@ func fetchEtherscanTxs(ctx context.Context, apiKey, chainID, address string, sta } func etherscanPage(ctx context.Context, apiKey, chainID, address, action string, startBlock uint64, endBlock, page, offset int) ([]etherscanTx, error) { + apiURL := etherscanV2 + if chainID == "" { + apiURL = basescanAPI + } params := url.Values{ - "chainid": {chainID}, "module": {"account"}, "action": {action}, "address": {address}, @@ -166,8 +169,11 @@ func etherscanPage(ctx context.Context, apiKey, chainID, address, action string, "sort": {"asc"}, "apikey": {apiKey}, } + if chainID != "" { + params.Set("chainid", chainID) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, etherscanV2+"?"+params.Encode(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL+"?"+params.Encode(), nil) if err != nil { return nil, err } diff --git a/src/app/apps/page.tsx b/src/app/apps/page.tsx index 92461fe6..c66da3b4 100644 --- a/src/app/apps/page.tsx +++ b/src/app/apps/page.tsx @@ -5,6 +5,7 @@ import { SITE } from "@/data/site"; import { fetchEVMRevenue } from "@/lib/evm-exec"; import { fetchExecLeaderboard } from "@/lib/solana-exec"; import { fetchSolPrice } from "@/lib/sol-price"; +import { fetchFOMORelayFees } from "@/lib/dune"; import { TRADING_APPS } from "@/lib/trading-apps-config"; import { TradingAppsLeaderboard, type UnifiedAppRow } from "@/components/trading-apps-leaderboard"; import { RevenueSummary } from "@/components/revenue-summary"; @@ -25,10 +26,11 @@ export const revalidate = 300; const WINDOWS = ["24h", "7d", "30d"] as const; export default async function AppsHubPage() { - const [evmData, solanaData, solPrice] = await Promise.all([ + const [evmData, solanaData, solPrice, fomoRelay] = await Promise.all([ fetchEVMRevenue(), fetchExecLeaderboard(), fetchSolPrice(), + fetchFOMORelayFees(), ]); const evmByPlatform = new Map( @@ -62,6 +64,14 @@ export default async function AppsHubPage() { solanaFees = (wData.txCount * wData.avgPlatformFeeLamports) / 1e9 * solPrice; } } + + // FOMO: supplement on-chain fees with off-chain relay fees from Dune + // (relay accounts for ~96% of FOMO's actual revenue) + if (meta.id === "fomo" && fomoRelay) { + const relayFee = w === "24h" ? fomoRelay.fees24h : w === "7d" ? fomoRelay.fees7d : fomoRelay.fees30d; + solanaFees = (solanaFees ?? 0) + relayFee; + } + windows[w] = { solana: solanaFees, ethereum: ethFees, diff --git a/src/lib/dune.ts b/src/lib/dune.ts new file mode 100644 index 00000000..d251e0cb --- /dev/null +++ b/src/lib/dune.ts @@ -0,0 +1,39 @@ +// Dune query 8304081: FOMO relay fees from dune.tryfomo.fomo_relay_fees +// Aggregated by: latest day (24h), last 7d, last 30d. Updated on each execution. +const FOMO_QUERY_ID = 8304081; + +export type FOMORelayFees = { + fees24h: number; + fees7d: number; + fees30d: number; + latestDate: string; +}; + +export async function fetchFOMORelayFees(): Promise { + const apiKey = process.env.DUNE_API_KEY; + if (!apiKey) return null; + + try { + const res = await fetch( + `https://api.dune.com/api/v1/query/${FOMO_QUERY_ID}/results`, + { + headers: { "X-Dune-API-Key": apiKey }, + next: { revalidate: 3600 }, + } + ); + if (!res.ok) return null; + + const json = await res.json(); + const row = json?.result?.rows?.[0]; + if (!row) return null; + + return { + fees24h: row.fees_24h ?? 0, + fees7d: row.fees_7d ?? 0, + fees30d: row.fees_30d ?? 0, + latestDate: row.latest_date ?? "", + }; + } catch { + return null; + } +}