diff --git a/benchmarks/asset-registry-coverage.yml b/benchmarks/asset-registry-coverage.yml index c29a75d7f..b252b2001 100644 --- a/benchmarks/asset-registry-coverage.yml +++ b/benchmarks/asset-registry-coverage.yml @@ -95,6 +95,19 @@ providers: sample_size: networks_supported_total{provider="coingecko"} series: networks_supported_total{provider="coingecko"} + - slug: serialized + name: Serialized + tag: Own indexers, 18 EVM + Solana + formula: "Count of chains returned as live by Serialized's `/v1/meta/chains` endpoint, refreshed every 6 hours." + queries: + p50: networks_supported_total{provider="serialized"} + p90: networks_supported_total{provider="serialized"} + p99: networks_supported_total{provider="serialized"} + mean: networks_supported_total{provider="serialized"} + success: clamp_max(networks_supported_total{provider="serialized"} > bool 0, 1) + sample_size: networks_supported_total{provider="serialized"} + series: networks_supported_total{provider="serialized"} + - slug: coinpaprika name: CoinPaprika tag: Market-data API asset registry diff --git a/benchmarks/dex-network-coverage.yml b/benchmarks/dex-network-coverage.yml index 198522514..8e7649b3f 100644 --- a/benchmarks/dex-network-coverage.yml +++ b/benchmarks/dex-network-coverage.yml @@ -90,6 +90,19 @@ providers: sample_size: networks_supported_total{provider="geckoterminal"} series: networks_supported_total{provider="geckoterminal"} + - slug: serialized + name: Serialized + tag: Own indexers, 18 EVM + Solana + formula: "Count of chains returned as live by Serialized's `/v1/meta/chains` endpoint; every listed chain carries DEX pool indexing. Refreshed every 6 hours." + queries: + p50: networks_supported_total{provider="serialized"} + p90: networks_supported_total{provider="serialized"} + p99: networks_supported_total{provider="serialized"} + mean: networks_supported_total{provider="serialized"} + success: clamp_max(networks_supported_total{provider="serialized"} > bool 0, 1) + sample_size: networks_supported_total{provider="serialized"} + series: networks_supported_total{provider="serialized"} + - slug: codex name: Codex tag: Defined.fi DEX data API diff --git a/harnesses/network-coverage/cmd/script/config.go b/harnesses/network-coverage/cmd/script/config.go index 83d27ab8d..99c7310f2 100644 --- a/harnesses/network-coverage/cmd/script/config.go +++ b/harnesses/network-coverage/cmd/script/config.go @@ -13,6 +13,7 @@ type Config struct { CodexSessionCookie string // fallback path: mint JWT from Defined.fi cookie DefinedTokenURL string // optional: pre-minted JWT sidecar CoinStatsAPIKey string + SerializedAPIKey string SimDuneAPIKey string // optional — Sim's public endpoint works keyless, but a key avoids rate limits HTTPProxy string RefreshInterval time.Duration @@ -26,6 +27,7 @@ func loadConfig() *Config { CodexSessionCookie: os.Getenv("DEFINED_SESSION_COOKIE"), DefinedTokenURL: os.Getenv("DEFINED_TOKEN_SERVICE_URL"), CoinStatsAPIKey: os.Getenv("COINSTATS_API_KEY"), + SerializedAPIKey: os.Getenv("SERIALIZED_API_KEY"), SimDuneAPIKey: os.Getenv("SIM_DUNE_API_KEY"), HTTPProxy: os.Getenv("HTTP_PROXY"), RefreshInterval: 6 * time.Hour, @@ -47,8 +49,8 @@ func loadConfig() *Config { } else if c.CodexSessionCookie != "" { codexAuth = "cookie+mint" } - fmt.Printf("Config: refresh=%v, testnets=%v, mobula_key=%v, codex=%s, coinstats_key=%v, sim_dune_key=%v\n", + fmt.Printf("Config: refresh=%v, testnets=%v, mobula_key=%v, codex=%s, coinstats_key=%v, sim_dune_key=%v, serialized_key=%v\n", c.RefreshInterval, c.IncludeTestnets, c.MobulaAPIKey != "", codexAuth, - c.CoinStatsAPIKey != "", c.SimDuneAPIKey != "") + c.CoinStatsAPIKey != "", c.SimDuneAPIKey != "", c.SerializedAPIKey != "") return c } diff --git a/harnesses/network-coverage/cmd/script/main.go b/harnesses/network-coverage/cmd/script/main.go index 885a9ffad..20693522d 100644 --- a/harnesses/network-coverage/cmd/script/main.go +++ b/harnesses/network-coverage/cmd/script/main.go @@ -76,6 +76,7 @@ func fetchAll(cfg *Config) { {"coinstats", fetchCoinStats}, {"coingecko", fetchCoinGecko}, {"dexpaprika", fetchDexPaprika}, + {"serialized", fetchSerialized}, } var wg sync.WaitGroup diff --git a/harnesses/network-coverage/cmd/script/serialized.go b/harnesses/network-coverage/cmd/script/serialized.go new file mode 100644 index 000000000..9ad325b64 --- /dev/null +++ b/harnesses/network-coverage/cmd/script/serialized.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// Serialized publishes its chain list at GET /v1/meta/chains. The endpoint +// is free (0 credits) and returns one row per chain with a `status` field. +// +// The same list answers both benches this harness feeds: Serialized runs its +// own indexers and does not separate "chains where we know tokens" from +// "chains where we index DEX pools" — every listed chain carries both. So +// the count is identical on bench 005 and bench 090 by construction, which +// is worth knowing when reading the two leaderboards side by side. +const serializedChainsURL = "https://api.serialized.xyz/v1/meta/chains" + +type serializedChain struct { + Chain string `json:"chain"` // "evm:8453" or "solana" + Name string `json:"name"` + Slug string `json:"slug"` + Family string `json:"family"` + Status string `json:"status"` +} + +type serializedChainsResponse struct { + Data []serializedChain `json:"data"` +} + +func fetchSerialized(cfg *Config) ProviderResult { + res := ProviderResult{Provider: "serialized"} + if cfg.SerializedAPIKey == "" { + res.Err = "missing_api_key" + return res + } + + client := &http.Client{Timeout: 15 * time.Second} + req, _ := http.NewRequest("GET", serializedChainsURL, nil) + // Raw key, no Bearer prefix — a prefixed key is rejected with 401. + req.Header.Set("Authorization", cfg.SerializedAPIKey) + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + res.Err = fmt.Sprintf("request_error: %v", err) + return res + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode != 200 { + res.Err = fmt.Sprintf("status_%d", resp.StatusCode) + return res + } + + var parsed serializedChainsResponse + if err := json.Unmarshal(body, &parsed); err != nil { + res.Err = fmt.Sprintf("parse_error: %v", err) + return res + } + + for _, c := range parsed.Data { + // Only chains the provider declares live. Everything on this + // endpoint is mainnet, so no testnet filter is needed, but a + // future "beta"/"deprecated" status must not inflate the count. + if c.Status != "live" { + continue + } + res.Networks = append(res.Networks, Network{ + ChainID: c.Chain, + Slug: c.Slug, + Name: c.Name, + }) + } + + return res +}