diff --git a/benchmarks/solana-avg-trade-size.yml b/benchmarks/solana-avg-trade-size.yml new file mode 100644 index 00000000..bf619e6d --- /dev/null +++ b/benchmarks/solana-avg-trade-size.yml @@ -0,0 +1,197 @@ +# OpenChainBench. Bench 206 + +slug: solana-avg-trade-size +number: "206" +title: "Average swap size on Solana: pump.fun vs GMGN vs Fomo vs Axiom, live" +seo_title: "Average trade size by Solana trading platform 2026: pump.fun vs GMGN vs Axiom vs Fomo" +seo_description: "{{best_name}} has the largest average swap size at ${{best_p50}} per trade. pump.fun vs GMGN vs Axiom vs Fomo vs Trojan ranked by average trade size, live from Mobula lighthouse." +subtitle: Live average swap size in USD per attributed transaction for each Solana trading platform and launchpad. Polled every 15 minutes from Mobula lighthouse. Reveals the trader profile of each platform, from retail micro-trades to sophisticated large-order flow. + +category: Trading +status: live +metric: Avg swap size +unit: usd +higher_is_better: true + +disclaimer: | + Average swap size is computed as 24h attributed volume divided by 24h attributed trade count from Mobula lighthouse. Both numerator and denominator include bot and MEV activity; the metric reflects all on-chain swaps attributed to each platform, not human-only trades. pump.fun volume comes from the byLaunchpad bucket (bonding-curve swaps via pump.fun's own frontend); all other platforms come from byPlatform. Attribution is mutually exclusive per Mobula's referral-tag methodology. + +seo_intro: | + The average swap size on Solana varies by more than 6x across platforms, revealing + sharply different trader profiles. pump.fun sits at the bottom with roughly $35 per + swap across 3.7 million daily transactions, reflecting the micro-cap degen activity + of its bonding-curve launchpad. GMGN and Trojan sit at the top near $100 to $120 per + swap, serving more sophisticated traders running larger positions. Fomo lands in the + middle at around $79 per swap. This benchmark tracks average swap size live across + all major Solana trading frontends and launchpads, updated every 15 minutes from + Mobula lighthouse attribution data. + +abstract: | + The harness polls Mobula lighthouse (api.mobula.io/api/2/market/lighthouse) every + 15 minutes and records volumeUSD.24h and trades.24h for each platform and launchpad. + Average swap size is derived as volumeUSD.24h divided by trades.24h. The harness + exposes solana_platform_volume_24h_usd, solana_platform_trades_24h and + solana_platform_health per platform. pump.fun is sourced from byLaunchpad and + cross-published into the platform gauge; all others come from byPlatform. + +methodology: + - "Source: Mobula lighthouse (api.mobula.io/api/2/market/lighthouse), poll every 15 min." + - "Average swap size: solana_platform_volume_24h_usd / solana_platform_trades_24h, computed in PromQL." + - "Attribution: Mobula assigns each on-chain swap to a platform via referral tags, program IDs and memo fields embedded in transactions. Each swap is attributed to exactly one platform." + - "Bot inclusion: all attributed swaps are counted including bots and MEV. Platforms with high bot activity (notably pump.fun snipers) show lower average sizes than human-only baselines would suggest." + - "pump.fun: sourced from byLaunchpad (bonding-curve swaps on pump.fun's own site). Trades routed through GMGN or Axiom to pump.fun tokens count under those platforms." + - "Meteora DBC excluded: Meteora DBC counts LP provision events and micro-rebalances as trades, making its avg size of ~$12 not comparable to pure swap activity." + +findings: + - "{{best_name}} attracts the largest average swap at ${{best_p50}} per transaction." + - "pump.fun has the smallest average swap size at around $35 across 3.7 million daily transactions, dominated by bot sniping and micro-cap degen retail flow." + - "GMGN and Trojan lead at $100 to $120 per swap, serving more sophisticated traders with larger position sizes." + - "Fomo sits mid-market at around $79 per swap, positioned between retail and professional." + - "Trade count tells the other story: pump.fun processes more trades per day than all other platforms combined." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/solana-launchpad-wars + +prometheus: + window: 24h + freshness_metric: solana_platform_trades_24h + +faq: + - q: "What does average swap size measure?" + a: "Average swap size is total 24h attributed volume divided by total 24h attributed trade count. It reflects the typical notional size of a single swap on each platform. A platform with many small trades will show a low average; one with fewer but larger trades will show a high average." + - q: "Why does pump.fun have so many trades?" + a: "pump.fun's bonding curve generates enormous transaction volume from sniping bots, copy-trade bots and retail buyers all racing to buy newly launched tokens in the first seconds. The 3.7 million daily trades include a large proportion of automated activity, which also drags the average size down toward $35." + - q: "Is this apple-to-apple between pump.fun and the other platforms?" + a: "Directionally yes. Mobula attributes each swap to exactly one frontend using referral tags. A pump.fun token bought via GMGN counts under GMGN, not pump.fun. So both reflect genuine frontend-attributed activity. The difference is that pump.fun's activity skews heavily toward bots and micro-trades on bonding-curve launches, while GMGN and Axiom serve more secondary-market trading." + - q: "Why is Fomo's average trade size lower than GMGN despite similar positioning?" + a: "Fomo attracts a broader retail base through its social copy-trading and wallet features, which pulls the average down compared to GMGN's power-trader demographic. Fomo also has notably low protocol fees in Mobula's data relative to DeFiLlama, suggesting part of its fee model may not be captured by on-chain referral attribution." + +providers: + - slug: pump-fun + name: pump.fun + tag: "Solana meme launchpad" + formula: "Average swap size in USD on pump.fun's bonding curve (solana_platform_volume_24h_usd{platform=pump-fun} / solana_platform_trades_24h{platform=pump-fun}, Mobula lighthouse byLaunchpad, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="pump-fun"} / solana_platform_trades_24h{platform="pump-fun"} + p90: solana_platform_volume_24h_usd{platform="pump-fun"} / solana_platform_trades_24h{platform="pump-fun"} + p99: solana_platform_volume_24h_usd{platform="pump-fun"} / solana_platform_trades_24h{platform="pump-fun"} + mean: solana_platform_volume_24h_usd{platform="pump-fun"} / solana_platform_trades_24h{platform="pump-fun"} + success: solana_platform_health{platform="pump-fun"} + sample_size: solana_platform_trades_24h{platform="pump-fun"} + series: solana_platform_volume_24h_usd{platform="pump-fun"} / solana_platform_trades_24h{platform="pump-fun"} + + - slug: gmgn + name: GMGN + tag: "AI trading terminal on Solana" + formula: "Average swap size in USD routed through GMGN (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="gmgn"} / solana_platform_trades_24h{platform="gmgn"} + p90: solana_platform_volume_24h_usd{platform="gmgn"} / solana_platform_trades_24h{platform="gmgn"} + p99: solana_platform_volume_24h_usd{platform="gmgn"} / solana_platform_trades_24h{platform="gmgn"} + mean: solana_platform_volume_24h_usd{platform="gmgn"} / solana_platform_trades_24h{platform="gmgn"} + success: solana_platform_health{platform="gmgn"} + sample_size: solana_platform_trades_24h{platform="gmgn"} + series: solana_platform_volume_24h_usd{platform="gmgn"} / solana_platform_trades_24h{platform="gmgn"} + + - slug: trojan + name: Trojan + tag: "Telegram trading bot on Solana" + formula: "Average swap size in USD routed through Trojan (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="trojan"} / solana_platform_trades_24h{platform="trojan"} + p90: solana_platform_volume_24h_usd{platform="trojan"} / solana_platform_trades_24h{platform="trojan"} + p99: solana_platform_volume_24h_usd{platform="trojan"} / solana_platform_trades_24h{platform="trojan"} + mean: solana_platform_volume_24h_usd{platform="trojan"} / solana_platform_trades_24h{platform="trojan"} + success: solana_platform_health{platform="trojan"} + sample_size: solana_platform_trades_24h{platform="trojan"} + series: solana_platform_volume_24h_usd{platform="trojan"} / solana_platform_trades_24h{platform="trojan"} + + - slug: maestro + name: Maestro + tag: "Telegram trading bot on Solana" + formula: "Average swap size in USD routed through Maestro (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="maestro"} / solana_platform_trades_24h{platform="maestro"} + p90: solana_platform_volume_24h_usd{platform="maestro"} / solana_platform_trades_24h{platform="maestro"} + p99: solana_platform_volume_24h_usd{platform="maestro"} / solana_platform_trades_24h{platform="maestro"} + mean: solana_platform_volume_24h_usd{platform="maestro"} / solana_platform_trades_24h{platform="maestro"} + success: solana_platform_health{platform="maestro"} + sample_size: solana_platform_trades_24h{platform="maestro"} + series: solana_platform_volume_24h_usd{platform="maestro"} / solana_platform_trades_24h{platform="maestro"} + + - slug: fomo + name: Fomo + tag: "Solana trading platform" + formula: "Average swap size in USD routed through Fomo (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="fomo"} / solana_platform_trades_24h{platform="fomo"} + p90: solana_platform_volume_24h_usd{platform="fomo"} / solana_platform_trades_24h{platform="fomo"} + p99: solana_platform_volume_24h_usd{platform="fomo"} / solana_platform_trades_24h{platform="fomo"} + mean: solana_platform_volume_24h_usd{platform="fomo"} / solana_platform_trades_24h{platform="fomo"} + success: solana_platform_health{platform="fomo"} + sample_size: solana_platform_trades_24h{platform="fomo"} + series: solana_platform_volume_24h_usd{platform="fomo"} / solana_platform_trades_24h{platform="fomo"} + + - slug: axiom + name: Axiom + tag: "Web trading terminal on Solana" + formula: "Average swap size in USD routed through Axiom (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="axiom"} / solana_platform_trades_24h{platform="axiom"} + p90: solana_platform_volume_24h_usd{platform="axiom"} / solana_platform_trades_24h{platform="axiom"} + p99: solana_platform_volume_24h_usd{platform="axiom"} / solana_platform_trades_24h{platform="axiom"} + mean: solana_platform_volume_24h_usd{platform="axiom"} / solana_platform_trades_24h{platform="axiom"} + success: solana_platform_health{platform="axiom"} + sample_size: solana_platform_trades_24h{platform="axiom"} + series: solana_platform_volume_24h_usd{platform="axiom"} / solana_platform_trades_24h{platform="axiom"} + + - slug: photon + name: Photon + tag: "Solana trading terminal" + formula: "Average swap size in USD routed through Photon (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="photon"} / solana_platform_trades_24h{platform="photon"} + p90: solana_platform_volume_24h_usd{platform="photon"} / solana_platform_trades_24h{platform="photon"} + p99: solana_platform_volume_24h_usd{platform="photon"} / solana_platform_trades_24h{platform="photon"} + mean: solana_platform_volume_24h_usd{platform="photon"} / solana_platform_trades_24h{platform="photon"} + success: solana_platform_health{platform="photon"} + sample_size: solana_platform_trades_24h{platform="photon"} + series: solana_platform_volume_24h_usd{platform="photon"} / solana_platform_trades_24h{platform="photon"} + + - slug: padre + name: Padre + tag: "AI Telegram bot on Solana" + formula: "Average swap size in USD routed through Padre (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="padre"} / solana_platform_trades_24h{platform="padre"} + p90: solana_platform_volume_24h_usd{platform="padre"} / solana_platform_trades_24h{platform="padre"} + p99: solana_platform_volume_24h_usd{platform="padre"} / solana_platform_trades_24h{platform="padre"} + mean: solana_platform_volume_24h_usd{platform="padre"} / solana_platform_trades_24h{platform="padre"} + success: solana_platform_health{platform="padre"} + sample_size: solana_platform_trades_24h{platform="padre"} + series: solana_platform_volume_24h_usd{platform="padre"} / solana_platform_trades_24h{platform="padre"} + + - slug: phantom + name: Phantom + tag: "Solana wallet, DEX aggregator" + formula: "Average swap size in USD routed through Phantom wallet (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="phantom"} / solana_platform_trades_24h{platform="phantom"} + p90: solana_platform_volume_24h_usd{platform="phantom"} / solana_platform_trades_24h{platform="phantom"} + p99: solana_platform_volume_24h_usd{platform="phantom"} / solana_platform_trades_24h{platform="phantom"} + mean: solana_platform_volume_24h_usd{platform="phantom"} / solana_platform_trades_24h{platform="phantom"} + success: solana_platform_health{platform="phantom"} + sample_size: solana_platform_trades_24h{platform="phantom"} + series: solana_platform_volume_24h_usd{platform="phantom"} / solana_platform_trades_24h{platform="phantom"} + + - slug: bloom + name: Bloom + tag: "Solana trading terminal" + formula: "Average swap size in USD routed through Bloom (Mobula lighthouse byPlatform, 15 min poll)." + queries: + p50: solana_platform_volume_24h_usd{platform="bloom"} / solana_platform_trades_24h{platform="bloom"} + p90: solana_platform_volume_24h_usd{platform="bloom"} / solana_platform_trades_24h{platform="bloom"} + p99: solana_platform_volume_24h_usd{platform="bloom"} / solana_platform_trades_24h{platform="bloom"} + mean: solana_platform_volume_24h_usd{platform="bloom"} / solana_platform_trades_24h{platform="bloom"} + success: solana_platform_health{platform="bloom"} + sample_size: solana_platform_trades_24h{platform="bloom"} + series: solana_platform_volume_24h_usd{platform="bloom"} / solana_platform_trades_24h{platform="bloom"} diff --git a/benchmarks/solana-trading-platform-wars.yml b/benchmarks/solana-trading-platform-wars.yml index edceb261..7c5e0a71 100644 --- a/benchmarks/solana-trading-platform-wars.yml +++ b/benchmarks/solana-trading-platform-wars.yml @@ -116,14 +116,14 @@ providers: - 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)." + formula: "24h swap volume in USD attributed to Fomo (Mobula lighthouse byPlatform[name=Fomo].volumeUSD.24h, updated every 15 min). Fees from DeFiLlama (dailyRevenue)." 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: solana_platform_fees_24h_usd{platform="fomo"} + sample_size: defillama_dex_fees_24h_usd{platform="fomo"} series: solana_platform_volume_24h_usd{platform="fomo"} - slug: trojan diff --git a/benchmarks/solana-unique-traders.yml b/benchmarks/solana-unique-traders.yml new file mode 100644 index 00000000..685a8814 --- /dev/null +++ b/benchmarks/solana-unique-traders.yml @@ -0,0 +1,163 @@ +# OpenChainBench. Bench 207 + +slug: solana-unique-traders +number: "207" +title: "Unique daily traders on Solana: pump.fun vs GMGN vs Fomo vs Axiom vs Trojan, live" +seo_title: "Unique daily active traders by Solana platform 2026: pump.fun vs GMGN vs Axiom vs Fomo vs Trojan" +seo_description: "{{best_name}} has the most unique daily traders at {{best_p50}} wallets in 24h. pump.fun vs GMGN vs Fomo vs Axiom vs Trojan ranked by unique daily active users, live from Dune Analytics." +subtitle: Live count of unique wallet addresses that executed a trade through each Solana platform in the last 24 hours. Refreshed hourly via Dune Analytics. Measures real user breadth, not volume, revealing which platforms serve the most distinct traders vs which are dominated by bots or whale concentration. + +category: Trading +status: live +metric: Unique traders 24h +unit: count +higher_is_better: true + +disclaimer: | + Unique trader count is derived from on-chain fee wallet attribution. For pump.fun, the source is pump_fun_solana.trades (distinct trader_id in 24h). For all other platforms, the source is transactions that include the platform's fee wallet as an account key; each unique fee_payer (transaction signer) is counted once. Counts include bots, MEV searchers, and multi-wallet traders. A wallet that trades on two platforms is counted once per platform. + +seo_intro: | + pump.fun dominates Solana by unique daily traders with over 400 thousand distinct + wallets per day, reflecting its position as the default entry point for new Solana + users chasing meme coin launches. GMGN ranks second at roughly 85 thousand unique + daily wallets, serving a more sophisticated trader segment with AI-powered copy + trading and wallet analysis. Axiom and Fomo each attract 20 to 40 thousand unique + daily wallets, while Trojan and Maestro serve a smaller but high-conviction base of + Telegram power users. This benchmark tracks unique daily active wallets per platform + live, updated hourly from Dune Analytics on-chain data, providing a user-breadth + view that volume metrics alone cannot reveal. + +abstract: | + The harness wraps the Dune Analytics API. A single saved query counts distinct + fee_payer addresses in solana.transactions that touched each platform's fee wallet + in the rolling 24-hour window, joined from solana.account_activity for efficiency. + pump.fun uses pump_fun_solana.trades (distinct trader_id). The harness re-executes + the query every hour, polls for completion, then updates the Prometheus gauge + solana_platform_unique_traders_24h per platform. Between executions it serves the + latest cached result every 15 minutes. + +methodology: + - "Source: Dune Analytics (dune.com). Query updated hourly, results fetched every 15 min." + - "pump.fun: COUNT(DISTINCT trader_id) from pump_fun_solana.trades in the rolling 24h window. Covers bonding-curve swaps via pump.fun's own frontend." + - "Other platforms: COUNT(DISTINCT fee_payer) from solana.transactions WHERE the tx account keys include at least one of the platform's known fee wallet addresses AND success = true." + - "Fee wallet sources: DeFiLlama dimension-adapters (master, 2026-08), verified on-chain." + - "Bot inclusion: all wallets counted including automated traders, sniping bots, and copy-trade bots. pump.fun in particular has a large bot fraction which inflates its unique-wallet count." + - "Double-count across platforms: a wallet that trades on GMGN and Axiom in the same 24h window is counted once in each platform's total. Counts are not mutually exclusive across platforms." + - "Fomo: attribution via USDC fee wallet (HrTf9CzXR1dRH4Sof5QrpmGWwpwAf3qZzwCsEjQpXcSq). Wallets whose fees are swept separately may be undercounted." + +findings: + - "{{best_name}} leads Solana platforms with {{best_p50}} unique daily traders." + - "pump.fun attracts the widest trader base at over 400K unique wallets per day, driven by retail and bot activity on meme coin launches." + - "GMGN is the clear leader among trading terminals at roughly 85K unique daily wallets, 2-3x ahead of Axiom and Fomo." + - "Trojan and Maestro serve a smaller but highly active Telegram-native segment, with fewer unique wallets but higher average trade size." + - "Unique traders and volume rankings diverge: Axiom processes more volume than Fomo but has a similar unique-wallet count, suggesting larger average position sizes." + +source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/solana-unique-traders + +prometheus: + window: 24h + freshness_metric: solana_platform_unique_traders_24h + +faq: + - q: "What does unique daily traders measure?" + a: "Unique daily traders counts the number of distinct wallet addresses (fee_payer in Solana transactions) that executed at least one trade through a platform in the rolling 24-hour window. It measures user breadth, not volume or frequency. A wallet that trades 1000 times counts as 1 unique trader." + - q: "Why does pump.fun have so many more unique traders than GMGN?" + a: "pump.fun is the default entry point for new Solana users and meme coin traders. Its bonding curve launch mechanic triggers massive multi-wallet bot activity as snipers race to buy newly created tokens. GMGN serves a more deliberate, power-trader segment that skews toward fewer unique wallets but larger position sizes." + - q: "Is this apple-to-apple between pump.fun and the others?" + a: "Directionally yes, but the data sources differ. pump.fun uses pump_fun_solana.trades (Dune's decoded table), while other platforms use fee wallet attribution from raw transactions. The core question, how many distinct wallets traded through each frontend today, is answered consistently, but bot fractions differ significantly across platforms." + - q: "Why might Fomo's unique trader count be understated?" + a: "Fomo collects protocol fees in USDC via a single fee wallet. Some of Fomo's fee revenue is swept in batches rather than collected per-trade, which may mean some user transactions do not touch the fee wallet directly. The count reflects wallets whose transactions include the fee wallet as an account key." + - q: "How is this different from bench 201 (24h volume)?" + a: "Bench 201 ranks platforms by 24h swap volume in USD. This bench ranks by unique daily active wallets. A platform can have high volume with few wallets (whale-concentrated) or low volume with many wallets (retail-dominated). Both metrics together reveal the full picture." + +providers: + - slug: pump-fun + name: pump.fun + tag: "Solana meme launchpad" + formula: "Unique wallet addresses (distinct trader_id) that executed a bonding-curve swap on pump.fun in the last 24h (Dune: pump_fun_solana.trades, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="pump-fun"} + p90: solana_platform_unique_traders_24h{platform="pump-fun"} + p99: solana_platform_unique_traders_24h{platform="pump-fun"} + mean: solana_platform_unique_traders_24h{platform="pump-fun"} + success: solana_platform_trader_health{platform="pump-fun"} + sample_size: solana_platform_unique_traders_24h{platform="pump-fun"} + series: solana_platform_unique_traders_24h{platform="pump-fun"} + + - slug: gmgn + name: GMGN + tag: "AI trading terminal on Solana" + formula: "Unique wallet addresses (distinct fee_payer) in transactions touching GMGN fee wallets in the last 24h (Dune: solana.account_activity + solana.transactions, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="gmgn"} + p90: solana_platform_unique_traders_24h{platform="gmgn"} + p99: solana_platform_unique_traders_24h{platform="gmgn"} + mean: solana_platform_unique_traders_24h{platform="gmgn"} + success: solana_platform_trader_health{platform="gmgn"} + sample_size: solana_platform_unique_traders_24h{platform="gmgn"} + series: solana_platform_unique_traders_24h{platform="gmgn"} + + - slug: axiom + name: Axiom + tag: "Web trading terminal on Solana" + formula: "Unique wallet addresses in transactions touching Axiom fee wallets in the last 24h (Dune, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="axiom"} + p90: solana_platform_unique_traders_24h{platform="axiom"} + p99: solana_platform_unique_traders_24h{platform="axiom"} + mean: solana_platform_unique_traders_24h{platform="axiom"} + success: solana_platform_trader_health{platform="axiom"} + sample_size: solana_platform_unique_traders_24h{platform="axiom"} + series: solana_platform_unique_traders_24h{platform="axiom"} + + - slug: fomo + name: Fomo + tag: "Solana trading platform" + formula: "Unique wallet addresses in transactions touching Fomo USDC fee wallet in the last 24h (Dune, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="fomo"} + p90: solana_platform_unique_traders_24h{platform="fomo"} + p99: solana_platform_unique_traders_24h{platform="fomo"} + mean: solana_platform_unique_traders_24h{platform="fomo"} + success: solana_platform_trader_health{platform="fomo"} + sample_size: solana_platform_unique_traders_24h{platform="fomo"} + series: solana_platform_unique_traders_24h{platform="fomo"} + + - slug: trojan + name: Trojan + tag: "Telegram trading bot on Solana" + formula: "Unique wallet addresses in transactions touching Trojan fee wallets in the last 24h (Dune, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="trojan"} + p90: solana_platform_unique_traders_24h{platform="trojan"} + p99: solana_platform_unique_traders_24h{platform="trojan"} + mean: solana_platform_unique_traders_24h{platform="trojan"} + success: solana_platform_trader_health{platform="trojan"} + sample_size: solana_platform_unique_traders_24h{platform="trojan"} + series: solana_platform_unique_traders_24h{platform="trojan"} + + - slug: photon + name: Photon + tag: "Solana trading terminal" + formula: "Unique wallet addresses in transactions touching Photon fee wallet in the last 24h (Dune, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="photon"} + p90: solana_platform_unique_traders_24h{platform="photon"} + p99: solana_platform_unique_traders_24h{platform="photon"} + mean: solana_platform_unique_traders_24h{platform="photon"} + success: solana_platform_trader_health{platform="photon"} + sample_size: solana_platform_unique_traders_24h{platform="photon"} + series: solana_platform_unique_traders_24h{platform="photon"} + + - slug: maestro + name: Maestro + tag: "Telegram trading bot on Solana" + formula: "Unique wallet addresses in transactions touching Maestro fee wallets in the last 24h (Dune, hourly refresh)." + queries: + p50: solana_platform_unique_traders_24h{platform="maestro"} + p90: solana_platform_unique_traders_24h{platform="maestro"} + p99: solana_platform_unique_traders_24h{platform="maestro"} + mean: solana_platform_unique_traders_24h{platform="maestro"} + success: solana_platform_trader_health{platform="maestro"} + sample_size: solana_platform_unique_traders_24h{platform="maestro"} + series: solana_platform_unique_traders_24h{platform="maestro"} diff --git a/harnesses/solana-launchpad-wars/cmd/script/metrics.go b/harnesses/solana-launchpad-wars/cmd/script/metrics.go index 31f24ce3..54cb19cb 100644 --- a/harnesses/solana-launchpad-wars/cmd/script/metrics.go +++ b/harnesses/solana-launchpad-wars/cmd/script/metrics.go @@ -40,6 +40,11 @@ var ( Help: "24h platform fees in USD collected by this Solana trading platform.", }, []string{"platform"}) + platformTrades24h = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "solana_platform_trades_24h", + Help: "24h trade count attributed to this Solana trading platform (Mobula lighthouse).", + }, []string{"platform"}) + platformHealth = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "solana_platform_health", Help: "1 if this platform returned data in the last lighthouse poll.", @@ -54,6 +59,7 @@ func init() { launchpadHealth, platformVol24h, platformFees24h, + platformTrades24h, platformHealth, ) } @@ -78,6 +84,7 @@ func publishLighthouse(data *lighthouseData) { if slug == "pumpfun" { platformVol24h.WithLabelValues("pump-fun").Set(e.VolumeUSD.H24) platformFees24h.WithLabelValues("pump-fun").Set(e.FeesPaidUSD.H24) + platformTrades24h.WithLabelValues("pump-fun").Set(e.Trades.H24) platformHealth.WithLabelValues("pump-fun").Set(1) } } @@ -85,6 +92,7 @@ func publishLighthouse(data *lighthouseData) { slug := slugify(e.Name) platformVol24h.WithLabelValues(slug).Set(e.VolumeUSD.H24) platformFees24h.WithLabelValues(slug).Set(e.FeesPaidUSD.H24) + platformTrades24h.WithLabelValues(slug).Set(e.Trades.H24) platformHealth.WithLabelValues(slug).Set(1) } } diff --git a/harnesses/solana-unique-traders/Dockerfile b/harnesses/solana-unique-traders/Dockerfile new file mode 100644 index 00000000..f88e4a0d --- /dev/null +++ b/harnesses/solana-unique-traders/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.24-alpine AS builder + +WORKDIR /app +RUN apk add --no-cache git + +COPY go.mod ./ +COPY cmd ./cmd +COPY queries ./queries +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/solana-unique-traders/cmd/script/dune.go b/harnesses/solana-unique-traders/cmd/script/dune.go new file mode 100644 index 00000000..83ea04c8 --- /dev/null +++ b/harnesses/solana-unique-traders/cmd/script/dune.go @@ -0,0 +1,176 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// querySQL is the Dune Trino SQL that counts unique traders per platform. +// Mirrors harnesses/solana-unique-traders/queries/unique_traders.sql. +const querySQL = ` +WITH pump_fun AS ( + SELECT 'pump-fun' AS platform, COUNT(DISTINCT trader_id) AS unique_traders_24h + FROM pump_fun_solana.trades + WHERE block_time >= NOW() - INTERVAL '1' DAY +), +fee_activity AS ( + SELECT + CASE + WHEN address IN ('BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT','7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA','HeZVpHj9jLwTVtMMbzQRf6mLtFPkWNSg11o68qrbUBa3','ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy','DXfkEGoo6WFsdL7x6gLZ7r6Hw2S6HrtrAQVPWYx2A1s9','3t9EKmRiAUcQUYzTZpNojzeGP1KBAVEEbDNmy6wECQpK','DymeoWc5WLNiQBaoLuxrxDnDRvLgGZ1QGsEoCAM7Jsrx','dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ','6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn') THEN 'gmgn' + WHEN address IN ('7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj','4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S','CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ','AaG6of1gbj1pbDumvbSiTuJhRCRkkUNaWVxijSbWvTJW','7oi1L8U9MRu5zDz5syFahsiLUric47LzvJBQX6r827ws','9kPrgLggBJ69tx1czYAbp7fezuUmL337BsqQTKETUEhP','DKyUs1xXMDy8Z11zNsLnUg3dy9HZf6hYZidB6WodcaGy','4FobGn5ZWYquoJkxMzh2VUAWvV36xMgxQ3M7uG1pGGhd','76sxKrPtgoJHDJvxwFHqb3cAXWfRHFLe3VpKcLCAHSEf','H2cDR3EkJjtTKDQKk8SJS48du9mhsdzQhy8xJx5UMqQK','8m5GkL7nVy95G4YVUbs79z873oVKqg2afgKRmqxsiiRm','4kuG6NsAFJNwqEkac8GFDMMheCGKUPEbaRVHHyFHSwWz','8vFGAKdwpn4hk7kc1cBgfWZzpyW3MEMDATDzVZhddeQb','86Vh4XGLW2b6nvWbRyDs4ScgMXbuvRCHT7WbUT3RFxKG','DZfEurFKFtSbdWZsKSDTqpqsQgvXxmESpvRtXkAdgLwM','5L2QKqDn5ukJSWGyqR4RPvFvwnBabKWqAqMzH4heaQNB','DYVeNgXGLAhZdeLMMYnCw1nPnMxkBN7fJnNpHmizTrrF','Hbj6XdxX6eV4nfbYTseysibp4zZJtVRRPn2J3BhGRuK9','846ah7iBSu9ApuCyEhA5xpnjHHX7d4QJKetWLbwzmJZ8','5BqYhuD4q1YD3DMAYkc1FeTu9vqQVYYdfBAmkZjamyZg') THEN 'axiom' + WHEN address IN ('HrTf9CzXR1dRH4Sof5QrpmGWwpwAf3qZzwCsEjQpXcSq') THEN 'fomo' + WHEN address IN ('9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco','92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH','2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ','65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH','BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp','8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn') THEN 'trojan' + WHEN address IN ('AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH') THEN 'photon' + WHEN address IN ('MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36','FRMxAnZgkW58zbYcE7Bxqsg99VWpJh6sMP5xLzAWNabN') THEN 'maestro' + END AS platform, + tx_id + FROM solana.account_activity + WHERE block_time >= NOW() - INTERVAL '1' DAY + AND address IN ( + 'BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT','7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA','HeZVpHj9jLwTVtMMbzQRf6mLtFPkWNSg11o68qrbUBa3','ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy','DXfkEGoo6WFsdL7x6gLZ7r6Hw2S6HrtrAQVPWYx2A1s9','3t9EKmRiAUcQUYzTZpNojzeGP1KBAVEEbDNmy6wECQpK','DymeoWc5WLNiQBaoLuxrxDnDRvLgGZ1QGsEoCAM7Jsrx','dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ','6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn', + '7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj','4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S','CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ','AaG6of1gbj1pbDumvbSiTuJhRCRkkUNaWVxijSbWvTJW','7oi1L8U9MRu5zDz5syFahsiLUric47LzvJBQX6r827ws','9kPrgLggBJ69tx1czYAbp7fezuUmL337BsqQTKETUEhP','DKyUs1xXMDy8Z11zNsLnUg3dy9HZf6hYZidB6WodcaGy','4FobGn5ZWYquoJkxMzh2VUAWvV36xMgxQ3M7uG1pGGhd','76sxKrPtgoJHDJvxwFHqb3cAXWfRHFLe3VpKcLCAHSEf','H2cDR3EkJjtTKDQKk8SJS48du9mhsdzQhy8xJx5UMqQK','8m5GkL7nVy95G4YVUbs79z873oVKqg2afgKRmqxsiiRm','4kuG6NsAFJNwqEkac8GFDMMheCGKUPEbaRVHHyFHSwWz','8vFGAKdwpn4hk7kc1cBgfWZzpyW3MEMDATDzVZhddeQb','86Vh4XGLW2b6nvWbRyDs4ScgMXbuvRCHT7WbUT3RFxKG','DZfEurFKFtSbdWZsKSDTqpqsQgvXxmESpvRtXkAdgLwM','5L2QKqDn5ukJSWGyqR4RPvFvwnBabKWqAqMzH4heaQNB','DYVeNgXGLAhZdeLMMYnCw1nPnMxkBN7fJnNpHmizTrrF','Hbj6XdxX6eV4nfbYTseysibp4zZJtVRRPn2J3BhGRuK9','846ah7iBSu9ApuCyEhA5xpnjHHX7d4QJKetWLbwzmJZ8','5BqYhuD4q1YD3DMAYkc1FeTu9vqQVYYdfBAmkZjamyZg', + 'HrTf9CzXR1dRH4Sof5QrpmGWwpwAf3qZzwCsEjQpXcSq', + '9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco','92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH','2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ','65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH','BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp','8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn', + 'AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH', + 'MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36','FRMxAnZgkW58zbYcE7Bxqsg99VWpJh6sMP5xLzAWNabN' + ) +), +fee_with_signer AS ( + SELECT fa.platform, t.fee_payer + FROM fee_activity fa + JOIN solana.transactions t ON t.id = fa.tx_id + WHERE t.success = true AND fa.platform IS NOT NULL +), +fee_counts AS ( + SELECT platform, COUNT(DISTINCT fee_payer) AS unique_traders_24h + FROM fee_with_signer + GROUP BY platform +) +SELECT platform, unique_traders_24h FROM pump_fun +UNION ALL +SELECT platform, unique_traders_24h FROM fee_counts +ORDER BY unique_traders_24h DESC +` + +const duneBase = "https://api.dune.com/api/v1" + +type duneClient struct { + apiKey string + http *http.Client +} + +type duneRow struct { + Platform string `json:"platform"` + UniqueTraders24h float64 `json:"unique_traders_24h"` +} + +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: Solana Unique Traders 24h by Platform", + "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/solana-unique-traders/cmd/script/main.go b/harnesses/solana-unique-traders/cmd/script/main.go new file mode 100644 index 00000000..793cee93 --- /dev/null +++ b/harnesses/solana-unique-traders/cmd/script/main.go @@ -0,0 +1,128 @@ +// solana-unique-traders -- Bench 207 +// +// Fetches the Dune query result (unique daily traders per Solana platform) +// and exposes it as Prometheus gauges. Triggers a fresh Dune execution every +// hour; serves the latest cached result every 15 minutes in between. +// +// Required env vars: +// DUNE_API_KEY - Dune Analytics API key +// DUNE_QUERY_ID - ID of the saved Dune query (see queries/unique_traders.sql) +// If not set, the harness creates the query automatically and exits. +// +// Metrics on :2112/metrics: +// solana_platform_unique_traders_24h{platform} +// solana_platform_trader_health{platform} +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +const ( + fetchInterval = 15 * time.Minute + refreshInterval = 1 * time.Hour +) + +func main() { + fmt.Println("=== solana-unique-traders harness ===") + fmt.Println("OpenChainBench Bench 207 -- Unique daily traders by Solana platform via Dune.") + + 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) + + // On startup: fetch latest cached result immediately, then trigger a fresh execution. + 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 30 { + 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/solana-unique-traders/cmd/script/metrics.go b/harnesses/solana-unique-traders/cmd/script/metrics.go new file mode 100644 index 00000000..86b285a8 --- /dev/null +++ b/harnesses/solana-unique-traders/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 ( + uniqueTraders24h = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "solana_platform_unique_traders_24h", + Help: "Unique wallet addresses that traded through this Solana platform in the last 24h (Dune Analytics, fee-wallet attribution).", + }, []string{"platform"}) + + traderHealth = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "solana_platform_trader_health", + Help: "1 if this platform returned unique-trader data in the last Dune poll.", + }, []string{"platform"}) +) + +func init() { + prometheus.MustRegister(uniqueTraders24h, traderHealth) +} + +func publishRows(rows []duneRow) { + for _, r := range rows { + if r.Platform == "" { + continue + } + uniqueTraders24h.WithLabelValues(r.Platform).Set(r.UniqueTraders24h) + traderHealth.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/solana-unique-traders/go.mod b/harnesses/solana-unique-traders/go.mod new file mode 100644 index 00000000..d5e7f65d --- /dev/null +++ b/harnesses/solana-unique-traders/go.mod @@ -0,0 +1,18 @@ +module solana-unique-traders + +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/solana-unique-traders/go.sum b/harnesses/solana-unique-traders/go.sum new file mode 100644 index 00000000..d6b8ca98 --- /dev/null +++ b/harnesses/solana-unique-traders/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/harnesses/solana-unique-traders/queries/unique_traders.sql b/harnesses/solana-unique-traders/queries/unique_traders.sql new file mode 100644 index 00000000..da82fafb --- /dev/null +++ b/harnesses/solana-unique-traders/queries/unique_traders.sql @@ -0,0 +1,143 @@ +-- Unique daily traders by Solana trading platform +-- Source: pump_fun_solana.trades (pump.fun) + solana.account_activity joined with solana.transactions (all others) +-- Attribution: fee wallet presence in transaction account keys +-- Window: rolling 24 hours +-- Used by: OpenChainBench Bench 207 (solana-unique-traders) + +WITH pump_fun AS ( + SELECT + 'pump-fun' AS platform, + COUNT(DISTINCT trader_id) AS unique_traders_24h + FROM pump_fun_solana.trades + WHERE block_time >= NOW() - INTERVAL '1' DAY +), + +fee_activity AS ( + SELECT + CASE + WHEN address IN ( + 'BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT', + '7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA', + 'HeZVpHj9jLwTVtMMbzQRf6mLtFPkWNSg11o68qrbUBa3', + 'ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy', + 'DXfkEGoo6WFsdL7x6gLZ7r6Hw2S6HrtrAQVPWYx2A1s9', + '3t9EKmRiAUcQUYzTZpNojzeGP1KBAVEEbDNmy6wECQpK', + 'DymeoWc5WLNiQBaoLuxrxDnDRvLgGZ1QGsEoCAM7Jsrx', + 'dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ', + '6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn' + ) THEN 'gmgn' + WHEN address IN ( + '7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj', + '4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S', + 'CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ', + 'AaG6of1gbj1pbDumvbSiTuJhRCRkkUNaWVxijSbWvTJW', + '7oi1L8U9MRu5zDz5syFahsiLUric47LzvJBQX6r827ws', + '9kPrgLggBJ69tx1czYAbp7fezuUmL337BsqQTKETUEhP', + 'DKyUs1xXMDy8Z11zNsLnUg3dy9HZf6hYZidB6WodcaGy', + '4FobGn5ZWYquoJkxMzh2VUAWvV36xMgxQ3M7uG1pGGhd', + '76sxKrPtgoJHDJvxwFHqb3cAXWfRHFLe3VpKcLCAHSEf', + 'H2cDR3EkJjtTKDQKk8SJS48du9mhsdzQhy8xJx5UMqQK', + '8m5GkL7nVy95G4YVUbs79z873oVKqg2afgKRmqxsiiRm', + '4kuG6NsAFJNwqEkac8GFDMMheCGKUPEbaRVHHyFHSwWz', + '8vFGAKdwpn4hk7kc1cBgfWZzpyW3MEMDATDzVZhddeQb', + '86Vh4XGLW2b6nvWbRyDs4ScgMXbuvRCHT7WbUT3RFxKG', + 'DZfEurFKFtSbdWZsKSDTqpqsQgvXxmESpvRtXkAdgLwM', + '5L2QKqDn5ukJSWGyqR4RPvFvwnBabKWqAqMzH4heaQNB', + 'DYVeNgXGLAhZdeLMMYnCw1nPnMxkBN7fJnNpHmizTrrF', + 'Hbj6XdxX6eV4nfbYTseysibp4zZJtVRRPn2J3BhGRuK9', + '846ah7iBSu9ApuCyEhA5xpnjHHX7d4QJKetWLbwzmJZ8', + '5BqYhuD4q1YD3DMAYkc1FeTu9vqQVYYdfBAmkZjamyZg' + ) THEN 'axiom' + WHEN address IN ( + 'HrTf9CzXR1dRH4Sof5QrpmGWwpwAf3qZzwCsEjQpXcSq' + ) THEN 'fomo' + WHEN address IN ( + '9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco', + '92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH', + '2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ', + '65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH', + 'BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp', + '8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn' + ) THEN 'trojan' + WHEN address IN ( + 'AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH' + ) THEN 'photon' + WHEN address IN ( + 'MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36', + 'FRMxAnZgkW58zbYcE7Bxqsg99VWpJh6sMP5xLzAWNabN' + ) THEN 'maestro' + END AS platform, + tx_id + FROM solana.account_activity + WHERE block_time >= NOW() - INTERVAL '1' DAY + AND address IN ( + -- GMGN (9 wallets) + 'BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT', + '7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA', + 'HeZVpHj9jLwTVtMMbzQRf6mLtFPkWNSg11o68qrbUBa3', + 'ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy', + 'DXfkEGoo6WFsdL7x6gLZ7r6Hw2S6HrtrAQVPWYx2A1s9', + '3t9EKmRiAUcQUYzTZpNojzeGP1KBAVEEbDNmy6wECQpK', + 'DymeoWc5WLNiQBaoLuxrxDnDRvLgGZ1QGsEoCAM7Jsrx', + 'dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ', + '6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn', + -- AXIOM (20 wallets) + '7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj', + '4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S', + 'CeA3sPZfWWToFEBmw5n1Y93tnV66Vmp8LacLzsVprgxZ', + 'AaG6of1gbj1pbDumvbSiTuJhRCRkkUNaWVxijSbWvTJW', + '7oi1L8U9MRu5zDz5syFahsiLUric47LzvJBQX6r827ws', + '9kPrgLggBJ69tx1czYAbp7fezuUmL337BsqQTKETUEhP', + 'DKyUs1xXMDy8Z11zNsLnUg3dy9HZf6hYZidB6WodcaGy', + '4FobGn5ZWYquoJkxMzh2VUAWvV36xMgxQ3M7uG1pGGhd', + '76sxKrPtgoJHDJvxwFHqb3cAXWfRHFLe3VpKcLCAHSEf', + 'H2cDR3EkJjtTKDQKk8SJS48du9mhsdzQhy8xJx5UMqQK', + '8m5GkL7nVy95G4YVUbs79z873oVKqg2afgKRmqxsiiRm', + '4kuG6NsAFJNwqEkac8GFDMMheCGKUPEbaRVHHyFHSwWz', + '8vFGAKdwpn4hk7kc1cBgfWZzpyW3MEMDATDzVZhddeQb', + '86Vh4XGLW2b6nvWbRyDs4ScgMXbuvRCHT7WbUT3RFxKG', + 'DZfEurFKFtSbdWZsKSDTqpqsQgvXxmESpvRtXkAdgLwM', + '5L2QKqDn5ukJSWGyqR4RPvFvwnBabKWqAqMzH4heaQNB', + 'DYVeNgXGLAhZdeLMMYnCw1nPnMxkBN7fJnNpHmizTrrF', + 'Hbj6XdxX6eV4nfbYTseysibp4zZJtVRRPn2J3BhGRuK9', + '846ah7iBSu9ApuCyEhA5xpnjHHX7d4QJKetWLbwzmJZ8', + '5BqYhuD4q1YD3DMAYkc1FeTu9vqQVYYdfBAmkZjamyZg', + -- FOMO (1 wallet, USDC ATA) + 'HrTf9CzXR1dRH4Sof5QrpmGWwpwAf3qZzwCsEjQpXcSq', + -- TROJAN (6 wallets) + '9yMwSPk9mrXSN7yDHUuZurAh1sjbJsfpUqjZ7SvVtdco', + '92Med3qeK7duC5iiYsHX38H2f2twJfRsSx93oNrza2VH', + '2jwHNxavSoMZMEDbT1eV9PcPt5dDcayCqM6MkgaPpmWQ', + '65gDv7pZQCZELsNpNYSFEBtNFpWZAbxmRFB6BGMqFkHH', + 'BWgb8wR1FEGiu1jCDSKuHKf752W27b4iN6SvoNCiK4qp', + '8jgg7moFJkHyTtAv9M6RBSPMp2oXeXhuiUMKW8YbYCWn', + -- PHOTON (1 wallet) + 'AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH', + -- MAESTRO (2 wallets) + 'MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36', + 'FRMxAnZgkW58zbYcE7Bxqsg99VWpJh6sMP5xLzAWNabN' + ) +), + +fee_with_signer AS ( + SELECT + fa.platform, + t.fee_payer + FROM fee_activity fa + JOIN solana.transactions t ON t.id = fa.tx_id + WHERE t.success = true + AND fa.platform IS NOT NULL +), + +fee_counts AS ( + SELECT + platform, + COUNT(DISTINCT fee_payer) AS unique_traders_24h + FROM fee_with_signer + GROUP BY platform +) + +SELECT platform, unique_traders_24h FROM pump_fun +UNION ALL +SELECT platform, unique_traders_24h FROM fee_counts +ORDER BY unique_traders_24h DESC