From 09275b24865e0dd6f8662fe897de1239cce891b0 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 10 Jul 2026 16:03:30 +0200 Subject: [PATCH] chain-kpis: cover 11 new chains, fix ton to gram slug, unfiltered stables endpoint --- harnesses/chain-kpis/cmd/script/config.go | 12 +++++--- harnesses/chain-kpis/cmd/script/defillama.go | 23 +++++++------- harnesses/chain-kpis/cmd/script/mobula.go | 4 +-- harnesses/chain-kpis/cmd/script/registry.go | 32 +++++++++++++++++++- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/harnesses/chain-kpis/cmd/script/config.go b/harnesses/chain-kpis/cmd/script/config.go index dc2733ce..06409f9a 100644 --- a/harnesses/chain-kpis/cmd/script/config.go +++ b/harnesses/chain-kpis/cmd/script/config.go @@ -11,21 +11,23 @@ import ( type Config struct { // Mobula REST key. Required for /market/data and /market/blockchain/stats // (both endpoints rate-limit free traffic to ~10 req/min, the key lifts - // that to a level that comfortably absorbs 21 chains × 2 endpoints + // that to a level that comfortably absorbs 32 chains × 2 endpoints // every refresh tick.) Without a key, Mobula returns 429 immediately. MobulaAPIKey string // How often DefiLlama is polled per chain. 15 min is the conservative // default: their public API is generous but the TVL value barely moves // inside a 15-min window (intra-day deltas are noise vs the day-over- - // day signal we surface on the chain page). 3 calls/h × 21 chains = 63 - // req/h, well under any sane rate-limit ceiling. + // day signal we surface on the chain page). 4 ticks/h × 31 mapped + // chains × 3 endpoints = 372 req/h, well under any sane rate-limit + // ceiling. DefillamaRefreshInterval time.Duration // Mobula tick. Faster because we own the source — and native-token // prices move on shorter cycles than DEX-TVL aggregates. 5 min keeps - // the price card fresh without hammering. 12 calls/h × 21 chains × 2 - // endpoints = 504 req/h, fine on a paid key. + // the price card fresh without hammering. Native calls dedup by + // symbol, so one tick is ~19 native plus ~27 stats requests, under + // 600 req/h, fine on a paid key. MobulaRefreshInterval time.Duration } diff --git a/harnesses/chain-kpis/cmd/script/defillama.go b/harnesses/chain-kpis/cmd/script/defillama.go index 1bda3e7d..c3ee658a 100644 --- a/harnesses/chain-kpis/cmd/script/defillama.go +++ b/harnesses/chain-kpis/cmd/script/defillama.go @@ -12,8 +12,7 @@ import ( // per chain that has a DefiLlama mapping: // 1. /v2/historicalChainTvl/ — last sample = current TVL // 2. /overview/dexs/ — total24h = aggregate DEX volume -// 3. /stablecoincharts/?stablecoin=1 -// — last point's USD-pegged total +// 3. /stablecoincharts/ — last point's USD-pegged total // // Each endpoint is its own goroutine inside fetchDefillamaChain so a // stuck TVL request doesn't starve the stables fetch for the same chain. @@ -113,20 +112,22 @@ func defillamaDexVolume24h(chainName string) (float64, error) { return resp.Total24h, nil } -// defillamaStablesMcap reads /stablecoincharts/?stablecoin=1 and -// returns the last sample's USD-pegged total. The `stablecoin=1` filter -// is required by the API though we don't filter by a specific stablecoin; -// the response is the chain-wide aggregate when no specific id is given. +// defillamaStablesMcap reads /stablecoincharts/ and returns the +// last sample's USD-pegged total. No `stablecoin=` query param: that +// param filters to ONE pegged asset (id 1 = USDT) and the API answers +// 200 + empty body on chains where that specific asset has no recorded +// issuance (Base, Blast, Stellar), which is what nulled their stables +// card. The unfiltered call returns the chain-wide aggregate. +// Chains DefiLlama tracks for TVL but not for stablecoins (Litecoin) +// answer 404 and land in the not_found error bucket, gauge unpublished. func defillamaStablesMcap(chainName string) (float64, error) { - url := fmt.Sprintf("%s/stablecoincharts/%s?stablecoin=1", stablesLlamaBase, encodePath(chainName)) + url := fmt.Sprintf("%s/stablecoincharts/%s", stablesLlamaBase, encodePath(chainName)) body, err := getJSON(httpClientDefillama, url) if err != nil { return 0, err } - // DefiLlama returns 200 + empty body for chains it tracks but where no - // stablecoin issuance has been recorded (Stellar, Blast, Base at time - // of writing). Surface as a typed sentinel so the harness logs it as - // "not_tracked" rather than spamming parse_error. + // Typed sentinel so an empty 200 logs as "not_tracked" rather than + // spamming parse_error. if len(body) == 0 { return 0, fmt.Errorf("not_tracked") } diff --git a/harnesses/chain-kpis/cmd/script/mobula.go b/harnesses/chain-kpis/cmd/script/mobula.go index 816689ff..bd47ee5b 100644 --- a/harnesses/chain-kpis/cmd/script/mobula.go +++ b/harnesses/chain-kpis/cmd/script/mobula.go @@ -33,8 +33,8 @@ func fetchAllMobula(cfg *Config) { return } - // Dedup native symbols. The 21 chains today flatten to 11 unique - // natives (every L2 = ETH). + // Dedup native symbols. The 32 chains today flatten to ~19 unique + // natives (every ETH gas rollup = ETH). natives := map[string][]Chain{} for _, c := range Registry { if c.NativeSymbol == "" { diff --git a/harnesses/chain-kpis/cmd/script/registry.go b/harnesses/chain-kpis/cmd/script/registry.go index b3ca6768..eb084453 100644 --- a/harnesses/chain-kpis/cmd/script/registry.go +++ b/harnesses/chain-kpis/cmd/script/registry.go @@ -35,7 +35,13 @@ var Registry = []Chain{ {Slug: "bnb", DefiLlama: "BSC", Mobula: "BNB Smart Chain (BEP20)", NativeSymbol: "BNB"}, {Slug: "avalanche", DefiLlama: "Avalanche", Mobula: "Avalanche C-Chain", NativeSymbol: "AVAX"}, {Slug: "sui", DefiLlama: "Sui", Mobula: "Sui", NativeSymbol: "SUI"}, - {Slug: "ton", DefiLlama: "TON", Mobula: "TON", NativeSymbol: "TON"}, + // Site canonical slug is "gram" since the Toncoin to Gram rename; the + // old "ton" label made the Prom selector miss and the KV blob stay null. + // Mobula still serves the asset under symbol TON: symbol GRAM resolves + // to an unrelated "GRAM Token" (~$24M mcap), verified live 2026-07-08. + // The stats endpoint still accepts blockchain=TON even though it is + // absent from /api/1/blockchains. + {Slug: "gram", DefiLlama: "TON", Mobula: "TON", NativeSymbol: "TON"}, {Slug: "stellar", DefiLlama: "Stellar", Mobula: "", NativeSymbol: "XLM"}, {Slug: "tron", DefiLlama: "Tron", Mobula: "TRON", NativeSymbol: "TRX"}, {Slug: "cardano", DefiLlama: "Cardano", Mobula: "", NativeSymbol: "ADA"}, @@ -52,4 +58,28 @@ var Registry = []Chain{ {Slug: "blast", DefiLlama: "Blast", Mobula: "Blast", NativeSymbol: "ETH"}, {Slug: "mantle", DefiLlama: "Mantle", Mobula: "Mantle", NativeSymbol: "MNT"}, {Slug: "taiko", DefiLlama: "Taiko", Mobula: "Taiko", NativeSymbol: "ETH"}, + // Chains added to the site registry after the original harness config + // was written; they published all-null KV blobs until this batch. + // Every DefiLlama name below verified live against /v2/chains, + // /overview/dexs/ and /stablecoincharts/ on 2026-07-08. + // Mobula blockchain names verified against /api/1/blockchains; empty + // means Mobula does not index the chain yet (only the tokens-indexed + // gauge is lost, the KPI strip does not read it). + {Slug: "monad", DefiLlama: "Monad", Mobula: "", NativeSymbol: "MON"}, + {Slug: "megaeth", DefiLlama: "MegaETH", Mobula: "MegaETH", NativeSymbol: "ETH"}, + {Slug: "sonic", DefiLlama: "Sonic", Mobula: "Sonic", NativeSymbol: "S"}, + // Gnosis gas is xDAI but the site strip labels the native token GNO, + // so we publish GNO to match what the page displays. Mobula indexes + // the chain under its legacy XDAI name. + {Slug: "gnosis", DefiLlama: "Gnosis", Mobula: "XDAI", NativeSymbol: "GNO"}, + {Slug: "celo", DefiLlama: "Celo", Mobula: "Celo", NativeSymbol: "CELO"}, + {Slug: "moonbeam", DefiLlama: "Moonbeam", Mobula: "Moonbeam", NativeSymbol: "GLMR"}, + {Slug: "unichain", DefiLlama: "Unichain", Mobula: "", NativeSymbol: "ETH"}, + {Slug: "berachain", DefiLlama: "Berachain", Mobula: "Berachain", NativeSymbol: "BERA"}, + {Slug: "cronos", DefiLlama: "Cronos", Mobula: "Cronos", NativeSymbol: "CRO"}, + // Fraxtal gas is frxETH, so we follow the same gas-token convention as + // the ETH rollups. Mobula symbol FRAX resolves to the legacy Frax + // stablecoin (~$1), the wrong asset for a native-token card. + {Slug: "fraxtal", DefiLlama: "Fraxtal", Mobula: "", NativeSymbol: "FRXETH"}, + {Slug: "soneium", DefiLlama: "Soneium", Mobula: "", NativeSymbol: "ETH"}, }