From 418b389267a38de01ade959a3fdb27b27268611b Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:58:49 +0200 Subject: [PATCH 1/3] fix: axiom solana re-activated, banana-gun evm nulled, evmCoverage complete, blockscout txlist added --- harnesses/evm-exec/cmd/collector/main.go | 14 +- .../evm-exec/internal/source/blockscout.go | 134 ++++++++++++++++++ harnesses/solana-exec/cmd/api/main.go | 12 +- src/lib/trading-apps-config.ts | 4 +- 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/harnesses/evm-exec/cmd/collector/main.go b/harnesses/evm-exec/cmd/collector/main.go index 0919ddcf..d72a5c42 100644 --- a/harnesses/evm-exec/cmd/collector/main.go +++ b/harnesses/evm-exec/cmd/collector/main.go @@ -224,13 +224,17 @@ func collectNativeBlockscout(ctx context.Context, db *store.DB, plt, chain, coll } rpc := chainRPC[chain] - txs, lastBlock, err := source.GetBlockscoutInternalTxs(ctx, source.BlockscoutRobinhood, collector, cursor) + internalTxs, lastInt, err := source.GetBlockscoutInternalTxs(ctx, source.BlockscoutRobinhood, collector, cursor) + if err != nil { + return err + } + normalTxs, lastNorm, err := source.GetBlockscoutNormalTxs(ctx, source.BlockscoutRobinhood, collector, cursor) if err != nil { return err } var events []store.EVMEvent - for _, tx := range txs { + for _, tx := range append(internalTxs, normalTxs...) { if tx.BlockNum > toBlock { continue } @@ -253,11 +257,11 @@ func collectNativeBlockscout(ctx context.Context, db *store.DB, plt, chain, coll if err := db.UpsertEvents(ctx, events); err != nil { return err } - if lastBlock > cursor { - _ = db.SaveCursor(ctx, chain, plt, "native", lastBlock) + if high := max64(lastInt, lastNorm); high > cursor { + _ = db.SaveCursor(ctx, chain, plt, "native", high) } if len(events) > 0 { - log.Printf("collector: %s/%s native ETH (Blockscout) %d events block=%d", plt, chain, len(events), lastBlock) + log.Printf("collector: %s/%s native ETH (Blockscout) %d events block=%d", plt, chain, len(events), max64(lastInt, lastNorm)) } return nil } diff --git a/harnesses/evm-exec/internal/source/blockscout.go b/harnesses/evm-exec/internal/source/blockscout.go index f6519c64..06ca775f 100644 --- a/harnesses/evm-exec/internal/source/blockscout.go +++ b/harnesses/evm-exec/internal/source/blockscout.go @@ -28,6 +28,17 @@ type blockscoutTx struct { Index string `json:"index"` // trace position within the tx (event key) } +// blockscoutNormalTx matches the Blockscout txlist (normal/external tx) result shape. +// Field names differ from txlistinternal: hash vs transactionHash, no index. +type blockscoutNormalTx struct { + Hash string `json:"hash"` + BlockNumber string `json:"blockNumber"` + TimeStamp string `json:"timeStamp"` + Value string `json:"value"` + To string `json:"to"` + IsError string `json:"isError"` +} + // GetBlockscoutInternalTxs fetches internal ETH transfers to address via Blockscout. // apiURL is the chain-specific Blockscout API base (e.g. blockscoutRobinhood). func GetBlockscoutInternalTxs(ctx context.Context, apiURL, address string, startBlock uint64) ([]NativeTx, uint64, error) { @@ -153,3 +164,126 @@ func GetBlockscoutInternalTxs(ctx context.Context, apiURL, address string, start return all, highestBlock, nil } + +// GetBlockscoutNormalTxs fetches direct ETH transfers (tx.value > 0, to == address) via txlist. +// Complements GetBlockscoutInternalTxs: direct EOA sends don't appear in txlistinternal. +func GetBlockscoutNormalTxs(ctx context.Context, apiURL, address string, startBlock uint64) ([]NativeTx, uint64, error) { + const offset = 1000 + const maxPage = 20 + + addrLower := strings.ToLower(address) + var all []NativeTx + highestBlock := startBlock + curStart := startBlock + + for { + var windowTxs []NativeTx + hitCap := false + + for page := 1; page <= maxPage; page++ { + time.Sleep(300 * time.Millisecond) + + params := url.Values{ + "module": {"account"}, + "action": {"txlist"}, + "address": {address}, + "startblock": {fmt.Sprintf("%d", curStart)}, + "endblock": {"99999999"}, + "page": {fmt.Sprintf("%d", page)}, + "offset": {fmt.Sprintf("%d", offset)}, + "sort": {"asc"}, + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL+"?"+params.Encode(), nil) + if err != nil { + return all, highestBlock, err + } + resp, err := httpClient.Do(req) + if err != nil { + return all, highestBlock, fmt.Errorf("blockscout txlist page %d: %w", page, err) + } + + var out struct { + Status string `json:"status"` + Message string `json:"message"` + Result json.RawMessage `json:"result"` + } + decErr := json.NewDecoder(resp.Body).Decode(&out) + resp.Body.Close() + if decErr != nil { + return all, highestBlock, fmt.Errorf("blockscout txlist decode: %w", decErr) + } + + if out.Status == "0" { + break + } + if out.Status != "1" && out.Status != "2" { + break + } + + var txs []blockscoutNormalTx + if err := json.Unmarshal(out.Result, &txs); err != nil { + break + } + + for _, tx := range txs { + if tx.IsError == "1" || tx.Value == "" || tx.Value == "0" { + continue + } + if strings.ToLower(tx.To) != addrLower { + continue + } + amt, ok := new(big.Int).SetString(tx.Value, 10) + if !ok || amt.Sign() <= 0 { + continue + } + blockNum, parseErr := parseDecU64(tx.BlockNumber) + if parseErr != nil { + continue + } + var bt time.Time + if ts, tsErr := parseDecU64(tx.TimeStamp); tsErr == nil && ts > 0 { + bt = time.Unix(int64(ts), 0).UTC() + } + ntx := NativeTx{ + TxHash: strings.ToLower(tx.Hash), + BlockNum: blockNum, + BlockTime: bt, + Amount: amt, + EventKey: "top", // direct tx value, not a trace + } + windowTxs = append(windowTxs, ntx) + if blockNum > highestBlock { + highestBlock = blockNum + } + } + + if len(txs) < offset { + break + } + if page == maxPage { + hitCap = true + break + } + } + + all = append(all, windowTxs...) + + if !hitCap || len(windowTxs) == 0 { + break + } + + var lastBlock uint64 + for _, tx := range windowTxs { + if tx.BlockNum > lastBlock { + lastBlock = tx.BlockNum + } + } + if lastBlock <= curStart { + break + } + curStart = lastBlock + } + + return all, highestBlock, nil +} diff --git a/harnesses/solana-exec/cmd/api/main.go b/harnesses/solana-exec/cmd/api/main.go index e20f8bd4..3d88dea0 100644 --- a/harnesses/solana-exec/cmd/api/main.go +++ b/harnesses/solana-exec/cmd/api/main.go @@ -234,10 +234,16 @@ func mustEnv(key string) string { // ---- EVM revenue endpoint ---- // evmCoverage is the static coverage map derived from the evm-exec platform config. -// "full" = native + stable; "stable-only" = only ERC-20 USDC tracked. +// "full" = native + stable; "stable-only" = only ERC-20 USDC/USDG tracked. +// Mirrors platform.Coverage() in evm-exec/internal/platform/platforms.go. var evmCoverage = map[string]map[string]string{ - "gmgn": {"ethereum": "full", "bsc": "full", "base": "stable-only"}, - "pumpfun": {"ethereum": "full", "bsc": "full", "base": "stable-only"}, + "pumpfun": {"ethereum": "full", "bsc": "full", "base": "full"}, + "gmgn": {"ethereum": "full", "bsc": "full", "base": "full"}, + "maestro": {"ethereum": "full", "bsc": "full", "base": "full"}, + "axiom": {"bsc": "full"}, + "banana-gun": {"ethereum": "stable-only", "bsc": "stable-only", "base": "stable-only"}, + "gmgn-robinhood": {"robinhood": "full"}, + "maestro-robinhood": {"robinhood": "full"}, } // coinGeckoIDs maps chain name → CoinGecko asset ID for native price lookup. diff --git a/src/lib/trading-apps-config.ts b/src/lib/trading-apps-config.ts index 6c75d051..fbfd71cf 100644 --- a/src/lib/trading-apps-config.ts +++ b/src/lib/trading-apps-config.ts @@ -18,8 +18,8 @@ export const TRADING_APPS: AppMeta[] = [ { id: "bullx", name: "BullX", category: "trading-terminal", logoKey: "bullx", productUrl: "https://bullx.io", benchUrl: "/benchmarks/trading-app-execution", evmKey: null, solanaKey: "bullx", robinhoodKey: null, inactive: true, inactiveSince: "2026-06-01" }, { id: "photon", name: "Photon", category: "trading-terminal", logoKey: "photon", productUrl: "https://photon-sol.tinyastro.io", benchUrl: "/benchmarks/trading-app-execution", evmKey: null, solanaKey: "photon", robinhoodKey: null }, { id: "gmgn", name: "GMGN", category: "telegram-bot", logoKey: "gmgn", productUrl: "https://gmgn.ai", benchUrl: "/benchmarks/trading-app-execution", evmKey: "gmgn", solanaKey: "gmgn", robinhoodKey: "gmgn-robinhood" }, - { id: "axiom", name: "Axiom", category: "telegram-bot", logoKey: "axiom", productUrl: "https://axiom.trade", benchUrl: "/benchmarks/trading-app-execution", evmKey: "axiom", solanaKey: "axiom", robinhoodKey: null, inactive: true, inactiveSince: "2026-06-29" }, + { id: "axiom", name: "Axiom", category: "telegram-bot", logoKey: "axiom", productUrl: "https://axiom.trade", benchUrl: "/benchmarks/trading-app-execution", evmKey: "axiom", solanaKey: "axiom", robinhoodKey: null }, { id: "maestro", name: "Maestro", category: "telegram-bot", logoKey: "maestro", productUrl: "https://maestro.bots.gg", benchUrl: "/benchmarks/trading-app-execution", evmKey: "maestro", solanaKey: "maestro", robinhoodKey: "maestro-robinhood" }, - { id: "banana-gun", name: "Banana Gun", category: "telegram-bot", logoKey: "banana-gun", productUrl: "https://t.me/BananaGunSniper_bot", benchUrl: "/benchmarks/trading-app-execution", evmKey: "banana-gun", solanaKey: "banana-gun", robinhoodKey: null }, + { id: "banana-gun", name: "Banana Gun", category: "telegram-bot", logoKey: "banana-gun", productUrl: "https://t.me/BananaGunSniper_bot", benchUrl: "/benchmarks/trading-app-execution", evmKey: null, solanaKey: "banana-gun", robinhoodKey: null }, { id: "trojan", name: "Trojan", category: "telegram-bot", logoKey: "trojan", productUrl: "https://trojan.bot", benchUrl: "/benchmarks/trading-app-execution", evmKey: null, solanaKey: "trojan", robinhoodKey: null }, ]; From f74adb16eb38c180401b8d48c33d83f95a7646e6 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:15:05 +0200 Subject: [PATCH 2/3] fix: numeric precision for token division, remove dead banana-gun evmCoverage entry --- harnesses/evm-exec/internal/store/store.go | 4 ++-- harnesses/solana-exec/cmd/api/main.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/harnesses/evm-exec/internal/store/store.go b/harnesses/evm-exec/internal/store/store.go index 0db698c2..35ad6595 100644 --- a/harnesses/evm-exec/internal/store/store.go +++ b/harnesses/evm-exec/internal/store/store.go @@ -146,8 +146,8 @@ func (db *DB) Materialize(ctx context.Context) error { chain, platform, date_trunc('hour', block_time) AS bucket_start, - COALESCE(SUM(amount_raw / POW(10, decimals)) FILTER (WHERE asset <> 'native'), 0), - COALESCE(SUM(amount_raw / POW(10, decimals)) FILTER (WHERE asset = 'native'), 0), + COALESCE(SUM(amount_raw::numeric / (10::numeric ^ decimals)) FILTER (WHERE asset <> 'native'), 0), + COALESCE(SUM(amount_raw::numeric / (10::numeric ^ decimals)) FILTER (WHERE asset = 'native'), 0), CASE WHEN chain = 'bsc' THEN 'BNB' ELSE 'ETH' END, now() FROM evm_exec_events diff --git a/harnesses/solana-exec/cmd/api/main.go b/harnesses/solana-exec/cmd/api/main.go index 3d88dea0..24181a6a 100644 --- a/harnesses/solana-exec/cmd/api/main.go +++ b/harnesses/solana-exec/cmd/api/main.go @@ -241,9 +241,10 @@ var evmCoverage = map[string]map[string]string{ "gmgn": {"ethereum": "full", "bsc": "full", "base": "full"}, "maestro": {"ethereum": "full", "bsc": "full", "base": "full"}, "axiom": {"bsc": "full"}, - "banana-gun": {"ethereum": "stable-only", "bsc": "stable-only", "base": "stable-only"}, "gmgn-robinhood": {"robinhood": "full"}, "maestro-robinhood": {"robinhood": "full"}, + // banana-gun: evmKey=null in frontend config; EVM data intentionally not displayed + // (router addresses receive trade principal, not fees; pending eth_getLogs on topic 0x72015ace…) } // coinGeckoIDs maps chain name → CoinGecko asset ID for native price lookup. From b7f5d6dd728f7123579fe6f929de7d2f16fd2ac7 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:29:18 +0200 Subject: [PATCH 3/3] feat(bench-202): add Invo to app-store-ratings bench --- benchmarks/app-store-ratings.yml | 18 ++++++++++++++++-- harnesses/app-store-ratings/cmd/script/main.go | 1 + src/lib/brand.ts | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/benchmarks/app-store-ratings.yml b/benchmarks/app-store-ratings.yml index 25756781..7bc4360c 100644 --- a/benchmarks/app-store-ratings.yml +++ b/benchmarks/app-store-ratings.yml @@ -3,8 +3,8 @@ slug: app-store-ratings number: "202" title: "Crypto trading app iOS ratings: Coinbase vs Robinhood vs Crypto.com vs Bybit, live" -seo_title: "Crypto trading app App Store ratings 2026: Coinbase, Robinhood, Crypto.com, Bybit, Kraken, Fomo, PumpFun, GMGN, Moonshot" -seo_description: "{{best_name}} leads crypto trading apps on the iOS App Store at {{best_p50}} stars. Coinbase vs Robinhood vs Crypto.com vs Bybit vs Kraken vs Fomo vs PumpFun vs GMGN vs Moonshot ranked live from iTunes ratings." +seo_title: "Crypto trading app App Store ratings 2026: Coinbase, Robinhood, Crypto.com, Bybit, Kraken, Invo, Fomo, PumpFun, GMGN, Moonshot" +seo_description: "{{best_name}} leads crypto trading apps on the iOS App Store at {{best_p50}} stars. Coinbase vs Robinhood vs Crypto.com vs Bybit vs Kraken vs Invo vs Fomo vs PumpFun vs GMGN vs Moonshot ranked live from iTunes ratings." subtitle: Live Apple App Store ratings for the leading crypto trading apps on iOS. Updated every 30 minutes from the iTunes lookup API. Rating is the current average across all user reviews. Review count shows how many users voted. category: Trading @@ -51,6 +51,7 @@ methodology: - "GMGN (ID 6745328711): AI-powered trading terminal focused on Solana meme coins." - "PumpFun (ID 6717572591): launched Oct 2024. Bonding-curve launchpad and trading app for Solana meme coins." - "Moonshot (ID 6503993131): mobile-first meme coin launchpad with fiat onramp via Apple Pay, by DEX Screener." + - "Invo (ID 1601301148): Hyperliquid-native social trading mobile app with 170+ perp pairs, copy trading, and 25k+ referrals via its registered builder code." findings: - "{{best_name}} leads crypto trading apps on the iOS App Store at {{best_p50}} stars." @@ -226,3 +227,16 @@ providers: success: app_store_health{app="binance-us"} sample_size: app_store_reviews_total{app="binance-us"} series: app_store_rating{app="binance-us"} + + - slug: invo + name: Invo + tag: "Hyperliquid social trading app" + formula: "Apple App Store average rating (iTunes lookup API, app ID 1601301148, US store, all-time average). Review count from app_store_reviews_total{app=\"invo\"}." + queries: + p50: app_store_rating{app="invo"} + p90: app_store_rating{app="invo"} + p99: app_store_rating{app="invo"} + mean: app_store_rating{app="invo"} + success: app_store_health{app="invo"} + sample_size: app_store_reviews_total{app="invo"} + series: app_store_rating{app="invo"} diff --git a/harnesses/app-store-ratings/cmd/script/main.go b/harnesses/app-store-ratings/cmd/script/main.go index 1c9b7b41..67ad2295 100644 --- a/harnesses/app-store-ratings/cmd/script/main.go +++ b/harnesses/app-store-ratings/cmd/script/main.go @@ -23,6 +23,7 @@ var apps = []struct { {"binance-us", "1492670702"}, {"robinhood", "938003185"}, {"cryptocom", "1262148500"}, + {"invo", "1601301148"}, } func main() { diff --git a/src/lib/brand.ts b/src/lib/brand.ts index d829e075..84a60b40 100644 --- a/src/lib/brand.ts +++ b/src/lib/brand.ts @@ -78,6 +78,7 @@ const BRANDS: Record = { across: { color: "#6CF9D8" }, // across aqua // ─── Crypto trading apps (bench № 202) ─── + invo: { color: "#7B5EA7" }, // invo purple robinhood: { color: "#00C805" }, // robinhood green cryptocom: { color: "#002D74", dark: true }, // crypto.com navy pumpfun: { color: "#00C851" }, // pump.fun green