sync: dev → main - #1240
Closed
Flotapponnier wants to merge 96 commits into
Closed
Conversation
solana cohort: provider pages polish
…#1136) Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ll mode), network-fees
Robinhood Chain: hub entry + 3 bench integrations
…fix tracker reset
…cution mutex primitive DailySpentUSD only ever incremented and was zeroed by every restart, so a crash-loop minted a fresh budget each time. The tracker resets exactly at UTC midnight, persists (date, spent) to SPEND_STATE_PATH (default ./spend-state.json, not /tmp) and is mutex-guarded. Also declares the package-level single-flight lock and the FAILED_TX_FEE_ESTIMATE_USD (default 0.50) helper for gas bled by failed broadcasts.
…get, single-flight lock Fund-loss fixes from hostile review: - a corrective transfer whose deposit broadcast but whose bridge status never resolved is now TERMINAL for the scheduler slot (funds are likely still in flight, a retry double-sends); only pre-broadcast failures may consume another attempt. executeOnBridge keeps TxHash on error paths so callers can tell the two apart. - ONE corrective-transfer budget (max 2) per scheduler slot, shared across all downgrade-ladder rungs (was per rung: up to 6 transfers per slot). - package-level execution mutex: scheduler slots own the wallets end to end, the reaper TryLocks and skips its tick instead of queueing. - broadcasts that failed or timed out after getting a TxHash book a flat FAILED_TX_FEE_ESTIMATE_USD against the daily cap instead of bleeding unaccounted gas; all spend goes through the persisted UTC-day tracker. - gas top-up now honors MaxDailySpendUSD before broadcasting.
The reaper (and any other consumer) used to accept simulated balances even in production with broadcast-capable keys, so phantom numbers could green-light real transfers. SimulateBalances now takes the execution mode and returns nil outside dry-run; startup logs a warning when the flag is set in a broadcast-capable mode.
bridge-monitor: self-healing execution loop (dormant)
…st-fill (#1141) Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
…reference (staging gated)
tokenized-stock-peg: bench 076, first onchain equity pricing bench
…he miss (bnb-rpc awaiting incident) (#1144) Co-authored-by: Florent Tapponnier <contact@mobula.io>
tokenized-stock-peg: fix spark parser for the flat response shape
… USDG methodology notes
tokenized-stock-peg: spec validation fix + methodology hardening
…ges==0) (#1195) Companion to feed-down banner: makes the 40min Codex blackout from this morning (08:23-09:04 UTC) visible on the 24h chart instead of a flat line covering the outage. Per-series match preserves the aggregate line as long as one chain still receives events. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Per-chain siblings of bench 081 for the WebSocket head-lag cluster. Same harness (harnesses/ws-head-latency), same race scoring, one spec per chain to avoid mixing block cadences (Ethereum 12s vs Base 2s vs Solana 400ms) in a single leaderboard. Base cohort: PublicNode + dRPC keyless endpoints. Tenderly does not expose a keyless Base WebSocket path. Solana cohort: PublicNode + Solana Foundation. slotSubscribe replaces newHeads on the harness side; metric names are shared so the queries are structurally identical to the EVM specs. Data already flows via the Paris VPS deployment (chain label carries base and solana). No harness change needed for this ship. Multi-region is a follow-up on the parent bench. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…s) (#1198) * ws-head-latency: region label via ConstLabels + Railway config Adds a static region ConstLabel to every ws-head-latency metric, bound at process startup from OCB_REGION (preferred) or RAILWAY_REPLICA_REGION (Railway auto-set), with eu-west fallback matching the historical single-vantage Paris VPS deployment. No observation site needs to change: the label is baked into every counter, gauge and histogram at registration, so all existing WithLabelValues(provider, chain) calls stay identical. Adds railway.toml so Railway can build the harness from Dockerfile with a /health check, matching the rpc-capabilities service layout. Follow-up: create the Railway ws-head-latency service with 3 replicas (us-east, eu-west, sgp) and OCB_REGION set on each, then decommission or leave the Paris VPS container as a 4th vantage. * ws-head-latency specs: add region dimension tabs (081 + 087 + 088) Adds `dimensions.region` block + `rank_matrix_query` on provider, region + per-provider `regions:` sub-arrays with region-scoped queries on the 3 ws-head-latency specs, following the polkadot-rpc pattern. Depends on the harness region label change (previous commit): every metric now carries a region ConstLabel so the region-scoped histogram queries return values as soon as multi-region replicas publish. Until Railway 3-replica deploy lands, only region=eu-west has data and the other tabs render empty; the All regions tab keeps working because the aggregate query is unscoped by region. --------- Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ot OCB_REGION) (#1200) (#1201) Every other multi-region harness on Railway (rpc-capabilities, aggregator-head-lag, evm-swap-quoting, pm-rate-limits, etc.) reads REGION from env. Making ws-head-latency read REGION too keeps the Railway service config uniform across the whole harness fleet. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Companion to #1195 (series null gaps). The gaps themselves render as line breaks but a break can read as 'the chart missed a sample'. A soft red column behind the break spans the exact outage window and makes the reader understand it was a real feed-side silence. Per line + per contiguous gap range, extended by one step so 1-bucket outages stay visible instead of collapsing to a zero-width sliver. Hidden for excluded (legend-toggled) providers. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…arity (#1204) 081 becomes ws-head-latency-ethereum so the 3 per-chain benches in the head-lag cluster share the same shape: ws-head-latency-ethereum / -base / -solana. The old ws-head-latency slug is dropped (was staging-gated via REMOVED_BENCH_SLUGS, never live on prod, no external SEO to preserve). Ungated on prod at the same time so the new slug ships alongside the base + solana siblings that are already live. Sibling text references updated to the new URL. Cache keys bench-unfiltered and all-benchmarks bumped so cached v29/v32 entries do not keep the old slug alive. Co-authored-by: Florent Tapponnier <contact@mobula.io>
CoinStats /wallet/blockchains returns HTTP 406 "Credits limit reached" on every fetch: the free tier quota is spent below our 6h cadence and the provider never publishes a network count. Dropping until a paid key is provisioned. Removes the coinstats.go adapter, the CoinStatsAPIKey config field, the coinstats entry in main.go, and the coinstats provider row + prose mentions in the spec. Cohort drops 6 -> 5 providers (GeckoTerminal, Codex, Mobula, CoinPaprika, Dune via Sim API). Co-authored-by: Florent Tapponnier <contact@mobula.io>
…ers (#1210) Feedback on #1203: the neutral red band read as 'something happened here' without telling the reader WHO. Tint each band in the provider's own color, add dashed edges at the outage boundaries, and label 'PROVIDER · SILENT' above the band when it fits (with a caret that survives on narrow single-bucket bands). Works uniformly for Mobula/Codex/Gecko or any future provider with a live_activity query. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Screenshot review: the small text-only label above the band was still too subtle to read as 'this provider is down', and 3 simultaneous provider outages produced overlapping unreadable labels stacked on the same y. Replace with high-contrast rounded pills (white text on provider color) that stack vertically when their X-ranges overlap. Renders as '● NAME DOWN' in bold small caps so a reader gets the event and the provider identity at a glance. Co-authored-by: Florent Tapponnier <contact@mobula.io>
#1212) Bench 080 published only 7 samples across 3 of 11 assets in 24h at 50 bps trigger. Two contributing causes fixed together: 1) Trigger from 50 -> 25 bps. 50 bps in 90s is a headline move on Nasdaq (earnings, macro prints), not a steady-state event. 25 bps is above normal intra-minute noise on liquid stocks and captures real arb-worthy moves without collapsing the semantic. 2) Sub-poll convergence credited. When the reference moves past the trigger AND the pool is already within band on the same tick, the arb closed inside one poll interval. Previously dropped silently; now observed at pollInterval (60s) so the fast-arb tail on liquid pools stops disappearing from the histogram. Convergence threshold stays at 20 bps: the "how close is the pool to fair" question is unchanged, only trigger sensitivity moves. Spec text (subtitle, methodology, FAQ) updated to 25 bps throughout. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
Two fixes based on 30D view review: - Leading gaps (Prom retention shorter than the visible window, harness started mid-range, provider added recently) rendered as a chart-wide band claiming every provider was down for the missing period. Skip runs that started before the first observed sample. - Rename 'DOWN' to 'DATA MISSING'. A silent series can be the provider going down, our harness losing its WebSocket, or a Prom scrape failure — the pill stays neutral instead of blaming the provider. Co-authored-by: Florent Tapponnier <contact@mobula.io>
HyperSync and The Graph need paid keys for the sustained cadence the bench expects; Mobula alone left the leaderboard single-provider and the spec never made it out of REMOVED_BENCH_SLUGS gating. Simpler to drop the whole cluster than to buy 2 SaaS subscriptions to prop up a bench nobody has cited yet. Removed: * benchmarks/indexer-latency.yml * harnesses/indexer-latency/ (whole directory) Kept in REMOVED_BENCH_SLUGS as 410-gone so any indexed URL returns Gone rather than 404. Cache keys bumped so cached v30/v33 entries that still enumerate the slug age out on next revalidate. VPS follow-up (separate ops): stop ocb-indexer-latency container, remove from docker-compose.yml + prometheus.yml scrape config. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Co-authored-by: Florent Tapponnier <contact@mobula.io>
#1220) Co-authored-by: Florent Tapponnier <contact@mobula.io>
) Bug: the guard was designed for modern 15s-cadence data, where changes([15m]) works cleanly. On backfilled historical data compacted to ~1 sample/hour, changes([15m]) returns 0 even though the gauge is healthy — the 30D view lost the first 8 days of data to false-drops (all three providers marked DATA MISSING for a whole week). Real-time silence is still surfaced by the live_activity scalar (pill + banner + downtime band), which runs against fresh scrape cadence and stays accurate. The chart series now shows every point Prom has, flat during old outages instead of gapped. Co-authored-by: Florent Tapponnier <contact@mobula.io>
- badge/[slug]/[provider]: serve pending SVG (200) instead of 404 when a provider is missing ranking data - hyperliquid/[slug]: render lightweight placeholder page (200) instead of 307 to /hyperliquid when history blob is missing the builder - compare adHocPairs: bump inclusion threshold to 3 (brand) / 4 (other) so a single bench flap can't cross both the sitemap gate and the page noindex gate Fixes 3 recurring Ahrefs issues (broken badge images, noindex-in-sitemap on compare, 3xx-in-sitemap on hyperliquid) all rooted in the sitemap/page ISR race. Co-authored-by: Florent Tapponnier <contact@mobula.io>
#1224) Follow-up to #1221. Dropping the guard entirely restored the 30D range but silently hid today's Codex morning blackout on the 24H view (frozen gauge value = flat line, indistinguishable from a healthy quiet period). Bring the guard back, gated on count_over_time > 5 samples in the window. Modern data at 15s cadence carries ~60 samples per 15 min so the gate fires the moment a WS goes silent. Backfilled data compacted to ~1 sample/hour has count <= 2 in a 15m window so the gate stays open and every point Prom kept is served — no more fake wall-to-wall gap at the start of the 30D range. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…#1225) Fix the leader-collapses-to-2.5ms artifact: histogram_quantile on a [0,5ms] first bucket interpolates every winner's p50 to ~2.5ms (chain-independent). Replace with a 0.5ms floor climbing to 10s so sub-ms wins actually resolve on Solana (~400ms slots) and Base (2s). Add keyed provider slots for Base (Alchemy/Infura/Chainstack/QuickNode) and Solana (Alchemy/Chainstack/Helius/QuickNode) so the cohort can extend beyond keyless once contributor keys are wired. Keyless tier is saturated on all three chains (audit failed: Ankr, Blast, LlamaRPC, BlockPI, 1RPC, NodeReal, Omniatech, base.org, extrnode, chainstack- public — none accepted a keyless WSS handshake as of 2026-07). Co-authored-by: Florent Tapponnier <contact@mobula.io>
Titan leads with sample=6973 in 24h, data healthy since the harness ship. Removing from REMOVED_BENCH_SLUGS so the slug renders on prod next revalidate. Cache keys bumped to bench-unfiltered-v32 + all-benchmarks-v35 so cached entries do not keep dropping the slug via the old prod gate. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Fine-grained outages (24h step = 20 min buckets) rarely align with coarser evaluation timestamps (7d step = 2h, 30d step = 12h) so the Prom query returns a healthy average and the reader sees the pill on 24H but nothing on 7D/30D. Post-process: for every contiguous null run detected on s24, translate the time-ago range to bucket indices on s7 and s30 and null every coarse bucket the run straddles. Contiguity preserved so a single short outage doesn't split into two adjacent pills after propagation. Same treatment applied to per-region series. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…tion (#1230) Bug: the previous math scaled by the fine window fraction, which projected a 7h-ago outage down to 40h-ago on the 7d grid (the pill landed at -1.7d instead of -6h). Both grids share 'now' as their right anchor and only the step sizes differ — take the fine idx's absolute time-ago and divide by the coarse step to find the covering bucket. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…1231) Their Uniswap v4 pools on Robinhood Chain have no arb activity: pool spot stays frozen at the last swap value tick after tick while the Yahoo reference moves normally. Events open on ref triggers but roll over as still_open with zero latency samples. Cohort restricted to the 6 tickers with observable arb activity: NVDA AAPL PLTR META AMD MU. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…order, GMX Subsquid error surfacing (#1233) - Paradex: cap fill ratio at 90% of visible book (depth=100). Tiers that eat through the tail now return book_too_thin and skip publishing instead of inflated 150-500 bps ghost values. - gains: only cache USD-quoted pairs during pair discovery, widen scan 60->200. Root cause was a SOL/BTC pair overwriting the SOL/USD cache entry, publishing the wrong feeIndex and uniform 4.333 bps across ETH/BTC/SOL. - dydx: sort v4-indexer level responses (asks ascending, bids descending) before walking the book. Insertion-order walk produced 1.7-2x inflated headline at 100k/1M tiers. - gmx: prefix Subsquid errors, surface GraphQL errors[], fall back to unsuffixed positionFeeFactor if the impact-branch field is absent, and error out on zero factor. Prevents the silent-6bps-constant fallback path. Verified: go build + go vet clean. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Screenshot review: rpc-reliability rendered a 'LAVANODIES DATA MISSING' pill even though its spec never declared live_activity — natural nulls in its series24h (sparse scrapes, backfill edges) triggered the DowntimeBands render. Gate the render on benchmark.results.some(r => r.liveStatus != null), which is only set when the spec declared live_activity. Same gate on propagateNullsToCoarser so other benches' 7d/30d series stay untouched. Co-authored-by: Florent Tapponnier <contact@mobula.io>
…#1236) * site: provider_notes chip on ranking rows + rpc-capabilities copy fixes - validator-yield now surfaces a per-provider chip (MEV inc./MEV excl./no MEV) beside each row so scan-readers can't accidentally compare Solana MEV-inclusive APR against Ethereum consensus-only APR without seeing the caveat - New optional spec field provider_notes: {slug: string} plumbed end-to-end (Zod schema, TS type, materialize loader, spec overlay, ledger-table render) - Chip renders only when the note is set, no visual change to any other bench - rpc-capabilities: '10 EVM chains' copy corrected to 26 chains (24 EVM + Solana + Polkadot) across title/subtitle/seo_intro/abstract/methodology + FAQ - rpc-capabilities disclaimer expanded to call out that the 'All chains' aggregate rewards single-chain foundation endpoints and to document Cloudflare-eth exclusion (returns -32603 on POST) * perp-fees harness: add Aster + edgeX venues - Aster (BSC): Binance-style depth walk against fapi.asterdex.com, hardcoded 5 bps taker (no public fee schedule API) - edgeX (offchain CLOB): getDepth REST + hardcoded 3.8 bps taker per gitbook docs. Asset→contractId map declared inline (verify on first prod run; wrong ID surfaces as an orderbook fetch error, not a silent 0) - Both venues probed for ETH/BTC/SOL at all 3 notional tiers ($1k/$100k/$1M) via applyBookTiers - Closes the biggest coverage gap flagged by the perp-fees GEO audit (Aster is #2 perp DEX by 30d volume) * validator-yield harness: add Cardano, Sui, Cosmos Hub, Avalanche Closes the 'Highest staking yield' coverage gap: the bench previously ranked only Solana, Ethereum and Hyperliquid but AI answers to that question almost always include Cardano ($14B staked) and other top chains. - Cardano (Koios API): top-50 pools by stake, net = ρ×(1-τ)×(1-margin). Simplified vs full a0/k/saturation formula but lands within ~50-100 bps of pooltool.io. - Sui (public RPC suix_getValidatorsApy + suix_getLatestSuiSystemState): all active validators, apy is already net of commission per Sui docs. - Cosmos Hub (LCD REST): top-200 bonded validators, net = inflation×(1-communityTax)/bondedRatio×(1-commission). - Avalanche (P-Chain platform.getCurrentValidators): top-100 by stake, base 8.5% APR × uptime × (1 - delegationFee/100). Reuses existing ocb_validator_* gauges from metrics.go (no new metrics, no go.mod changes). Env-overridable endpoints declared inline in each fetcher matching solana.go style. Shortcuts (uptime=100% for Cardano/Sui/Cosmos, hardcoded community_tax=2%, base 8.5% for Avalanche) documented in code. --------- Co-authored-by: Florent Tapponnier <contact@mobula.io>
…bloXroute) (#1238) Broadens the keyless cohort for benches 010 (rpc-capabilities), 083 (rpc-reliability) and the per-chain RPC benches. All 4 providers verified live returning valid eth_getBlockByNumber on their respective chains before inclusion. New (provider, chain) cells: ThirdWeb 8 chains (ETH POL ARB OP BASE BSC AVAX LINEA) Blast API 4 chains (ETH ARB BASE BSC) — POL OP AVAX LINEA are 403 paid tier bloXroute 3 chains (ETH BASE BSC) Gateway.fm 1 chain (ETH) Total = 16 new cells. Metrics auto-flow into bench 083 (rank_matrix groups by provider+chain) and the per-chain leaderboards after the Railway rebuild picks up main. Co-authored-by: Florent Tapponnier <contact@mobula.io>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full sync of `dev` into `main`. Includes this session's fixes plus other in-flight work already on dev.
This session's fixes (already cherry-picked to main separately)
Other dev commits pending on main
After merge
Run `vercel --prod` from a fresh checkout of `main` to deploy the site changes to production.