Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ Two long-lived branches:
**Rules:**

- Never push directly to `main` or `dev`. PRs only.
- The staging URL emits `<meta robots=noindex>` and serves `robots.txt: Disallow /` see `src/app/robots.ts` and `src/app/layout.tsx`. Don't undo without thinking through duplicate-content SEO impact on the prod domain. Vercel Preview SSO is disabled (URL is publicly shareable), so the noindex is the only thing keeping crawlers out.
- The staging URL emits `<meta robots=noindex>` and serves `robots.txt: Disallow /`, see `src/app/robots.ts` and `src/app/layout.tsx`. Don't undo without thinking through duplicate-content SEO impact on the prod domain. Vercel Preview SSO is disabled (URL is publicly shareable), so the noindex is the only thing keeping crawlers out.
- Hotfix path: if prod is on fire and waiting through `dev` is unacceptable, open the fix PR directly against `main`, merge (CI deploys automatically), then immediately fast-forward `dev` from `main` (`git checkout dev && git merge --ff-only main && git push`) so the two branches don't diverge.
- Required GitHub secrets (repo-level, not environment-scoped): `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[![License: MIT](https://img.shields.io/badge/code-MIT-blue.svg)](./LICENSE) [![Data: CC-BY-4.0](https://img.shields.io/badge/data-CC--BY--4.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) [![Benchmarks](https://img.shields.io/badge/benchmarks-94_live-green.svg)](https://openchainbench.com/benchmarks) [![MCP](https://img.shields.io/badge/MCP-ready-purple.svg)](https://openchainbench.com/mcp) [![llms.txt](https://img.shields.io/badge/llms.txt-yes-orange.svg)](https://openchainbench.com/llms.txt)

OpenChainBench publishes one benchmark at a time, each shipping with the harness that produces its data. The goal is to make performance an observable property of crypto infra measured in the open, by anyone who wants to add a provider or a metric.
OpenChainBench publishes one benchmark at a time, each shipping with the harness that produces its data. The goal is to make performance an observable property of crypto infra, measured in the open, by anyone who wants to add a provider or a metric.

Community-run, MIT-licensed harnesses + CC-BY-4.0 data, PRs from any party including the providers we benchmark.

Expand All @@ -14,7 +14,7 @@ Community-run, MIT-licensed harnesses + CC-BY-4.0 data, PRs from any party inclu
- **50** self-contained Go harnesses, each shipping a `/metrics` Prometheus endpoint
- **32** curated question/answer pages · **23** "alternatives to X" landing pages
- **26** machine-readable API routes (citation, badges, MCP, feeds, per-format quotes)
- One shared Prometheus, one materialization worker, CDN-fastpath snapshot store cold reads in < 100 ms edge, no origin fan-out on hot paths
- One shared Prometheus, one materialization worker, CDN-fastpath snapshot store: cold reads in < 100 ms edge, no origin fan-out on hot paths

## Quickstart

Expand Down
10 changes: 5 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ build clones the OCB `dev` branch fresh).
## Known constraints

- Prometheus must be reachable from the materialization worker; the site only needs the Redis store (plus Prometheus for the live fallback path).
- Worker sweep cost grows linearly with providers (one Prometheus call per provider per metric per benchmark) the heaviest bench, hyperliquid-frontends, declares 100+ providers. The Prom client bounds concurrency with a global query semaphore.
- Worker sweep cost grows linearly with providers (one Prometheus call per provider per metric per benchmark), the heaviest bench, hyperliquid-frontends, declares 100+ providers. The Prom client bounds concurrency with a global query semaphore.
- `generateStaticParams` reads the YAML filenames directly so the route table is stable even when Prometheus or the store is offline.
- Spec changes that alter a bench's provider list require a worker image rebuild to show up on the site (the worker bakes its OCB checkout at build time).

Expand Down Expand Up @@ -105,7 +105,7 @@ The live dashboard at [openchainbench.com](https://openchainbench.com) has its *
```

The browser opens a WebSocket **directly to the Railway relay**. Vercel
Functions are never invoked for live data keeps cost flat at ~$5/mo
Functions are never invoked for live data, keeps cost flat at ~$5/mo
regardless of viewer count.

### Why a separate relay (not Vercel SSE)
Expand All @@ -122,9 +122,9 @@ Three message types arrive on `/ws`, all JSON:

