From 7a1e476ccb37fdc2b255bf6c4e6a6fe8dda15a36 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:11:03 +0200 Subject: [PATCH 1/2] feat: split token-quote availability from route coverage Add token_quote_api_ok_total counter (HTTP 200 regardless of outAmount) and RecordProbeDetailed(apiOk, routeFound) in all 6 provider adapters. YAML gets availability and coverage_given_available query slots per provider. --- benchmarks/token-quote-coverage.yml | 14 ++++++++++--- .../cmd/monitor/jupiter.go | 10 +++++----- .../cmd/monitor/kyberswap.go | 12 +++++------ .../cmd/monitor/metrics.go | 20 ++++++++++++++++--- .../cmd/monitor/mobula.go | 14 ++++++------- .../token-quote-coverage/cmd/monitor/odos.go | 16 +++++++-------- .../cmd/monitor/paraswap.go | 12 +++++------ .../token-quote-coverage/cmd/monitor/relay.go | 12 ++++++----- 8 files changed, 67 insertions(+), 43 deletions(-) diff --git a/benchmarks/token-quote-coverage.yml b/benchmarks/token-quote-coverage.yml index 2bcc7d42..f0c2874c 100644 --- a/benchmarks/token-quote-coverage.yml +++ b/benchmarks/token-quote-coverage.yml @@ -77,7 +77,7 @@ methodology: - "Aggregator endpoints: Jupiter lite-api.jup.ag/swap/v1/quote (Solana), Mobula /api/2/swap/quoting (EVM + Solana, API key), Relay api.relay.link/quote (EVM + Solana, no key), KyberSwap aggregator-api.kyberswap.com/{chain}/api/v1/routes (EVM only, no key), ParaSwap apiv5.paraswap.io/prices (EVM only, no key). Each adapter normalises chain identifiers, input/output decimals, and error response shapes before recording outcome." - "Jupiter strictMode=false: queried without the curated-token filter so Jupiter will attempt routing for any token with a discoverable on-chain pool, matching how most wallets call the API. strictMode=true would trivially exclude new tokens and defeat the purpose of the benchmark." - "Chain-scope enforcement: Jupiter probes Solana-only. KyberSwap and ParaSwap probe EVM only (Base, BNB, Robinhood). Mobula and Relay probe all four chains including Solana. Providers are never penalised for chains they do not support: null cells are excluded from cross-chain aggregates." - - "Coverage metric: Prometheus counters token_quote_coverage_success_total and token_quote_coverage_attempts_total carry labels {provider, venue, chain}. The bench queries compute increase(success[24h]) / increase(attempts[24h]) * 100 live at read time. No pre-aggregation; the ratio reflects exactly the tokens probed in the rolling window." + - "Coverage metric: three Prometheus counters per probe. token_quote_coverage_attempts_total (every probe), token_quote_api_ok_total (HTTP 200 received, regardless of outAmount), and token_quote_coverage_success_total (HTTP 200 + outAmount > 0). The headline coverage rate (p50 slot) remains success / attempts for historical continuity. Two derived slots expose the split: availability = api_ok / attempts (is the API reachable and returning 200?); coverage_given_available = success / api_ok (given the API responded, did it find a route?)." - "Probe cadence and deduplication: each batch cycles through the current Dexscreener token-boosts feed. Tokens are not deduplicated across batches; the same token may appear in multiple 30-minute windows if it remains boosted. The score therefore reflects freshness of the aggregator's current routing graph, not historical coverage depth." - "Single origin: all probes originate from the eu-west OCB VPS. Regional latency is not a factor in this bench (the outcome is binary hit/miss, not latency). No region dimension in v1." @@ -150,6 +150,8 @@ providers: success: clamp_max(sum(increase(token_quote_coverage_success_total{provider="mobula"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="mobula"}[24h])), 1) sample_size: sum(increase(token_quote_coverage_attempts_total{provider="mobula"}[24h])) series: sum(rate(token_quote_coverage_success_total{provider="mobula"}[1h])) / sum(rate(token_quote_coverage_attempts_total{provider="mobula"}[1h])) * 100 + availability: clamp_max(sum(increase(token_quote_api_ok_total{provider="mobula"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="mobula"}[24h])), 1) * 100 + coverage_given_available: sum(increase(token_quote_coverage_success_total{provider="mobula"}[24h])) / sum(increase(token_quote_api_ok_total{provider="mobula"}[24h])) * 100 - slug: jupiter name: Jupiter @@ -163,6 +165,8 @@ providers: success: clamp_max(sum(increase(token_quote_coverage_success_total{provider="jupiter"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="jupiter"}[24h])), 1) sample_size: sum(increase(token_quote_coverage_attempts_total{provider="jupiter"}[24h])) series: sum(rate(token_quote_coverage_success_total{provider="jupiter"}[1h])) / sum(rate(token_quote_coverage_attempts_total{provider="jupiter"}[1h])) * 100 + availability: clamp_max(sum(increase(token_quote_api_ok_total{provider="jupiter"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="jupiter"}[24h])), 1) * 100 + coverage_given_available: sum(increase(token_quote_coverage_success_total{provider="jupiter"}[24h])) / sum(increase(token_quote_api_ok_total{provider="jupiter"}[24h])) * 100 - slug: relay name: Relay @@ -176,6 +180,8 @@ providers: success: clamp_max(sum(increase(token_quote_coverage_success_total{provider="relay"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="relay"}[24h])), 1) sample_size: sum(increase(token_quote_coverage_attempts_total{provider="relay"}[24h])) series: sum(rate(token_quote_coverage_success_total{provider="relay"}[1h])) / sum(rate(token_quote_coverage_attempts_total{provider="relay"}[1h])) * 100 + availability: clamp_max(sum(increase(token_quote_api_ok_total{provider="relay"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="relay"}[24h])), 1) * 100 + coverage_given_available: sum(increase(token_quote_coverage_success_total{provider="relay"}[24h])) / sum(increase(token_quote_api_ok_total{provider="relay"}[24h])) * 100 - slug: kyberswap name: KyberSwap @@ -189,6 +195,8 @@ providers: success: clamp_max(sum(increase(token_quote_coverage_success_total{provider="kyberswap"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="kyberswap"}[24h])), 1) sample_size: sum(increase(token_quote_coverage_attempts_total{provider="kyberswap"}[24h])) series: sum(rate(token_quote_coverage_success_total{provider="kyberswap"}[1h])) / sum(rate(token_quote_coverage_attempts_total{provider="kyberswap"}[1h])) * 100 + availability: clamp_max(sum(increase(token_quote_api_ok_total{provider="kyberswap"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="kyberswap"}[24h])), 1) * 100 + coverage_given_available: sum(increase(token_quote_coverage_success_total{provider="kyberswap"}[24h])) / sum(increase(token_quote_api_ok_total{provider="kyberswap"}[24h])) * 100 - slug: paraswap name: ParaSwap @@ -202,5 +210,5 @@ providers: success: clamp_max(sum(increase(token_quote_coverage_success_total{provider="paraswap"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="paraswap"}[24h])), 1) sample_size: sum(increase(token_quote_coverage_attempts_total{provider="paraswap"}[24h])) series: sum(rate(token_quote_coverage_success_total{provider="paraswap"}[1h])) / sum(rate(token_quote_coverage_attempts_total{provider="paraswap"}[1h])) * 100 - - + availability: clamp_max(sum(increase(token_quote_api_ok_total{provider="paraswap"}[24h])) / sum(increase(token_quote_coverage_attempts_total{provider="paraswap"}[24h])), 1) * 100 + coverage_given_available: sum(increase(token_quote_coverage_success_total{provider="paraswap"}[24h])) / sum(increase(token_quote_api_ok_total{provider="paraswap"}[24h])) * 100 diff --git a/harnesses/token-quote-coverage/cmd/monitor/jupiter.go b/harnesses/token-quote-coverage/cmd/monitor/jupiter.go index 0f0197e2..4ad1460e 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/jupiter.go +++ b/harnesses/token-quote-coverage/cmd/monitor/jupiter.go @@ -40,14 +40,14 @@ func (p *JupiterProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } resp, err := p.client.Do(req) if err != nil { fmt.Printf("[jupiter] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() @@ -55,7 +55,7 @@ func (p *JupiterProvider) Quote(ctx context.Context, token Token) (ok bool) { if resp.StatusCode != 200 { fmt.Printf("[jupiter] %s/%s status=%d body=%s\n", token.Chain, token.Address, resp.StatusCode, snippet(body)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -64,12 +64,12 @@ func (p *JupiterProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[jupiter] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, false) return false } n, _ := strconv.ParseInt(r.OutAmount, 10, 64) ok = n > 0 - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, ok) return ok } diff --git a/harnesses/token-quote-coverage/cmd/monitor/kyberswap.go b/harnesses/token-quote-coverage/cmd/monitor/kyberswap.go index fdae51b9..eb404c6b 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/kyberswap.go +++ b/harnesses/token-quote-coverage/cmd/monitor/kyberswap.go @@ -43,7 +43,7 @@ func (p *KyberSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { slug = "robinhood" usdcAddr = kyberUSDGRobinhood default: - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -56,7 +56,7 @@ func (p *KyberSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } req.Header.Set("x-client-id", "openchainbench") @@ -64,7 +64,7 @@ func (p *KyberSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { resp, err := p.client.Do(req) if err != nil { fmt.Printf("[kyberswap] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() @@ -72,7 +72,7 @@ func (p *KyberSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { if resp.StatusCode != 200 { fmt.Printf("[kyberswap] %s/%s status=%d body=%s\n", token.Chain, token.Address, resp.StatusCode, snippet(body)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -85,11 +85,11 @@ func (p *KyberSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[kyberswap] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, false) return false } ok = r.Data.RouteSummary.AmountOut != "" && r.Data.RouteSummary.AmountOut != "0" - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, ok) return ok } diff --git a/harnesses/token-quote-coverage/cmd/monitor/metrics.go b/harnesses/token-quote-coverage/cmd/monitor/metrics.go index af183f0f..c1766571 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/metrics.go +++ b/harnesses/token-quote-coverage/cmd/monitor/metrics.go @@ -10,6 +10,7 @@ import ( var ( coverageSuccess *prometheus.CounterVec coverageAttempts *prometheus.CounterVec + coverageAPIok *prometheus.CounterVec ) func init() { @@ -30,12 +31,25 @@ func init() { []string{"provider", "venue", "chain"}, ) prometheus.MustRegister(coverageAttempts) + + coverageAPIok = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "token_quote_api_ok_total", + Help: "HTTP 200 responses received per provider/venue/chain, regardless of outAmount", + }, + []string{"provider", "venue", "chain"}, + ) + prometheus.MustRegister(coverageAPIok) } -// RecordProbe records one probe attempt and optionally a success. -func RecordProbe(provider, venue, chain string, ok bool) { +// RecordProbeDetailed records one probe: apiOk=true when the provider returned HTTP 200, +// routeFound=true when outAmount > 0 was parsed from the response. +func RecordProbeDetailed(provider, venue, chain string, apiOk, routeFound bool) { coverageAttempts.WithLabelValues(provider, venue, chain).Inc() - if ok { + if apiOk { + coverageAPIok.WithLabelValues(provider, venue, chain).Inc() + } + if routeFound { coverageSuccess.WithLabelValues(provider, venue, chain).Inc() } } diff --git a/harnesses/token-quote-coverage/cmd/monitor/mobula.go b/harnesses/token-quote-coverage/cmd/monitor/mobula.go index 8ad927de..082de4cb 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/mobula.go +++ b/harnesses/token-quote-coverage/cmd/monitor/mobula.go @@ -54,7 +54,7 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) { case "solana": chainId, tokenIn, wallet = "solana", mobulaUSDCSolana, mobulaSolanaWallet default: - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -72,7 +72,7 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } req.Header.Set("Authorization", p.apiKey) @@ -80,7 +80,7 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) { resp, err := p.client.Do(req) if err != nil { fmt.Printf("[mobula] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() @@ -88,7 +88,7 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) { if resp.StatusCode != 200 { fmt.Printf("[mobula] %s/%s status=%d body=%s\n", token.Chain, token.Address, resp.StatusCode, snippet(body)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -100,18 +100,18 @@ func (p *MobulaProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[mobula] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, false) return false } // For Solana: Mobula finds the route but fails to build the tx because the // dummy wallet has no SOL for fees. Route found = coverage HIT. if token.Chain == "solana" && r.Error == "Transaction build failed" { - RecordProbe(p.Slug(), token.Venue, token.Chain, true) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, true) return true } ok = r.Data.AmountOutTokens != "" && r.Data.AmountOutTokens != "0" - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, ok) return ok } diff --git a/harnesses/token-quote-coverage/cmd/monitor/odos.go b/harnesses/token-quote-coverage/cmd/monitor/odos.go index 8dae9538..2883f7bf 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/odos.go +++ b/harnesses/token-quote-coverage/cmd/monitor/odos.go @@ -37,7 +37,7 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { case p.sem <- struct{}{}: defer func() { <-p.sem }() case <-ctx.Done(): - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -54,7 +54,7 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { chainId = 4663 usdcAddr = odosUSDGRobinhood default: - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -73,7 +73,7 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "POST", "https://api.odos.xyz/sor/quote/v2", bytes.NewBuffer(bb)) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } req.Header.Set("Content-Type", "application/json") @@ -81,7 +81,7 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { resp, err := p.client.Do(req) if err != nil { fmt.Printf("[odos] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() @@ -89,12 +89,12 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { if resp.StatusCode == 429 { fmt.Printf("[odos] %s/%s throttled\n", token.Chain, token.Address) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } if resp.StatusCode != 200 { fmt.Printf("[odos] %s/%s status=%d body=%s\n", token.Chain, token.Address, resp.StatusCode, snippet(body)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -103,11 +103,11 @@ func (p *OdosProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[odos] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, false) return false } ok = len(r.OutAmounts) > 0 && r.OutAmounts[0] != "" && r.OutAmounts[0] != "0" - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, ok) return ok } diff --git a/harnesses/token-quote-coverage/cmd/monitor/paraswap.go b/harnesses/token-quote-coverage/cmd/monitor/paraswap.go index 0be207c7..5d64f220 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/paraswap.go +++ b/harnesses/token-quote-coverage/cmd/monitor/paraswap.go @@ -42,7 +42,7 @@ func (p *ParaSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { network = "4663" usdcAddr = paraswapUSDGRobinhood default: - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -58,14 +58,14 @@ func (p *ParaSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } resp, err := p.client.Do(req) if err != nil { fmt.Printf("[paraswap] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() @@ -73,7 +73,7 @@ func (p *ParaSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { if resp.StatusCode != 200 { fmt.Printf("[paraswap] %s/%s status=%d body=%s\n", token.Chain, token.Address, resp.StatusCode, snippet(body)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -84,11 +84,11 @@ func (p *ParaSwapProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[paraswap] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, false) return false } ok = r.PriceRoute.DestAmount != "" && r.PriceRoute.DestAmount != "0" - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, true, ok) return ok } diff --git a/harnesses/token-quote-coverage/cmd/monitor/relay.go b/harnesses/token-quote-coverage/cmd/monitor/relay.go index 62dae5a0..f5e45423 100644 --- a/harnesses/token-quote-coverage/cmd/monitor/relay.go +++ b/harnesses/token-quote-coverage/cmd/monitor/relay.go @@ -49,7 +49,7 @@ func (p *RelayProvider) Quote(ctx context.Context, token Token) (ok bool) { case "robinhood": chainId, tokenIn, user = 4663, relayUSDGRobinhood, relayEVMUser default: - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } @@ -65,7 +65,7 @@ func (p *RelayProvider) Quote(ctx context.Context, token Token) (ok bool) { req, err := http.NewRequestWithContext(ctx, "POST", "https://api.relay.link/quote", bytes.NewReader(payload)) if err != nil { - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } req.Header.Set("Content-Type", "application/json") @@ -73,12 +73,14 @@ func (p *RelayProvider) Quote(ctx context.Context, token Token) (ok bool) { resp, err := p.client.Do(req) if err != nil { fmt.Printf("[relay] %s/%s net error: %s\n", token.Chain, token.Address, classifyNetErr(err)) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, false, false) return false } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) + apiOk := resp.StatusCode == 200 + var r struct { Details struct { CurrencyOut struct { @@ -89,7 +91,7 @@ func (p *RelayProvider) Quote(ctx context.Context, token Token) (ok bool) { } if err := json.Unmarshal(body, &r); err != nil { fmt.Printf("[relay] %s/%s parse error: %v\n", token.Chain, token.Address, err) - RecordProbe(p.Slug(), token.Venue, token.Chain, false) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, apiOk, false) return false } @@ -97,6 +99,6 @@ func (p *RelayProvider) Quote(ctx context.Context, token Token) (ok bool) { if !ok && r.Message != "" { fmt.Printf("[relay] %s/%s: %s\n", token.Chain, token.Address, r.Message) } - RecordProbe(p.Slug(), token.Venue, token.Chain, ok) + RecordProbeDetailed(p.Slug(), token.Venue, token.Chain, apiOk, ok) return ok } From 8c89846526e4a8edf4b1678aec9d64667e5bdb9f Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:25:18 +0200 Subject: [PATCH 2/2] fix: data-api report corrections + drop duplicate citation footer - publishedAt 2026-08-04 -> 2026-08-05 - Mobula aggregate 677 -> 678 ms (2 occurrences: table + scorecard) - GeckoTerminal aggregate 12,340 -> 12,319 ms (3 occurrences) - StatTable caption: p50 -> mean of per-chain p50s - Removed speculative Mobula Solana mechanism sentence; replaced with caveated explanation noting reorg exposure - 8.2x Mobula-Base gap -> 7.9x (773/98) - RH Chain 4x faster blocks -> 2x faster block cadence (Base 200ms/RH 100ms) - 28 pp wallet-labels gap -> 24 pp (79.9 - 55.6 = 24.3) - Quote section: removed 'entire cohort' claim; fixed Mobula framing to reference availability confound instead of 29% miss rate - Methodology: removed dangling Singapore regional-table reference - coverage_given_success -> coverage_given_available (correct metric name) - Removed auto-rendered duplicate citation block from page.tsx layout --- src/app/reports/[category]/[slug]/page.tsx | 21 +------------ .../2026-08-state-of-crypto-data-apis.mdx | 30 +++++++++---------- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/app/reports/[category]/[slug]/page.tsx b/src/app/reports/[category]/[slug]/page.tsx index ac41b2a7..9baf0893 100644 --- a/src/app/reports/[category]/[slug]/page.tsx +++ b/src/app/reports/[category]/[slug]/page.tsx @@ -407,26 +407,7 @@ export default async function ReportPage({ params }: Props) { - {/* Citation */} -
- Cite this report -
-- OpenChainBench Research. {report.title}. OpenChainBench,{" "} - {new Date(report.publishedAt).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - timeZone: "UTC", - })} - .{" "} - - {report.canonical} - - . Licensed under CC BY 4.0. -
-