Skip to content

ship dev to main: LKG fallback + cache-key purge (prod aggregate poisoned)#1189

Open
Flotapponnier wants to merge 66 commits into
mainfrom
ship/dev-to-main-cachefix
Open

ship dev to main: LKG fallback + cache-key purge (prod aggregate poisoned)#1189
Flotapponnier wants to merge 66 commits into
mainfrom
ship/dev-to-main-cachefix

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Prod incident: the all-benchmarks aggregate cache holds 9 draft benches (poisoned during an SRH congestion window at 20:03 UTC) and every revalidation throws AllBenchmarksDraftError, preserving the poison indefinitely. No revalidation route exists; the repo pattern is a cache-key bump + deploy.

Ships dev (all staging-gated benches stay hidden on prod via REMOVED_BENCH_SLUGS) including:

Infra fixes already applied VPS-side: SRH_CONNECTION_POOL_SIZE=25 (root cause), worker on direct TCP.

Florent Tapponnier and others added 30 commits July 12, 2026 21:39
…#1136)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
Robinhood Chain: hub entry + 3 bench integrations
…cution mutex primitive

DailySpentUSD only ever incremented and was zeroed by every restart, so a
crash-loop minted a fresh budget each time. The tracker resets exactly at
UTC midnight, persists (date, spent) to SPEND_STATE_PATH (default
./spend-state.json, not /tmp) and is mutex-guarded. Also declares the
package-level single-flight lock and the FAILED_TX_FEE_ESTIMATE_USD
(default 0.50) helper for gas bled by failed broadcasts.
…get, single-flight lock

Fund-loss fixes from hostile review:
- a corrective transfer whose deposit broadcast but whose bridge status
  never resolved is now TERMINAL for the scheduler slot (funds are likely
  still in flight, a retry double-sends); only pre-broadcast failures may
  consume another attempt. executeOnBridge keeps TxHash on error paths so
  callers can tell the two apart.
- ONE corrective-transfer budget (max 2) per scheduler slot, shared across
  all downgrade-ladder rungs (was per rung: up to 6 transfers per slot).
- package-level execution mutex: scheduler slots own the wallets end to
  end, the reaper TryLocks and skips its tick instead of queueing.
- broadcasts that failed or timed out after getting a TxHash book a flat
  FAILED_TX_FEE_ESTIMATE_USD against the daily cap instead of bleeding
  unaccounted gas; all spend goes through the persisted UTC-day tracker.
