From 018511c9151c9e4606522aa80d60a0719755b32c Mon Sep 17 00:00:00 2001
From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com>
Date: Thu, 25 Jun 2026 15:54:41 +0300
Subject: [PATCH 1/3] chore(benchmarks): drop solana-tx-landing-latency
(harness offline, 404 bleeding SEO) (#701)
---
docs/methodology/solana-tx-landing-active.md | 163 ------------------
harnesses/solana-tx-landing/README.md | 7 +-
.../cmd/script/active_metrics.go | 5 +-
.../solana-tx-landing/cmd/script/main.go | 1 -
.../solana-tx-landing/cmd/script/prober.go | 3 +-
src/app/answers/[slug]/page.tsx | 8 +-
src/app/answers/page.tsx | 7 +-
src/data/bench-published.json | 1 -
src/data/compare-pairs.ts | 9 -
9 files changed, 14 insertions(+), 190 deletions(-)
delete mode 100644 docs/methodology/solana-tx-landing-active.md
diff --git a/docs/methodology/solana-tx-landing-active.md b/docs/methodology/solana-tx-landing-active.md
deleted file mode 100644
index c0cb98ce..00000000
--- a/docs/methodology/solana-tx-landing-active.md
+++ /dev/null
@@ -1,163 +0,0 @@
-# Methodology - Solana TX Landing (Active Probing)
-
-> **Pre-registered methodology.** Pinned commit before any sponsor contract is signed. Changes ship as public PRs with a 14-day comment window. Disputes go through public GitHub issues.
->
-> **Version :** v1.0 - first commit 2026-05-21. Bench № 016 (Solana TX Landing).
-> **Replaces / extends :** the observational tip-wallet attribution methodology that ships with the same bench page (kept as the "Market Share" tab).
-
----
-
-## 1. Question we answer
-
-For each Solana transaction landing service, **how long does it take for a transaction submitted via that service to be confirmed on mainnet, and what fraction never confirms within a usable window** - measured from a single fixed geographic origin, on a uniform synthetic payload, at a uniform cadence.
-
-The bench does **not** answer "which service is best for your trading bot" - that requires modeling your own payload size, tip elasticity, and venue. The bench answers "what is the typical, comparable, reproducible time-to-land per service today."
-
-## 2. Scope (V0-Lean launch)
-
-| Dimension | Value |
-|---|---|
-| Services probed | 5 - Jito Block Engine, Helius Sender, Astralane Iris, Nozomi (Temporal), 0slot.trade |
-| Region | 1 - Railway us-east (Newark / NY area) |
-| Cadence | 1 cycle per hour |
-| Duration | continuous, 24 / 7 |
-| Window for headline metrics | rolling 7-day weekly leaderboard |
-| Confirmation level | `confirmed` (1+ block confirmation) |
-
-Services and regions are added through a public PR with a 14-day comment window. Any expansion is a PR + 14-day window - never a silent change.
-
-## 3. Probe payload (exact)
-
-Every probe is a single Solana transaction containing three instructions, in this order :
-
-1. `ComputeBudgetProgram.SetComputeUnitLimit(50 000)` - caps compute units.
-2. `ComputeBudgetProgram.SetComputeUnitPrice(50 000 micro-lamports)` - priority fee per CU.
-3. `SystemProgram.Transfer(from = prober keypair, to = prober keypair, lamports = 1)` - the payload itself, self-transfer of 1 lamport. Solana requires non-zero state-touching for a tx to be valid; self-transfer is the minimum honest payload.
-4. `SystemProgram.Transfer(from = prober keypair, to = , lamports = )` - the tip required by the landing service.
-5. `MemoProgram.Memo("ocb---")` where `cycle_id` is a per-cycle 8-byte random hex generated once and shared across all per-service probes in the cycle. This lets us correlate the 5 simultaneous probes on-chain.
-
-The exact tip amount per service is published as part of this methodology and frozen unless a methodology PR amends it :
-
-| Service | Tip lamports | Source / justification |
-|---|---:|---|
-| Jito Block Engine | 10 000 | "Competitive" floor per docs.jito.wtf - above 1 000 doc minimum, below 50th-percentile observed real-traffic tip |
-| Helius Sender | 10 000 | Same with `?swqos_only=true` (isolates Helius own path from Jito fan-out) |
-| Astralane Iris | 500 000 | Mid-range net of refunds per astralane.gitbook.io |
-| Nozomi (Temporal) | 1 000 000 | Hard floor per use.temporal.xyz/nozomi/tipping-and-faq |
-| 0slot.trade | 1 000 000 | Hard floor per 0slot.trade |
-
-We do **not** vary tip amount across cycles. A "tip elasticity" experiment is a separate, sponsored methodology PR.
-
-## 4. Submission flow (per service, per cycle)
-
-1. Fetch a recent blockhash via the public mainnet RPC (`api.mainnet-beta.solana.com`) with `commitment = "processed"`. The same blockhash is used for all services in the same cycle so they share a chain-state reference point. `processed` is preferred over `confirmed` because the resulting blockhash is fresher (~400 ms vs ~6 s); the marginal fork risk is acceptable since landing services dedup on signature, not blockhash.
-2. Build the transaction described in §3 for that service (the tip-transfer differs per service).
-3. Sign with the region's persistent keypair. The signature is known at this point, before any network call.
-4. **Subscribe to the signature via `signatureSubscribe` on the public mainnet WebSocket** (`wss://api.mainnet-beta.solana.com`) at `commitment = "confirmed"`. The subscription is registered **before submission** so a fast-confirming tx cannot complete before we are listening (otherwise we would miss the notification and incorrectly timeout).
-5. Capture `submit_slot = getSlot(commitment="processed")` and `submit_wallclock = time.Now()`.
-6. POST the base64-encoded signed transaction to the service's documented submission endpoint (exact URLs published in the harness source) with `skipPreflight = true`, `maxRetries = 0`, `encoding = "base64"`. Per-service auth headers / query params are applied as documented.
-7. Capture the returned signature (or fail-fast on RPC error).
-8. Block on the `signatureNotification` push from the WebSocket. On notification, record `land_slot` from the notification context and `land_wallclock = time.Now()`. Classify as **landed**. Resolution is RTT-bounded (~30-50 ms us-east → mainnet-beta) since the RPC pushes the notification the instant the commitment level is reached, with no client polling cadence floor.
-9. **Fallback:** if the WebSocket connection fails to establish at the start of the cycle (transient network issue, RPC overload), every probe in that cycle falls back to HTTP polling of `getSignatureStatuses` every 200 ms. This preserves bench continuity but adds a ~200 ms quantization penalty for the affected cycle. The fallback path is logged.
-10. If 60 seconds elapse without a notification (or without a non-null `confirmationStatus` on the polling fallback), abandon the wait. Classify as **dropped** with reason `timeout`.
-11. If the original submission returned a transport error (HTTP timeout, DNS, EOF, connection refused), classify as **dropped** with reason `network_error`. If the upstream returned HTTP 419 / 429 or a JSON-RPC error containing "rate limit" / "too many requests", classify as `rate_limited`. If the submission was rejected with a structured RPC error (`InstructionError`, `BlockhashNotFound`, etc.) or the on-chain status comes back with an `Err`, classify as `invalid`.
-
-The 5 services for a given cycle are submitted **in parallel** (Go goroutines) so they sample the same congestion window. The order of `submit_slot` reads is arbitrary but all reads happen within 200 ms.
-
-## 5. Metrics (Prometheus, exposed at `:2112/metrics`)
-
-```
-solana_landing_probe_success_total{service, mode, region} counter
-solana_landing_probe_dropped_total{service, mode, region, reason} counter
- # reason: timeout | invalid | network_error | rate_limited
-
-solana_landing_probe_latency_slots{service, mode, region} gauge (last observed slot delta)
-solana_landing_probe_latency_slots_histogram{service, mode, region} histogram
- # buckets: 1, 2, 3, 5, 10, 20, 50, 100
-
-solana_landing_probe_latency_ms{service, mode, region} gauge (last observed wall-clock ms)
-solana_landing_probe_latency_ms_histogram{service, mode, region} histogram
- # buckets: 100, 250, 500, 1000, 2000, 5000, 10000, 30000, 60000
-
-solana_landing_probe_keypair_balance_sol{region} gauge
-solana_landing_probe_keypair_low_balance_total{region} counter
-solana_landing_probe_cycle_total{region} counter
-solana_landing_probe_last_cycle_timestamp_seconds{region} gauge
-solana_landing_probe_enabled{region} gauge
- # 1 when prober configured + running, 0 in pure observational mode
-```
-
-The `rate_limited` reason groups responses where the upstream service returns HTTP 419 / 429 or a JSON-RPC error containing "rate limit" / "too many requests". Reported separately from `invalid` because rate-limiting is a quota/operational state, not a landing-quality signal.
-
-**Label semantics :**
-
-- `service` ∈ {jito, helius-sender, astralane, nozomi, 0slot}
-- `mode` is set on `service = helius-sender` only and takes values `swqos_only` or `dual` (other services: `mode = "default"`)
-- `region` ∈ {us-east} (V0-Lean); will expand under §7
-
-**Headline metrics** displayed publicly on the bench page :
-
-- `landing_rate = success_total / (success_total + dropped_total{reason="timeout"})` over the last 7 days
-- `p50_latency_ms` and `p99_latency_ms` over the last 7 days, per service, per region
-- `p50_slot_delta` and `p99_slot_delta` over the last 7 days, per service, per region
-
-## 6. Jito control probe
-
-Three services (Helius default mode, Astralane Iris, Nozomi) submit a portion of their flow through Jito internally. Without controlling for this, their measured landing time conflates "this service's own path" with "Jito's auction outcome via this service".
-
-The Jito control probe is the standard Jito-direct probe **fired in the same cycle as the suspect services**, with the same tip floor (10 000 lamports) and same blockhash. If a suspect service consistently lands at the same `submit_slot + Δ` as the Jito control, the suspect service is interpreted as a Jito routing wrapper for that cycle. We do not currently publish a derived "service-net-of-Jito" metric, but the raw data supports such derivation.
-
-For Helius we additionally probe in `?swqos_only=true` mode in every cycle (see §3) to publish a clean Helius-only metric series.
-
-## 7. Versioning rules
-
-1. **Any** change to §3 (payload), §4 (submission flow), §5 (metric definitions), or the per-service URLs ships as a public PR against this file with a 14-day comment window before merge.
-2. Adding a new service requires a PR that updates §2 + §3 + §5 and is announced on the OpenChainBench blog with a 14-day window. Removing a service same.
-3. Tip-amount changes (§3) require a PR with a written explanation of why the new amount better reflects "competitive" floor.
-4. Region additions and cadence changes are PR + 14-day window.
-5. Metric removal or label rename is **prohibited within a version**. Such changes require a major version bump (v1 → v2) with a 30-day shadow-run period publishing both old and new metric families in parallel.
-
-## 8. Reproducibility
-
-The full harness source is published at `mobula-api/miniapps/solana-tx-landing/` (Go). Anyone with a funded Solana keypair (~1 SOL) can clone, set the `SOLANA_PROBE_KEYPAIR_BASE58` env var, run the binary, and reproduce all metrics. The bench does not rely on any internal Mobula service for measurement.
-
-Anyone replicating the bench from a different geographic origin will see different absolute latencies (since service POPs vary by region) but should see the same relative ranking trends over a 7-day window. Discrepancies of more than 1 percentile rank between independent replications should be filed as GitHub issues.
-
-## 9. Statistical power statement
-
-At V0-Lean cadence (1 / h × 1 region × 5 services) a single service accumulates 168 probes per rolling 7-day window. With this sample size :
-
-- `p50_latency_ms` standard error ≈ ±5 % at typical service variance
-- `p99_latency_ms` standard error ≈ ±15 % (broad; published with confidence interval)
-- Difference in `landing_rate` between two services detectable at 5 pp gap, p < 0.05, after ~18 days of data
-
-For sub-weekly resolution, cadence must increase. Any such change ships as a public PR + 14-day comment window.
-
-## 10. Limitations (explicit)
-
-- **Single-region**: latency reflects what a us-east client sees. Services with non-us-east-anchored POPs may rank differently from sgp or eu-west.
-- **Synthetic payload**: a 1-lamport self-transfer + memo is the smallest valid mainnet tx. Real trading payloads (Jupiter swap, Raydium add-liquidity) are heavier and may behave differently in tip elasticity. The bench does not extrapolate.
-- **Fixed tip per service**: the bench measures landing performance *at one tip level*. A service that lands at 99 % for 1 M lamports may land at 50 % for 100 k lamports. The bench does not characterize the tip elasticity curve in V0-Lean.
-- **Fan-out attribution**: §6 partially handles Helius / Astralane / Nozomi fan-out via the Jito control probe, but does not produce a derived "service-net" published metric in V0-Lean.
-- **Confirmation level**: we use `confirmed` (1+ confirmation). A service that lands at `processed` but never reaches `confirmed` would be undercounted. We do not currently publish a `processed` series.
-- **Mainnet incidents**: when Solana mainnet halts or congests beyond 60-second confirmation, all services rank identically high in `dropped{reason=timeout}`. The bench page must surface a "chain health overlay" so readers can distinguish service problems from chain problems.
-- **Geographic biases**: services with no us-east POP (none in the V0-Lean scope) are penalized vs services with one. We disclose POP locations in the bench page methodology block.
-
-## 11. Sponsor independence
-
-Sponsorship contracts (when present) follow a fixed public template with these clauses :
-
-- Sponsors fund operations and receive newsletter visibility, case studies, integration support. Never leaderboard influence, advance results, or methodology changes.
-- The non-suppression clause grants the sponsor a single remedy for unfavorable results : terminate the agreement and receive a pro-rata refund. Never edit, delay, or selectively publish.
-- Methodology changes (this document) ship as PRs and are independent of sponsor contracts.
-
-## 12. Change log
-
-| Version | Date | Change |
-|---|---|---|
-| v1.0 | 2026-05-21 | Initial pre-registration. V0-Lean scope : 5 services × 1 region × 1 / h. |
-| v1.1 | 2026-05-21 | Pre-launch reconciliation with implementation : metric names from `ocb_solana_landing_*` to `solana_landing_probe_*` (matches sibling OCB benches), `rate_limited` added as 4th drop reason, latency_ms histogram bucket list adjusted to include 250 ms and 30 s, blockhash commitment rationale clarified, memo format clarified (`ocb---` where cycle_id is itself the 8-byte random hex shared across all per-service probes in the cycle), `solana_landing_probe_enabled` gauge added so dashboards distinguish "prober disabled" from "prober stuck". |
-| v1.2 | 2026-05-23 | `getSignatureStatuses` poll interval reduced from 1 s to 200 ms after the first 7 days of live data showed all services collapsing to identical 1.0 s p50. The 1 s poll was the measurement floor (Solana confirmed status arrives in ~400 ms-1 s), so 200 ms restores 5x the resolution. Bench page queries also switched from `histogram_quantile` on the latency_ms histogram to `quantile_over_time` on the latency_ms gauge, since the histogram bucket list only had ~3 buckets in the 1-5 s zone where probes actually land, collapsing p50 to bucket midpoints. |
-| v1.3 | 2026-05-23 | Primary observation path switched from HTTP polling to `signatureSubscribe` over the public mainnet WebSocket (`wss://api.mainnet-beta.solana.com`). The RPC pushes the notification at the instant the commitment level is reached, so resolution is RTT-bounded (~30-50 ms us-east → mainnet-beta) rather than poll-cadence-bounded. Subscription is registered BEFORE submission to prevent missing fast confirmations. HTTP polling at 200 ms remains an automatic fallback if the WebSocket connect fails. Validated locally against mainnet-beta with end-to-end slot and signature subscribe tests before deploy. |
-| v1.4 | 2026-05-23 | Nozomi endpoint switched from `https://ewr.nozomi.temporal.xyz/` (Newark, region-pinned) to `http://edge.nozomi.temporal.xyz/` (geo-routed DNS, HTTP). Reason: Railway us-east is Ashburn, the previous endpoint forced a cross-region hop adding ~30 ms RTT. The geo-routed DNS resolves to the POP closest to the caller. HTTP instead of HTTPS skips the TLS handshake on the hot path (Solana tx is already signed so plain-text body is not a confidentiality risk - the signature is public the moment the tx is on chain). Change requested by Jakob @ Temporal Labs on 2026-05-23 and applied to all probes from the same region. The same review pass is open for Jito / Helius / Astralane / 0slot - any service whose **publicly-documented** best-practice configuration differs from what we currently probe is invited to file an issue or PR. We do NOT accept private deals to alter the probe surface for any single service; we DO apply optimisations that the service publishes as their standard production recommendation. |
-| v1.5 | 2026-05-24 | Two further Nozomi refinements per Jakob @ Temporal Labs follow-up. (a) Endpoint moved from `edge.nozomi.temporal.xyz` (JSON-RPC sendTransaction) to `http://edge.nozomi.temporal.xyz/api/sendBatch?c=` (binary `[u16_BE_len][tx_bytes]` framing, `Content-Type: application/octet-stream`). Per Jakob, this batch endpoint handles single-tx submissions and is the path most clients use. Implementation: single-tx wrap in the batch container. (b) Tip wallet switched from the saturated main wallet `TEMPaMeCRFAS9EKF53Jd6KpHxgL47uWLcpFArU1Fanq` to a non-saturated wallet `nEFs3jph8HJt7honu3k7XtGUufMnwAvSXmXcKSPxryP` recommended by Jakob; the main wallets receive heavy MEV-load traffic and clients typically rotate over less-busy alternates. The `TEMPaMe...` wallet remains in the Nozomi tip-wallet list as a fallback for the v1 anti-fingerprint randomisation feature. No change to tip floor (1 000 000 lamports). |
diff --git a/harnesses/solana-tx-landing/README.md b/harnesses/solana-tx-landing/README.md
index 1ea5e8f4..9c69b4e5 100644
--- a/harnesses/solana-tx-landing/README.md
+++ b/harnesses/solana-tx-landing/README.md
@@ -1,9 +1,10 @@
# solana-tx-landing harness
-Source for two OpenChainBench benches that share a single binary:
+Source for the OpenChainBench bench:
- [`solana-tx-landing`](https://openchainbench.com/benchmarks/solana-tx-landing) — observational market-share view of Solana transaction landing services (Jito, Helius Sender, Nozomi, Astralane, 0slot, etc.) measured via on-chain tip-wallet attribution.
-- [`solana-tx-landing-latency`](https://openchainbench.com/benchmarks/solana-tx-landing-latency) — active probing: a synthetic 1-lamport self-transfer submitted through each service, timing the slot delta to confirmation. See [`docs/methodology/solana-tx-landing-active.md`](../../docs/methodology/solana-tx-landing-active.md) for the pre-registered methodology.
+
+The binary also embeds an opt-in active prober (slot-delta latency), kept around for re-enablement but not currently wired to a public bench page on OCB.
Exposes Prometheus metrics on `:2112/metrics` (OCB Railway convention).
@@ -56,7 +57,7 @@ curl localhost:2112/metrics | grep solana_landing
| `LOGS_TOKEN` | (unset) | Optional, gates `/logs?tail=N` |
| `SLACK_WEBHOOK_URL` | (unset) | Optional, posts probe failures + low-balance alerts |
-See [`docs/methodology/solana-tx-landing-active.md`](../../docs/methodology/solana-tx-landing-active.md) for the exact probe payload, tip floors per service, and statistical thresholds.
+The probe payload, tip floors per service, and statistical thresholds are documented inline in `cmd/script/prober.go` and `cmd/script/senders.go`.
## Reproducibility
diff --git a/harnesses/solana-tx-landing/cmd/script/active_metrics.go b/harnesses/solana-tx-landing/cmd/script/active_metrics.go
index 2f16dce5..ddca8171 100644
--- a/harnesses/solana-tx-landing/cmd/script/active_metrics.go
+++ b/harnesses/solana-tx-landing/cmd/script/active_metrics.go
@@ -16,9 +16,8 @@ import (
// • landing_rate = success_total / (success_total + dropped{reason=timeout})
// • p50 / p99 derived in Prom from the *_histogram series
//
-// Methodology pinned at docs/methodology/solana-tx-landing-active.md
-// (OpenChainBench repo). Any label / metric change is a methodology PR
-// with the 14-day comment window.
+// Label / metric shape is stable; any change should ship as a public PR
+// with a 14-day comment window before redeploy.
var (
// One increment per landed (confirmed) probe. Headline numerator of
diff --git a/harnesses/solana-tx-landing/cmd/script/main.go b/harnesses/solana-tx-landing/cmd/script/main.go
index 2e1fdd05..73893eec 100644
--- a/harnesses/solana-tx-landing/cmd/script/main.go
+++ b/harnesses/solana-tx-landing/cmd/script/main.go
@@ -53,7 +53,6 @@ func main() {
go runSubscriber(ctx, wsURL)
// Active prober (opt-in: requires SOLANA_PROBE_KEYPAIR_BASE58).
- // Methodology: docs/methodology/solana-tx-landing-active.md
go runProber(ctx)
sig := make(chan os.Signal, 1)
diff --git a/harnesses/solana-tx-landing/cmd/script/prober.go b/harnesses/solana-tx-landing/cmd/script/prober.go
index 42e127db..e5bbe1f5 100644
--- a/harnesses/solana-tx-landing/cmd/script/prober.go
+++ b/harnesses/solana-tx-landing/cmd/script/prober.go
@@ -22,8 +22,7 @@ import (
// Active prober — submits a synthetic mainnet tx through each landing
// service every cycle, then polls confirmation. Headline metrics =
-// landing_rate + p50/p99 latency per service. Methodology pinned at
-// OpenChainBench/docs/methodology/solana-tx-landing-active.md.
+// landing_rate + p50/p99 latency per service.
//
// The prober is OPT-IN. It only runs when SOLANA_PROBE_KEYPAIR_BASE58
// is set; absence keeps the harness in pure observational mode.
diff --git a/src/app/answers/[slug]/page.tsx b/src/app/answers/[slug]/page.tsx
index 25a26b26..f33a21a7 100644
--- a/src/app/answers/[slug]/page.tsx
+++ b/src/app/answers/[slug]/page.tsx
@@ -43,10 +43,10 @@ export async function generateMetadata({
const url = `${SITE.url}/answers/${ans.slug}`;
const title = ans.seo_title ?? ans.question;
const descSource = ans.seo_description ?? ans.short_answer;
- // Clean leftover tokens AFTER renderTemplate so a draft bench
- // (e.g. solana-tx-landing-latency mid-soak) never leaks a literal
- // `{{best_name}}` into the meta description, og:description or
- // twitter:description, all of which feed the SERP and social previews.
+ // Clean leftover tokens AFTER renderTemplate so a draft bench never
+ // leaks a literal `{{best_name}}` into the meta description,
+ // og:description or twitter:description, all of which feed the SERP
+ // and social previews.
const description = capDescription(
cleanLeftoverTokens(renderTemplate(descSource, ans.bench)),
158,
diff --git a/src/app/answers/page.tsx b/src/app/answers/page.tsx
index 2b3e1451..653ce8de 100644
--- a/src/app/answers/page.tsx
+++ b/src/app/answers/page.tsx
@@ -28,10 +28,9 @@ export default async function AnswersHubPage() {
// before the JSX touches the string.
//
// Tokens that renderTemplate can't resolve get a neutral fallback so
- // a draft / awaiting-data bench (e.g. solana-tx-landing-latency mid-soak
- // with every provider's p50 still at 0) never surfaces raw `{{best_name}}`
- // to the SERP. Same pattern as resolveLeftoverPlaceholders on the
- // per-chain bench page.
+ // a draft / awaiting-data bench (every provider's p50 still at 0)
+ // never surfaces raw `{{best_name}}` to the SERP. Same pattern as
+ // resolveLeftoverPlaceholders on the per-chain bench page.
const rendered = await Promise.all(
answers.map(async (a) => {
const bench = await loadBenchmark(a.benchmark, { chain: a.chain });
diff --git a/src/data/bench-published.json b/src/data/bench-published.json
index fc0f53c9..0dc2c4f1 100644
--- a/src/data/bench-published.json
+++ b/src/data/bench-published.json
@@ -13,7 +13,6 @@
"perp-fees": "2026-05-07T14:44:23.000Z",
"pm-data-freshness": "2026-06-04T17:50:08.000Z",
"rpc-capabilities": "2026-05-20T18:43:56.000Z",
- "solana-tx-landing-latency": "2026-05-21T17:54:38.000Z",
"stablecoin-peg": "2026-05-20T18:43:56.000Z",
"stablecoin-peg-usdt-anchored": "2026-05-20T21:20:02.000Z",
"token-deployment-cost": "2026-06-09T15:34:12.000Z",
diff --git a/src/data/compare-pairs.ts b/src/data/compare-pairs.ts
index a7a5148b..786994d1 100644
--- a/src/data/compare-pairs.ts
+++ b/src/data/compare-pairs.ts
@@ -157,15 +157,6 @@ export const COMPARE_PAIRS: ComparePair[] = [
providerB: "hyperliquid",
publishedAt: "2026-06-17",
},
- {
- // solana-tx-landing-latency exposes the Helius RPC sender under the
- // slug `helius-sender`, not bare `helius`, so the pair canonical
- // slug uses helius-sender to match the bench provider id.
- slug: "helius-sender-vs-jito",
- providerA: "helius-sender",
- providerB: "jito",
- publishedAt: "2026-06-09",
- },
{
slug: "hyperliquid-vs-lighter",
providerA: "hyperliquid",
From a088e46c71203e9529e7ad2cfb58d7057507fe91 Mon Sep 17 00:00:00 2001
From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com>
Date: Thu, 25 Jun 2026 15:54:46 +0300
Subject: [PATCH 2/3] perf(benchmarks): slim card projection cuts /benchmarks
payload 3.2MB to 280KB (#704)
---
src/app/benchmarks/page.tsx | 4 +--
src/components/benchmark-card.tsx | 8 +++--
src/components/benchmark-grid.tsx | 4 +--
src/components/mini-chart.tsx | 14 ++++++--
src/data/benchmarks.ts | 57 +++++++++++++++++++++++++++++++
5 files changed, 79 insertions(+), 8 deletions(-)
diff --git a/src/app/benchmarks/page.tsx b/src/app/benchmarks/page.tsx
index 3126513c..b65001c9 100644
--- a/src/app/benchmarks/page.tsx
+++ b/src/app/benchmarks/page.tsx
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
-import { getBenchmarksSafe } from "@/data/benchmarks";
+import { getBenchmarksSafe, toBenchmarkCardData } from "@/data/benchmarks";
import { BenchmarkGrid } from "@/components/benchmark-grid";
import { safeJsonLd } from "@/lib/jsonld";
@@ -86,7 +86,7 @@ export default async function BenchmarksPage() {
{DESCRIPTION}
-
+
);
}
diff --git a/src/components/benchmark-card.tsx b/src/components/benchmark-card.tsx
index 77f079cb..6a03581f 100644
--- a/src/components/benchmark-card.tsx
+++ b/src/components/benchmark-card.tsx
@@ -1,5 +1,5 @@
import Link from "next/link";
-import type { Benchmark } from "@/types/benchmark";
+import type { BenchmarkCardData } from "@/data/benchmarks";
import { Hint } from "@/components/hint";
import { MiniChart } from "@/components/mini-chart";
import { CATEGORY_COLOR } from "@/lib/category-colors";
@@ -16,8 +16,12 @@ import { fmtValue, unitSuffix } from "@/lib/format";
* - Insufficient samples: published but the harness has no usable p50
* yet. "Insufficient samples" pill, greyed-out value, chart skipped.
* - Live: standard rendering, leader p50 in display style.
+ *
+ * Prop is the slim `BenchmarkCardData` projection, not the full
+ * Benchmark. Keeps the RSC payload for `/benchmarks` to the fields the
+ * card actually reads.
*/
-export function BenchmarkCard({ benchmark }: { benchmark: Benchmark }) {
+export function BenchmarkCard({ benchmark }: { benchmark: BenchmarkCardData }) {
const b = benchmark;
const isDraft = b.status === "draft";
const insufficient = !isDraft && isInsufficient(b);
diff --git a/src/components/benchmark-grid.tsx b/src/components/benchmark-grid.tsx
index 33cbd071..380ee9e2 100644
--- a/src/components/benchmark-grid.tsx
+++ b/src/components/benchmark-grid.tsx
@@ -2,7 +2,7 @@
import { useMemo, useState } from "react";
import { LayoutGrid, List, Search } from "lucide-react";
-import type { Benchmark } from "@/types/benchmark";
+import type { BenchmarkCardData } from "@/data/benchmarks";
import { BenchmarkCard } from "@/components/benchmark-card";
/**
@@ -11,7 +11,7 @@ import { BenchmarkCard } from "@/components/benchmark-card";
* the only fully-implemented mode here - list view degrades to a single
* column) and a search input with a ⌘K affordance.
*/
-export function BenchmarkGrid({ benchmarks }: { benchmarks: Benchmark[] }) {
+export function BenchmarkGrid({ benchmarks }: { benchmarks: BenchmarkCardData[] }) {
const [query, setQuery] = useState("");
const [activeCategory, setActiveCategory] = useState(null);
const [view, setView] = useState<"grid" | "list">("grid");
diff --git a/src/components/mini-chart.tsx b/src/components/mini-chart.tsx
index b107afc0..6154a60f 100644
--- a/src/components/mini-chart.tsx
+++ b/src/components/mini-chart.tsx
@@ -1,5 +1,4 @@
import { useMemo } from "react";
-import type { Benchmark } from "@/types/benchmark";
import { buildProviderColors } from "@/lib/series-colors";
/**
@@ -7,10 +6,21 @@ import { buildProviderColors } from "@/lib/series-colors";
* full TimeSeriesChart on the bench detail page. every provider gets a
* line in their signature color. but stripped of axes, hover and tabs.
* Reads `series24h` from the benchmark's `extras` payload.
+ *
+ * Structural prop type: accepts the full `Benchmark` (home table) and
+ * the slim `BenchmarkCardData` projection (hub grid) so the hub doesn't
+ * need to ship the full Benchmark shape (series7d, series30d,
+ * metricPanels, editorial copy, ...) per card in the RSC payload.
*/
+type MiniChartBenchmark = {
+ results: { slug: string; name: string; ms: { p50: number } }[];
+ higherIsBetter: boolean;
+ extras: { series24h: Record };
+};
+
type Props = {
- benchmark: Benchmark;
+ benchmark: MiniChartBenchmark;
/** Internal viewBox width used for path math. Visual width is 100% of parent. */
viewBoxWidth?: number;
height?: number;
diff --git a/src/data/benchmarks.ts b/src/data/benchmarks.ts
index 9f73820e..e0cca6f9 100644
--- a/src/data/benchmarks.ts
+++ b/src/data/benchmarks.ts
@@ -24,6 +24,63 @@ export type {
Series24h,
} from "@/types/benchmark";
+/**
+ * Card-shaped projection used by the hub grid and category pages. The
+ * full Benchmark object carries ~30 fields per bench (extras.series7d,
+ * series30d, seriesByRegion*, metricPanels, methodology, abstract, faq,
+ * findings, perChainExplainer, seoIntro, bestPerChain, worstPerChain,
+ * cellRanks, providersPerChain, ...) that the card never reads. Passing
+ * the full shape to `` (a client component) baked the
+ * whole thing into the RSC payload — 3.2 MB of HTML for 37 cards, which
+ * is what made `/benchmarks` feel like an 8 s page even though the
+ * server TTFB is sub-second.
+ *
+ * Project once at the page boundary so the wire payload only carries
+ * what the card + the grid's search/filter use.
+ */
+export type BenchmarkCardData = {
+ slug: string;
+ title: string;
+ subtitle: string;
+ category: Benchmark["category"];
+ status: Benchmark["status"];
+ unit: Benchmark["unit"];
+ higherIsBetter: boolean;
+ sampleSize: number;
+ lastRunAt: string;
+ metric: string;
+ results: {
+ slug: string;
+ name: string;
+ ms: { p50: number };
+ }[];
+ extras: { series24h: Record };
+};
+
+/** Strip a Benchmark to the fields the hub card actually renders. Keeps
+ * `results` in its original sort order so the card's own sort
+ * (best-leader heuristic) stays correct. */
+export function toBenchmarkCardData(b: Benchmark): BenchmarkCardData {
+ return {
+ slug: b.slug,
+ title: b.title,
+ subtitle: b.subtitle,
+ category: b.category,
+ status: b.status,
+ unit: b.unit,
+ higherIsBetter: b.higherIsBetter,
+ sampleSize: b.sampleSize,
+ lastRunAt: b.lastRunAt,
+ metric: b.metric,
+ results: b.results.map((r) => ({
+ slug: r.slug,
+ name: r.name,
+ ms: { p50: r.ms.p50 },
+ })),
+ extras: { series24h: b.extras?.series24h ?? {} },
+ };
+}
+
/**
* Strict loader. Throws AllBenchmarksDraftError when every bench has
* collapsed to draft (Prom blackout, cold start with no KV snapshot).
From dbef56cb0586f3b9fe9fbd491459effd61b3dc85 Mon Sep 17 00:00:00 2001
From: Florent Tapponnier
Date: Thu, 25 Jun 2026 15:07:14 +0200
Subject: [PATCH 3/3] fix(types): widen isInsufficient to accept
BenchmarkCardData slim shape
Cherry-pick fallout: #704 introduced BenchmarkCardData (slim projection
for the hub) but main's isInsufficient still required full Benchmark.
The hub card calls isInsufficient(b) where b is the slim type, which
typecheck rejected. Widening the predicate input to a structural
subset lets both Benchmark and BenchmarkCardData pass without losing
the runtime behavior.
---
src/data/benchmarks.ts | 4 ++++
src/lib/citation.ts | 16 ++++++++++++++--
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/data/benchmarks.ts b/src/data/benchmarks.ts
index e0cca6f9..ff2890aa 100644
--- a/src/data/benchmarks.ts
+++ b/src/data/benchmarks.ts
@@ -44,6 +44,7 @@ export type BenchmarkCardData = {
subtitle: string;
category: Benchmark["category"];
status: Benchmark["status"];
+ editorialStatus: Benchmark["editorialStatus"];
unit: Benchmark["unit"];
higherIsBetter: boolean;
sampleSize: number;
@@ -52,6 +53,7 @@ export type BenchmarkCardData = {
results: {
slug: string;
name: string;
+ availability?: "live" | "unavailable";
ms: { p50: number };
}[];
extras: { series24h: Record };
@@ -67,6 +69,7 @@ export function toBenchmarkCardData(b: Benchmark): BenchmarkCardData {
subtitle: b.subtitle,
category: b.category,
status: b.status,
+ editorialStatus: b.editorialStatus,
unit: b.unit,
higherIsBetter: b.higherIsBetter,
sampleSize: b.sampleSize,
@@ -75,6 +78,7 @@ export function toBenchmarkCardData(b: Benchmark): BenchmarkCardData {
results: b.results.map((r) => ({
slug: r.slug,
name: r.name,
+ availability: r.availability,
ms: { p50: r.ms.p50 },
})),
extras: { series24h: b.extras?.series24h ?? {} },
diff --git a/src/lib/citation.ts b/src/lib/citation.ts
index c1108db5..544bbc14 100644
--- a/src/lib/citation.ts
+++ b/src/lib/citation.ts
@@ -38,7 +38,17 @@ import { fmtUnit } from "@/lib/format";
* - Use this predicate BEFORE deriving leader / fieldValue / headline
* for any externally-visible surface.
*/
-export function isInsufficient(b: Benchmark): boolean {
+/** Structural subset accepted by `isInsufficient`. Lets the hub card
+ * call this with the slim BenchmarkCardData shape (which omits the
+ * full Benchmark fields the predicate does not read) without breaking
+ * the existing full-Benchmark call sites. */
+export type InsufficientCheckInput = {
+ editorialStatus: Benchmark["editorialStatus"];
+ status: Benchmark["status"];
+ results: { ms: { p50: number }; availability?: "live" | "unavailable" }[];
+};
+
+export function isInsufficient(b: InsufficientCheckInput): boolean {
if (b.editorialStatus !== "live") return true;
if (b.status !== "live") return true;
// Note: do NOT key on b.sampleSize === 0. The aggregator loader can
@@ -47,7 +57,9 @@ export function isInsufficient(b: Benchmark): boolean {
// benches as insufficient on /api/citable while /api/stat returned
// live values for the same slug. The liveResults length and p50
// finiteness checks below already catch the genuine empty case.
- const live = liveResults(b.results);
+ const live = b.results.filter(
+ (r) => r.availability !== "unavailable" && r.ms.p50 > 0,
+ );
if (live.length === 0) return true;
return live.every((r) => !Number.isFinite(r.ms.p50) || r.ms.p50 <= 0);
}