diff --git a/benchmarks/solana-trading-platform-wars.yml b/benchmarks/solana-trading-platform-wars.yml index 5c42bac9..1ea97b67 100644 --- a/benchmarks/solana-trading-platform-wars.yml +++ b/benchmarks/solana-trading-platform-wars.yml @@ -2,10 +2,10 @@ slug: solana-trading-platform-wars number: "201" -title: "Solana trading platform leaderboard: pump.fun vs GMGN vs Axiom vs Fomo, live 24h volume" -seo_title: "Solana trading platform comparison 2026: pump.fun, GMGN, Axiom, Fomo, Trojan" -seo_description: "{{best_name}} leads Solana trading platforms at ${{best_p50}} 24h volume. pump.fun vs GMGN vs Axiom vs Fomo vs Trojan vs Padre vs Phantom ranked live from Mobula lighthouse data." -subtitle: Live 24-hour trading volume for each Solana trading platform and Telegram bot. Polled every 15 minutes from Mobula lighthouse, which aggregates on-chain swap events routed through each frontend across all Solana DEX programs. +title: "Trading platform leaderboard: GMGN vs Axiom vs Fomo vs pump.fun, live 24h volume" +seo_title: "Trading platform comparison 2026: GMGN, Axiom, Fomo, pump.fun, Trojan, Terminal" +seo_description: "{{best_name}} leads trading platforms at ${{best_p50}} 24h volume. GMGN vs Axiom vs Fomo vs Trojan vs Terminal vs BasedBot ranked live from Dune community datasets." +subtitle: Live 24-hour trading volume per platform and Telegram bot, cross-chain (Solana + BNB + Base + Robinhood node + HyperEVM + Monad). Sourced from Sacha's Dune community datasets, refreshed every 4 hours. category: Trading status: live @@ -14,7 +14,7 @@ unit: usd higher_is_better: true disclaimer: | - Volume figures are sourced from Mobula's lighthouse aggregator, which indexes on-chain swap events attributed to each trading platform or Telegram bot. Data is refreshed every 15 minutes. Platforms with zero volume may indicate no attributable on-chain activity in the last 24 hours or a temporary attribution gap in Mobula's indexing. + Volume figures are sourced from Sacha's (adam_tehc) Dune community datasets, which aggregate cross-chain trading activity per platform (Solana, BNB Chain, Base, Robinhood node, HyperEVM, Monad, and more). Data is refreshed every 4 hours. pump.fun volume = bonding-curve Solana swaps (dataset_pumpapp_sol_daily) + cross-chain relay swaps (dataset_pumpfun_relay_daily). Fomo = Solana only (dataset_fomo_sol_daily). All others sum across all blockchains. seo_intro: | Solana's trading frontend market is a contested race between GMGN, Axiom and @@ -42,12 +42,12 @@ abstract: | trading platforms as providers. methodology: - - "Source: Mobula lighthouse (api.mobula.io/api/2/market/lighthouse), byPlatform field. Mobula attributes swap events using on-chain referral tags, program IDs and memo fields." - - "Poll cadence: harness fetches the endpoint every 15 minutes and updates Prometheus gauges. Prometheus scrapes :2112/metrics every 60 seconds." - - "Volume: sum of all swap event notional values (in USD at time of swap) within the last 24 hours for swaps attributed to this platform. Higher = more user activity routed through this frontend." - - "Attribution: Mobula uses on-chain referral tags, program IDs and memo fields. Each swap is attributed to exactly one platform. Platforms without standard referral tags may be undercounted." - - "pump.fun: sourced from Mobula's byLaunchpad bucket (bonding-curve swaps via pump.fun's own frontend). PumpSwap trades routed through GMGN or Axiom count under those platforms." - - "Phantom: wallet-initiated swaps via Jupiter or other aggregators, not active trading sessions." + - "Source: Sacha's (adam_tehc) Dune community datasets (dune.adam_tehc_co.dataset_*_daily). Each dataset covers one trading platform across all blockchains it operates on." + - "Poll cadence: harness queries Dune every 4 hours and updates Prometheus gauges. Prometheus scrapes :2112/metrics every 60 seconds." + - "Volume: sum of volume_usd across all blockchains for the most recent complete day in each platform's dataset. Higher = more total user activity." + - "pump.fun: dataset_pumpapp_sol_daily (Solana bonding-curve) + dataset_pumpfun_relay_daily filtered to req_class='swap'. pump.fun cut fees to 0% in Aug 2026; volume is for their own frontend only (pumpapp), not all bonding-curve activity." + - "fomo: dataset_fomo_sol_daily (Solana only). Includes Fomo's relay cross-chain volume when Sacha incorporates it." + - "BasedBot: multi-chain bot (Robinhood node, BNB, Base, Solana, ETH, HyperEVM, Avalanche)." findings: - "{{best_name}} leads Solana trading platforms at ${{best_p50}} 24h volume." @@ -56,11 +56,11 @@ findings: - "Trojan serves the Telegram-native segment and accounts for significant bot-driven Solana swap flow. Padre is a web trading terminal, not a Telegram bot." - "Phantom collects a 0.85% platform fee on in-wallet swaps routed to its own fee wallets; it is a monetised frontend, not a passive aggregator pass-through." -source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/solana-trading-platform-wars +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/dune-platform-volume prometheus: window: 24h - freshness_metric: solana_platform_volume_24h_usd + freshness_metric: dune_platform_volume_24h_usd faq: - q: "Which Solana trading platform has the most volume?" @@ -79,106 +79,106 @@ faq: a: "BullX has limited Solana attribution in Mobula's lighthouse data. BullX primarily targets EVM chains and its Solana swap volume may not be tagged with referral data that Mobula can attribute. DEX volume for BullX across chains is tracked in bench 205." providers: - - slug: pump-fun - name: pump.fun - tag: "Solana meme launchpad, bonding curve" - formula: "24h swap volume in USD on pump.fun's bonding curve via pump.fun's own frontend (Mobula lighthouse byLaunchpad[name=PumpFun].volumeUSD.24h, updated every 15 min)." - queries: - p50: solana_platform_volume_24h_usd{platform="pump-fun"} - p90: solana_platform_volume_24h_usd{platform="pump-fun"} - p99: solana_platform_volume_24h_usd{platform="pump-fun"} - mean: solana_platform_volume_24h_usd{platform="pump-fun"} - success: solana_platform_health{platform="pump-fun"} - sample_size: solana_platform_fees_24h_usd{platform="pump-fun"} - series: solana_platform_volume_24h_usd{platform="pump-fun"} - - slug: gmgn name: GMGN - tag: "AI trading terminal on Solana" - formula: "24h swap volume in USD attributed to GMGN (Mobula lighthouse byPlatform[name=GMGN].volumeUSD.24h, updated every 15 min)." + tag: "AI trading terminal, multi-chain" + formula: "24h cross-chain volume in USD (Solana + BNB + Robinhood node + Base + ETH + more) from dune.adam_tehc_co.dataset_gmgn_daily, latest complete day." queries: - p50: solana_platform_volume_24h_usd{platform="gmgn"} - p90: solana_platform_volume_24h_usd{platform="gmgn"} - p99: solana_platform_volume_24h_usd{platform="gmgn"} - mean: solana_platform_volume_24h_usd{platform="gmgn"} - success: solana_platform_health{platform="gmgn"} - sample_size: solana_platform_fees_24h_usd{platform="gmgn"} - series: solana_platform_volume_24h_usd{platform="gmgn"} + p50: dune_platform_volume_24h_usd{platform="gmgn"} + p90: dune_platform_volume_24h_usd{platform="gmgn"} + p99: dune_platform_volume_24h_usd{platform="gmgn"} + mean: dune_platform_volume_24h_usd{platform="gmgn"} + success: dune_platform_volume_health{platform="gmgn"} + sample_size: dune_platform_volume_24h_usd{platform="gmgn"} + series: dune_platform_volume_24h_usd{platform="gmgn"} - slug: axiom name: Axiom - tag: "Web trading terminal on Solana" - formula: "24h swap volume in USD attributed to Axiom (Mobula lighthouse byPlatform[name=Axiom].volumeUSD.24h, updated every 15 min)." + tag: "Web trading terminal, multi-chain" + formula: "24h cross-chain volume in USD (Solana + Robinhood node + BNB + ETH) from dune.adam_tehc_co.dataset_axiom_daily, latest complete day." queries: - p50: solana_platform_volume_24h_usd{platform="axiom"} - p90: solana_platform_volume_24h_usd{platform="axiom"} - p99: solana_platform_volume_24h_usd{platform="axiom"} - mean: solana_platform_volume_24h_usd{platform="axiom"} - success: solana_platform_health{platform="axiom"} - sample_size: solana_platform_fees_24h_usd{platform="axiom"} - series: solana_platform_volume_24h_usd{platform="axiom"} + p50: dune_platform_volume_24h_usd{platform="axiom"} + p90: dune_platform_volume_24h_usd{platform="axiom"} + p99: dune_platform_volume_24h_usd{platform="axiom"} + mean: dune_platform_volume_24h_usd{platform="axiom"} + success: dune_platform_volume_health{platform="axiom"} + sample_size: dune_platform_volume_24h_usd{platform="axiom"} + series: dune_platform_volume_24h_usd{platform="axiom"} - slug: fomo name: Fomo tag: "Solana trading platform" - formula: "24h swap volume in USD attributed to Fomo (Mobula lighthouse byPlatform[name=Fomo].volumeUSD.24h, updated every 15 min). Fees from DeFiLlama (dailyRevenue)." + formula: "24h Solana volume in USD from dune.adam_tehc_co.dataset_fomo_sol_daily, latest complete day." + queries: + p50: dune_platform_volume_24h_usd{platform="fomo"} + p90: dune_platform_volume_24h_usd{platform="fomo"} + p99: dune_platform_volume_24h_usd{platform="fomo"} + mean: dune_platform_volume_24h_usd{platform="fomo"} + success: dune_platform_volume_health{platform="fomo"} + sample_size: dune_platform_volume_24h_usd{platform="fomo"} + series: dune_platform_volume_24h_usd{platform="fomo"} + + - slug: basedbot + name: BasedBot + tag: "Multi-chain trading bot" + formula: "24h cross-chain volume in USD (Robinhood node + BNB + Base + Solana + ETH + HyperEVM) from dune.adam_tehc_co.dataset_basedbot_daily, latest complete day." queries: - p50: solana_platform_volume_24h_usd{platform="fomo"} - p90: solana_platform_volume_24h_usd{platform="fomo"} - p99: solana_platform_volume_24h_usd{platform="fomo"} - mean: solana_platform_volume_24h_usd{platform="fomo"} - success: solana_platform_health{platform="fomo"} - sample_size: defillama_dex_fees_24h_usd{platform="fomo"} - series: solana_platform_volume_24h_usd{platform="fomo"} + p50: dune_platform_volume_24h_usd{platform="basedbot"} + p90: dune_platform_volume_24h_usd{platform="basedbot"} + p99: dune_platform_volume_24h_usd{platform="basedbot"} + mean: dune_platform_volume_24h_usd{platform="basedbot"} + success: dune_platform_volume_health{platform="basedbot"} + sample_size: dune_platform_volume_24h_usd{platform="basedbot"} + series: dune_platform_volume_24h_usd{platform="basedbot"} - - slug: trojan - name: Trojan - tag: "Telegram trading bot on Solana" - formula: "24h swap volume in USD attributed to Trojan (Mobula lighthouse byPlatform[name=Trojan].volumeUSD.24h, updated every 15 min)." + - slug: pump-fun + name: pump.fun + tag: "Solana meme launchpad + cross-chain relay" + formula: "24h volume = pump.fun app Solana swaps (dataset_pumpapp_sol_daily) + cross-chain relay swaps (dataset_pumpfun_relay_daily, req_class=swap). pump.fun cut fees to 0% Aug 2026; volume is for pumpapp frontend only." queries: - p50: solana_platform_volume_24h_usd{platform="trojan"} - p90: solana_platform_volume_24h_usd{platform="trojan"} - p99: solana_platform_volume_24h_usd{platform="trojan"} - mean: solana_platform_volume_24h_usd{platform="trojan"} - success: solana_platform_health{platform="trojan"} - sample_size: solana_platform_fees_24h_usd{platform="trojan"} - series: solana_platform_volume_24h_usd{platform="trojan"} + p50: dune_platform_volume_24h_usd{platform="pump-fun"} + p90: dune_platform_volume_24h_usd{platform="pump-fun"} + p99: dune_platform_volume_24h_usd{platform="pump-fun"} + mean: dune_platform_volume_24h_usd{platform="pump-fun"} + success: dune_platform_volume_health{platform="pump-fun"} + sample_size: dune_platform_volume_24h_usd{platform="pump-fun"} + series: dune_platform_volume_24h_usd{platform="pump-fun"} - slug: padre - name: Padre - tag: "AI web trading terminal on Solana" - formula: "24h swap volume in USD attributed to Padre (Mobula lighthouse byPlatform[name=Padre].volumeUSD.24h, updated every 15 min)." + name: Terminal + tag: "pump.fun's own trading app (formerly Padre, acquired Apr 2025)" + formula: "24h cross-chain volume in USD (Solana + Robinhood node + Base + BNB) from dune.adam_tehc_co.dataset_terminal_daily, latest complete day." + queries: + p50: dune_platform_volume_24h_usd{platform="padre"} + p90: dune_platform_volume_24h_usd{platform="padre"} + p99: dune_platform_volume_24h_usd{platform="padre"} + mean: dune_platform_volume_24h_usd{platform="padre"} + success: dune_platform_volume_health{platform="padre"} + sample_size: dune_platform_volume_24h_usd{platform="padre"} + series: dune_platform_volume_24h_usd{platform="padre"} + + - slug: trojan + name: Trojan + tag: "Telegram trading bot on Solana" + formula: "24h Solana volume in USD from dune.adam_tehc_co.dataset_trojan_daily, latest complete day." queries: - p50: solana_platform_volume_24h_usd{platform="padre"} - p90: solana_platform_volume_24h_usd{platform="padre"} - p99: solana_platform_volume_24h_usd{platform="padre"} - mean: solana_platform_volume_24h_usd{platform="padre"} - success: solana_platform_health{platform="padre"} - sample_size: solana_platform_fees_24h_usd{platform="padre"} - series: solana_platform_volume_24h_usd{platform="padre"} + p50: dune_platform_volume_24h_usd{platform="trojan"} + p90: dune_platform_volume_24h_usd{platform="trojan"} + p99: dune_platform_volume_24h_usd{platform="trojan"} + mean: dune_platform_volume_24h_usd{platform="trojan"} + success: dune_platform_volume_health{platform="trojan"} + sample_size: dune_platform_volume_24h_usd{platform="trojan"} + series: dune_platform_volume_24h_usd{platform="trojan"} - slug: photon name: Photon tag: "Solana trading terminal" - formula: "24h swap volume in USD attributed to Photon (Mobula lighthouse byPlatform[name=Photon].volumeUSD.24h, updated every 15 min)." - queries: - p50: solana_platform_volume_24h_usd{platform="photon"} - p90: solana_platform_volume_24h_usd{platform="photon"} - p99: solana_platform_volume_24h_usd{platform="photon"} - mean: solana_platform_volume_24h_usd{platform="photon"} - success: solana_platform_health{platform="photon"} - sample_size: solana_platform_fees_24h_usd{platform="photon"} - series: solana_platform_volume_24h_usd{platform="photon"} - - - slug: maestro - name: Maestro - tag: "Telegram trading bot on Solana" - formula: "24h swap volume in USD attributed to Maestro (Mobula lighthouse byPlatform[name=Maestro].volumeUSD.24h, updated every 15 min)." + formula: "24h Solana volume in USD from dune.adam_tehc_co.dataset_photon_daily, latest complete day." queries: - p50: solana_platform_volume_24h_usd{platform="maestro"} - p90: solana_platform_volume_24h_usd{platform="maestro"} - p99: solana_platform_volume_24h_usd{platform="maestro"} - mean: solana_platform_volume_24h_usd{platform="maestro"} - success: solana_platform_health{platform="maestro"} - sample_size: solana_platform_fees_24h_usd{platform="maestro"} - series: solana_platform_volume_24h_usd{platform="maestro"} + p50: dune_platform_volume_24h_usd{platform="photon"} + p90: dune_platform_volume_24h_usd{platform="photon"} + p99: dune_platform_volume_24h_usd{platform="photon"} + mean: dune_platform_volume_24h_usd{platform="photon"} + success: dune_platform_volume_health{platform="photon"} + sample_size: dune_platform_volume_24h_usd{platform="photon"} + series: dune_platform_volume_24h_usd{platform="photon"} diff --git a/harnesses/dune-platform-volume/Dockerfile b/harnesses/dune-platform-volume/Dockerfile new file mode 100644 index 00000000..2f083814 --- /dev/null +++ b/harnesses/dune-platform-volume/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod go.sum ./ +COPY cmd ./cmd +RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -o /app/harness ./cmd/script + +FROM debian:bookworm-slim +WORKDIR /app +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/harness /app/harness +EXPOSE 2112 +CMD ["/app/harness"] diff --git a/harnesses/dune-platform-volume/cmd/script/dune.go b/harnesses/dune-platform-volume/cmd/script/dune.go new file mode 100644 index 00000000..a10a020c --- /dev/null +++ b/harnesses/dune-platform-volume/cmd/script/dune.go @@ -0,0 +1,174 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// querySQL fetches yesterday's total cross-chain volume per platform from +// Sacha's community datasets (dune.adam_tehc_co.dataset_*). +// +// Dataset schemas: +// dataset_{platform}_daily → day, blockchain, volume_usd, fees_usd, txns, wallets +// dataset_fomo_sol_daily → day, volume_usd, fees_usd, txns, wallets, wallets_dedup (no blockchain col) +// dataset_pumpapp_sol_daily → day, blockchain, volume_usd, fees_usd, txns, wallets, wallets_dedup +// dataset_pumpfun_relay_daily → day, fill_chain, req_class, volume_usd, app_fees_usd, txns, wallets +// +// pump.fun total = pumpapp Solana + relay swaps (req_class='swap'). +// All others: SUM(volume_usd) across all blockchains for latest available day. +const querySQL = ` +WITH latest AS ( + SELECT + 'gmgn' AS platform, SUM(volume_usd) AS volume_usd FROM dune.adam_tehc_co.dataset_gmgn_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_gmgn_daily) + UNION ALL + SELECT 'axiom', SUM(volume_usd) FROM dune.adam_tehc_co.dataset_axiom_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_axiom_daily) + UNION ALL + SELECT 'trojan', SUM(volume_usd) FROM dune.adam_tehc_co.dataset_trojan_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_trojan_daily) + UNION ALL + SELECT 'padre', SUM(volume_usd) FROM dune.adam_tehc_co.dataset_terminal_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_terminal_daily) + UNION ALL + SELECT 'photon', SUM(volume_usd) FROM dune.adam_tehc_co.dataset_photon_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_photon_daily) + UNION ALL + SELECT 'basedbot', SUM(volume_usd) FROM dune.adam_tehc_co.dataset_basedbot_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_basedbot_daily) + UNION ALL + SELECT 'fomo', volume_usd FROM dune.adam_tehc_co.dataset_fomo_sol_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_fomo_sol_daily) + UNION ALL + SELECT 'pump-fun', + COALESCE((SELECT SUM(volume_usd) FROM dune.adam_tehc_co.dataset_pumpapp_sol_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_pumpapp_sol_daily)), 0) + + COALESCE((SELECT SUM(volume_usd) FROM dune.adam_tehc_co.dataset_pumpfun_relay_daily + WHERE day = (SELECT MAX(day) FROM dune.adam_tehc_co.dataset_pumpfun_relay_daily) + AND req_class = 'swap'), 0) +) +SELECT platform, volume_usd FROM latest ORDER BY volume_usd DESC +` + +const duneBase = "https://api.dune.com/api/v1" + +type duneClient struct { + apiKey string + http *http.Client +} + +type duneRow struct { + Platform string `json:"platform"` + VolumeUSD float64 `json:"volume_usd"` +} + +func newDuneClient(apiKey string) *duneClient { + return &duneClient{ + apiKey: apiKey, + http: &http.Client{Timeout: 30 * time.Second}, + } +} + +func (d *duneClient) req(method, path string, body any) (*http.Response, error) { + var r io.Reader + if body != nil { + b, err := json.Marshal(body) + if err != nil { + return nil, err + } + r = bytes.NewReader(b) + } + req, err := http.NewRequest(method, duneBase+path, r) + if err != nil { + return nil, err + } + req.Header.Set("X-Dune-Api-Key", d.apiKey) + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + return d.http.Do(req) +} + +func (d *duneClient) createQuery() (string, error) { + payload := map[string]any{ + "name": "OCB: Cross-chain trading platform volume (Sacha datasets)", + "query_sql": querySQL, + "is_private": false, + "parameters": []any{}, + } + resp, err := d.req("POST", "/query", payload) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, body) + } + var out struct { + QueryID int `json:"query_id"` + } + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return "", err + } + return fmt.Sprintf("%d", out.QueryID), nil +} + +func (d *duneClient) execute(queryID string) (string, error) { + resp, err := d.req("POST", "/query/"+queryID+"/execute", map[string]any{}) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, body) + } + var out struct { + ExecutionID string `json:"execution_id"` + } + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return "", err + } + return out.ExecutionID, nil +} + +func (d *duneClient) executionState(execID string) (string, error) { + resp, err := d.req("GET", "/execution/"+execID+"/status", nil) + if err != nil { + return "", err + } + defer resp.Body.Close() + var out struct { + State string `json:"state"` + } + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return "", err + } + return out.State, nil +} + +func (d *duneClient) latestResult(queryID string) ([]duneRow, error) { + resp, err := d.req("GET", "/query/"+queryID+"/results", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, body) + } + var out struct { + Result struct { + Rows []duneRow `json:"rows"` + } `json:"result"` + } + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return nil, err + } + return out.Result.Rows, nil +} diff --git a/harnesses/dune-platform-volume/cmd/script/main.go b/harnesses/dune-platform-volume/cmd/script/main.go new file mode 100644 index 00000000..84ce6664 --- /dev/null +++ b/harnesses/dune-platform-volume/cmd/script/main.go @@ -0,0 +1,127 @@ +// dune-platform-volume -- Bench 201 (replaces Mobula lighthouse source) +// +// Queries Sacha's community datasets on Dune (dune.adam_tehc_co.dataset_*_daily) +// for cross-chain trading platform volume. Each dataset has columns: +// day, blockchain, volume_usd, fees_usd, txns, wallets. +// pump.fun uses dataset_pumpapp_sol_daily (Solana) + dataset_pumpfun_relay_daily (relay). +// fomo uses dataset_fomo_sol_daily (no blockchain column). +// +// Required env vars: +// DUNE_API_KEY - Dune Analytics API key +// +// Metrics on :2112/metrics: +// dune_platform_volume_24h_usd{platform} +// dune_platform_volume_health{platform} +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +const ( + fetchInterval = 15 * time.Minute + refreshInterval = 4 * time.Hour +) + +func main() { + fmt.Println("=== dune-platform-volume harness ===") + fmt.Println("OpenChainBench Bench 201 -- Cross-chain trading platform volume via Sacha's Dune datasets.") + + apiKey := os.Getenv("DUNE_API_KEY") + if apiKey == "" { + fmt.Fprintln(os.Stderr, "[fatal] DUNE_API_KEY not set") + os.Exit(1) + } + + queryID := os.Getenv("DUNE_QUERY_ID") + client := newDuneClient(apiKey) + + if queryID == "" { + fmt.Println("[init] DUNE_QUERY_ID not set, creating Dune query...") + id, err := client.createQuery() + if err != nil { + fmt.Fprintf(os.Stderr, "[fatal] failed to create Dune query: %v\n", err) + os.Exit(1) + } + fmt.Printf("[init] Dune query created: %s\n", id) + fmt.Printf("[init] Set DUNE_QUERY_ID=%s and restart the harness.\n", id) + os.Exit(0) + } + + go func() { + if err := startMetricsServer(":2112"); err != nil { + fmt.Printf("[fatal] metrics server: %v\n", err) + os.Exit(1) + } + }() + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGTERM) + + runFetch(client, queryID) + go func() { + execID, err := client.execute(queryID) + if err != nil { + fmt.Printf("[refresh] execute failed: %v\n", err) + return + } + pollUntilDone(client, queryID, execID) + }() + + fetchTick := time.NewTicker(fetchInterval) + refreshTick := time.NewTicker(refreshInterval) + defer fetchTick.Stop() + defer refreshTick.Stop() + + for { + select { + case <-sig: + fmt.Println("[shutdown] received signal") + return + case <-fetchTick.C: + runFetch(client, queryID) + case <-refreshTick.C: + execID, err := client.execute(queryID) + if err != nil { + fmt.Printf("[refresh] execute failed: %v\n", err) + continue + } + go pollUntilDone(client, queryID, execID) + } + } +} + +func runFetch(c *duneClient, queryID string) { + rows, err := c.latestResult(queryID) + if err != nil { + fmt.Printf("[fetch] failed: %v\n", err) + return + } + publishRows(rows) + fmt.Printf("[fetch] updated %d platform(s)\n", len(rows)) +} + +func pollUntilDone(c *duneClient, queryID, execID string) { + for range 60 { + time.Sleep(30 * time.Second) + state, err := c.executionState(execID) + if err != nil { + fmt.Printf("[poll] state check failed: %v\n", err) + return + } + switch state { + case "QUERY_STATE_COMPLETED": + fmt.Printf("[poll] execution %s complete\n", execID) + runFetch(c, queryID) + return + case "QUERY_STATE_FAILED", "QUERY_STATE_CANCELLED", "QUERY_STATE_EXPIRED": + fmt.Printf("[poll] execution %s ended with state %s\n", execID, state) + return + } + } + fmt.Printf("[poll] execution %s timed out waiting\n", execID) +} diff --git a/harnesses/dune-platform-volume/cmd/script/metrics.go b/harnesses/dune-platform-volume/cmd/script/metrics.go new file mode 100644 index 00000000..b087b85a --- /dev/null +++ b/harnesses/dune-platform-volume/cmd/script/metrics.go @@ -0,0 +1,44 @@ +package main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + platformVolume = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "dune_platform_volume_24h_usd", + Help: "24h trading volume in USD per platform, summed across all blockchains (Sacha's Dune community datasets).", + }, []string{"platform"}) + + platformHealth = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "dune_platform_volume_health", + Help: "1 if this platform returned volume data in the last Dune poll.", + }, []string{"platform"}) +) + +func init() { + prometheus.MustRegister(platformVolume, platformHealth) +} + +func publishRows(rows []duneRow) { + for _, r := range rows { + if r.Platform == "" { + continue + } + platformVolume.WithLabelValues(r.Platform).Set(r.VolumeUSD) + platformHealth.WithLabelValues(r.Platform).Set(1) + } +} + +func startMetricsServer(addr string) error { + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.Handler()) + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("OK")) + }) + return http.ListenAndServe(addr, mux) +} diff --git a/harnesses/dune-platform-volume/go.mod b/harnesses/dune-platform-volume/go.mod new file mode 100644 index 00000000..342382f7 --- /dev/null +++ b/harnesses/dune-platform-volume/go.mod @@ -0,0 +1,18 @@ +module dune-platform-volume + +go 1.24.0 + +require github.com/prometheus/client_golang v1.23.2 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect +) diff --git a/harnesses/dune-platform-volume/go.sum b/harnesses/dune-platform-volume/go.sum new file mode 100644 index 00000000..d6b8ca98 --- /dev/null +++ b/harnesses/dune-platform-volume/go.sum @@ -0,0 +1,46 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/app/trading-apps/page.tsx b/src/app/trading-apps/page.tsx index ea76cd87..ed66e625 100644 --- a/src/app/trading-apps/page.tsx +++ b/src/app/trading-apps/page.tsx @@ -27,17 +27,21 @@ const BENCH_SLUGS = [ "app-store-ratings", ] as const; -// pump.fun is tracked via byLaunchpad (bonding curve, no referral tag system). -// All other platforms are tracked via byPlatform (referral tags). Volume is -// not directly comparable across the two groups — tooltip discloses this. +// Volume source: Sacha's (adam_tehc) Dune community datasets (dataset_*_daily). +// Each platform is a separate dataset with cross-chain breakdown (blockchain col). +// pump.fun = pumpapp Solana + relay swaps, shown with proper attribution now. +// Terminal (slug: padre) = pump.fun's own trading app (formerly Padre, acq Apr 2025). +// BasedBot is a multi-chain bot (Robinhood node, BNB, Base, Solana, ETH, HyperEVM). const PLATFORMS = [ { slug: "pump-fun", name: "pump.fun" }, + { slug: "padre", name: "Terminal" }, { 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: "basedbot", name: "BasedBot" }, ] as const; const COLUMNS = [ @@ -46,7 +50,7 @@ const COLUMNS = [ label: "24h Volume", bench: "solana-trading-platform-wars", fmt: fmtUSD, - tip: "Mobula attribution. pump.fun = bonding-curve launchpad volume. All others = terminal routing volume via referral tag. Not directly comparable across the two groups.", + tip: "Cross-chain 24h volume from Sacha's (adam_tehc) Dune community datasets. Includes Solana + BNB + Base + Robinhood node + HyperEVM + Monad etc. pump.fun = pumpapp frontend only (not all bonding-curve). Terminal = pump.fun's own trading app (formerly Padre, acq. Apr 2025).", higherBetter: true, }, { @@ -420,9 +424,11 @@ export default async function TradingAppsHubPage() { Methodology

- Volume via Mobula lighthouse: pump.fun uses bonding-curve launchpad - attribution; terminals use referral-tag attribution — not directly - comparable across the two groups. Swap transaction counts from Dune + Volume from Sacha's (adam_tehc) Dune community datasets: cross-chain + totals per platform (Solana + BNB + Base + Robinhood node + HyperEVM + + Monad). pump.fun = pumpapp frontend + relay swaps only, not all + bonding-curve activity. Terminal = pump.fun's own app (formerly Padre, + acq. Apr 2025). Swap transaction counts from Dune Analytics (pump.fun: dex-level; terminals: fee-wallet detection). Average trade size = volume ÷ trade count, includes bots and MEV. Fee rate = on-chain fee revenue ÷ fee-paying volume (Dune tx join);