| `type` | When | Payload |
|---|---|---|
| `snapshot` | First message on every connect | `{ buckets: [{ ts, perChain }], nowMs }` the last 10 min of incremental per-chain volume, padded with empty buckets on the left so the chart is full from t=0. |
| `swap` | Per real trade (filtered: `buy`/`sell`, `operation=regular\|arbitrage`) | `{ chain, pool, pair, exchange, side, usd, hash, onChainMs, mobulaMs, receivedMs }` enriched with pair name + DEX from a top-N catalog fetched at boot. |
| `stats` | Every 1 s | `{ global: {vol24h, trades24h, …, mcap, byChain[]}, live: {swaps, vol, chains}, nowMs }` ground-truth global stats from REST + per-session live counters. |
| `snapshot` | First message on every connect | `{ buckets: [{ ts, perChain }], nowMs }`, the last 10 min of incremental per-chain volume, padded with empty buckets on the left so the chart is full from t=0. |
| `swap` | Per real trade (filtered: `buy`/`sell`, `operation=regular\|arbitrage`) | `{ chain, pool, pair, exchange, side, usd, hash, onChainMs, mobulaMs, receivedMs }`, enriched with pair name + DEX from a top-N catalog fetched at boot. |
| `stats` | Every 1 s | `{ global: {vol24h, trades24h, …, mcap, byChain[]}, live: {swaps, vol, chains}, nowMs }`, ground-truth global stats from REST + per-session live counters. |

The browser snapshots the first message, then accumulates `swap` events into incremental buckets matching the relay's bucket boundaries (using the snapshot's `nowMs` to learn the relay/browser clock offset).

