diff --git a/benchmarks/mev-protect-rpc.yml b/benchmarks/mev-protect-rpc.yml
new file mode 100644
index 00000000..e490e236
--- /dev/null
+++ b/benchmarks/mev-protect-rpc.yml
@@ -0,0 +1,161 @@
+# OpenChainBench. Bench № 074
+
+slug: mev-protect-rpc
+number: "074"
+title: Fastest MEV protection RPC, live wallet latency and method coverage
+seo_title: "Best MEV protection RPC 2026"
+seo_description: "{{best_name}} leads MEV protection RPC wallet latency at {{best_p50}} (p50, 24h). Flashbots Protect, MEV Blocker, Blink probed on the wallet method set from 3 regions, keyless."
+subtitle: "Median latency of the JSON-RPC method set wallets call constantly (balance, gas, call simulation), measured against public MEV protection gateways from three regions. Read-path hygiene only: inclusion rates and refunds are the write path and cannot be measured without sending transactions."
+category: RPCs
+status: live
+metric: Wallet call latency
+unit: ms
+higher_is_better: false
+
+seo_intro: |
+ Every guide that tells you to paste a custom RPC into MetaMask to
+ dodge sandwich bots skips the same question. what does that RPC do to
+ the rest of your wallet. Once Flashbots Protect, MEV Blocker or Blink
+ becomes your default endpoint, every balance refresh, every gas
+ estimate and every simulation runs through their gateway, and a slow
+ or method-crippled proxy makes the whole wallet feel broken. This
+ page measures that read path live. seven wallet methods (chainId,
+ blockNumber, gasPrice, getBalance, call, estimateGas, feeHistory)
+ probed keyless every minute from us-east, eu-west and Singapore, with
+ a coverage panel showing which methods each gateway actually serves.
+ Flashbots Protect currently rejects eth_call on its public endpoint;
+ Blink serves the full wallet set but blocks full-node reads like
+ eth_getBlockByNumber. Numbers nobody publishes, refreshed around the
+ clock. What this page deliberately does not claim to rank: inclusion
+ speed, refund rates and sandwich protection efficacy are the write
+ path, they require funded transactions to measure, and the one
+ rigorous study there is a one-off academic snapshot, not a live feed.
+
+abstract: |
+ We probe the public gateways of MEV protection RPCs with the JSON-RPC
+ method set wallets call constantly, keyless, every minute, from three
+ regions. The headline number is the per-tick median latency across
+ the methods a gateway serves; method coverage is tracked separately
+ so a gateway that rejects a method is shown as a coverage gap rather
+ than a latency penalty. No transactions are sent.
+
+methodology:
+ - "Providers measured: Flashbots Protect (rpc.flashbots.net), MEV Blocker (rpc.mevblocker.io), Blink, formerly Merkle (ethereum.blinklabs.xyz). SecureRPC probed dead 2026-07-10; the legacy merkle.io hosts alias Blink behind a stricter rate limit and are not probed."
+ - "Method set: eth_chainId, eth_blockNumber, eth_gasPrice, eth_getBalance, eth_call (USDC balanceOf), eth_estimateGas, eth_feeHistory. One request per method per tick, 1.5s apart, rotating request ids against body-keyed edge caches."
+ - "Cadence: every 60 seconds per region (us-east, eu-west, sgp), 7 requests per provider per tick. Deliberately polite: these gateways rate-ban aggressive callers."
+ - "Headline: median latency across the methods the provider served that tick, aggregated over 24h with quantile_over_time. A rejected method does not poison the latency; it lowers the coverage panel instead."
+ - "Coverage: mev_rpc_methods_supported counts the wallet methods served on the last tick (max 7). Flashbots rejects eth_call on the public endpoint; Blink blocks full-node reads like eth_getBlockByNumber, which keeps it off the general RPC benches but not off this one."
+ - "Out of scope, disclosed: inclusion rate, refund economics and sandwich protection efficacy are the write path and require funded transactions. See the arXiv study Private MEV Protection RPCs (2505.19708) for a one-off execution-quality comparison."
+ - "Failures (timeouts, 403, 429) increment mev_rpc_call_total{result} and count against the success rate; the gauge keeps its last value so the chart shows the outage in the success column rather than a fake zero."
+
+findings:
+ - "{{best_name}} leads at {{best_p50}} (p50, 24h) on the cross-region wallet call median over {{count}} measured gateways."
+ - "{{name:mevblocker}} sits at {{p50:mevblocker}} (p50, 24h) and serves the full 7-method wallet set. MEV Blocker was acquired by Consensys in January 2026; its gateway proxies reads with the lowest overhead of the cohort."
+ - "{{name:flashbots}} clocks {{p50:flashbots}} (p50, 24h) but rejects eth_call on the public endpoint, so wallets relying on it for simulation fall back to another provider for that path."
+ - "{{name:blinklabs}} returns {{p50:blinklabs}} (p50, 24h) across the full wallet set. Blink, formerly Merkle, is a wallet-facing MEV proxy by design: it serves everything a wallet needs while blocking full-node reads."
+ - "The p50 to p99 gap is the number wallet teams should read: a protect RPC that stalls on one refresh per hundred makes the whole wallet feel unreliable, whatever its median."
+
+source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/mev-protect-rpc
+
+prometheus:
+ window: 24h
+
+faq:
+ - q: "Which MEV protection RPC is fastest for wallet usage?"
+ a: "{{best_name}} currently leads at {{best_p50}} (p50, 24h) on the wallet method median measured from three regions. The number covers the read path a wallet hits constantly once you set a protect RPC as default: balance refreshes, gas estimates, call simulations. It does not rank inclusion speed or refunds, which are write-path properties that cannot be measured without sending funded transactions."
+ - q: "Does an MEV protection RPC slow down my wallet?"
+ a: "It adds a proxy hop, and this page measures exactly how much. The gateways in this cohort answer wallet reads in tens to hundreds of milliseconds at p50, but the p99 column shows the stalls you feel. A gateway can also reject methods outright: Flashbots Protect currently rejects eth_call on its public endpoint, so simulation-heavy wallets silently fall back to another provider."
+ - q: "What is the difference between Flashbots Protect, MEV Blocker and Blink?"
+ a: "All three route your transactions away from the public mempool to prevent sandwich attacks, and they monetize the order flow differently: refunds shared with users (MEV Blocker pays a share of backrun value), enterprise deals with wallets (Blink powers Ledger and sells through the QuickNode marketplace), or the Flashbots auction. This page compares the part of the product you can verify from outside: the latency and coverage of the read gateway your wallet talks to all day."
+ - q: "Why is inclusion rate not on this benchmark?"
+ a: "Measuring inclusion or refunds requires signing and funding real transactions on every gateway around the clock, which is expensive and hard to keep neutral. The one rigorous comparison of that layer is a one-off academic study (arXiv 2505.19708). This benchmark tracks the layer that can be measured continuously and keyless, and says so instead of extrapolating."
+
+# Per-region badge scoping, same alias pattern as the other RPC benches:
+# the harness emits `provider`, the badge infra expects `provider`.
+rank_matrix_query: avg by (provider, region) (quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds[24h]))
+
+dimensions:
+ region:
+ - { value: all, label: All regions }
+ - { value: us-east, label: US-East }
+ - { value: eu-west, label: EU-West }
+ - { value: sgp, label: Singapore }
+
+metric_panels:
+ - id: coverage
+ label: Method coverage
+ metric: avg(mev_rpc_methods_supported)
+ label_key: provider
+ unit: count
+ higher_is_better: true
+ description: "Wallet methods served on the last probe tick, out of 7. A gateway that rejects a method is a coverage gap wallets must engineer around, not a latency problem."
+
+providers:
+ - slug: flashbots
+ name: Flashbots Protect
+ tag: Public protect RPC, rejects eth_call on the free gateway
+ formula: "Median latency across the wallet methods served per 60s tick against rpc.flashbots.net, aggregated over 24h via quantile_over_time; 3-region average."
+ queries:
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h])
+ p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h])
+ p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h])
+ mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[24h])
+ success: sum(increase(mev_rpc_call_total{provider="flashbots",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="flashbots"}[24h])), 1)
+ sample_size: sum(increase(mev_rpc_call_total{provider="flashbots"}[24h]))
+ series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots"}[1h]))
+ regions:
+ - region: us-east
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="us-east"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="us-east"}[1h])
+ - region: eu-west
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="eu-west"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="sgp"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="flashbots", region="sgp"}[1h])
+
+ - slug: mevblocker
+ name: MEV Blocker
+ tag: CoW built, Consensys owned since 2026, full wallet method set
+ formula: "Median latency across the wallet methods served per 60s tick against rpc.mevblocker.io, aggregated over 24h via quantile_over_time; 3-region average."
+ queries:
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h])
+ p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h])
+ p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h])
+ mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[24h])
+ success: sum(increase(mev_rpc_call_total{provider="mevblocker",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="mevblocker"}[24h])), 1)
+ sample_size: sum(increase(mev_rpc_call_total{provider="mevblocker"}[24h]))
+ series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker"}[1h]))
+ regions:
+ - region: us-east
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="us-east"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="us-east"}[1h])
+ - region: eu-west
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="eu-west"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="sgp"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="mevblocker", region="sgp"}[1h])
+
+ - slug: blinklabs
+ name: Blink
+ tag: Formerly Merkle, wallet MEV proxy, full wallet set, powers Ledger
+ formula: "Median latency across the wallet methods served per 60s tick against ethereum.blinklabs.xyz, aggregated over 24h via quantile_over_time; 3-region average."
+ queries:
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h])
+ p90: quantile_over_time(0.90, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h])
+ p99: quantile_over_time(0.99, mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h])
+ mean: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[24h])
+ success: sum(increase(mev_rpc_call_total{provider="blinklabs",result="ok"}[24h])) / clamp_min(sum(increase(mev_rpc_call_total{provider="blinklabs"}[24h])), 1)
+ sample_size: sum(increase(mev_rpc_call_total{provider="blinklabs"}[24h]))
+ series: avg(avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs"}[1h]))
+ regions:
+ - region: us-east
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="us-east"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="us-east"}[1h])
+ - region: eu-west
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="eu-west"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="eu-west"}[1h])
+ - region: ap-southeast
+ p50: quantile_over_time(0.50, mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="sgp"}[24h])
+ series: avg_over_time(mev_rpc_wallet_latency_milliseconds{provider="blinklabs", region="sgp"}[1h])
diff --git a/harnesses/mev-protect-rpc/Dockerfile b/harnesses/mev-protect-rpc/Dockerfile
new file mode 100644
index 00000000..63108cfc
--- /dev/null
+++ b/harnesses/mev-protect-rpc/Dockerfile
@@ -0,0 +1,22 @@
+FROM golang:1.24-alpine AS builder
+
+WORKDIR /app
+RUN apk add --no-cache git
+
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+
+RUN CGO_ENABLED=0 GOOS=linux go build -o /app/monitor ./cmd/script
+
+FROM debian:bookworm-slim
+
+WORKDIR /app
+RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
+
+COPY --from=builder /app/monitor /app/monitor
+
+EXPOSE 2112
+
+CMD ["/app/monitor"]
diff --git a/harnesses/mev-protect-rpc/cmd/script/main.go b/harnesses/mev-protect-rpc/cmd/script/main.go
new file mode 100644
index 00000000..9066af86
--- /dev/null
+++ b/harnesses/mev-protect-rpc/cmd/script/main.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+// MEV-protect wallet RPC bench (074). Probes the JSON-RPC method set
+// wallets actually call against public MEV-protection gateways
+// (Flashbots Protect, MEV Blocker, Blink). No transactions are sent;
+// the write path (inclusion, refunds, sandwich protection) is out of
+// scope by design and the spec discloses it.
+//
+// Metrics on :2112, the OCB harness convention.
+
+var currentRegion = loadRegion()
+
+func loadRegion() string {
+ if r := strings.TrimSpace(os.Getenv("REGION")); r != "" {
+ return r
+ }
+ if r := normalizeRailwayRegion(os.Getenv("RAILWAY_REPLICA_REGION")); r != "" {
+ return r
+ }
+ return "eu-west"
+}
+
+func normalizeRailwayRegion(raw string) string {
+ raw = strings.ToLower(strings.TrimSpace(raw))
+ if raw == "" {
+ return ""
+ }
+ switch {
+ case strings.HasPrefix(raw, "us-"), strings.HasPrefix(raw, "northamerica"):
+ return "us-east"
+ case strings.HasPrefix(raw, "europe"), strings.HasPrefix(raw, "eu-"):
+ return "eu-west"
+ case strings.HasPrefix(raw, "asia"), strings.HasPrefix(raw, "ap-"):
+ return "sgp"
+ default:
+ return ""
+ }
+}
+
+func main() {
+ fmt.Printf("[mev-protect] starting, region=%s, %d providers\n", currentRegion, len(providers))
+
+ mux := http.NewServeMux()
+ mux.Handle("/metrics", promhttp.Handler())
+ mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("ok"))
+ })
+ port := os.Getenv("PORT")
+ if port == "" {
+ port = "2112"
+ }
+ go func() {
+ if err := http.ListenAndServe(":"+port, mux); err != nil {
+ fmt.Printf("[mev-protect] metrics server: %v\n", err)
+ os.Exit(1)
+ }
+ }()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ go runProbeLoop(ctx)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+ <-sig
+ cancel()
+ fmt.Println("[mev-protect] shutting down")
+}
diff --git a/harnesses/mev-protect-rpc/cmd/script/metrics.go b/harnesses/mev-protect-rpc/cmd/script/metrics.go
new file mode 100644
index 00000000..3655633f
--- /dev/null
+++ b/harnesses/mev-protect-rpc/cmd/script/metrics.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+var (
+ // Headline gauge: median latency across the wallet methods the
+ // provider served this tick. quantile_over_time in the spec turns
+ // it into the 24h p50/p90/p99 columns.
+ mevWalletLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "mev_rpc_wallet_latency_milliseconds",
+ Help: "Median latency across the wallet method set served this tick, per MEV-protect provider.",
+ }, []string{"provider", "region"})
+
+ mevWalletLatencyHist = promauto.NewHistogramVec(prometheus.HistogramOpts{
+ Name: "mev_rpc_wallet_latency_milliseconds_histogram",
+ Help: "Distribution of per-tick median wallet latency.",
+ Buckets: prometheus.ExponentialBuckets(25, 2, 10),
+ }, []string{"provider", "region"})
+
+ mevMethodLatency = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "mev_rpc_method_latency_milliseconds",
+ Help: "Latency of the last successful call per method.",
+ }, []string{"provider", "method", "region"})
+
+ mevMethodOK = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "mev_rpc_method_ok",
+ Help: "1 when the method succeeded on the last tick, 0 otherwise.",
+ }, []string{"provider", "method", "region"})
+
+ mevMethodsSupported = promauto.NewGaugeVec(prometheus.GaugeOpts{
+ Name: "mev_rpc_methods_supported",
+ Help: "Count of wallet methods served on the last tick (max 7).",
+ }, []string{"provider", "region"})
+
+ mevCallTotal = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "mev_rpc_call_total",
+ Help: "Probe outcomes per provider/method.",
+ }, []string{"provider", "method", "region", "result"})
+)
diff --git a/harnesses/mev-protect-rpc/cmd/script/probe.go b/harnesses/mev-protect-rpc/cmd/script/probe.go
new file mode 100644
index 00000000..0139b429
--- /dev/null
+++ b/harnesses/mev-protect-rpc/cmd/script/probe.go
@@ -0,0 +1,178 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "sort"
+ "time"
+)
+
+// Providers: public no-key MEV-protection gateways on Ethereum mainnet.
+// SecureRPC (Manifold) probed dead 2026-07-10; the legacy merkle.io
+// hosts alias Blink's gateway behind a far more aggressive Cloudflare
+// rate limit, so Blink is probed on the blinklabs.xyz host only.
+var providers = []struct {
+ Slug string
+ URL string
+}{
+ {Slug: "flashbots", URL: "https://rpc.flashbots.net"},
+ {Slug: "mevblocker", URL: "https://rpc.mevblocker.io"},
+ {Slug: "blinklabs", URL: "https://ethereum.blinklabs.xyz"},
+}
+
+// walletMethods is the read set wallets fire constantly (balance
+// refresh, gas estimation, simulation). One request per method per
+// provider per tick: 7 req/min/provider/region, polite for gateways
+// that rate-ban aggressive callers.
+var walletMethods = []struct {
+ Name string
+ Params func() []any
+}{
+ {"eth_chainId", func() []any { return []any{} }},
+ {"eth_blockNumber", func() []any { return []any{} }},
+ {"eth_gasPrice", func() []any { return []any{} }},
+ {"eth_getBalance", func() []any { return []any{probeAddress, "latest"} }},
+ {"eth_call", func() []any {
+ return []any{map[string]string{"to": usdcContract, "data": balanceOfData}, "latest"}
+ }},
+ {"eth_estimateGas", func() []any {
+ return []any{map[string]string{"from": probeAddress, "to": probeAddress, "value": "0x1"}}
+ }},
+ {"eth_feeHistory", func() []any { return []any{"0x5", "latest", []int{50}} }},
+}
+
+const (
+ probeAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
+ usdcContract = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
+ balanceOfData = "0x70a08231000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
+
+ tickInterval = 60 * time.Second
+ methodGap = 1500 * time.Millisecond
+ probeTimeout = 10 * time.Second
+)
+
+type rpcEnvelope struct {
+ Result json.RawMessage `json:"result"`
+ Error *struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ } `json:"error"`
+}
+
+func runProbeLoop(ctx context.Context) {
+ client := &http.Client{Timeout: probeTimeout}
+ t := time.NewTicker(tickInterval)
+ defer t.Stop()
+ tick(ctx, client)
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-t.C:
+ tick(ctx, client)
+ }
+ }
+}
+
+func tick(ctx context.Context, client *http.Client) {
+ for _, p := range providers {
+ latencies := make([]float64, 0, len(walletMethods))
+ supported := 0
+ for _, m := range walletMethods {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ }
+ lat, result := call(ctx, client, p.URL, m.Name, m.Params())
+ mevCallTotal.WithLabelValues(p.Slug, m.Name, currentRegion, result).Inc()
+ if result == "ok" {
+ supported++
+ latencies = append(latencies, lat)
+ mevMethodLatency.WithLabelValues(p.Slug, m.Name, currentRegion).Set(lat)
+ mevMethodOK.WithLabelValues(p.Slug, m.Name, currentRegion).Set(1)
+ } else {
+ mevMethodOK.WithLabelValues(p.Slug, m.Name, currentRegion).Set(0)
+ }
+ time.Sleep(methodGap)
+ }
+ mevMethodsSupported.WithLabelValues(p.Slug, currentRegion).Set(float64(supported))
+ if len(latencies) > 0 {
+ sort.Float64s(latencies)
+ median := latencies[len(latencies)/2]
+ if len(latencies)%2 == 0 {
+ median = (latencies[len(latencies)/2-1] + latencies[len(latencies)/2]) / 2
+ }
+ // Median across the methods the provider actually serves,
+ // so coverage gaps (flashbots rejects eth_call) do not
+ // poison the latency figure; coverage is its own metric.
+ mevWalletLatency.WithLabelValues(p.Slug, currentRegion).Set(median)
+ mevWalletLatencyHist.WithLabelValues(p.Slug, currentRegion).Observe(median)
+ }
+ fmt.Printf("[mev-protect][%s][%s] supported=%d/%d median=%s\n",
+ p.Slug, currentRegion, supported, len(walletMethods), fmtMedian(latencies))
+ }
+}
+
+func fmtMedian(l []float64) string {
+ if len(l) == 0 {
+ return "n/a"
+ }
+ return fmt.Sprintf("%.0fms", l[len(l)/2])
+}
+
+// call issues one JSON-RPC request with a rotating id (anti body-keyed
+// edge cache, same rule as the RPC cluster probes) and classifies the
+// outcome.
+func call(ctx context.Context, client *http.Client, url, method string, params []any) (float64, string) {
+ body, _ := json.Marshal(map[string]any{
+ "jsonrpc": "2.0",
+ "method": method,
+ "params": params,
+ "id": time.Now().UnixNano(),
+ })
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
+ if err != nil {
+ return 0, "request_build"
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("User-Agent", "OpenChainBench-MevProtect/1.0 contact@mobula.io")
+
+ start := time.Now()
+ resp, err := client.Do(req)
+ lat := float64(time.Since(start).Milliseconds())
+ if err != nil {
+ if ctx.Err() != nil {
+ return lat, "canceled"
+ }
+ return lat, "network"
+ }
+ defer resp.Body.Close()
+ raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
+
+ if resp.StatusCode == http.StatusTooManyRequests {
+ return lat, "throttled"
+ }
+ if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized {
+ return lat, "blocked"
+ }
+
+ var env rpcEnvelope
+ if err := json.Unmarshal(raw, &env); err != nil {
+ return lat, "parse"
+ }
+ if env.Error != nil {
+ if env.Error.Code == -32601 {
+ return lat, "method_not_found"
+ }
+ return lat, "rpc_error"
+ }
+ if len(env.Result) == 0 || string(env.Result) == "null" {
+ return lat, "empty"
+ }
+ return lat, "ok"
+}
diff --git a/harnesses/mev-protect-rpc/go.mod b/harnesses/mev-protect-rpc/go.mod
new file mode 100644
index 00000000..0d0a6837
--- /dev/null
+++ b/harnesses/mev-protect-rpc/go.mod
@@ -0,0 +1,18 @@
+module mev-protect-rpc
+
+go 1.24.0
+
+require github.com/prometheus/client_golang v1.23.2
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.66.1 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ go.yaml.in/yaml/v2 v2.4.2 // indirect
+ golang.org/x/sys v0.35.0 // indirect
+ google.golang.org/protobuf v1.36.8 // indirect
+)
diff --git a/harnesses/mev-protect-rpc/go.sum b/harnesses/mev-protect-rpc/go.sum
new file mode 100644
index 00000000..d6b8ca98
--- /dev/null
+++ b/harnesses/mev-protect-rpc/go.sum
@@ -0,0 +1,46 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
+github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
+go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
+google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/harnesses/mev-protect-rpc/railway.toml b/harnesses/mev-protect-rpc/railway.toml
new file mode 100644
index 00000000..2abbb1f0
--- /dev/null
+++ b/harnesses/mev-protect-rpc/railway.toml
@@ -0,0 +1,7 @@
+[build]
+builder = "DOCKERFILE"
+dockerfilePath = "Dockerfile"
+
+[deploy]
+healthcheckPath = "/health"
+restartPolicyType = "ON_FAILURE"
diff --git a/infrastructure/monitoring/prometheus/prometheus.yml b/infrastructure/monitoring/prometheus/prometheus.yml
index 6e7ce52f..840e35af 100644
--- a/infrastructure/monitoring/prometheus/prometheus.yml
+++ b/infrastructure/monitoring/prometheus/prometheus.yml
@@ -175,6 +175,18 @@ scrape_configs:
# Chainstack, Ankr, Helius). Companion to rpc-capabilities: same
# metric names + tier="keyed" label, picked up by the shared
# ocb:rpc_latency_* recording rules with zero rule changes.
+ # MEV-protect wallet RPC probes (bench 074): Flashbots Protect,
+ # MEV Blocker, Blink. Same 3-region layout as rpc-keyed-latency.
+ - job_name: 'mev-protect-rpc'
+ honor_labels: true
+ static_configs:
+ - targets:
+ - 'mev-protect-us.railway.internal:2112' # us-east
+ - 'mev-protect-eu.railway.internal:2112' # eu-west
+ - 'mev-protect-sgp.railway.internal:2112' # sgp
+ labels:
+ benchmark: mev-protect-rpc
+
- job_name: 'rpc-keyed-latency'
honor_labels: true
static_configs:
diff --git a/public/logos/blinklabs.svg b/public/logos/blinklabs.svg
new file mode 100644
index 00000000..d8038824
--- /dev/null
+++ b/public/logos/blinklabs.svg
@@ -0,0 +1,9 @@
+
diff --git a/public/logos/mevblocker.svg b/public/logos/mevblocker.svg
new file mode 100644
index 00000000..5502cd43
--- /dev/null
+++ b/public/logos/mevblocker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx
index 513452f9..1ce586d2 100644
--- a/src/app/products/[slug]/page.tsx
+++ b/src/app/products/[slug]/page.tsx
@@ -69,11 +69,11 @@ export async function generateMetadata({
// crawlers that peek at the response before the 308 fires still see the
// right canonical target.
// Merkle rebranded to Blink Labs and went keyed-only; the provider was
- // fully delisted 2026-07-10 so this page has no data left. 301 to the
- // capabilities bench (whose exclusion note explains both names) instead
- // of 404ing away the page's accumulated link equity.
+ // fully delisted 2026-07-10 from the keyless RPC benches. 308 to the
+ // Blink Labs product page (bench 074 lists them under the new name) so
+ // the old URL's link equity lands on the successor.
if (slug === "merkle") {
- permanentRedirect("/benchmarks/rpc-capabilities");
+ permanentRedirect("/products/blinklabs");
}
if (await isHlBuilderWithHistory(slug)) {
const canonicalUrl = `${SITE.url}/hyperliquid/${slug}`;
diff --git a/src/lib/logo-manifest.ts b/src/lib/logo-manifest.ts
index fa34f6c3..0d6cbd60 100644
--- a/src/lib/logo-manifest.ts
+++ b/src/lib/logo-manifest.ts
@@ -308,6 +308,8 @@ const RAW: Record = {
flowbot: "/logos/flowbot.jpg",
"nautilus-trader": "/logos/nautilus-trader.png",
blink: "/logos/blink.png",
+ blinklabs: "/logos/blinklabs.svg",
+ mevblocker: "/logos/mevblocker.svg",
// ─── Hyperliquid frontends registry expansion (60 → 66, 2026-06-28) ───
// Identified via on-chain HL referral codes + brand cross-reference