diff --git a/benchmarks/buyback-audit.md b/benchmarks/buyback-audit.md index 500605c0a..89258898d 100644 --- a/benchmarks/buyback-audit.md +++ b/benchmarks/buyback-audit.md @@ -28,7 +28,7 @@ v2 will add Jupiter Litterbox Trust (50% of Jupiter fees → on-market JUP buyba A ratio well below 1.0 is not automatically under-delivery. Two regimes produce it: 1. **Batched execution.** Sky's SBE accumulates surplus inside the Maker / Sky protocol and concentrates on-market SKY buys into irregular batches via Uniswap. A 7-day window taken mid-accumulation reads low; the long-run accrual to the destination address tracks closer to 1.0. -2. **Sustained under-funding.** The promised share overstates what is actually being routed to the buyback program. This shows up as a 30-day ratio that stays well below 1.0 across multiple snapshots — the cadence signal in (1) damps out, the structural signal does not. +2. **Sustained under-funding.** The promised share overstates what is actually being routed to the buyback program. This shows up as a 30-day ratio that stays well below 1.0 across multiple snapshots. The cadence signal in (1) damps out, the structural signal does not. The 7d vs 30d split is the cheapest tell for which regime is in play. Continuous executors (Hyperliquid AF) show 7d ≈ 30d; batched executors (Sky SBE) show 7d < 30d; structurally under-funded programs would show both windows low. diff --git a/benchmarks/hyperliquid-frontends.yml b/benchmarks/hyperliquid-frontends.yml index 03af19237..492a13af5 100644 --- a/benchmarks/hyperliquid-frontends.yml +++ b/benchmarks/hyperliquid-frontends.yml @@ -13,7 +13,7 @@ unit: bps higher_is_better: false disclaimer: | - Volume-share dashboards already exist (ASXN, Coinmarketman, Flowscan, Allium). OCB leads with user cost instead: effective fee is a volume-weighted `builder_fee / notional` over 24h, so fee-free promo windows pull it down. Native HL UI orders carry no builder code and are excluded — covered by `aggregator-head-lag`. + Volume-share dashboards already exist (ASXN, Coinmarketman, Flowscan, Allium). OCB leads with user cost instead: effective fee is a volume-weighted `builder_fee / notional` over 24h, so fee-free promo windows pull it down. Native HL UI orders carry no builder code and are excluded. Coverage is in `aggregator-head-lag`. seo_intro: | Every Hyperliquid order carries an optional on-chain `builder` @@ -39,7 +39,7 @@ seo_intro: | them publish retention per builder because none of them keep the persistent state. OCB does. Volume share is exposed as a secondary gauge so the number readers expect to see is still - on the page — it just doesn't drive the ranking. + on the page. It just doesn't drive the ranking. abstract: | We compute four quality metrics per Hyperliquid builder code @@ -51,7 +51,7 @@ abstract: | the builder address. (2) D7 and D30 cohort retention, the percentage of users whose first observed fill for the builder lands in the UTC-day window 7 (or 30) days ago and who traded - again within the last 24 hours — derived from a local SQLite + again within the last 24 hours, derived from a local SQLite state of `(builder, user, first_seen_ms, last_seen_ms)`. (3) USD captured per unique trader, `sum(builder_fee) / count(distinct user)` over the UTC-day window. (4) Volume share in @@ -79,23 +79,23 @@ methodology: - "Methodology versioning. Any change (registry additions, formula tweaks, exclusion thresholds, window sizes) ships as a public PR with a 14-day comment window before activation. Major changes run a 30-day shadow period publishing old and new metric series in parallel." findings: - - "{{best_name}} currently charges {{best_p50}} effective on the last 24 hours of attributed Hyperliquid flow — the most aligned frontend in the {{count}}-builder set we track. Lower is better here: 100 bps is the on-chain cap, 0 bps means the frontend is fully waiving its builder rebate." + - "{{best_name}} currently charges {{best_p50}} effective on the last 24 hours of attributed Hyperliquid flow. That is the most aligned frontend in the {{count}}-builder set we track. Lower is better here: 100 bps is the on-chain cap, 0 bps means the frontend is fully waiving its builder rebate." - "{{name:phantom-perps}} sits at {{p50:phantom-perps}}. Phantom routes the largest share of attributed Hyperliquid volume but the fee column tells you what its users actually pay on top of HL's base schedule, which is the number that matters for choosing a frontend." - - "{{name:axiom}} reports {{p50:axiom}}. Axiom expanded from Solana to Hyperliquid in 2025 and runs a transparent flat fee model — useful counterpoint to frontends that promote fee-free windows." + - "{{name:axiom}} reports {{p50:axiom}}. Axiom expanded from Solana to Hyperliquid in 2025 and runs a transparent flat fee model, a useful counterpoint to frontends that promote fee-free windows." - "{{worst_name}} extracts {{worst_p50}} from its users on the same window. The gap between {{best_name}} and {{worst_name}} is the operational signal traders should weigh against the UX advantages each frontend ships with." - "Fee discipline (the secondary p90 column, 30-day stddev) flags frontends running promotional cycles. A builder with a low 24h fee but a high stddev is in a fee-free phase and will normalize; one with both low is what alignment looks like." faq: - q: "Why does OpenChainBench not show Hyperliquid volume share?" - a: "Volume share is already published cleanly by ASXN HyperScreener, Coinmarketman HyperTracker, Flowscan, Allium, and at least three Dune dashboards. Republishing it would add no value. The unmeasured part of the frontend question is user cost — how much does each frontend take per dollar routed, and how stable is that take rate. That's what this bench answers. Raw yesterday's UTC notional per builder is still exposed as a secondary metric for readers who want it; it just doesn't drive the headline ranking." + a: "Volume share is already published cleanly by ASXN HyperScreener, Coinmarketman HyperTracker, Flowscan, Allium, and at least three Dune dashboards. Republishing it would add no value. The unmeasured part of the frontend question is user cost: how much does each frontend take per dollar routed, and how stable is that take rate. That's what this bench answers. Raw yesterday's UTC notional per builder is still exposed as a secondary metric for readers who want it; it just doesn't drive the headline ranking." - q: "How is the effective fee actually computed?" - a: "For each Hyperliquid builder address in our registry, the harness fetches `https://stats-data.hyperliquid.xyz/Mainnet/builder_fills/{address}/{YYYYMMDD}.csv.lz4` once per hour. Each row of the CSV is one attributed fill with a `builder_fee` field and the `px × sz` notional. We sum `builder_fee` and `px × sz` across every row in the last 24 hours and compute `sum(builder_fee) / sum(notional) × 10000`. That's the volume-weighted effective fee in basis points — what a representative dollar of flow paid that frontend." + a: "For each Hyperliquid builder address in our registry, the harness fetches `https://stats-data.hyperliquid.xyz/Mainnet/builder_fills/{address}/{YYYYMMDD}.csv.lz4` once per hour. Each row of the CSV is one attributed fill with a `builder_fee` field and the `px × sz` notional. We sum `builder_fee` and `px × sz` across every row in the last 24 hours and compute `sum(builder_fee) / sum(notional) × 10000`. That's the volume-weighted effective fee in basis points, what a representative dollar of flow paid that frontend." - q: "What's the on-chain cap?" a: "Hyperliquid caps builder fees at 100 basis points on perps and 1000 basis points on spot, configured via the `ApproveBuilderFee` action signed by each user's main wallet. The bench's headline is denominated in basis points so 100 = the on-chain cap and 0 = fully waived rebate. Most frontends sit well below the cap; the bench surfaces who sits where." - q: "Which frontends are tracked?" a: "The registry tracks Phantom Perps, Axiom, OKX, GMGN, pvp.trade, Dexterity, KGEN, Insilico Terminal, and ASXN. Builder addresses are cross-referenced against Flowscan's builder leaderboard, governance forum disclosures, and each frontend's public announcement of their address. New builders meeting the 0.5% volume threshold get added on the next weekly review." - q: "What is the 'unattributed' slice?" - a: "Builder addresses that appear in Hyperliquid's public dumps but aren't in our registry, aggregated together. Surfacing this slice explicitly is honest — it's the share of attributed flow our methodology doesn't yet cover. When the unattributed share crosses 2%, a registry-update issue is filed; the bench freshness counter on the page tells you when the registry was last reviewed." + a: "Builder addresses that appear in Hyperliquid's public dumps but aren't in our registry, aggregated together. Surfacing this slice explicitly is honest. It's the share of attributed flow our methodology doesn't yet cover. When the unattributed share crosses 2%, a registry-update issue is filed; the bench freshness counter on the page tells you when the registry was last reviewed." - q: "What's fee discipline and why is it useful?" a: "The 30-day standard deviation of the daily effective fee in basis points. Frontends running rotating promotions (fee-free week → normalize the next week → discount cycle) show high variance even when the 24h headline is low. Frontends with stable transparent pricing show low variance. The discipline column is what tells you whether the headline number you're reading is a stable rate or a snapshot mid-promo." - q: "Does the bench measure native HL UI orders?" diff --git a/benchmarks/l1-finality.md b/benchmarks/l1-finality.md index 57f62ca97..949b09257 100644 --- a/benchmarks/l1-finality.md +++ b/benchmarks/l1-finality.md @@ -59,7 +59,7 @@ This works for: **BNB, Avalanche, TON**. | TON | High (after SSE refactor) | tonapi `workchain=-1` SSE stream, ms-precise | | Cardano | Medium | 15-conf compromise between Coinbase 10 and Kraken 15. Academic k=2160 is theoretical; no actor uses it | | Litecoin | Medium | 12-conf post-April-2026 reorg; standard is evolving | -| TRON | Medium | CEX confirmation counts vary 19–30; we use the 19-block protocol minimum | +| TRON | Medium | CEX confirmation counts vary 19 to 30; we use the 19-block protocol minimum | | Monero | Lower | XMR delisted from major regulated CEXes since 2024; no canonical confirmation count today | ## Why we chose the depths we did diff --git a/benchmarks/network-coverage.yml b/benchmarks/network-coverage.yml index 50109e18e..1d7be5043 100644 --- a/benchmarks/network-coverage.yml +++ b/benchmarks/network-coverage.yml @@ -46,10 +46,10 @@ methodology: - "GeckoTerminal: GET /api/v2/networks (paginated, no auth)." - "Codex: GraphQL `getNetworks` query at https://graph.codex.io/graphql with an official API key." - "Mobula: GET /api/1/blockchains with an Authorization API key." - - "CoinPaprika: GET /v1/contracts (no auth) — list of platforms supported for contract lookup." + - "CoinPaprika: GET /v1/contracts (no auth). Lists platforms supported for contract lookup." - "Covalent / GoldRush: GET /v1/chains/ with a Bearer API key; testnets filtered via `is_testnet`." - "CoinStats: GET /wallet/blockchains with X-API-KEY." - - "Dune (via Sim API): GET https://api.sim.dune.com/v1/evm/supported-chains (no auth) — EVM only, mainnets filtered via the `mainnet` tag." + - "Dune (via Sim API): GET https://api.sim.dune.com/v1/evm/supported-chains (no auth). EVM only, mainnets filtered via the `mainnet` tag." - "Cadence: full refresh every 6 hours." - "Counting: a provider's network is counted once per unique chain id; mainnet only." - "Failures (network errors, rate limits, auth errors) leave the previous count in place and increment a fetch_errors counter. the page falls back to its last successful sample." diff --git a/benchmarks/solana-dex-quote-latency.yml b/benchmarks/solana-dex-quote-latency.yml index 87138175b..40bc75407 100644 --- a/benchmarks/solana-dex-quote-latency.yml +++ b/benchmarks/solana-dex-quote-latency.yml @@ -41,22 +41,22 @@ abstract: | methodology: - "Providers measured: Jupiter, Mobula, OpenOcean, Raydium. (DFlow added when partnership API key is available.)" - - "Canonical request: 100 USDC → tokenOut at 1% slippage. The tokenOut rotates every tick — see below." - - "Token rotation (Pulse-fed). A persistent WebSocket to Mobula Pulse V2 (bonded view, Solana) maintains a 30-min sliding pool of trending bonded tokens — typically 50-300 active mints. Each tick picks one at random. A REST snapshot fallback (`market/query` volume ranks 6-55) refreshes every 10 min for Pulse outages. In steady state every tick is sourced from the WS feed." + - "Canonical request: 100 USDC → tokenOut at 1% slippage. The tokenOut rotates every tick (see below)." + - "Token rotation (Pulse-fed). A persistent WebSocket to Mobula Pulse V2 (bonded view, Solana) maintains a 30-min sliding pool of trending bonded tokens, typically 50-300 active mints. Each tick picks one at random. A REST snapshot fallback (`market/query` volume ranks 6-55) refreshes every 10 min for Pulse outages. In steady state every tick is sourced from the WS feed." - "Cadence: one quote per provider per region every 60 seconds. 1,440 samples per provider per region per day." - "Regions: us-east, eu-west, sgp." - - "Latency definition: wallclock from HTTP request dispatch to the first byte of a response body that includes the quoted output amount (Jupiter `outAmount`, Mobula `data.amountOutTokens`, OpenOcean `data.outAmount`, Raydium `data.outputAmount`). The HTTP client reuses TCP + TLS connections across ticks (warm-path), matching how a long-running backend integration actually consumes the API — the bench measures steady-state round-trip cost, not the first-request cold-start penalty." - - "Failure classes. Each non-success outcome lands on exactly one counter and is excluded from the latency histogram: `solana_quote_throttled_total` (HTTP 429), `solana_quote_auth_error_total` (401/403), `solana_quote_no_route_total` (provider can't route the picked token), `solana_quote_other_error_total` (network/timeout/parse). Each provider's no-route signal is verified — Jupiter NO_ROUTES_FOUND, Mobula 'No route found', Raydium INSUFFICIENT_LIQUIDITY, OpenOcean payload-level." + - "Latency definition: wallclock from HTTP request dispatch to the first byte of a response body that includes the quoted output amount (Jupiter `outAmount`, Mobula `data.amountOutTokens`, OpenOcean `data.outAmount`, Raydium `data.outputAmount`). The HTTP client reuses TCP + TLS connections across ticks (warm-path), matching how a long-running backend integration actually consumes the API. The bench measures steady-state round-trip cost, not the first-request cold-start penalty." + - "Failure classes. Each non-success outcome lands on exactly one counter and is excluded from the latency histogram: `solana_quote_throttled_total` (HTTP 429), `solana_quote_auth_error_total` (401/403), `solana_quote_no_route_total` (provider can't route the picked token), `solana_quote_other_error_total` (network/timeout/parse). Each provider's no-route signal is verified: Jupiter NO_ROUTES_FOUND, Mobula 'No route found', Raydium INSUFFICIENT_LIQUIDITY, OpenOcean payload-level." - "Raydium scope. Raydium's compute API is single-venue (Raydium AMM v4 / CPMM / CLMM only) and does not multi-hop via SOL like Jupiter or Mobula. On Pulse-fed bonded tokens (mostly Pump.fun graduates living on PumpSwap, plus Meteora and Orca pools) it returns no-route ~80% of the time. That's an honest coverage signal and surfaces on the success-rate column, not as latency outliers." - "Direct egress from Railway, no residential proxy. The bench measures backend-to-backend latency as a real integration would see it." - "Histogram buckets: 10, 25, 50, 100, 200, 500, 1000, 2000, 5000 ms." findings: - "{{best_name}} leads at {{best_p50}} (p50, 24 h) across {{count}} measured Solana DEX quote APIs. The headline is the cross-region median across a rotating long-tail token target; the per-region tabs below show where each provider's infrastructure actually sits." - - "{{name:jupiter}} returns quotes in {{p50:jupiter}} (p50, 24 h). Without an SOL ↔ USDC edge-cache shortcut to lean on, Jupiter's lite endpoint is doing the real routing search every tick — that's the steady-state number a long-running backend integration would actually see." - - "{{name:mobula}} returns quotes in {{p50:mobula}} (p50, 24 h). Mobula's quote API runs a live multi-DEX routing search per request — no per-pair body caching — so the gap to providers that historically cached the canonical 1 SOL → USDC pair closes once the bench rotates targets." + - "{{name:jupiter}} returns quotes in {{p50:jupiter}} (p50, 24 h). Without an SOL ↔ USDC edge-cache shortcut to lean on, Jupiter's lite endpoint is doing the real routing search every tick. That's the steady-state number a long-running backend integration would actually see." + - "{{name:mobula}} returns quotes in {{p50:mobula}} (p50, 24 h). Mobula's quote API runs a live multi-DEX routing search per request, no per-pair body caching, so the gap to providers that historically cached the canonical 1 SOL → USDC pair closes once the bench rotates targets." - "{{name:openocean}} returns quotes in {{p50:openocean}} (p50, 24 h). OpenOcean's v4 Solana endpoint runs a separate multi-DEX path engine; the routing search dominates the latency curve on long-tail tokens." - - "{{name:raydium}} returns quotes in {{p50:raydium}} (p50, 24 h). Raydium's Trade API is a single-venue compute endpoint (Raydium pools only) — it cannot multi-hop via SOL, so it no-routes on long-tail tokens whose dominant liquidity lives elsewhere (Pump.fun, Meteora, Orca). Latency below is the conditional p50 on the subset of tokens it does cover." + - "{{name:raydium}} returns quotes in {{p50:raydium}} (p50, 24 h). Raydium's Trade API is a single-venue compute endpoint (Raydium pools only). It cannot multi-hop via SOL, so it no-routes on long-tail tokens whose dominant liquidity lives elsewhere (Pump.fun, Meteora, Orca). Latency below is the conditional p50 on the subset of tokens it does cover." - "p99 is the integration-grade number. {{p99:jupiter}} / {{p99:mobula}} / {{p99:openocean}} / {{p99:raydium}} is what a swap UI feels when a region spikes or a provider's route engine churns." source: https://github.com/ChainBench/OpenChainBench/tree/main/harnesses/solana-quote-latency @@ -82,13 +82,13 @@ faq: - q: "What is quote latency on a DEX aggregator?" a: "Quote latency is the wallclock time between a swap UI asking a DEX aggregator 'what would I get if I traded 100 USDC for this token right now?' and the aggregator answering with a usable routed price. A quote API at 80 ms feels instant to the user; a quote API at 600 ms introduces a visible delay between input change and updated output. The number sets the floor on how live a swap UI can feel before any other latency (RPC, signing, broadcast) is added." - q: "Why does the bench rotate the target token instead of always quoting SOL → USDC?" - a: "SOL → USDC is the most-cached pair on Solana — Jupiter's lite endpoint serves it from a CloudFront edge in 30-50 ms, but that measures cache hit, not routing. Rotating the target every tick — and rotating against the tokens that are *actually trending right now* via Mobula Pulse V2's bonded WebSocket feed — defeats every per-pair edge cache and forces each provider to actually run a routing search. The metric becomes a fair comparison of routing engines, not CDN configurations." + a: "SOL → USDC is the most-cached pair on Solana: Jupiter's lite endpoint serves it from a CloudFront edge in 30-50 ms, but that measures cache hit, not routing. Rotating the target every tick, and rotating against the tokens that are *actually trending right now* via Mobula Pulse V2's bonded WebSocket feed, defeats every per-pair edge cache and forces each provider to actually run a routing search. The metric becomes a fair comparison of routing engines, not CDN configurations." - q: "How is quote latency measured on OpenChainBench?" a: "A harness in each of three Railway regions (us-east, eu-west, sgp) ticks every 60 seconds: it picks one Solana token from a sliding 30-minute window of bonded tokens emitted by Mobula Pulse V2 over WebSocket, then asks each provider for a 100 USDC → tokenOut quote at 1% slippage in parallel. The HTTP client reuses TCP and TLS connections across ticks, so the recorded number is the steady-state round-trip a long-lived backend integration sees, not the one-off cold-start handshake. The wallclock from request dispatch to the first byte containing a usable quoted output amount is the recorded latency. p50, p90 and p99 are aggregated via Prometheus `histogram_quantile` over 24 hours." - q: "What if a provider can't quote a particular long-tail token?" a: "It's counted as a no-route, not as a slow quote. Each provider has a recognisable 'I have no path for this pair' signal (Jupiter `NO_ROUTES_FOUND` / `TOKEN_NOT_TRADABLE`, Mobula `No route found` / `Token not found`, Raydium `INSUFFICIENT_LIQUIDITY` / `ROUTE_NOT_FOUND`, OpenOcean payload-level signal). When the harness sees it, the tick lands on `solana_quote_no_route_total` and is excluded from the latency histogram entirely, so providers that fail fast on coverage gaps aren't penalised on the percentiles. The success-rate column reflects the no-route rate honestly." - q: "Why does Raydium have a much lower success rate than the aggregators?" - a: "Raydium's compute endpoint is single-venue: it only routes against Raydium's own AMM v4 / CPMM / CLMM pools and does not multi-hop via SOL the way Jupiter or Mobula do. Because the rotation now pulls from Mobula Pulse V2's bonded feed — most of which are Pump.fun graduates living on PumpSwap, plus Meteora and Orca pools — Raydium returns INSUFFICIENT_LIQUIDITY or ROUTE_NOT_FOUND on roughly 80% of picks. That's accurate — Raydium's API is a Raydium-pool route engine, not an aggregator. The latency column for Raydium is the conditional p50 over the small subset of bonded tokens that happen to have a Raydium pool." + a: "Raydium's compute endpoint is single-venue: it only routes against Raydium's own AMM v4 / CPMM / CLMM pools and does not multi-hop via SOL the way Jupiter or Mobula do. Because the rotation now pulls from Mobula Pulse V2's bonded feed (most of which are Pump.fun graduates living on PumpSwap, plus Meteora and Orca pools), Raydium returns INSUFFICIENT_LIQUIDITY or ROUTE_NOT_FOUND on roughly 80% of picks. That's accurate: Raydium's API is a Raydium-pool route engine, not an aggregator. The latency column for Raydium is the conditional p50 over the small subset of bonded tokens that happen to have a Raydium pool." - q: "Are authentication errors and rate limits counted as 'slow'?" a: "No. HTTP 401/403/429 responses are excluded from the latency histogram entirely. They are counted in separate counters (`solana_quote_auth_error_total`, `solana_quote_throttled_total`) which show up on the success rate column. A provider that's fast but rate-limits us at the configured cadence loses points on success rate, not on latency." diff --git a/benchmarks/solana-tx-landing-latency.yml b/benchmarks/solana-tx-landing-latency.yml index f7f44ca40..9bb14231c 100644 --- a/benchmarks/solana-tx-landing-latency.yml +++ b/benchmarks/solana-tx-landing-latency.yml @@ -96,7 +96,7 @@ methodology: - "Methodology v1.3 pre-registered at github.com/ChainBench/OpenChainBench/blob/main/docs/methodology/solana-tx-landing-active.md. Any change (tip floor, probe payload, cadence, region, metric definitions) ships as a public PR with a 14-day comment window. Major version bumps run a 30-day shadow period publishing old and new metrics in parallel." findings: - - "{{best_name}} leads the V0-Lean probe set at p50 = {{best_p50}} slot delta over the rolling 7-day window. Lower = fewer Solana slots between submission and confirmation. The gap between fastest and slowest is the operational signal, every service claims '99 %+ landing rate' in marketing copy, but the chain doesn't lie about which slot included your tx. A 1-slot difference is ~400 ms — enough for a MEV bot to front-run a competitor." + - "{{best_name}} leads the V0-Lean probe set at p50 = {{best_p50}} slot delta over the rolling 7-day window. Lower = fewer Solana slots between submission and confirmation. The gap between fastest and slowest is the operational signal, every service claims '99 %+ landing rate' in marketing copy, but the chain doesn't lie about which slot included your tx. A 1-slot difference is ~400 ms, enough for a MEV bot to front-run a competitor." - "{{name:jito}} is the baseline / control. Helius (default mode), Astralane, and Nozomi all internally fan out to Jito, so the Jito p50 is the floor any premium service must beat. Same slot_delta as Jito on a given cycle = the service is essentially using Jito as its inclusion path. {{name:jito}} sits at p50 = {{p50:jito}} slot delta." - "{{name:helius-sender}} in `swqos_only` mode isolates Helius's own routing path from the Jito leg. p50 = {{p50:helius-sender}} slot delta. A v1.0.1 methodology update will publish Helius default mode (with Jito fan-out) side-by-side for direct comparison." - "{{name:nozomi}} premium pricing (1M lamport hard floor, ~10 × Jito's competitive level) only makes economic sense if the slot_delta advantage is meaningful. p50 = {{p50:nozomi}} slot delta. The gap vs Jito quantifies whether the tip premium buys real slot priority." diff --git a/src/app/alternatives/[slug]/page.tsx b/src/app/alternatives/[slug]/page.tsx index fa3653d42..1488a2092 100644 --- a/src/app/alternatives/[slug]/page.tsx +++ b/src/app/alternatives/[slug]/page.tsx @@ -89,7 +89,7 @@ export default async function AlternativePage({ const peers = altCount > 0 ? `${altCount} live alternative${altCount === 1 ? "" : "s"}` : "live alternatives"; - const pad = ` — open benchmark snapshot of ${alt.target_product} versus ${peers} on ${bench.metric}, measured continuously and published with full methodology.`; + const pad = `. Open benchmark snapshot of ${alt.target_product} versus ${peers} on ${bench.metric}, measured continuously and published with full methodology.`; const joined = `${altDescriptionBase}${pad}`; return joined.slice(0, 5000); })(); diff --git a/src/app/api/mcp/[transport]/route.ts b/src/app/api/mcp/[transport]/route.ts index 788a08ea2..4348ea368 100644 --- a/src/app/api/mcp/[transport]/route.ts +++ b/src/app/api/mcp/[transport]/route.ts @@ -364,7 +364,7 @@ const mcpHandler = createMcpHandler( .max(604_800) .optional() .describe("If set, run a range query over the last N seconds (max 7 days = 604800). Omit for an instant query."), - steps: z.number().int().min(2).max(360).optional().describe("Number of samples for a range query (2–360). Default 60. Step duration = windowSec / steps."), + steps: z.number().int().min(2).max(360).optional().describe("Number of samples for a range query (2 to 360). Default 60. Step duration = windowSec / steps."), }, }, async ({ query, windowSec, steps }) => { diff --git a/src/app/benchmarks/[slug]/opengraph-image.tsx b/src/app/benchmarks/[slug]/opengraph-image.tsx index 2a59fb45f..6670ba06e 100644 --- a/src/app/benchmarks/[slug]/opengraph-image.tsx +++ b/src/app/benchmarks/[slug]/opengraph-image.tsx @@ -42,7 +42,7 @@ export async function generateImageMetadata({ }, ...chains.map((c) => ({ id: c.value, - alt: `${alt} — ${c.label}`, + alt: `${alt}. ${c.label}`, size, contentType, })), diff --git a/src/app/benchmarks/[slug]/twitter-image.tsx b/src/app/benchmarks/[slug]/twitter-image.tsx index 083f91097..3e2b3683a 100644 --- a/src/app/benchmarks/[slug]/twitter-image.tsx +++ b/src/app/benchmarks/[slug]/twitter-image.tsx @@ -38,7 +38,7 @@ export async function generateImageMetadata({ }, ...chains.map((c) => ({ id: c.value, - alt: `${alt} — ${c.label}`, + alt: `${alt}. ${c.label}`, size, contentType, })), diff --git a/src/components/donut-chart.tsx b/src/components/donut-chart.tsx index 81025ad49..67aba5b0e 100644 --- a/src/components/donut-chart.tsx +++ b/src/components/donut-chart.tsx @@ -243,7 +243,7 @@ export function DonutChart({ {r.name} - {isOff ? "—" : `${(share * 100).toFixed(1)}%`} + {isOff ? "-" : `${(share * 100).toFixed(1)}%`} ); diff --git a/src/components/ledger-table.tsx b/src/components/ledger-table.tsx index 533557285..cdefb4109 100644 --- a/src/components/ledger-table.tsx +++ b/src/components/ledger-table.tsx @@ -290,7 +290,7 @@ function Row({ {hasSlots && (