diff --git a/harnesses/memecoin-platforms/cmd/monitor/main.go b/harnesses/memecoin-platforms/cmd/monitor/main.go index 5142b5ec..b19ac938 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/main.go +++ b/harnesses/memecoin-platforms/cmd/monitor/main.go @@ -3,6 +3,7 @@ package main import ( "log" "net/http" + "net/url" "os" "time" @@ -19,8 +20,27 @@ func main() { log.Fatal("MOBULA_API_KEY is required") } + heliusKey := os.Getenv("HELIUS_API_KEY") + mobulaClient := &http.Client{Timeout: 30 * time.Second} - rpcClient := &http.Client{Timeout: 10 * time.Second} + + // rpcClient routes through rotating proxy to avoid per-IP rate limits + var rpcClient *http.Client + if proxyRaw := os.Getenv("HTTPS_PROXY"); proxyRaw != "" { + if proxyURL, err := url.Parse(proxyRaw); err == nil { + rpcClient = &http.Client{ + Timeout: 15 * time.Second, + Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}, + } + log.Printf("rpc: rotating proxy enabled") + } + } + if rpcClient == nil { + rpcClient = &http.Client{Timeout: 15 * time.Second} + } + + // heliusClient used as fallback for older tx not in public RPC history + heliusClient := &http.Client{Timeout: 15 * time.Second} setSolPrice(175.0) updateSolPrice(mobulaClient) @@ -42,12 +62,12 @@ func main() { log.Fatal(http.ListenAndServe(":9090", nil)) }() - runPoll(mobulaClient, rpcClient, apiKey) + runPoll(mobulaClient, rpcClient, heliusClient, heliusKey, apiKey) ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { - runPoll(mobulaClient, rpcClient, apiKey) + runPoll(mobulaClient, rpcClient, heliusClient, heliusKey, apiKey) } } @@ -57,7 +77,7 @@ type platformStats struct { n int } -func runPoll(mobulaClient, rpcClient *http.Client, apiKey string) { +func runPoll(mobulaClient, rpcClient, heliusClient *http.Client, heliusKey, apiKey string) { tokens, err := fetchTopTokens(mobulaClient, 10) if err != nil { log.Printf("[pump.fun] %v", err) @@ -95,7 +115,7 @@ func runPoll(mobulaClient, rpcClient *http.Client, apiKey string) { byPlatform[p] = s } _, cached := txCache.Load(t.Hash) - if !cached && freshLookups >= 15 { + if !cached && freshLookups >= 20 { s.tradeValueSum += t.AmountUSD s.n++ continue @@ -103,7 +123,7 @@ func runPoll(mobulaClient, rpcClient *http.Client, apiKey string) { if !cached { freshLookups++ } - feeUSD := computeExplicitFees(rpcClient, t.Hash, t.Sender) + feeUSD := computeExplicitFees(rpcClient, heliusClient, heliusKey, t.Hash, t.Sender) s.totalFeeSum += feeUSD s.tradeValueSum += t.AmountUSD s.n++ diff --git a/harnesses/memecoin-platforms/cmd/monitor/mobula.go b/harnesses/memecoin-platforms/cmd/monitor/mobula.go index 3a65f19c..135b7aab 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/mobula.go +++ b/harnesses/memecoin-platforms/cmd/monitor/mobula.go @@ -20,7 +20,7 @@ type mobulaTradesResp struct { } func fetchTrades(client *http.Client, apiKey, mint string) ([]MobulaTrade, error) { - from := time.Now().Add(-10 * time.Minute).UnixMilli() + from := time.Now().Add(-2 * time.Hour).UnixMilli() url := fmt.Sprintf( "https://api.mobula.io/api/2/token/trades-enriched?address=%s&chainId=solana:solana&sortOrder=desc&limit=50&from=%d", mint, from, diff --git a/harnesses/memecoin-platforms/cmd/monitor/solana.go b/harnesses/memecoin-platforms/cmd/monitor/solana.go index 9d9091b8..252301d8 100644 --- a/harnesses/memecoin-platforms/cmd/monitor/solana.go +++ b/harnesses/memecoin-platforms/cmd/monitor/solana.go @@ -13,10 +13,11 @@ import ( ) const ( - solanaRPC = "https://solana-rpc.publicnode.com" + rpcPublic = "https://api.mainnet-beta.solana.com" + rpcHelius = "https://mainnet.helius-rpc.com/?api-key=" usdcMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" wsolMint = "So11111111111111111111111111111111111111112" - rpcSleep = 1 * time.Second + rpcSleep = 200 * time.Millisecond ) // Known fee wallet owners confirmed via on-chain analysis of tagged Mobula trades. @@ -104,7 +105,7 @@ type tokBal struct { // gas + known platform fee wallet receipts + heuristic small SOL/USDC recipients. // AMM LP fees that stay inside pool accounts are not counted. // Returns 0 on RPC error (logs the failure). -func computeExplicitFees(rpcClient *http.Client, txHash, sender string) float64 { +func computeExplicitFees(proxyClient, heliusClient *http.Client, heliusKey, txHash, sender string) float64 { if txHash == "" { return 0 } @@ -115,7 +116,10 @@ func computeExplicitFees(rpcClient *http.Client, txHash, sender string) float64 txCache.Range(func(k, _ any) bool { txCache.Delete(k); return true }) txCacheSize.Store(0) } - fee, err := fetchOnChainFee(rpcClient, txHash, sender) + fee, err := fetchOnChainFee(proxyClient, rpcPublic, txHash, sender) + if err != nil && heliusKey != "" { + fee, err = fetchOnChainFee(heliusClient, rpcHelius+heliusKey, txHash, sender) + } if err != nil { if err.Error() != "not found" { log.Printf("[solana] %s: %v", txHash[:min(12, len(txHash))], err) @@ -130,7 +134,7 @@ func computeExplicitFees(rpcClient *http.Client, txHash, sender string) float64 return fee } -func fetchOnChainFee(rpcClient *http.Client, txHash, sender string) (float64, error) { +func fetchOnChainFee(rpcClient *http.Client, rpcURL, txHash, sender string) (float64, error) { body, _ := json.Marshal(map[string]interface{}{ "jsonrpc": "2.0", "id": 1, "method": "getTransaction", @@ -139,7 +143,7 @@ func fetchOnChainFee(rpcClient *http.Client, txHash, sender string) (float64, er "maxSupportedTransactionVersion": 0, }}, }) - req, _ := http.NewRequest("POST", solanaRPC, bytes.NewReader(body)) + req, _ := http.NewRequest("POST", rpcURL, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := rpcClient.Do(req) diff --git a/src/app/apps/page.tsx b/src/app/apps/page.tsx index 4f12a426..c849bc20 100644 --- a/src/app/apps/page.tsx +++ b/src/app/apps/page.tsx @@ -2,7 +2,6 @@ import type { Metadata } from "next"; import { pageMetadata } from "@/lib/page-metadata"; import { safeJsonLd, buildBreadcrumbJsonLd } from "@/lib/jsonld"; import { SITE } from "@/data/site"; -import { fetchAppsLeaderboard } from "@/lib/apps-leaderboard"; import { fetchEVMRevenue } from "@/lib/evm-exec"; import { fetchExecLeaderboard } from "@/lib/solana-exec"; import { fetchSolPrice } from "@/lib/sol-price"; @@ -11,19 +10,18 @@ import { TradingAppsLeaderboard, type UnifiedAppRow } from "@/components/trading import Link from "next/link"; const DESCRIPTION = - "Protocol fees collected by trading apps: meme bots, telegram bots, and perps. On-chain data, updated hourly."; + "Protocol fees collected by trading apps: meme bots and telegram bots. On-chain data, updated hourly."; export const metadata: Metadata = pageMetadata({ path: "/apps", - title: "Trading App Revenue — pump.fun, Axiom, GMGN, Hyperliquid | OpenChainBench", + title: "Trading App Revenue — pump.fun, Axiom, GMGN, BullX | OpenChainBench", description: DESCRIPTION, }); export const revalidate = 300; export default async function AppsHubPage() { - const [perpsData, evmData, solanaData, solPrice] = await Promise.all([ - fetchAppsLeaderboard(), + const [evmData, solanaData, solPrice] = await Promise.all([ fetchEVMRevenue(), fetchExecLeaderboard(), fetchSolPrice(), @@ -37,22 +35,7 @@ export default async function AppsHubPage() { (solanaData?.platforms ?? []).map((p) => [p.platform, p]) ); - const perpsBySlug = new Map( - (perpsData?.protocols ?? []).map((p) => [p.slug, p]) - ); - const rows: UnifiedAppRow[] = TRADING_APPS.map((meta) => { - if (meta.perpsSlug !== null) { - const perp = perpsBySlug.get(meta.perpsSlug); - const gross = perp?.windows["24h"]?.gross ?? 0; - return { - meta, - fees: { solana: null, ethereum: null, bsc: null, base: null }, - stableOnly: { ethereum: false, bsc: false, base: false }, - total24h: gross, - }; - } - const evmRow = meta.evmKey ? evmByPlatform.get(meta.evmKey) : undefined; const solRow = meta.solanaKey ? solanaByPlatform.get(meta.solanaKey) : undefined; @@ -87,8 +70,7 @@ export default async function AppsHubPage() { }; }); - const updatedAt = - evmData?.updatedAt ?? solanaData?.updatedAt ?? perpsData?.updatedAt ?? null; + const updatedAt = evmData?.updatedAt ?? solanaData?.updatedAt ?? null; const breadcrumb = { "@context": "https://schema.org", @@ -119,8 +101,7 @@ export default async function AppsHubPage() {
Solana fees = txCount × avgPlatformFeeLamports / 1e9 × SOL price.
- EVM fees = stable (USDC/USDT) + native where traceable.
- Perps = gross fees 24h from on-chain data.{" "}
+ EVM fees = stable (USDC/USDT) + native where traceable.{" "}
Detailed execution metrics →
diff --git a/src/components/trading-apps-leaderboard.tsx b/src/components/trading-apps-leaderboard.tsx
index d77f55c8..ea80b9cf 100644
--- a/src/components/trading-apps-leaderboard.tsx
+++ b/src/components/trading-apps-leaderboard.tsx
@@ -17,25 +17,22 @@ export type UnifiedAppRow = {
total24h: number;
};
-type TabKey = "all" | "meme-bot" | "telegram-bot" | "perps";
+type TabKey = "all" | "meme-bot" | "telegram-bot";
const TABS: { key: TabKey; label: string }[] = [
{ key: "all", label: "All" },
{ key: "meme-bot", label: "Meme Bots" },
{ key: "telegram-bot", label: "Telegram Bots" },
- { key: "perps", label: "Perps" },
];
const CATEGORY_BADGE: Record