- gas top-up now honors MaxDailySpendUSD before broadcasting.
The reaper (and any other consumer) used to accept simulated balances even
in production with broadcast-capable keys, so phantom numbers could
green-light real transfers. SimulateBalances now takes the execution mode
and returns nil outside dry-run; startup logs a warning when the flag is
set in a broadcast-capable mode.
bridge-monitor: self-healing execution loop (dormant)
…st-fill (#1141)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
tokenized-stock-peg: bench 076, first onchain equity pricing bench
…he miss (bnb-rpc awaiting incident) (#1144)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
tokenized-stock-peg: fix spark parser for the flat response shape
tokenized-stock-peg: spec validation fix + methodology hardening
Flotapponnier and others added 30 commits July 13, 2026 20:36
registry: googl url swap, abc.xyz 403s
…serves

koios cache + stellar reserve fix
…gram buckets (#1152)

* spec: missing store blob for a live bench throws instead of caching the miss (bnb-rpc awaiting incident)

* bridge-monitor: sub-50ms buckets + nanosecond precision on Observe

Histogram Buckets grew from [50, 100, 200, 500, 1000, 2000, 5000, 10000]
to [10, 25, 50, 100, 200, 500, 1000, 2000, 5000, 10000]. Near Intents
on solver-cached corridors (HyperCore in particular) returns via a
1Click coordinator fast path in <50ms, and the previous scheme lumped
every one of those observations into (0, 50] so histogram_quantile
linearly interpolated to ~25ms regardless of true value. The p50 shown
on /benchmarks/bridge-quote-latency?chain=HyperCore was therefore a
bucket floor, not a measurement. Sub-50 buckets let the real bimodality
surface.

Also swap Observe(float64(quoteLatency.Milliseconds())) for
Observe(float64(quoteLatency.Nanoseconds()) / 1e6) across all 6
per-bridge observers (across, debridge, lifi, mobula, nearintents,
relay). int64 Milliseconds() truncates every sub-1ms round-trip to 0,
same bug class as the RPC harness fix in PR #1128. Not a hot path here
(network RTT dominates), but keeps precision consistent with the new
low buckets and cheap to fix while we are already touching each file.

Bench YAML methodology + FAQ updated to reflect the new bucket set.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ers, draft asOf) (#1154)

* fix: 3 machine-readable surface bugs surfaced by GEO audit

1. /api/citable/{date} unit scaling.
   Snapshot route emitted `value: 627` for `aggregator-head-lag` while the
   live /api/citable emitted `value: 0.63` on the same measurement. The
   live route already wraps with valueInDeclaredUnit(raw, b.unit) but the
   snapshot route was left with the raw internal-ms value. Wrap the same
   way; sub-1 s benches now render in the declared `s` unit on both routes.

2. /api/stat/{slug} silently ignored ?chain= / ?region= / ?kind= / ?venue=.
   getBenchmark accepted a filters object but the route never parsed the
   URL. A citer asking /api/stat/rpc-capabilities?chain=ethereum got the
   cross-chain aggregate. Parse the four dimension params, pass through
   to the loader; unknown values fall back to unfiltered inside the
   loader. Also widen getBenchmark options type to include kind and
   venue — the underlying loadBenchmark already supports both.

3. Draft benches leaked a fake freshness signal.
   Draft placeholder set lastRunAt to new Date for type safety
   (Benchmark.lastRunAt is non-null), and both citable routes passed it
   straight through as asOf. LLM crawlers treating asOf as ground truth
   would think a draft bench was measured every minute. Null asOf in the
   JSON when status is draft; the internal type stays intact.

* fix: propagate draft-asOf null-out to /api/stat, MCP, JSON-LD dateModified

The GEO audit review found the initial fix in commit f6f84b8 only covered
the two /api/citable JSON surfaces, but the same "draft benches spoof
freshness" class of bug lives on three more machine-readable channels
that LLM crawlers also key on:

- /api/stat/{slug} asOf field.
- MCP tool responses (list_benchmarks, get_benchmark, resource template,
  and the plain-text "Last sample" line).
- JSON-LD dateModified on the bench page, per-chain sub-page, answer
  page and alternatives page — Google, Bing, Perplexity all consume
  dateModified as a freshness ranking signal.

Centralize the guard as citableAsOf(b) in @/lib/citation, rewire every
call site. Draft benches now uniformly omit or null the freshness
timestamp; live benches unchanged. On the visible /answers page,
the "Data as of..." line and its <time dateTime> anchor also hide for
drafts rather than displaying the wall-clock placeholder.

Follow-up to PR #1154 (commit f6f84b8) covering the surfaces the review
agent flagged as still spoofing.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
)

* feat: /api/compare/{a}/{b} + openapi.json coverage

Two GEO surfaces flagged by the audit as high-value gaps.

## /api/compare/{a}/{b}

Head-to-head comparison as a single citable payload. LLMs asked "Mobula
vs Codex head lag" would otherwise need two /api/stat calls plus their
own delta reasoning; this endpoint returns one flat structure with per
benchmark p50 for each side, the winner on the metric's
higher-is-better convention, and the absolute delta.

The intersection logic mirrors src/lib/related-providers.ts and the
HTML /compare/{slug} page: shared benchmark = both providers appear in
it. Intentionally returns flat leader-p50 only (no chain/region
breakdowns, no cache-heavy fan out) so the agent tool call stays fast.

## openapi.json coverage

Added /api/citable/{date} (was missing despite being a real API
endpoint) and /api/compare/{a}/{b} to the OpenAPI 3.1 spec. Also
documented the dimension query params (chain/region/kind/venue) on
/api/stat/{slug} that shipped in the previous PR but were undocumented.
Added Compare schema; extended Stat schema with `filters` field.

/llms.txt and /llms-full.txt stay outside the OpenAPI surface — they
are text files not RPC endpoints, and OpenAPI consumers do not handle
plain-text specs well anyway.

## citableAsOf helper widened

Type signature relaxed from full Benchmark to
`Pick<Benchmark, "status" | "lastRunAt">` so the slim
ProviderAppearance.benchmark used by the new compare route can pass
through without a downcast. No behavior change on existing callers.

## Notes on items NOT shipped in this batch

- Cache-Control on /llms.txt / /api/citable / /api/llm-context: the
  audit report showed `cache-control: public` and inferred no CDN
  caching, but the source already sets
  `public, s-maxage=300, stale-while-revalidate=900`. Vercel strips the
  `s-maxage` directive from the client-visible header while honoring it
  at the edge; `x-vercel-cache: HIT` with `age: 256` confirms the CDN
  cache is active. Not a real bug.
- .bib / .ris file MIME endpoints: string versions already ship inside
  the /api/stat JSON via cite.bibtex / cite.apa. Marginal upside for
  Zotero-style one-click imports; downgraded to P2.
- /api/stat/{slug}/sparkline.svg: sparkline data already ships as a
  float array in /api/stat and the OG image at /api/og/{slug} covers
  the "embed a chart in an answer" case. P2.
- JSON-LD `Dataset.variableMeasured.value`: current schema emits bare
  strings; upgrading to PropertyValue objects would duplicate the
  measured value already carried by the StatisticalReport node's
  Observation.measuredValue. P2.

* openapi: add totalWins to Compare schema

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ue (#1158)

* feat: Wave 1 P2 batch — .bib/.ris/apa MIME, sparkline SVG, Dataset PropertyValue

Three GEO surface additions the audit downgraded to P2. Each one is
duplicative with an existing channel (cite.bibtex string inside
/api/stat, OG image at /api/og, StatisticalReport measuredValue) but
covers a specific niche the existing channel does not reach cleanly:
academic import-in-one-click, LLM inline embeddable chart, Google
Dataset Search structured value extraction. Ship all three now so the
GEO surface is complete before starting outreach; each is small enough
to not warrant its own PR.

## /api/cite/{slug}/{format}

Per-bench citation with the correct MIME type. Four formats:
  - bib -> application/x-bibtex (Zotero, LaTeX)
  - ris -> application/x-research-info-systems (EndNote, Mendeley)
  - apa -> text/plain (ready-to-paste APA string)
  - txt -> text/plain (ready-to-paste plain attribution)

Content is the same as the cite.* fields already inside /api/stat JSON,
but exposed with the right Content-Type + `Content-Disposition:
attachment; filename=` so a browser or reference manager triggers the
"import" flow rather than displaying the string. Added RIS to CiteBundle
in src/lib/citation.ts (TY GEN neutral fallback, AU / TI / PY / UR / Y2
/ ER fields, CRLF line endings per RFC convention).

## /api/sparkline/{slug}

Standalone SVG sparkline of the current leader's 24 h series. Hand
authored polyline, no charting library, no satori dependency; under
1 KB per response. Query params: `w` (40..1200, default 240), `h`
(20..400, default 60), `theme=light|dark`. Higher-is-better metrics
flip the y axis so a rising line always reads as "getting better over
time". Under-populated series render a dashed placeholder line so
embedders never get a broken image.

Cases this reaches that /api/og and the raw JSON sparkline array do
not: img/svg inline embeds in Perplexity Pages, ChatGPT answers, dev.to
posts, Notion pages. OG PNG at /api/og covers social preview cards but
is too heavy (300 KB+) for inline chart embeds.

## Dataset.variableMeasured -> PropertyValue

Bare-string array in Dataset JSON-LD upgraded to include structured
PropertyValue objects when the current leader has real numeric
aggregates. Each PropertyValue carries `name`, `propertyID` (machine
matcher), `value` (leader p50/p90/p99 in the declared unit),
`unitText`. sample_size stays a bare string — it labels an axis, not a
measurement. Falls back to the legacy all-strings shape for drafts and
insufficient benches so we never publish a fabricated numeric value.

New helper: buildBenchVariableMeasured() in src/lib/dataset-jsonld.ts.
BenchDatasetInput.variableMeasured now accepts
Array<string | VariableMeasuredValue>. `keywords` extraction pulls the
`.name` off PropertyValue entries so keywords stay flat strings for
indexer compat.

## openapi.json

Both new endpoints registered under paths, so agent frameworks
discovering the API through OpenAPI can call them.

* sparkline: XML-escape bench title + provider name in SVG

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…posits) (#1160)

* bridge-monitor: implement Mobula EIP-712 signature-confirm flow (fix stuck deposits)

* mobula sig: inject EIP712Domain type definition, API omits it

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Robinhood Chain Vague 1: weekend drift + arb latency + subsidy tracker
pm-api-latency: add Predexon as 6th provider
publicnode.com burst-limits the VPS on getAccountInfo, dropping
~60% of samples. Two retries with 500ms backoff bring hit rate to
>95% without switching endpoints. Also add commitment=confirmed
to reduce slot-miss null returns.

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ility, indexer latency, block builders) (#1170)

* add bench 081 ws-head-latency: WS block-push lag harness + spec

* add bench 084 indexer-latency: HyperSync vs The Graph vs Alchemy time-to-queryable

* idx-latency: only classify error when every poll errored, else timeout

* bump bench-set cache keys for 084 (lockstep rule)

* add bench 082 oracle-freshness: staleness metrics in oracle-deviation harness (multi-chain chainlink, pyth publish_time fix, redstone), bump bench-set cache keys

* oracle-freshness: count unsupported mainnet feeds in freshness error counter

* add bench 085 evm-block-builders: ETH builder share via extraData + relay bidtraces + Arb/Base soft-conf lag, bump bench-set cache keys

* ebb: don't count historical relay payloads on first poll, retry arb calibration on zero support, reset WS backoff after stable session

* add bench 083 rpc-reliability: consensus lag, hash quorum, fixed-vector integrity in rpc-capabilities harness, bump bench-set cache keys

* bench 083: clamp_min rank_matrix_query so zero-incident providers keep cell ranks

* gate benches 081-085 behind staging until 48h clean data

* drop shipped RWA slugs from prod gate (live on main since #1162/#1167)

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
hyperliquid frontend page: show the frontend logo
…ies; dark-mode chip for slash + markets-mobile
hl frontends: 3 more logos (slash, topdog, markets-mobile)
…nch races $0 pipelines only) (#1176)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
* fix(bench): sample-size PromQL + leader reliability floor

Two bench-correctness fixes surfaced by an audit of the oracle and
gas surfaces we plan to cite in outreach.

Bench 025 (oracle-deviation):
- sample_size was 'count(count by (source) (ocb_oracle_price))'
  which returns the count of distinct sources (3 or 4), not the
  count of samples over the window. The citable API therefore
  reported sampleSize=3-4 on every pair, at odds with the spec's
  'polled every 30s' claim. Rewrites the query to
  'sum(count_over_time(ocb_oracle_price[24h]))' across all 10
  pair entries so the number reflects actual poll volume.

Leader ranking:
- citationCandidates() now excludes providers with a measured
  success rate below 50 percent from the leader pool. Before this
  fix, gas-estimation showed Owlracle as #1 with successRate=6.48
  and a p50 gap of 0.001 gwei: technically accurate on the rare
  successful poll but useless as a leader claim. Falls back to the
  full live pool when every provider is under the floor so a
  totally degraded bench still surfaces a best-of-bad-options
  leader rather than vanishing. Freshness benches without a
  success query default to 100 percent in the loader so the guard
  is inert there.

Adds src/lib/citation.test.ts with 5 tests covering the Owlracle
scenario, higher-is-better ranking, and the degraded-pool
fallback.

* fix(bench): propagate reliability floor to rankings + template + hub

Follow-up on the previous commit. Review flagged that filtering the
leader without filtering the ranking arrays leaves self-contradictory
JSON: /api/stat can report leader=Etherscan while rankings[0]=Owlracle
on the same document.

Exports citationCandidates and adds rankedCandidates helper that
returns the same eligible pool sorted by the bench's higher_is_better
direction. leader() and fieldValue() now share it.

Wires the shared helper into every surface that emits a rankings-like
list or a best-of claim:

- src/app/api/stat/[slug]: rankings array now sorted from
  rankedCandidates so rankings[0].slug always equals leader.slug on
  the same JSON.
- src/app/api/llm-context: the Rankings numbered list under each
  bench matches the Headline sentence above it.
- src/app/api/mcp/[transport]: get_benchmark tool response and the
  openchainbench://benchmark/{slug} resource both share the pool.
- src/lib/bench-template: {{best_name}} / {{best_p50}} / {{worst_name}}
  tokens in bench copy resolve against the same pool so a bench body
  never crowns a provider its own headline excludes. Per-slug tokens
  {{p50:some-slug}} still resolve against the raw live pool so unknown
  tokens still fall through.
- src/lib/providers: rankProviders (drives /products hub wins count
  and per-chain leadership chips) now runs on the reliable pool.

Adds a consistency test locking that rankedCandidates[0].slug always
matches leader().slug on the same bench. Total: 6 tests in
citation.test.ts, 95/95 pass across src/.

Uses (r.successRate ?? 100) for the numeric guard to future-proof
against ProviderResult shapes that skip the field. Load path still
defaults to 100 (materialize/load.ts:748) so this is inert today, but
the extra ?? removes the fragility.

/api/compare/[a]/[b] is intentionally left unfiltered: the user
picks the two providers explicitly, so the head-to-head is a
requested comparison rather than a leader claim. Applying the floor
there would drop rows the user explicitly asked to see.

* fix(hub-card): honor citation leader in the hub grid headline

Follow-up review flagged that benchmark-card.tsx re-sorted b.results
locally and picked results[0], so a bench where citation demotes the
raw-p50 winner (e.g. Owlracle on gas-estimation) would show the
demoted provider on the hub grid while the bench page names the
correct leader. Two documents contradicting each other in one click.

Adds leaderSlug: string | null to BenchmarkCardData and populates it
at the projection boundary via leader(b), which already applies the
reliability + insufficient-sample filter. The card picks the leader
by matching that slug in the projected results, and falls back to the
raw best when the projection returned no leader (draft, insufficient,
or all providers filtered out) so a card still renders a headline
value in every state.

Kept the local sort so the fallback path and any downstream chip
using sorted[0].name keeps working.

Test suite unchanged: 95/95 pass across src/. No test needed since
this is a projection-boundary wire-up.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Commit 349b786 (hl frontends: add slash, topdog, markets-mobile logos +
registry entries) added a 'topdog' entry at line 313 without noticing a
pre-existing 'topdog' entry at line 1061, breaking staging build with a
duplicate object literal type error.

Keeps the newer entry (Telegram bot at t.me/topdog_trade_bot, HL
frontend context) since it was the one intentionally added for the
Hyperliquid frontends leaderboard chip work. Drops the older Discord
signal bot entry (topdog.gg): the bench spec at
benchmarks/hyperliquid-frontends.yml has slug: topdog with name TopDog
and matches the newer, HL-native product.

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…en 080 findings, drop dead 085 builder rows, 083 expected_n 40 (#1178)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…1179)

Extends rpc-capabilities to Polkadot, the first non-EVM, non-Solana
chain in the cluster. Substrate JSON-RPC via chain_getHeader (returns
hex-encoded block number matching EVM's eth_getBlockByNumber shape),
staleness by relay-block gap (40 blocks, ~4 min at Polkadot's ~6 s
cadence).

Harness changes:
- Chain.Kind gains "polkadot" alongside "solana" and default (EVM).
- New callSubstrateHeader: chain_getHeader POST, parses hex block
  number, returns parentHash as the observed hash. Same latency
  measurement + result classification as the EVM path.
- probeOne switches on Kind (was if solana / else). polkadot uses
  polkadotStaleBlockGap = 40. Consensus observation is skipped
  because chain_getHeader returns parentHash (N-1), not the current
  block hash (deriving current hash needs Blake2 over the SCALE-
  encoded header, out of scope).
- Archive loop is skipped for polkadot the same way as solana:
  Polkadot state accesses via state_getStorage keyed by Blake2 storage
  keys, no chain-agnostic depth analog for eth_getBalance-by-depth.

Providers at launch (3, live-verified on 2026-07-15 with four
consecutive chain_getHeader probes each):
- polkadot-official (Parity, rpc.polkadot.io)
- onfinality (public gateway, Web3 Foundation grantee)
- publicnode (Allnodes, extends its multi-chain footprint to
  Substrate keyless)

Audited and excluded from the launch cohort:
- 1RPC: filters chain_getHeader as "Not Allowed" on the privacy
  relay. Revisit if they expose a Substrate-friendly method set.
- Dwellir: 503 Service Unavailable during the audit sweep, revisit
  once the gateway stabilises.
- Ankr: paid Polkadot tier only.
- Chainstack: key-gated on Substrate.
- RadiumBlock: rejects without referral header.
- Grove / Thirdweb: invalid-chain response.

Spec (benchmarks/polkadot-rpc.yml, bench № 086):
- Same schema as moonbeam-rpc / avalanche-rpc etc. so the parent
  rpc-capabilities benchmark, chain cluster page and hub card
  render it identically.
- Methodology documents the Substrate specifics: probe method
  choice, staleness scaling, why no archive-depth or hash-quorum
  columns.
- SEO/finding tokens use the standard {{best_name}} / {{best_p50}}
  / {{p50:slug}} template surface, consistent with other -rpc
  benches.
- Provider tag copy explains OnFinality and Dwellir's Web3
  Foundation grantee status so the leaderboard reads correctly
  for someone who knows the Polkadot infra landscape.

Local checks:
- go build ./... clean.
- go test ./... clean.
- bun test src/ - 95/95 pass across 10 files, including the
  citation reliability floor and consistency tests added in #1175.

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…cle logos (pyth/redstone) (#1181)

* products: registry entries + logos for bench 085 block builders

* oracle logos: redstone official logo + pyth/redstone manifest + registry entries

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…1182)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
* add bench 086 polkadot-rpc: first Substrate chain in the RPC cluster

Extends rpc-capabilities to Polkadot, the first non-EVM, non-Solana
chain in the cluster. Substrate JSON-RPC via chain_getHeader (returns
hex-encoded block number matching EVM's eth_getBlockByNumber shape),
staleness by relay-block gap (40 blocks, ~4 min at Polkadot's ~6 s
cadence).

Harness changes:
- Chain.Kind gains "polkadot" alongside "solana" and default (EVM).
- New callSubstrateHeader: chain_getHeader POST, parses hex block
  number, returns parentHash as the observed hash. Same latency
  measurement + result classification as the EVM path.
- probeOne switches on Kind (was if solana / else). polkadot uses
  polkadotStaleBlockGap = 40. Consensus observation is skipped
  because chain_getHeader returns parentHash (N-1), not the current
  block hash (deriving current hash needs Blake2 over the SCALE-
  encoded header, out of scope).
- Archive loop is skipped for polkadot the same way as solana:
  Polkadot state accesses via state_getStorage keyed by Blake2 storage
  keys, no chain-agnostic depth analog for eth_getBalance-by-depth.

Providers at launch (3, live-verified on 2026-07-15 with four
consecutive chain_getHeader probes each):
- polkadot-official (Parity, rpc.polkadot.io)
- onfinality (public gateway, Web3 Foundation grantee)
- publicnode (Allnodes, extends its multi-chain footprint to
  Substrate keyless)

Audited and excluded from the launch cohort:
- 1RPC: filters chain_getHeader as "Not Allowed" on the privacy
  relay. Revisit if they expose a Substrate-friendly method set.
- Dwellir: 503 Service Unavailable during the audit sweep, revisit
  once the gateway stabilises.
- Ankr: paid Polkadot tier only.
- Chainstack: key-gated on Substrate.
- RadiumBlock: rejects without referral header.
- Grove / Thirdweb: invalid-chain response.

Spec (benchmarks/polkadot-rpc.yml, bench № 086):
- Same schema as moonbeam-rpc / avalanche-rpc etc. so the parent
  rpc-capabilities benchmark, chain cluster page and hub card
  render it identically.
- Methodology documents the Substrate specifics: probe method
  choice, staleness scaling, why no archive-depth or hash-quorum
  columns.
- SEO/finding tokens use the standard {{best_name}} / {{best_p50}}
  / {{p50:slug}} template surface, consistent with other -rpc
  benches.
- Provider tag copy explains OnFinality and Dwellir's Web3
  Foundation grantee status so the leaderboard reads correctly
  for someone who knows the Polkadot infra landscape.

Local checks:
- go build ./... clean.
- go test ./... clean.
- bun test src/ - 95/95 pass across 10 files, including the
  citation reliability floor and consistency tests added in #1175.

* polkadot page + parity/onfinality/dwellir products

Wires the /chains/polkadot hub page, provider profiles, and logo
assets that surface once bench 086 polkadot-rpc starts feeding Prom.

- chains.ts: add Polkadot to CHAINS registry (L1, nativeSymbol DOT).
  Auto-populates /chains/polkadot once benches referencing chain="polkadot"
  land. Adaptive KPI strip fills in DefiLlama TVL, DEX volume and stables
  when the chain-kpis harness starts sourcing Polkadot alongside the
  other L1s.
- provider-registry.ts: three new entries for the Polkadot cohort.
    * parity: brand entry for Parity Technologies (Polkadot / Substrate
      implementation shop, operator of rpc.polkadot.io).
    * polkadot-official: endpoint-level entry describing the reference
      rpc.polkadot.io keyless endpoint; providers.ts aliases the slug
      to parity so the /products page collapses under the operator's
      brand.
    * dwellir: brand entry for Dwellir (Web3 Foundation grantee, keyless
      Substrate infra) — excluded from the 086 launch cohort due to a
      503 during the audit sweep but kept in the registry for the
      revisit and for other places their brand might appear.
- providers.ts: add polkadot-official → parity alias and canonical
  brand casings for polkadot / parity / onfinality / dwellir so the
  /products hub, badges, and OG images render the correct name
  without the title-case fallback butchering them.
- logo-manifest.ts: polkadot.png (chain hero), parity.png (product page
  logo), plus polkadot-official → parity slug alias so the endpoint
  chip on the bench page picks up Parity's mark automatically.

95/95 pass on bun test src/. Local build clean.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
…#1186)

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant