From 61e770ae73075bd374e82fa1a3b52ae04d109c10 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:28:43 +0200 Subject: [PATCH 1/4] SEO/E-E-A-T pack: /team page, Person schema, 23 positioning, 8 expert_take (#1249) Adds a broad SEO and GEO upgrade landing three surfaces at once: - /team page with Florent Tapponnier as Maintainer, sameAs GitHub + LinkedIn, Person JSON-LD wired into Organization.founder and into every bench StatisticalReport.contributor + TechArticle.author + reviewedBy so YMYL crypto queries see a named human accountable. - 23 positioning fields on alternatives specs (200 words each, factual, sourced from official docs, fact-checked via WebFetch). Cuts the shared-template surface from ~65 percent to ~30 percent across pages that reference the same benchmark. - 8 expert_take fields on answers specs (150-200 words each, structural commentary that stays valid regardless of who currently leads the leaderboard) with QAPage schema so the acceptedAnswer (Org, auto) and suggestedAnswer (Person, expert_take) both reach Google + AI Overviews. - Byline on answers pages when expert_take is present, linking to /team, hidden when dataPending so editorial context never renders next to an empty leaderboard. - noindex on awaiting benches so Prom outages do not surface thin placeholder pages to crawlers. - Optional Zod fields positioning + expert_take + expert_take_reviewed with noAiDashes refine so future drafts stay in policy. - Sitemap prebuild script emits real git-commit timestamps for the 8 editorial pages so lastmod stops resetting to build time on every unrelated Vercel deploy. - scripts/editorial-check.sh lints the two new fields for em/en dashes, marketing adjectives, pronouns, year references. Co-authored-by: Florent Tapponnier --- .gitignore | 6 + alternatives/alchemy.yml | 9 ++ alternatives/birdeye.yml | 9 ++ alternatives/bitquery.yml | 9 ++ alternatives/chainlink.yml | 10 ++ alternatives/coingecko.yml | 9 ++ alternatives/drpc.yml | 10 ++ alternatives/dune.yml | 10 ++ alternatives/dydx.yml | 10 ++ alternatives/etherscan.yml | 10 ++ alternatives/gmx.yml | 9 ++ alternatives/hyperliquid.yml | 10 ++ alternatives/jupiter.yml | 10 ++ alternatives/kalshi.yml | 11 ++ alternatives/lifi.yml | 10 ++ alternatives/polymarket.yml | 10 ++ alternatives/publicnode.yml | 10 ++ alternatives/pump-portal.yml | 9 ++ alternatives/pyth.yml | 10 ++ alternatives/quicknode.yml | 9 ++ alternatives/relay.yml | 9 ++ alternatives/tenderly.yml | 10 ++ alternatives/the-graph.yml | 9 ++ alternatives/zerion.yml | 10 ++ answers/is-usdc-safer-than-usdt.yml | 10 ++ .../what-is-the-fastest-free-ethereum-rpc.yml | 10 ++ ...which-bridge-has-the-cheapest-usdc-fee.yml | 11 ++ ...o-data-api-covers-the-most-blockchains.yml | 10 ++ ...ich-crypto-oracle-is-the-most-accurate.yml | 11 ++ .../which-crypto-price-api-is-the-fastest.yml | 10 ++ .../which-gas-oracle-is-the-most-accurate.yml | 11 ++ .../which-perp-dex-has-the-lowest-fees.yml | 10 ++ next.config.ts | 7 ++ package.json | 1 + scripts/editorial-check.sh | 72 ++++++++++++ scripts/emit-page-mtimes.ts | 111 ++++++++++++++++++ src/app/answers/[slug]/page.tsx | 70 +++++++++-- src/app/benchmarks/[slug]/[chain]/page.tsx | 7 +- src/app/benchmarks/[slug]/page.tsx | 20 +++- src/app/layout.tsx | 13 ++ src/app/sitemap.ts | 28 ++++- src/app/team/page.tsx | 96 +++++++++++++++ src/components/byline.tsx | 37 ++++++ src/components/site-footer.tsx | 1 + src/lib/alternatives.ts | 20 ++++ src/lib/answers.ts | 29 +++++ src/lib/dataset-jsonld.ts | 6 + src/lib/hub-jsonld.ts | 50 ++++++++ 48 files changed, 868 insertions(+), 11 deletions(-) create mode 100755 scripts/editorial-check.sh create mode 100644 scripts/emit-page-mtimes.ts create mode 100644 src/app/team/page.tsx create mode 100644 src/components/byline.tsx diff --git a/.gitignore b/.gitignore index f21b1015..c9d710d4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,12 @@ yarn-error.log* # vercel .vercel +# Prebuild-generated sitemap freshness manifest. Written by +# scripts/emit-page-mtimes.ts on every build (git log %ct of editorial +# hub pages) so the sitemap can emit a real from git history +# without depending on Vercel's build-container file mtimes. +/data/page-mtimes.json + # Internal-only docs that must NOT ship with the open-source repo. # Patterns reserved as a tripwire: if a contributor adds a new doc with # one of these naming conventions, git won't track it without an diff --git a/alternatives/alchemy.yml b/alternatives/alchemy.yml index a2d7aa69..c088e3ff 100644 --- a/alternatives/alchemy.yml +++ b/alternatives/alchemy.yml @@ -9,3 +9,12 @@ intro: | seo_title: "Alchemy alternatives: live benchmark" seo_description: Compare Alchemy alternatives on real-time data freshness. Live head-lag against a canonical archive node, refreshed every minute and published openly. + +positioning: | + Alchemy's moat is a vertically integrated node platform. It runs archive nodes across 100+ chains (approximately 120 listed on the chain-connect page) and layers a data stack on to + p of the same infrastructure (Node API, Prices API, Notify webhooks, Subgraphs). For head lag, the relevant surface is the WebSocket subscription endpoint (eth_subscribe, plus prop + rietary alchemy_pendingTransactions and alchemy_minedTransactions) and the Notify webhook system for Address Activity and Custom Webhooks. Alchemy pushes filtered logs rather than + pre-decoded swap events, which means an integrator must maintain a Transfer or Swap topic filter and decode the ABI client-side. The Prices API is a REST product with cached quotes + drawn from CEX plus DEX aggregation, not a live swap feed. Alchemy tends to win when a team already runs the Node API for reads and wants notifications on the same account boundar + y; teams that need a decoded, cross-chain DEX trade stream typically migrate to a purpose-built aggregator because the client-side decoding overhead adds pipeline hops. Chain cover + age skews heavily to EVM plus Solana; long-tail EVM L2s and Bitcoin sit inside the standard Node API surface. diff --git a/alternatives/birdeye.yml b/alternatives/birdeye.yml index 1dead0c1..6d820c16 100644 --- a/alternatives/birdeye.yml +++ b/alternatives/birdeye.yml @@ -10,3 +10,12 @@ intro: | seo_title: "Birdeye alternatives: live benchmark" seo_description: "Birdeye alternatives on Solana token metadata coverage: logo, description, socials in seconds. Codex vs Mobula live." + +positioning: | + Birdeye's architectural weight sits on Solana. The public docs list 14 supported networks with Solana first and a longer tail of EVM and non-EVM chains (Ethereum, Arbitrum, BSC, Ba + se, zkSync, Sui, Aptos, Monad, MegaETH, Fogo, Avalanche, Optimism, Polygon). The DEX Protocols page cites data drawn from more than 180 AMMs, with Solana-native venues like Raydium + , Raydium CLMM, Orca and Whirlpool getting first-class treatment. The API surface that matters for metadata coverage is the DeFi and Token endpoints (/defi/price, /defi/token_overv + iew, /defi/token_security), which return price, liquidity, supply, market cap and holder-side signals as normalized JSON. Because Solana pools are volatile and permissionless, a me + tadata provider either owns direct pool ingestion or inherits gaps; Birdeye publishes coverage caveats on Sui (no wallet APIs, no token security data, missing market cap and supply + ), which suggests per-chain pipelines rather than one uniform indexer. Teams pick Birdeye when the primary target is Solana memecoin and DEX token metadata under a single API. Team + s tend to migrate when they need long-tail EVM L2 coverage or deep historical DEX reconstruction across chains where Birdeye ships less depth per chain. diff --git a/alternatives/bitquery.yml b/alternatives/bitquery.yml index 76380c11..66ce4070 100644 --- a/alternatives/bitquery.yml +++ b/alternatives/bitquery.yml @@ -9,3 +9,12 @@ intro: | seo_title: "Bitquery alternatives: live benchmark" seo_description: Compare Bitquery alternatives on the number of blockchains each major onchain data provider officially supports. Live data, refreshed every six hours. + +positioning: | + Bitquery's moat is a unified GraphQL schema fanned out across four transports: GraphQL over HTTP, WebSocket subscriptions, Kafka with Protobuf encoding, and a Solana gRPC product ( + CoreCast) fed by validator-side Geyser plugins. Coverage is 40+ chains, uniform schema, decoded DEX trades with USD notional pre-computed server-side. For a chain-coverage benchmar + k that measures how many networks a provider officially supports for market data, Bitquery is structurally competitive because the same query works across every indexed chain witho + ut per-chain re-plumbing. The DEX Trades API is the load-bearing endpoint for market data: it exposes buys, sells, swaps, OHLCV at 1-second resolution, and cross-DEX aggregates on + a single graph. Delivery latency is tiered by transport: gRPC under 300ms, Kafka under 500ms, WebSocket around a second, which lets integrators pick the durability vs speed trade-o + ff explicitly. Teams win with Bitquery when they need historical plus real-time on the same schema, especially for analytics or accounting pipelines; teams that only want a decoded + firehose of Solana Pump.fun trades sometimes pick a narrower vendor because Bitquery's breadth carries a higher entry price. diff --git a/alternatives/chainlink.yml b/alternatives/chainlink.yml index 401fbea0..c557b4f8 100644 --- a/alternatives/chainlink.yml +++ b/alternatives/chainlink.yml @@ -11,3 +11,13 @@ seo_title: "Chainlink alternatives: live benchmark" seo_description: "Chainlink vs Pyth, Binance and Coinbase on live oracle deviation. Max pairwise gap on BTC, ETH, SOL." status: live + +positioning: | + Chainlink Data Feeds publish aggregated prices on-chain through an AggregatorV3 contract that is fed by a Decentralized Oracle Network running Offchain Reporting, with new rounds t + riggered either by a configured deviation threshold or by a heartbeat interval, and read via a proxy contract that returns latestRoundData() in the same transaction as the consumin + g logic. Its architectural moat is on-chain durability and the largest deployed integration surface across EVM chains, exposed through AggregatorV3Interface with no off-chain refre + sh step required. That model does not maximise for oracle-deviation on fast-moving assets, because the on-chain price only advances when the deviation trigger fires or the heartbea + t elapses, so the reported value can lag a moving spot market by roughly one trigger-width until the next round lands. Chainlink also ships Data Streams, a pull-based sub-second la + tency product accessed over REST, WebSocket and SDKs, aimed at perpetual futures, options and prediction market venues that need low-latency data with on-chain verification. Lendin + g markets, stablecoin issuers and blue-chip settlement continue on Data Feeds for the on-chain read pattern. Perp DEXes and derivatives protocols that cannot tolerate deviation lag + on volatile assets migrate the price-sensitive path to Data Streams or a pull oracle. diff --git a/alternatives/coingecko.yml b/alternatives/coingecko.yml index 45c32a58..a9b544ca 100644 --- a/alternatives/coingecko.yml +++ b/alternatives/coingecko.yml @@ -9,3 +9,12 @@ intro: | seo_title: "CoinGecko alternatives: live benchmark" seo_description: Compare CoinGecko alternatives on the number of blockchains each major onchain data API officially supports. Live data, refreshed every six hours. + +positioning: | + CoinGecko's moat is aggregation breadth across both centralized and decentralized venues under a single independent methodology. The core surface indexes 1,500+ CEXs and 18,000+ co + ins; the on-chain arm, delivered through GeckoTerminal and exposed under the /onchain endpoint family, adds 200+ blockchain networks, 1,500+ DEXs and tens of millions of tokens. Fo + r a chain-coverage benchmark on market data, this is the widest reported footprint of any listed provider in the cluster. Delivery is REST across every tier, with WebSocket and web + hook channels gated to higher tiers. Data is not sourced from partner feeds or exchange-owned data providers but crawled and aggregated independently, which is the architectural re + ason CoinGecko's price on a long-tail token often differs from a venue-native quote. The /onchain/networks and /onchain/pools endpoints are the practical entry points for chain-sco + ped queries. CoinGecko wins for teams building broad market-data dashboards, tax and PnL products, or research tools that need one API to cover every chain a user might touch; team + s focused on a single L2 or a specific DEX with millisecond freshness typically pair it with a narrower, WebSocket-native vendor. diff --git a/alternatives/drpc.yml b/alternatives/drpc.yml index cfc1b373..5c4e37f7 100644 --- a/alternatives/drpc.yml +++ b/alternatives/drpc.yml @@ -11,3 +11,13 @@ seo_title: "dRPC alternatives: live benchmark" seo_description: "dRPC alternatives on eth_blockNumber p50 latency. PublicNode, 1RPC, Tenderly, Nodies, Lava ranked live." status: live + +positioning: | + dRPC is a routing layer, not a node operator. Every request lands on Dproxy, which scores third-party providers on health, capability, location, and recent compute-unit distributio + n; the request is then handed to Dshackle, an open-source fault-tolerant load balancer that picks the specific upstream node based on the method called, the current chain head, and + the block context required. The routing is method-aware: a sendRawTransaction fans out to every capable node, a balance read routes only to lag-free upstreams, and a historical re + ad filters to nodes that have synced to the requested block. This is architecturally distinct from single-operator RPCs, where a request runs against one homogeneous fleet, and dis + tinct from public free RPCs, which have no cross-provider selection at all. For the rpc-capabilities axis, the routing model directly shapes the observed method coverage: a method + that any upstream supports counts as covered network-wide, and uptime aggregates across upstreams rather than depending on any one operator's incident window. Chain coverage spans + 120+ chains across 217 networks (Chainlist product) spanning EVM L1s and L2s, Solana, Bitcoin, Cosmos, and TON. The typical use case is teams that want one endpoint per chain with + automatic failover; migration happens when a team needs a proprietary method only one upstream exposes. diff --git a/alternatives/dune.yml b/alternatives/dune.yml index 42784527..601aaa5b 100644 --- a/alternatives/dune.yml +++ b/alternatives/dune.yml @@ -9,3 +9,13 @@ intro: | seo_title: "Dune alternatives: live benchmark" seo_description: "Dune alternatives on wallet labelling depth. Recently active address coverage across major EVM chains." + +positioning: | + Dune runs two distinct product surfaces with different pipelines. The core Dune Analytics product exposes SQL over decoded blockchain tables (Trino-backed, dbt-integrated), where c + ommunity-maintained Spellbook models sit on top of raw, decoded, and traces tables. The Sim API is the real-time product, positioned in the docs as covering more than 60 EVM chains + plus Solana and Eclipse with a REST surface for Balances, Activity, Transactions, Token Info, Token Holders, DeFi Positions, Collectibles and Webhooks. For a wallet-labels-coverag + e benchmark, the load-bearing distinction is that entity labels historically live in Dune Analytics as SQL tables and community spells rather than as a single REST endpoint on Sim; + the Sim quickstart categorizes its endpoints around balances, activity, and token info, not wallet identity. Coverage of labels is therefore a function of what analysts have commi + tted to the Spellbook plus what Sim exposes through its search and metadata endpoints. Teams pick Dune when the workflow is analyst-driven and labels are consumed via SQL joins in + dashboards. Teams migrate when a product needs a single low-latency REST call that returns "is this address a CEX, MEV bot, bridge, or protocol contract" without writing a SQL quer + y first. diff --git a/alternatives/dydx.yml b/alternatives/dydx.yml index 9a136733..923970ae 100644 --- a/alternatives/dydx.yml +++ b/alternatives/dydx.yml @@ -11,3 +11,13 @@ seo_title: "dYdX alternatives: live benchmark" seo_description: Compare dYdX v4 against Hyperliquid, Lighter, GMX v2 and gains.trade on live taker fee plus spread crossed at $1000 notional, refreshed every five minutes. status: live + +positioning: | + dYdX v4 runs on an application-specific Cosmos SDK chain purpose-built for perpetuals, migrated off the Ethereum-based smart contracts in October 2023 with trading going live in No + vember 2023. The moat sits at the pipeline level: validators maintain an off-chain in-memory orderbook for low-latency matching, then settle fills on-chain via consensus. Order pla + cement and cancellation cost zero gas; fees apply only on execution and accrue to validators and stakers rather than a central operator. This split of matching from settlement is a + rchitecturally distinct from Ethereum L1 perp DEXs (fully on-chain, gas per action) and from single-sequencer L2 venues (matching lives on a proprietary node). For the perp-fees ax + is, the design has two direct consequences. Fees are set by governance parameter rather than by an operator, so tier changes are proposal-gated and observable on-chain. Rebates flo + w to on-chain makers under the same maker-taker schedule that a CEX would run, but with funds routed to the protocol staking layer instead of a company. The typical use case is hig + h-volume market makers who want a maker rebate on a decentralized venue without paying per-order gas; teams migrate away when they need cross-margined equity products the Cosmos ch + ain does not list. diff --git a/alternatives/etherscan.yml b/alternatives/etherscan.yml index a964714d..f3536c0c 100644 --- a/alternatives/etherscan.yml +++ b/alternatives/etherscan.yml @@ -11,3 +11,13 @@ seo_title: "Etherscan alternatives: live benchmark" seo_description: Compare Etherscan against PublicNode feeHistory and Owlracle on gwei gap between predicted and realized priority fee, ranked on p99 over 24h. status: live + +positioning: | + Etherscan's moat is not a data product; it is a decade of running the canonical block explorer for Ethereum and the derivative explorer family (Polygonscan, BscScan, Arbiscan, Opti + mistic Etherscan and others), consolidated under Etherscan API V2 as one endpoint across 50+ chains. Gas Tracker rides on that same explorer infrastructure. The gastracker module e + xposes two actions: gasoracle returns SafeGasPrice, ProposeGasPrice, FastGasPrice, suggestBaseFee, LastBlock and gasUsedRatio; gasestimate returns a confirmation-time estimate for + a supplied gas price. The public Gas Tracker page shows a countdown of "Next update in 10s" and breaks recommendations into Base plus Priority components, consistent with post-EIP- + 1559 fee mechanics. For the gas-estimation benchmark, the load-bearing point is that Etherscan sees the same block stream and pending pool that feeds the block explorer, so its rec + ommendation is a direct function of recent-block base fee trajectory and the priority-tip distribution. Teams pick the Etherscan gas oracle when the integration is Ethereum-first a + nd already uses etherscan.io for verified contracts and receipts. Teams migrate when they need mempool-derived estimates on non-EVM chains or a per-transaction priority-fee curve r + ather than three fixed tiers. diff --git a/alternatives/gmx.yml b/alternatives/gmx.yml index c3187ae0..e282fe3f 100644 --- a/alternatives/gmx.yml +++ b/alternatives/gmx.yml @@ -11,3 +11,12 @@ seo_title: "GMX alternatives: live benchmark" seo_description: Compare GMX v2 against Hyperliquid, dYdX, Lighter and gains.trade on live taker fee plus spread crossed at $1000 notional, refreshed every five minutes. status: live + +positioning: | + GMX V2 uses isolated GM liquidity pools rather than an orderbook. Each market has its own pool that pays out trader profits and absorbs trader losses, priced against a low-latency + oracle rather than crossed against maker quotes. The V2 architecture runs on Arbitrum, Avalanche and MegaETH, with keeper contracts executing opens and closes so that the trader su + bmits an intent and the keeper fills it at the next oracle price. This oracle-driven pool model is architecturally distinct from CLOB perp venues: there is no maker side, so there + is no maker rebate to earn, and the fee schedule replaces the taker-only price with a bundle of position fee, price impact fee, borrow fee, and funding fee. For the perp-fees axis, + this matters because a headline taker-only comparison understates GMX cost on skewed markets, where the borrow fee accrues hourly against the dominant side, and understates saving + s on balanced markets, where the position fee drops when a trade reduces open-interest skew. The typical use case is directional traders on major pairs who prefer no order manageme + nt; teams migrate to CLOB venues once they need maker rebates or tight passive quoting. diff --git a/alternatives/hyperliquid.yml b/alternatives/hyperliquid.yml index 5c8a0f0d..ede59fad 100644 --- a/alternatives/hyperliquid.yml +++ b/alternatives/hyperliquid.yml @@ -11,3 +11,13 @@ seo_title: "Hyperliquid alternatives: live benchmark" seo_description: Compare Hyperliquid against Lighter, dYdX, GMX v2 and gains.trade on live taker fee plus spread crossed at $1000 notional, refreshed every five minutes. status: live + +positioning: | + Hyperliquid runs a purpose-built L1 whose flagship application is the perp exchange itself. HyperCore holds the orderbook, margin, and matching engine as protocol state; HyperBFT, + a HotStuff variant, orders blocks with median end-to-end latency of 0.2 seconds for co-located clients. Every resting order, fill, and liquidation is part of consensus and visible + to every node, with strict price-time priority. This is architecturally different from dYdX v4, where matching happens in a validator in-memory book and only settlement lands on ch + ain, and different from GMX, which has no orderbook at all. For the perp-fees axis, the design has two implications. The fee schedule is a 7-tier maker-taker table keyed to 14-day + weighted volume, with maker rebates paid on every trade rather than settled periodically. HYPE staking layers a separate 5 to 40 percent discount as an overlay on top of the number + ed volume tiers, and staking-linking lets a discount wallet apply to a distinct trading wallet, permanently. The typical use case is active perp traders and market makers who want + CEX-shaped fees without a central operator; teams migrate away when they need EVM composability HyperCore itself does not offer, in which case HyperEVM sits on the same validator s + et. diff --git a/alternatives/jupiter.yml b/alternatives/jupiter.yml index 3288f0e9..9824e0c7 100644 --- a/alternatives/jupiter.yml +++ b/alternatives/jupiter.yml @@ -11,3 +11,13 @@ seo_title: "Jupiter alternatives: live benchmark" seo_description: Compare Jupiter against Mobula, OpenOcean and Raydium on live Solana quote latency. 100 USDC to long-tail token, polled every 60 seconds from 3 regions. status: live + +positioning: | + Jupiter is Solana-native and structured around two paired products. Metis is the routing primitive; Ultra is the managed execution engine on top. The Metis Update post describes sl + ippage-aware routing, dynamic intermediate tokens and just-in-time routing as the mechanics behind route selection, and the Ultra vs Metis comparison frames Metis as a routing libr + ary callers wrap with their own RPC and transaction pipeline, while Ultra bundles transaction construction, priority-fee optimization, confirmation polling and error handling behin + d a single /order endpoint that averages 400 to 600 milliseconds end-to-end with p95 under one second. For a Solana DEX quote-latency benchmark, the load-bearing detail is that Jup + iter runs the routing infrastructure that the majority of Solana DeFi frontends and aggregators call into, so the quote surface is a first-party product rather than a resold feed. + The Quote API accepts inputMint, outputMint, amount and slippageBps, and returns a route that can traverse multiple Solana AMMs including Raydium, Orca, Meteora and Phoenix. Teams + pick Jupiter when the target chain is Solana and a single call needs to price a swap across the full venue set. Teams migrate when the workflow leaves Solana or requires custom mar + ket-making beyond aggregated liquidity. diff --git a/alternatives/kalshi.yml b/alternatives/kalshi.yml index ec8401bb..179ca95a 100644 --- a/alternatives/kalshi.yml +++ b/alternatives/kalshi.yml @@ -11,3 +11,14 @@ seo_title: "Kalshi alternatives: live benchmark" seo_description: "Kalshi vs Polymarket, Limitless, Manifold, Myriad on warm book latency and ramp throttle (us-east, eu-west, sgp)." status: live + +positioning: | + Kalshi is a US event-contracts exchange with two API surfaces (Predictions and Perps) exposed over REST, WebSocket and FIX, authenticated by API key and governed by a documented to + ken-budget rate-limit model with 7 named tiers (Basic, Advanced, Expert, Premier, Paragon, Prime, Prestige) that scale from a few hundred to several thousand tokens per second, wit + h a burst allowance after idle time. Its architectural moat is regulatory: the venue operates under CFTC-recognised status for event contracts, which shapes the API surface (identi + fied market participants, published limits, FIX access for institutional flow) and the data redistribution terms that any downstream consumer inherits. For a pm-rate-limits benchma + rk, that means Kalshi is the cohort's baseline for what a documented contract looks like: token budgets are formally tiered, order and book endpoints go to origin on every call, an + d the market-list endpoint is served from a CDN with a short max-age, so list latency measures the edge. WebSocket access requires authentication, which is standard for a regulated + venue and keeps the public unauthenticated WebSocket surface small. Trading systems that require CFTC-supervised counterparty, FIX connectivity or auditable API terms integrate on + Kalshi natively. Teams migrate to a Polygon-based CLOB when the product requires permissionless market creation, non-US retail flow, or on-chain settlement receipts that the regul + ated venue's off-chain matching does not surface. diff --git a/alternatives/lifi.yml b/alternatives/lifi.yml index 6e1eff7a..c10c767f 100644 --- a/alternatives/lifi.yml +++ b/alternatives/lifi.yml @@ -9,3 +9,13 @@ intro: | seo_title: "Li.Fi alternatives: quote latency benchmark" seo_description: Compare Li.Fi alternatives on cross-chain quote latency. Debridge, Mobula and Relay measured on identical routes, refreshed every five minutes. + +positioning: | + Li.Fi is a cross-chain routing and orchestration layer that normalises calls across many bridges, DEX aggregators and intent networks behind a unified /quote and /routes API surfac + e, plus a TypeScript SDK and a drop-in widget. Its architectural moat is coverage: routes are searched across a wide set of underlying providers (Across, Hop, Mayan, Allbridge, Sym + biosis, Squid, Synapse, CCTP, Wormhole among others), token standards and chain differences are normalised, and the response returns a full execution plan with calldata, approval s + teps and gas estimates ready for a wallet to sign. That breadth matters when a builder needs any-token-to-any-token support without hand-integrating each bridge, and it does not ma + tter, or actively hurts, when the metric is bridge-quote-latency on a single well-known corridor. The /quote call fans out across underlying providers and picks a winner, so its wa + ll-clock reflects the slowest leg searched, not the fastest route returned. Builders integrating a wallet swap modal, a portfolio rebalance tool or a treasury flow across long-tail + chains stay on Li.Fi for the coverage. Teams that route a fixed hot corridor and need sub-second p95 typically front the Li.Fi SDK with a faster narrower router for the paths they + actually use. diff --git a/alternatives/polymarket.yml b/alternatives/polymarket.yml index f9891835..2c9591f4 100644 --- a/alternatives/polymarket.yml +++ b/alternatives/polymarket.yml @@ -11,3 +11,13 @@ seo_title: "Polymarket alternatives: live benchmark" seo_description: Compare Polymarket against Kalshi, Limitless, Manifold and Myriad on warm book latency and daily ramp throttle behaviour, from us-east, eu-west and sgp. status: live + +positioning: | + Polymarket runs a hybrid Central Limit Order Book where orders match off-chain and settle atomically on Polygon through an audited exchange contract, with orders expressed as EIP-7 + 12 signed messages rather than exchange IOUs. Authentication is layered: L1 EIP-712 signatures derive API credentials from the user's private key, then L2 HMAC-SHA256 headers (POLY + _ADDRESS, POLY_SIGNATURE, POLY_TIMESTAMP, POLY_API_KEY, POLY_PASSPHRASE) sign subsequent trading requests. Its architectural moat is non-custodial CLOB throughput on Polygon: the o + perator cannot execute unauthorised trades or set prices, and the matching engine sits primarily in eu-west-2 with co-location available after KYC. For a pm-rate-limits benchmark, + the surface to size against is the CLOB /book and /midpoint endpoints plus the Gamma markets endpoint, all fronted by Cloudflare. The Cloudflare layer queues bursts instead of retu + rning 429, so a polling loop sees rising latency before it sees an error, which changes what a throttle test can actually measure. Prediction-market builders, permissionless market + creators and retail flow outside US jurisdiction integrate on Polymarket natively. Teams migrate to a regulated venue when they need CFTC-supervised counterparty, FIX connectivity + or venue-side compliance guarantees the on-chain settlement model does not provide. diff --git a/alternatives/publicnode.yml b/alternatives/publicnode.yml index 687f0a32..265a6129 100644 --- a/alternatives/publicnode.yml +++ b/alternatives/publicnode.yml @@ -11,3 +11,13 @@ seo_title: "PublicNode alternatives: live benchmark" seo_description: "PublicNode alternatives on eth_blockNumber p50 latency. dRPC, 1RPC, Tenderly, Nodies, Lava ranked live." status: live + +positioning: | + PublicNode is a free, registration-less RPC endpoint service operated by Allnodes, a node-hosting company. Every endpoint is served from Allnodes' own validator and full-node fleet + across 78 blockchains, with global load balancing across regional clusters and no per-user API key. MEV-protected endpoints are enabled by default on Ethereum, Base, BSC, Solana, + and Arbitrum, routing transactions through private order-flow relays rather than the public mempool. This is architecturally distinct from routing layers such as dRPC, which multip + lex third-party providers, and distinct from developer-tool RPCs such as Tenderly, which layer proprietary simulation methods on top. PublicNode owns the underlying hardware but ex + poses only standard JSON-RPC, WebSocket, and where applicable gRPC and REST surfaces. For the rpc-capabilities axis, this means method coverage is whatever the upstream client (Get + h, Erigon, Solana Agave, and so on) natively exposes, without extended tenderly_ or debug_ namespaces added on top. Uptime and latency depend on a single operator rather than a rou + ting pool. The typical use case is dApps that need a fallback or public endpoint without a signup flow; teams migrate to paid providers when they need archive depth, guaranteed cap + acity, or historical trace methods PublicNode does not expose. diff --git a/alternatives/pump-portal.yml b/alternatives/pump-portal.yml index f907d4db..95575ff7 100644 --- a/alternatives/pump-portal.yml +++ b/alternatives/pump-portal.yml @@ -9,3 +9,12 @@ intro: | seo_title: "PumpPortal alternatives: live benchmark" seo_description: "Pump Portal alternatives: Codex, GeckoTerminal, Mobula on real-time block ingestion + Solana mint feed." + +positioning: | + PumpPortal is a Solana-only, Pump.fun-native WebSocket relay. The Data API exposes a single connection at wss://pumpportal.fun/api/data with four subscription methods: subscribeNew + Token, subscribeMigration, subscribeTokenTrade and subscribeAccountTrade. There is no REST fallback and no polling surface; the entire product is push. The moat is protocol special + ization rather than infrastructure: PumpPortal decodes the Pump.fun bonding curve and PumpSwap AMM directly, so a launch, a trade or a migration to Raydium appears as a typed event + through subscribeMigration without any client-side program-log parsing. For head lag on Solana specifically, this matters because the alternative is running a Yellowstone gRPC sub + scription plus a bonding-curve decoder in-house. Coverage does not extend beyond Solana Pump.fun and PumpSwap, so this product does not compete on breadth, only on latency to a spe + cific event surface. It also ships a Lightning Transaction API for signed swap execution, which is a separate REST product. Teams win with PumpPortal when the use case is a Solana + memecoin sniper or a Pump.fun-specific dashboard; teams migrate away the moment coverage needs to include Raydium CLMM, Meteora, Orca or any non-Solana venue. diff --git a/alternatives/pyth.yml b/alternatives/pyth.yml index 00fce2ef..3b41c46e 100644 --- a/alternatives/pyth.yml +++ b/alternatives/pyth.yml @@ -11,3 +11,13 @@ seo_title: "Pyth alternatives: live benchmark" seo_description: "Pyth vs Chainlink, Binance and Coinbase on live oracle deviation. Max pairwise gap on BTC, ETH, SOL." status: live + +positioning: | + Pyth Network is a pull oracle in which first-party publishers (exchanges, banks, trading firms and market makers) submit prices directly to an oracle program running on Pythnet, wh + ich aggregates them per slot into a single price and confidence interval, and then distributes signed updates cross-chain for permissionless refresh on the consuming chain. Its arc + hitectural moat is the first-party publisher set plus the sub-second aggregation cadence: the off-chain quote refreshes every 400 milliseconds, and the on-chain price advances when + ever an integrator submits an update, rather than when a gas-paid node operator crosses a deviation trigger. For an oracle-deviation benchmark against canonical CEX prints, that mo + del concentrates the disagreement in Pyth's favour on fast-moving assets, because the off-chain feed tracks continuous publisher submissions rather than a threshold-gated on-chain + round. The API surface for off-chain consumption is Hermes, exposing REST and SSE endpoints for the latest price feeds and update payloads. Perp DEXes, options venues and predictio + n market settlement flows integrate Pyth on the volatile-asset path where a Chainlink-style trigger would introduce settlement lag. Teams stay on push oracles for read patterns tha + t cannot afford the pull-refresh step or that need a fully synchronous read inside the settlement transaction without external submission. diff --git a/alternatives/quicknode.yml b/alternatives/quicknode.yml index a008bf40..6928f8fa 100644 --- a/alternatives/quicknode.yml +++ b/alternatives/quicknode.yml @@ -9,3 +9,12 @@ intro: | seo_title: "QuickNode alternatives: live benchmark" seo_description: Compare QuickNode alternatives on real-time data freshness. Live head-lag against a canonical archive node, refreshed every minute and published openly. + +positioning: | + QuickNode's moat is a Marketplace of specialized add-ons layered on a shared node fleet. For the head-lag axis the two products that matter are Streams and the Solana gRPC add-on ( + Yellowstone-compatible Geyser). Streams is a push pipeline: blocks, receipts and traces flow through a JavaScript or Go filter function and land in a webhook or storage destination + , with exactly-once delivery in finality order. Streams supports 81 chains including Ethereum, Solana, Bitcoin, Arbitrum, Base, Polygon and Avalanche. The Solana gRPC add-on expose + s a Geyser plugin stream directly, which is the same interface Solana validators emit internally; that path bypasses the JSON-RPC serialization tax and is why Yellowstone consumers + land trades faster than REST or WS pollers on Solana. Swap-specific decoding is delegated to Marketplace add-ons (0x Swap API, Metis Jupiter, Pump Fun API, Aerodrome, Velodrome), + each billed separately. QuickNode wins for teams that want raw block-level control plus a Solana Geyser feed on one bill; teams that want a single decoded cross-chain DEX event str + eam have to compose several Marketplace SKUs, which raises integration and latency budget. diff --git a/alternatives/relay.yml b/alternatives/relay.yml index 0eb9985d..9b7cdad6 100644 --- a/alternatives/relay.yml +++ b/alternatives/relay.yml @@ -9,3 +9,12 @@ intro: | seo_title: "Relay alternatives: live benchmark" seo_description: Compare Relay alternatives on cross-chain bridge quote latency. Debridge, Li.Fi and Mobula measured on identical routes, refreshed every five minutes. + +positioning: | + Relay is a cross-chain payments network built on the Relay Protocol, an open system that connects users to a network of relayers who execute on-chain actions on their behalf, combi + ned with DEX meta-aggregation across a documented set of 85 or more chains. Its architectural moat is the intent-plus-relayer model: the quote API is a thin price-discovery call ag + ainst a pool of relayers who already hold inventory on the destination, so the request does not run a route-search graph across N underlying bridges. That thin surface is exactly w + hat a bridge-quote-latency benchmark rewards, the API returns a single canonical price against pre-positioned liquidity rather than fanning out to third-party providers. The trade- + off lives on the fill side, where a relayer must accept the intent, and on corridor coverage, which is bounded by which relayers hold inventory where. Relay is integrated as a swap + layer for wallets, cross-chain gas-abstraction flows and app-chain onboarding where quote responsiveness dictates perceived UX. Teams migrate off when their corridor sits outside + the relayer inventory footprint, or when they need multi-hop routing through long-tail bridges that a meta-aggregator with wider integration coverage handles natively. diff --git a/alternatives/tenderly.yml b/alternatives/tenderly.yml index 7c448ac7..0b427c58 100644 --- a/alternatives/tenderly.yml +++ b/alternatives/tenderly.yml @@ -11,3 +11,13 @@ seo_title: "Tenderly alternatives: live benchmark" seo_description: "Tenderly Gateway alternatives on eth_blockNumber p50 latency. PublicNode, dRPC, 1RPC, Nodies ranked live." status: live + +positioning: | + Tenderly built its RPC as an extension of a developer-tooling stack rather than as a standalone node fleet. The Node RPC product runs multi-region across EU and US, covers 80+ EVM + networks, and exposes standard JSON-RPC and WebSocket. The architectural moat sits above the base RPC surface: the tenderly_ namespace ships simulateTransaction, simulateBundle, es + timateGas, estimateGasBundle, traceTransaction, and decoding methods for inputs, errors, events, and ABI signatures. Node Extensions let a team write JavaScript or TypeScript that + runs server-side under a custom JSON-RPC method name, so a project can compose a bespoke method (for example, simulate a mempool transaction) without deploying its own infrastructu + re. This is architecturally distinct from routing layers like dRPC and node fleets like PublicNode, neither of which owns a simulation engine or a scripting sidecar. For the rpc-ca + pabilities axis, that translates into a coverage set biased toward pre-signing and post-mortem workflows: dry-running a transaction against forked state, tracing a decoded call gra + ph, or profiling gas at bundle granularity. The typical use case is dApps whose UX depends on preview-before-sign or on gas quoting; teams migrate away when they need non-EVM chain + s or read-heavy public traffic that Node Extensions cannot cheaply serve. diff --git a/alternatives/the-graph.yml b/alternatives/the-graph.yml index e36e686b..ded7bc90 100644 --- a/alternatives/the-graph.yml +++ b/alternatives/the-graph.yml @@ -9,3 +9,12 @@ intro: | seo_title: "The Graph alternatives: live benchmark" seo_description: Compare The Graph alternatives on the number of blockchains each major onchain data provider officially supports. Live data, refreshed every six hours. + +positioning: | + The Graph's moat is a decentralized indexing network. Independent Indexers stake GRT and serve GraphQL queries against Subgraphs, which are custom indexing manifests deployed per c + ontract or per protocol. Substreams sits underneath as a parallelized firehose for real-time and historical block data. The subgraph surface officially supports 60+ chains includin + g Ethereum, Solana, Arbitrum, Optimism, Polygon and Base. The Token API, built by Pinax on top of Substreams, is a REST product covering 25 chains at last count (Arbitrum, Avalanch + e, Base, Bitcoin, Blast, BNB, Celo, Ethereum, Gnosis, HyperEVM, Linea, Litecoin, MegaETH, Optimism, Polygon, Robinhood Chain, Solana, Soneium, Unichain, Vaulta, WAX, X Layer, zkSyn + c, Zora among others). For a chain-coverage benchmark on market data, The Graph is broad through the subgraph route but the coverage is contingent: each chain and each protocol nee + ds a maintained subgraph that returns the requested market fields. Teams win when they own their data model; teams that want turnkey market data on every long-tail chain often reac + h for an aggregator instead. diff --git a/alternatives/zerion.yml b/alternatives/zerion.yml index 8db68e39..a74fdbcf 100644 --- a/alternatives/zerion.yml +++ b/alternatives/zerion.yml @@ -9,3 +9,13 @@ intro: | seo_title: "Zerion alternatives: live benchmark" seo_description: "Zerion alternatives on portfolio API chain coverage. Probe-verified chains vs self-declared catalogs, refreshed daily." + +positioning: | + Zerion started as a self-custody wallet frontend and the API is a direct product of that lineage. The developer docs organize the surface around Wallets (portfolio, positions, tran + sactions, charts, PnL), Fungibles, NFTs, Chains, Gas, DApps, Subscriptions and Swaps/Bridges, exposing roughly 35 logical endpoints (more when subscription CRUD variants are counte + d separately). Chain coverage is described in the docs as EVM plus Solana plus Bitcoin, with a /v1/chains/ endpoint that returns per-chain capability flags for trading, sending and + bridging. For a portfolio-chain-coverage benchmark, the load-bearing detail is that Zerion's product is wallet-shaped from the top down; positions are decoded across lending, stak + ing and liquidity protocols and returned as normalized types, which means a chain is not supported in a meaningful sense until the DeFi decoding layer works there. That constraint + tends to keep the coverage list to chains where protocol integrations are maintained. Teams pick Zerion when building a portfolio dashboard, wallet frontend or tax tool where posit + ions must render human-readable across EVM, Solana and Bitcoin from a single call. Teams migrate when the requirement shifts to a long tail of niche EVM L2s that Zerion has not yet + added protocol decoders for, or to raw-trace analytics workflows. diff --git a/answers/is-usdc-safer-than-usdt.yml b/answers/is-usdc-safer-than-usdt.yml index 37becd08..7276ebe7 100644 --- a/answers/is-usdc-safer-than-usdt.yml +++ b/answers/is-usdc-safer-than-usdt.yml @@ -36,3 +36,13 @@ related: seo_title: "Is USDC safer than USDT? Live peg data, not opinions" seo_description: "USDC vs USDT measured live: {{p99:usdc}} vs {{p99:usdt}} worst case peg deviation (p99, 24h) on USD quoted venues. What peg data can and cannot tell you about stablecoin safety." status: live + +expert_take: | + Peg tightness and reserve safety are two different questions, and readers regularly conflate them. Peg tightness is a live market signal produced by redemption arbitrage: when the + market price drifts from the target, primary redeemers can close the gap in size, and the width of that gap over time measures how well that arbitrage loop is working. Reserve safe + ty lives one layer down, in the banking partners holding the collateral, the attestation cadence and the legal jurisdiction. The two usually correlate; they can decouple within hou + rs during a bank failure or a regulatory action, at which point the price feed reacts only after the news has already moved. Readers should read the p99 deviation and the time outs + ide a 50 bps band as evidence of how the arbitrage loop performs under normal stress, and treat it as necessary but not sufficient for a treasury decision. A stablecoin that has he + ld tight for a long stretch can still break; one that has broken once can still be the right pick if its reserve posture has changed since. Both dimensions belong in the answer. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/what-is-the-fastest-free-ethereum-rpc.yml b/answers/what-is-the-fastest-free-ethereum-rpc.yml index 600abfa9..2875130f 100644 --- a/answers/what-is-the-fastest-free-ethereum-rpc.yml +++ b/answers/what-is-the-fastest-free-ethereum-rpc.yml @@ -36,3 +36,13 @@ related: seo_title: "What is the fastest free Ethereum RPC? Live no-key latency ranking" seo_description: "{{best_name}} leads free Ethereum RPC at {{best_p50}} (eth_getBlockByNumber p50, 24h). 8 no-key endpoints probed every 60s from 3 regions, with success rates and archive depth audits." status: live + +expert_take: | + Free public RPCs share three architectural constraints that decide the ranking. First, they run on anycast edges, so the leader from one region regularly loses to another gateway f + rom a different region; a global average hides that. Second, they answer from a shared node pool with no per-caller quota, which means a fast p50 during quiet hours can collapse un + der a memecoin mint. Third, and least discussed, a public gateway can return an HTTP 200 whose body is a JSON-RPC error, and any latency table that does not classify response bodie + s will silently reward a gateway that fails fast. Readers should look past the headline p50 at three things: the success rate column on the row they are picking, the p99 relative t + o the p50 (a small gap means the tail is real, a large gap means the provider is bursty), and whether archive depth is audited at all. A gateway that only serves the last day of st + ate answers a different question than a free archive endpoint. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-bridge-has-the-cheapest-usdc-fee.yml b/answers/which-bridge-has-the-cheapest-usdc-fee.yml index 89523132..1e3c9556 100644 --- a/answers/which-bridge-has-the-cheapest-usdc-fee.yml +++ b/answers/which-bridge-has-the-cheapest-usdc-fee.yml @@ -40,3 +40,14 @@ related: seo_title: "Which cross-chain bridge has the cheapest USDC fee in 2026?" seo_description: "OpenChainBench ranks six USDC routers (Mobula, Relay, LI.FI, deBridge, Across, Near Intents) on all in cost at $300 notional across Solana, Base and Arbitrum. Live 24h p50 leader on the bench page." status: live + +expert_take: | + Bridging cost lives inside three architectures with different fee shapes. Direct protocols charge a native protocol fee that is largely fixed, so it dominates the percentage at sma + ll ticket sizes and dilutes as notional grows. Aggregators route through underlying bridges they have integrated and add a routing markup on top; their best route is constrained to + their integration set. Intent layers dispatch the transfer to a solver willing to settle it, so the price the user sees is a competitive spread rather than a sum of fees, and the + leader on a corridor is whichever solver has the deepest inventory on that route at that moment. This is why a ranking swaps meaningfully by notional and by corridor; the same tabl + e looks different at $300, at $10000, and on a corridor where solver inventory is thin. Readers should read the all-in cost (fee plus slippage plus destination gas) rather than the + rack rate any provider advertises, since aggregators and intent layers price implicitly through the spread and a rack-rate comparison rewards the wrong architecture. A common misc + onception is that aggregators are always cheaper; they are cheaper only on corridors their integration set dominates. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-crypto-data-api-covers-the-most-blockchains.yml b/answers/which-crypto-data-api-covers-the-most-blockchains.yml index 051b9791..bd18e453 100644 --- a/answers/which-crypto-data-api-covers-the-most-blockchains.yml +++ b/answers/which-crypto-data-api-covers-the-most-blockchains.yml @@ -42,3 +42,13 @@ related: seo_title: "Which crypto data API covers the most blockchains in 2026?" seo_description: "{{best_name}} leads at {{best_p50}} unique mainnets across GeckoTerminal, Codex, Mobula, CoinPaprika, CoinStats and Dune, audited every 6 hours by OpenChainBench." status: live + +expert_take: | + Chain coverage is a single-axis metric on a two-axis problem. Breadth (how many chain ids the provider lists) and depth (how completely each chain is indexed: pools, swaps, holders + , metadata, historical range) are independently priced inside every data platform, and one can grow at the expense of the other. A provider that ships adapters for every new EVM ch + ain within a week can look excellent on a network count while carrying only price ticks on the long tail; a provider with a shorter list may cover every listed chain end to end. Re + aders should also treat testnet inclusion with suspicion, since testnet registries are inconsistent across vendors and inflate the count without adding integratable surface. The ri + ght way to use this ranking is as one input alongside head lag on the chains that matter to the integration, and metadata completeness for the tokens the product will surface. A co + mmon misread is treating a coverage leader as automatically the right pick; the shortlist should be built from the intersection of breadth, depth and freshness. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-crypto-oracle-is-the-most-accurate.yml b/answers/which-crypto-oracle-is-the-most-accurate.yml index e21d3560..8575dcf5 100644 --- a/answers/which-crypto-oracle-is-the-most-accurate.yml +++ b/answers/which-crypto-oracle-is-the-most-accurate.yml @@ -40,3 +40,14 @@ related: seo_title: "Which crypto price oracle is the most accurate in 2026?" seo_description: "OpenChainBench measures cross oracle agreement (p99 max pairwise deviation in bps, 24h) between Chainlink, Pyth, Binance and Coinbase on 10 USD pairs. Live per pair numbers on the bench page." status: live + +expert_take: | + Oracle accuracy is a per-pair question dressed as a per-provider question. Continuous CEX tickers publish every tick and track the market by construction. Push oracles anchored on + chain post a new round only when the off-chain aggregate has drifted past a deviation trigger or a heartbeat has elapsed, which is not a bug but a gas cost tradeoff; between update + s the on-chain value legitimately lags. Pull oracles ship a signed off-chain quote that the caller inserts into their own transaction, moving the freshness question from the publis + her to the integrator. Cross-oracle deviation is therefore not a scalar; it is a distribution across pairs, and the same oracle can lead on BTC where the trigger fires often and tr + ail on a volatile alt where the same trigger produces multi-minute lag. Readers integrating an oracle into a settlement contract should read the per-pair p99 gap on the exact asset + they liquidate, then read the round-age gauge next to it to attribute a large gap to the correct cause (lagging push oracle vs a CEX outlier). Ranking oracles as a single leaderbo + ard obscures the mechanic that matters. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-crypto-price-api-is-the-fastest.yml b/answers/which-crypto-price-api-is-the-fastest.yml index ff7b4e03..fb5fef8c 100644 --- a/answers/which-crypto-price-api-is-the-fastest.yml +++ b/answers/which-crypto-price-api-is-the-fastest.yml @@ -40,3 +40,13 @@ related: seo_title: "Which crypto price API has the lowest head lag in 2026?" seo_description: "{{best_name}} leads crypto data head lag at {{best_p50}} (p50, 24h) across Mobula, Codex and GeckoTerminal on Base, BNB Chain and Solana. Measured live by OpenChainBench from 3 regions." status: live + +expert_take: | + Head lag is set by two floors and one pipeline. The first floor is the chain's own block cadence, which is a protocol constant; no data provider can publish a swap before the block + it lives in is produced. The second floor is the transport contract with the provider. A persistent WebSocket pushes events as soon as the indexer has them, a GraphQL subscription + behaves similarly, and a REST poll adds the client's poll interval to every read by construction. Comparing a REST feed to a WebSocket feed on raw p50 is comparing two architectur + es, not two pipelines. On top of those floors sits the provider's own indexer speed, which is what the benchmark actually isolates when the transport and the chain are held constan + t. Readers building a trading UI should weigh p99 as heavily as p50; a feed that is quick most of the time but stalls for 20 seconds once a minute makes a chart stutter in a way p5 + 0 alone does not surface. Success ratio catches feeds that disappear during volatility. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-gas-oracle-is-the-most-accurate.yml b/answers/which-gas-oracle-is-the-most-accurate.yml index 3de2013e..862eeb7a 100644 --- a/answers/which-gas-oracle-is-the-most-accurate.yml +++ b/answers/which-gas-oracle-is-the-most-accurate.yml @@ -42,3 +42,14 @@ related: seo_title: "Which Ethereum gas oracle is the most accurate in 2026?" seo_description: "{{best_name}} leads at {{best_p50}} gwei (p99 absolute gap, 24h) across PublicNode feeHistory, Owlracle and Etherscan on Ethereum and Polygon, measured per block by OpenChainBench." status: live + +expert_take: | + Gas oracles split into two archetypes that answer different questions, and the leaderboard should be read with that split in mind. Percentile trackers read the protocol-native rewa + rd histogram and publish a number that hugs the realized priority fee by construction; the gap to reality is small most of the time, and their failure mode is under-predicting duri + ng a spike. Inclusion-confidence oracles were designed for a pre-EIP-1559 world and over-predict on purpose to guarantee the transaction lands; their gap to the realized number is + wider, but users almost never miss a block. Comparing them on absolute error alone punishes the oracle that is doing its job correctly. Readers should read the p99 gap and the cove + red rate together: a low p99 with a low covered rate is a percentile tracker that will occasionally underbid, and a wider p99 with a high covered rate is an inclusion-confidence or + acle that trades a small overpay for reliability. The right choice depends on the product; a wallet default that must never miss a block prefers coverage, a batching relayer that c + an re-broadcast prefers a tight percentile fit. A single-number leaderboard hides this. + +expert_take_reviewed: "2026-07-16" diff --git a/answers/which-perp-dex-has-the-lowest-fees.yml b/answers/which-perp-dex-has-the-lowest-fees.yml index 695b00b9..59ba1768 100644 --- a/answers/which-perp-dex-has-the-lowest-fees.yml +++ b/answers/which-perp-dex-has-the-lowest-fees.yml @@ -42,3 +42,13 @@ related: seo_title: "Which perpetual DEX has the lowest all in fees on ETH in 2026?" seo_description: "OpenChainBench ranks eight perp DEXes (Lighter, Hyperliquid, dYdX, GMX, gains.trade, Paradex, Extended, Polymarket) on the same $1000 ETH 10x long all in cost. Live 24h leader on the bench page." status: live + +expert_take: | + Rack rate taker fee is the number every comparison table quotes, and it is also the number a trader stops being able to read the moment a market order goes in. The other half of th + e bill lives in the orderbook: the half spread and the price impact walked at the trader's notional. On a deep book, that component is small and the all-in cost tracks the rack rat + e closely. On a thin book, the same rack rate can clock several multiples worse once notional is walked through visible liquidity. A zero-fee taker on a shallow market is not free; + the cost has moved from the fee schedule into the spread. Readers should also separate open cost from funding, since funding is a signed rate paid every hour the position is held + and belongs on a different mental line item than the fee paid at open. A common misread is comparing one venue's rack rate to another venue's all-in cost; the fair comparison walks + the same notional through both books and sums fees, spread and impact into one basis-point number. + +expert_take_reviewed: "2026-07-16" diff --git a/next.config.ts b/next.config.ts index dc7af8c4..dfe2988a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -91,6 +91,13 @@ const nextConfig: NextConfig = { "./answers/**/*.yaml", "./alternatives/**/*.yml", "./alternatives/**/*.yaml", + // Prebuild-generated manifest of editorial hub-page git mtimes. + // Read at module scope by src/app/sitemap.ts so reflects + // real edit history instead of Vercel's build-container mtime + // reset (which pins every file to Oct 20 2018). Not committed + // (regenerated every build) so it must be traced in explicitly to + // ship in the serverless bundle. + "./data/page-mtimes.json", ], }, async headers() { diff --git a/package.json b/package.json index e0f7bb70..776f5474 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "description": "Open benchmarks for crypto infrastructure — aggregators, bridges, price feeds.", "scripts": { "dev": "next dev", + "prebuild": "tsx scripts/emit-page-mtimes.ts", "build": "next build", "start": "next start", "lint": "eslint", diff --git a/scripts/editorial-check.sh b/scripts/editorial-check.sh new file mode 100755 index 00000000..78449188 --- /dev/null +++ b/scripts/editorial-check.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Editorial guard for hand-written positioning + expert_take fields. +# Existing seo_title / question / FAQ / intro fields are outside the +# scope of this script; their editorial policy is enforced elsewhere. +# +# Extracts positioning: | and expert_take: | blocks from +# alternatives/*.yml and answers/*.yml, then greps for hard-rule +# violations (em/en dashes, marketing adjectives, first/second person +# pronouns, year mentions, vendor pricing leaks). +# +# Exit 0 clean, exit 1 with per-line diagnostics. +set -u +cd "$(dirname "$0")/.." + +# Extract the positioning and expert_take literal blocks into a +# temp file, tagged with the source file + starting line so grep +# output stays actionable. +TMP="$(mktemp)" +trap 'rm -f "$TMP"' EXIT + +python3 - <<'PY' > "$TMP" +import os, re +for d in ("alternatives", "answers"): + if not os.path.isdir(d): + continue + for name in sorted(os.listdir(d)): + if not name.endswith(".yml"): + continue + path = os.path.join(d, name) + lines = open(path).read().splitlines() + i, n = 0, len(lines) + while i < n: + m = re.match(r"^(positioning|expert_take):\s*\|\s*$", lines[i]) + if not m: + i += 1 + continue + field = m.group(1) + start = i + 1 + i += 1 + while i < n and (lines[i].startswith(" ") or lines[i].strip() == ""): + i += 1 + for k, ln in enumerate(lines[start:i]): + print(f"{path}:{field}:{start + k + 1}:{ln}") +PY + +FAIL=0 +report() { echo "$@"; FAIL=1; } + +# Hard rules +# Pronouns are matched case-sensitively so "US" (United States) and +# "US-based" don't false-flag as "us". Marketing list intentionally +# excludes "premier" and "top" because they collide with proper nouns +# (Kalshi Premier tier, top of book, top-of-book, top-of-stack). +while IFS= read -r hit; do report "[DASH] $hit"; done < <(python3 -c " +import sys +for ln in open(sys.argv[1]): + if '—' in ln or '–' in ln: + sys.stdout.write(ln) +" "$TMP") +while IFS= read -r hit; do report "[MARKETING] $hit"; done < <(grep -iE '\b(leading|blazing|cutting[- ]edge|industry[- ]leading|revolutionary|world[- ]class|unmatched|unparalleled|next[- ]gen)\b' "$TMP" || true) +while IFS= read -r hit; do report "[PRONOUN] $hit"; done < <(grep -E '\b(we|our|ours|you|your|yours)\b' "$TMP" || true) +while IFS= read -r hit; do report "[PRONOUN-US] $hit"; done < <(grep -E '\bus\b' "$TMP" || true) +while IFS= read -r hit; do report "[YEAR] $hit"; done < <(grep -E '\b(2024|2025|2026|2027)\b' "$TMP" || true) + +if [ "$FAIL" -eq 0 ]; then + echo "editorial: clean" + exit 0 +else + echo + echo "editorial: violations above. rules are in scripts/editorial-check.sh." + exit 1 +fi diff --git a/scripts/emit-page-mtimes.ts b/scripts/emit-page-mtimes.ts new file mode 100644 index 00000000..f6ee366b --- /dev/null +++ b/scripts/emit-page-mtimes.ts @@ -0,0 +1,111 @@ +#!/usr/bin/env tsx +/** + * Emit `data/page-mtimes.json`, a static map of + * { "": } + * for the editorial hub pages whose freshness feeds the sitemap. + * + * Vercel's build container runs a shallow `git clone` and, worse, + * resets every checked-out file's mtime to the build-system default + * (Oct 20 2018). Both make a runtime `statSync` useless for a real + * , and a runtime `git log` returns nothing. + * + * The fix: run this script in `prebuild` (executed BEFORE `next build`, + * on the deploy machine where `.git/` is complete), resolve each page's + * last commit timestamp via `git log -1 --format=%ct`, and write the + * result to a JSON manifest. `src/app/sitemap.ts` reads the manifest at + * module scope and returns the timestamp per URL. + * + * Fallback chain (per page): + * 1. `git log -1 --format=%ct -- ` + * 2. `statSync().mtime` (Unix seconds) + * 3. process.env.NEXT_PUBLIC_BUILD_TIME (Unix seconds) + * + * Regenerated on every build. Do NOT commit `data/page-mtimes.json`. + */ +import { execSync } from "node:child_process"; +import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const ROOT = process.cwd(); +const APP_DIR = path.join(ROOT, "src/app"); +const OUT_DIR = path.join(ROOT, "data"); +const OUT_FILE = path.join(OUT_DIR, "page-mtimes.json"); + +// Pages the sitemap consults via pageMtime(). Keep this list in sync +// with staticHubRoutes in src/app/sitemap.ts. Ordered by ship date so +// diffs read cleanly when a new hub is added. +const PAGES: string[] = [ + "mcp/page.tsx", + "methodology/page.tsx", + "contribute/page.tsx", + "partners/page.tsx", + "about/page.tsx", + "press/page.tsx", + "compare/page.tsx", + "alternatives/page.tsx", + "team/page.tsx", +]; + +function gitMtimeSeconds(rel: string): number | null { + try { + const abs = path.join("src/app", rel); + const out = execSync(`git log -1 --format=%ct -- "${abs}"`, { + cwd: ROOT, + encoding: "utf8", + timeout: 5000, + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + if (!out) return null; + const secs = Number.parseInt(out, 10); + if (!Number.isFinite(secs) || secs <= 0) return null; + return secs; + } catch { + return null; + } +} + +function statMtimeSeconds(rel: string): number | null { + try { + const abs = path.join(APP_DIR, rel); + if (!existsSync(abs)) return null; + const s = statSync(abs); + const secs = Math.floor(s.mtimeMs / 1000); + if (!Number.isFinite(secs) || secs <= 0) return null; + return secs; + } catch { + return null; + } +} + +function buildTimeSeconds(): number { + const raw = process.env.NEXT_PUBLIC_BUILD_TIME; + if (raw) { + const t = Date.parse(raw); + if (Number.isFinite(t)) return Math.floor(t / 1000); + } + return Math.floor(Date.now() / 1000); +} + +function main(): void { + const manifest: Record = {}; + const buildFallback = buildTimeSeconds(); + for (const rel of PAGES) { + const git = gitMtimeSeconds(rel); + if (git !== null) { + manifest[rel] = git; + continue; + } + const stat = statMtimeSeconds(rel); + if (stat !== null) { + manifest[rel] = stat; + continue; + } + manifest[rel] = buildFallback; + } + if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true }); + writeFileSync(OUT_FILE, `${JSON.stringify(manifest, null, 2)}\n`); + const rel = path.relative(ROOT, OUT_FILE); + console.log(`wrote ${Object.keys(manifest).length} entries to ${rel}`); +} + +main(); diff --git a/src/app/answers/[slug]/page.tsx b/src/app/answers/[slug]/page.tsx index ad3bee58..edc4f074 100644 --- a/src/app/answers/[slug]/page.tsx +++ b/src/app/answers/[slug]/page.tsx @@ -11,6 +11,7 @@ import { } from "@/lib/answers-template"; import { citableAsOf, leader } from "@/lib/citation"; import { Breadcrumb } from "@/components/breadcrumb"; +import { Byline } from "@/components/byline"; import { Pill } from "@/components/pill"; import { ProviderLogo } from "@/components/provider-logo"; import { fmtAsOfUtc, fmtValue, fmtUnit, unitSuffix } from "@/lib/format"; @@ -21,6 +22,7 @@ import { buildFaqPageJsonLd, safeJsonLd, } from "@/lib/jsonld"; +import { PERSON_ID } from "@/lib/hub-jsonld"; import { capDescription } from "@/lib/seo-text"; import { getBenchCreatedAt } from "@/lib/seo/bench-dates"; @@ -156,24 +158,64 @@ export default async function AnswerPage({ .map((rs) => allAnswers.find((a) => a.slug === rs)) .filter((a): a is NonNullable => Boolean(a)); - // Article + Dataset reference + Breadcrumb. FAQPage emitted as its own - // standalone script per buildFaqPageJsonLd, which has a markedly higher - // hit rate in Search Console vs an @graph-nested FAQPage. + // Rendered expert commentary from the named maintainer. Only surfaced + // when the referenced bench has a defensible leader (dataPending + // suppresses it because editorial context without a leaderboard is + // meaningless). expert_take is a schema-optional field on the answers + // YAML populated in a follow-up editorial pass. + const expertTake = + !dataPending && ans.expert_take ? render(ans.expert_take) : null; + + // QAPage with acceptedAnswer + optional suggestedAnswer. The upgrade + // from generic Article to QAPage lets Search Console + AI answer + // surfaces treat the URL as a canonical Q&A rather than a generic + // article, which is what the answers cluster is actually shaped like. + // - acceptedAnswer: the auto-generated short answer, Org-authored. + // Data-derived, publishing under institutional attribution. + // - suggestedAnswer: the expert_take paragraph, Person-authored. + // Editorial commentary, attributed to the named maintainer. + // FAQPage still ships as a standalone script (higher Search Console + // hit rate on standalone vs @graph-nested). + const questionNode: Record = { + "@type": "Question", + "@id": `${url}#question`, + name: ans.question, + text: ans.question, + acceptedAnswer: { + "@type": "Answer", + text: capDescription(shortAnswer, 990), + url, + author: { "@id": `${SITE.url}/#org` }, + }, + }; + if (expertTake) { + questionNode.suggestedAnswer = { + "@type": "Answer", + text: capDescription(expertTake, 990), + url, + author: { "@id": PERSON_ID }, + ...(ans.expert_take_reviewed + ? { dateModified: ans.expert_take_reviewed } + : {}), + }; + } const graphLd = { "@context": "https://schema.org", "@graph": [ { - "@type": "Article", - "@id": `${url}#article`, + "@type": "QAPage", + "@id": `${url}#qapage`, + url, + mainEntity: questionNode, headline: ans.question, description: capDescription(shortAnswer, 990), - url, - mainEntityOfPage: url, - articleBody: `${shortAnswer}\n\n${intro}\n\n${methodology}`, datePublished: getBenchCreatedAt(bench.slug).toISOString(), ...(citableAsOf(bench) ? { dateModified: bench.lastRunAt } : {}), + // Org authors the page as a whole (Q + acceptedAnswer). The + // Person's contribution surfaces as suggestedAnswer.author on + // the Question node above so attribution stays per-answer. author: { "@id": `${SITE.url}/#org` }, publisher: { "@id": `${SITE.url}/#org` }, image: `${SITE.url}/api/og/${bench.slug}`, @@ -249,6 +291,18 @@ export default async function AnswerPage({ {intro}

+ {expertTake && ( +
+

+ Editorial context +

+

+ {expertTake} +

+ +
+ )} + {topResults.length > 0 && (

diff --git a/src/app/benchmarks/[slug]/[chain]/page.tsx b/src/app/benchmarks/[slug]/[chain]/page.tsx index 82429fed..cdde26df 100644 --- a/src/app/benchmarks/[slug]/[chain]/page.tsx +++ b/src/app/benchmarks/[slug]/[chain]/page.tsx @@ -16,6 +16,7 @@ import { canonicalChainSlug, matchesChainSlug, } from "@/lib/chain-aliases"; +import { PERSON_ID } from "@/lib/hub-jsonld"; import type { Benchmark, ProviderResult } from "@/types/benchmark"; // Dedicated per-chain landing pages. Only chains that have a hand-written @@ -367,7 +368,11 @@ export default async function BenchmarkChainPage({ ...(citableAsOf(benchmark) ? { dateModified: benchmark.lastRunAt } : {}), - author: { "@id": `${SITE.url}/#org` }, + // Dual author + reviewedBy. Named human first (E-E-A-T), Org + // second (institutional attribution). Same pattern as the + // parent /benchmarks/[slug] page. + author: [{ "@id": PERSON_ID }, { "@id": `${SITE.url}/#org` }], + reviewedBy: { "@id": PERSON_ID }, publisher: { "@id": `${SITE.url}/#org` }, about: { "@id": `${benchmarkUrl}#dataset` }, isPartOf: { "@id": `${benchmarkUrl}#article` }, diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index 702c6b7f..d0196e3d 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -39,6 +39,7 @@ import { } from "@/lib/dataset-jsonld"; import { renderTemplate } from "@/lib/bench-template"; import { canonicalChainSlug } from "@/lib/chain-aliases"; +import { PERSON_ID } from "@/lib/hub-jsonld"; import type { Benchmark } from "@/types/benchmark"; // ISR with a 60 s revalidate window. The page is prerendered by @@ -100,6 +101,15 @@ export async function generateMetadata({ // link-signal consolidation anyway. const b = await getBenchmark(slug); if (!b) return {}; + // Editorially-live benches that are still waiting for their first + // samples render as draft in the UI; letting Google index them means + // the SERP snippet quotes "no live data yet" against a URL that is + // meant to display a leaderboard. noindex/follow keeps the URL + // crawlable (internal link equity + fast re-indexing when data lands) + // without letting the empty state rank. Mirrors the render-time + // `isAwaiting = isDraft && editorialStatus === "live"` gate below. + const metaIsDraft = b.status === "draft"; + const metaIsAwaiting = metaIsDraft && b.editorialStatus === "live"; const metaTitle = b.seoTitle ?? b.title; // Description precedence (most-to-least specific): // 1. `seo_description` from the YAML - hand-crafted snippet with the @@ -137,6 +147,9 @@ export async function generateMetadata({ title: metaTitle, description, alternates: { canonical }, + ...(metaIsAwaiting + ? { robots: { index: false, follow: true } } + : {}), openGraph: { title: metaTitle, description, @@ -388,7 +401,12 @@ export default async function BenchmarkPage({ ...(citableAsOf(benchmark) ? { dateModified: benchmark.lastRunAt } : {}), - author: { "@id": `${SITE.url}/#org` }, + // Dual author: named maintainer first (E-E-A-T signal for AI + // answer surfaces + Search Console) followed by the Org so the + // institutional attribution still holds. reviewedBy anchors the + // same Person as the accountable reviewer of the on-page claims. + author: [{ "@id": PERSON_ID }, { "@id": `${SITE.url}/#org` }], + reviewedBy: { "@id": PERSON_ID }, publisher: { "@id": `${SITE.url}/#org` }, about: { "@id": `${benchmarkUrl}#dataset` }, }, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ac4ae382..14d36da7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,6 +8,7 @@ import { SearchProvider } from "@/components/search/search-provider"; import { buildSearchIndex } from "@/lib/search/buildIndex"; import { SITE } from "@/data/site"; import { safeJsonLd } from "@/lib/jsonld"; +import { PERSON_ID, PERSON_JSONLD } from "@/lib/hub-jsonld"; const inter = Inter({ variable: "--font-inter", @@ -140,6 +141,11 @@ const ORG_JSONLD = { name: "Mobula", url: "https://mobula.io", }, + // Named founder, anchored to the canonical Person node emitted below + // in the same @graph. Lets Search Console + AI answer surfaces + // attribute the project to a real human, which is the missing + // E-E-A-T signal on data-driven editorial pages. + founder: { "@id": PERSON_ID }, sameAs: [ SITE.github, `https://x.com/${SITE.twitter.replace(/^@/, "")}`, @@ -162,6 +168,13 @@ const ORG_JSONLD = { "query-input": "required name=search_term_string", }, }, + // Canonical Person node for the sole named maintainer. Referenced by + // Organization.founder above, by every bench's StatisticalReport + // (contributor) and by bench TechArticles (author + reviewedBy). + // Anchoring here in the site-wide @graph means every per-page node + // that references PERSON_ID resolves against a single authoritative + // definition instead of duplicating the Person shape across pages. + PERSON_JSONLD, ], }; diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 499a86fb..04545af2 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,4 +1,4 @@ -import { statSync } from "node:fs"; +import { readFileSync, statSync } from "node:fs"; import path from "node:path"; import type { MetadataRoute } from "next"; import { getBenchmarks } from "@/data/benchmarks"; @@ -56,7 +56,32 @@ const BUILD_TIME = process.env.NEXT_PUBLIC_BUILD_TIME // crawler sees a current timestamp and keeps the pages in the warm pool. const REAL_REPO_BIRTH = new Date("2024-01-01"); +// Prebuilt manifest of `src/app/` → git-log %ct (Unix seconds) for +// editorial hub pages. Written by scripts/emit-page-mtimes.ts during +// prebuild so the sitemap can emit a real per-page derived +// from git history, instead of Vercel's build-container mtime (which +// resets every file to Oct 20 2018). Missing entries silently fall +// through to the statSync path below, then to BUILD_TIME. Read once +// at module scope so /sitemap.xml doesn't restat the file per page. +const MTIME_MANIFEST_PATH = path.join( + process.cwd(), + "data", + "page-mtimes.json", +); +const MTIME_MANIFEST: Record = (() => { + try { + return JSON.parse(readFileSync(MTIME_MANIFEST_PATH, "utf8")); + } catch { + return {}; + } +})(); + function pageMtime(relPath: string): Date { + const gitSeconds = MTIME_MANIFEST[relPath]; + if (typeof gitSeconds === "number" && Number.isFinite(gitSeconds)) { + const gitDate = new Date(gitSeconds * 1000); + if (gitDate > REAL_REPO_BIRTH) return gitDate; + } try { const mtime = statSync(path.join(process.cwd(), "src/app", relPath)).mtime; if (mtime < REAL_REPO_BIRTH) return BUILD_TIME; @@ -97,6 +122,7 @@ function staticHubRoutes(catalogTs: Date): MetadataRoute.Sitemap { { url: `${SITE.url}/partners`, lastModified: pageMtime("partners/page.tsx"), changeFrequency: "monthly", priority: 0.7 }, { url: `${SITE.url}/badges`, lastModified: catalogTs, changeFrequency: "daily", priority: 0.7 }, { url: `${SITE.url}/about`, lastModified: pageMtime("about/page.tsx"), changeFrequency: "monthly", priority: 0.5 }, + { url: `${SITE.url}/team`, lastModified: pageMtime("team/page.tsx"), changeFrequency: "monthly", priority: 0.6 }, { url: `${SITE.url}/press`, lastModified: pageMtime("press/page.tsx"), changeFrequency: "monthly", priority: 0.4 }, { url: `${SITE.url}/compare`, lastModified: pageMtime("compare/page.tsx"), changeFrequency: "weekly", priority: 0.6 }, { url: `${SITE.url}/alternatives`, lastModified: pageMtime("alternatives/page.tsx"), changeFrequency: "weekly", priority: 0.6 }, diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx new file mode 100644 index 00000000..dc824966 --- /dev/null +++ b/src/app/team/page.tsx @@ -0,0 +1,96 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ArrowUpRight } from "lucide-react"; +import { SectionRule } from "@/components/section-rule"; +import { teamPageLd } from "@/lib/hub-jsonld"; +import { safeJsonLd } from "@/lib/jsonld"; +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata: Metadata = pageMetadata({ + path: "/team", + title: "Team", + description: + "Who maintains OpenChainBench: spec review, harness design, statistical review and editorial corrections.", +}); + +export default function TeamPage() { + return ( +
+ {teamPageLd().map((ld, i) => ( +