Expand Down
2 changes: 1 addition & 1 deletion src/app/api/badge/[slug]/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ function placeholderSvg(benchTitle: string): NextResponse {
Math.max(W_MIN, TEXT_X + title.length * CH_11 + 30),
);
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-label="OpenChainBench: ${escapeXml(title)} measurement pending">
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-label="OpenChainBench: ${escapeXml(title)}, measurement pending">
<title>OpenChainBench. ${escapeXml(title)}. Measurement pending.</title>
<rect width="${W}" height="${H}" rx="6" fill="#F5F1E8"/>
<rect x="0.5" y="0.5" width="${W - 1}" height="${H - 1}" rx="5.5" fill="none" stroke="#22272F" stroke-opacity="0.12"/>
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/sparkline/[slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export async function GET(
`stroke-linejoin="round"/>`;
}
const title = xmlEscape(
`${bench.title} 24h sparkline${top ? ` (${top.name})` : ""}`,
`${bench.title}: 24h sparkline${top ? ` (${top.name})` : ""}`,
);
const svg =
`<?xml version="1.0" encoding="UTF-8"?>\n` +
Expand Down
2 changes: 1 addition & 1 deletion src/app/benchmarks/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ export default async function BenchmarkPage({
benchmark.faq,
benchmarkUrl,
null,
`${benchmark.title} frequently asked questions`,
`${benchmark.title}: frequently asked questions`,
);

return (
Expand Down
2 changes: 1 addition & 1 deletion src/app/compare/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ export default async function ComparePage({
// FAQPage parent even when Question.name is set. Add here + on
// Answer nodes so the compare page matches the shared FAQ builder
// in src/lib/jsonld.ts.
name: `${a.name} vs ${b.name} frequently asked questions`,
name: `${a.name} vs ${b.name}: frequently asked questions`,
mainEntity: faqEntries.map((e) => ({
"@type": "Question",
name: e.q,
Expand Down
6 changes: 3 additions & 3 deletions src/app/hyperliquid/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function generateMetadata({
if (!frontend) {
return pageMetadata({
path: `/hyperliquid/${slug}`,
title: `${displayName} Hyperliquid frontend`,
title: `${displayName}, Hyperliquid frontend`,
description: `${displayName} on Hyperliquid: 12-month builder fees, volume and first-active date. Rolling 30-day metrics refresh as data becomes available.`,
});
}
Expand All @@ -78,7 +78,7 @@ export async function generateMetadata({
)}). 12-month history with volume, fees and first-active date.`;
return pageMetadata({
path: `/hyperliquid/${slug}`,
title: `${displayName} Hyperliquid frontend`,
title: `${displayName}, Hyperliquid frontend`,
description,
});
}
Expand Down Expand Up @@ -157,7 +157,7 @@ export default async function HlFrontendPage({
"@context": "https://schema.org",
"@type": "Dataset",
"@id": `${pageUrl}#dataset`,
name: `${frontend.name} HyperLiquid frontend revenue + volume`,
name: `${frontend.name}, HyperLiquid frontend revenue + volume`,
description: `Daily HyperLiquid frontend fees and volume for ${frontend.name}, sourced from the OpenChainBench hyperliquid-frontends benchmark. First measured ${firstDay}.`,
url: pageUrl,
identifier: slug,
Expand Down
4 changes: 2 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export default async function RootLayout({
<link
rel="alternate"
type="application/rss+xml"
title="OpenChainBench new benchmarks"
title="OpenChainBench: new benchmarks"
href="/rss.xml"
/>
{/* JSON Feed 1.1 companion. Modern feed readers + AI agent
Expand All @@ -207,7 +207,7 @@ export default async function RootLayout({
<link
rel="alternate"
type="application/feed+json"
title="OpenChainBench new benchmarks (JSON Feed)"
title="OpenChainBench: new benchmarks (JSON Feed)"
href="/feed.json"
/>
<script
Expand Down
2 changes: 1 addition & 1 deletion src/app/mcp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default async function McpPage() {
Claude Desktop
</p>
<p className="mt-2 text-sm text-ink-soft leading-relaxed">
No deeplink yet — the fastest path is{" "}
No deeplink yet. The fastest path is{" "}
<strong className="text-ink">
Settings → Developer → Model Context Protocol → Add server
</strong>{" "}
Expand Down
4 changes: 2 additions & 2 deletions src/app/rpc/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default async function RpcHubPage() {
the same probe: one identical <code>eth_blockNumber</code> call
every 15 seconds from 3 regions (N. Virginia, Amsterdam,
Singapore). The matrix below folds the per-chain leaderboards
into one view fastest provider per chain, fastest per region,
into one view: fastest provider per chain, fastest per region,
and which gateway covers your whole multichain stack. Headline
numbers are 24h p50 round-trip latency; methodology and
exclusion rules live on the{" "}
Expand Down Expand Up @@ -205,7 +205,7 @@ export default async function RpcHubPage() {
Data warming up
</p>
<p className="text-sm text-ink-soft max-w-2xl leading-relaxed">
The cross-chain snapshot has not been published yet — the
The cross-chain snapshot has not been published yet. The
materialize worker writes it every minute once the RPC
cluster is sweeping. The per-chain leaderboards are already
live on their bench pages:
Expand Down
6 changes: 3 additions & 3 deletions src/data/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export const PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
"DEX aggregator and DeFi middleware layer. The Augustus Router splits orders across AMMs, PMMs, and direct pools on 10+ EVM chains to minimize price impact and gas costs. Also offers a developer SDK for integrating best-execution routing into dApps and wallets.",
twitter: "@paraswap",
longDescription:
"ParaSwap aggregates liquidity from Uniswap, Curve, Balancer, AAVE, and dozens of other AMMs and market makers via its Augustus Router. Trades are split across multiple paths atomically, selecting the combination that maximises net output after gas. ParaSwap also runs a delta trading layer a private RFQ network where professional market makers post competitive quotes for large orders, bypassing on-chain pools entirely. The SDK and API are used by Ledger Live, Safe, and other wallets as the routing engine behind their swap UI.",
"ParaSwap aggregates liquidity from Uniswap, Curve, Balancer, AAVE, and dozens of other AMMs and market makers via its Augustus Router. Trades are split across multiple paths atomically, selecting the combination that maximises net output after gas. ParaSwap also runs a delta trading layer: a private RFQ network where professional market makers post competitive quotes for large orders, bypassing on-chain pools entirely. The SDK and API are used by Ledger Live, Safe, and other wallets as the routing engine behind their swap UI.",
docs: "https://developers.paraswap.network",
},
bebop: {
Expand Down Expand Up @@ -1793,7 +1793,7 @@ export const PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
ousg: {
url: "https://ondo.finance/ousg",
description:
"Ondo OUSG is a tokenized short-term U.S. Treasury fund. NAV appreciation model the share price grows daily as yield accrues. Delivered yield on bench 089 is read from Ondo's on-chain oracle (Aave IPriceOracle interface).",
"Ondo OUSG is a tokenized short-term U.S. Treasury fund. NAV appreciation model: the share price grows daily as yield accrues. Delivered yield on bench 089 is read from Ondo's on-chain oracle (Aave IPriceOracle interface).",
twitter: "@OndoFinance",
docs: "https://docs.ondo.finance/",
},
Expand All @@ -1807,7 +1807,7 @@ export const PROVIDER_REGISTRY: Record<string, ProviderRegistryEntry> = {
"syrup-usdc": {
url: "https://syrup.fi",
description:
"Maple SyrupUSDC is an ERC-4626 vault whose underlying is a book of institutional USDC loans. Not a T-bill wrapper — yield reflects loan-book performance. Delivered yield on bench 089 reads convertToAssets(1 share) at latest and t-30d blocks and compares to the pool's base APY (excludes SYRUP token rewards).",
"Maple SyrupUSDC is an ERC-4626 vault whose underlying is a book of institutional USDC loans. Not a T-bill wrapper. Yield reflects loan-book performance. Delivered yield on bench 089 reads convertToAssets(1 share) at latest and t-30d blocks and compares to the pool's base APY (excludes SYRUP token rewards).",
twitter: "@maplefinance",
docs: "https://maplefinance.gitbook.io/maple/",
},
Expand Down
Loading