Agent navigation — Grep the heading you need: Methodology Versioning · DEX Liquidity Score · Published chain-label casing · Discovery Cron · DEX Price Cross-Validation.
Methodology Versioning
- Current methodology version: <!-- GENERATED-START: methodology-version-liquidity-score -->
v6.92<!-- GENERATED-END: methodology-version-liquidity-score --> - TVL-basis breaks: a liquidity release that changes how retained TVL is measured (not just weighting or volume admission) must also append its version to
LIQUIDITY_TVL_BASIS_BREAK_VERSIONSinshared/lib/dex-liquidity-evidence.ts, which is append-only. The 30-day stability series never mix epochs across a listed break (see Basis homogeneity under Durability Sub-Component Weights); current breaks are 6.91 and 6.92. - v6.92 (2026-09-28): Dead-pool floor (see Dead-pool floor under DEX Liquidity Score). A retained pool with at least $1M of scoring TVL (
DEX_DEAD_POOL_TVL_MIN_USD) is dropped from scoring inputs and DEX-implied prices when its admitted 24h reading is a trade-verified zero (a CoinGecko Onchain registry zero: explicit zero volume with zero 24h buys and sells) on a chain where CoinGecko indexes trades, and no other leg is a tracked stablecoin deployment on its chain. A live-lane pool with no volume reading now adopts the reading of the registry view it deduplicates. Replaying the 13:10 UTC production stage through the real scorer (baseline exact for all 274 coins) takes global DEX TVL from $8.401B to $8.009B (-4.7%); USDT -16.0% (68 unchanged), USDC -5.0% (73 -> 74), DAI -34.0% (60 -> 59), PYUSD -24.3% (68 -> 66), PAXG -68.9% (55 -> 62). Moves above 5 points: USDB 70 -> 51, USDGLO 69 -> 84, PAXG, USDz 56 -> 49, vnxAU 59 -> 65. No coin crosses below the volume-coverage floor. Downstream on deploy: the scoring-stage payload moves to v3, so a stage written by the other Worker version fails closed for that slot (see the v6.9 deploy operations); the daily digest rejects the cutover history pair asmethodology-basis-change; PAXG (previous top ten, -68.9%) raisesmajor-tvl-cliff:paxg-paxoson runs 2-6 before it is rebaselined; run status staysok; the 30-day TVL and turnover stability series stay inside one TVL-measurement epoch (see Durability Sub-Component Weights). Expected transient, not a market move: the APItvlChange7d, the Depeg Resolver's 7d/30d TVL changes and the DEWS liquidity-erosion signal compare against the history row nearest 7 (or 30) days ago with no methodology check, so for about seven days (30 for the resolver's 30d change) they read the step as TVL erosion on the coins that lose more than 40% (SBC, EURI, vnxAU, USDGLO, JPYC, USDz, USDB, PAXG, jupUSD, LUSD) and, less, on USDT, DAI, PYUSD and EURC. Measured with the real DEWS scorer on production inputs (internal working notes): USDz moves WATCH -> ALERT (30 -> 43), a one-off Telegram-eligible DEWS alert caused by the methodology step (the removed USDz / sUSDz pool held about 13.9 sUSDz against 1.28M USDz), and DAI, pathUSD, PYUSD, SBC, USDB and USDGLO move CALM -> WATCH. Guarding these readers on methodology basis was measured and rejected: it blinds the liquidity signal for every coin for seven days and moves six unrelated coins into ALERT through signal reweighting. - v6.91 (2026-09-28): NEAR Intents (
near-intents) joinsBLOCKED_DEX_IDSas a non-AMM venue (see Known exclusions under DEX Liquidity Score). The block applies at CoinGecko Onchain, DexScreener and GeckoTerminal discovery intake, in the stale-pool refresh candidate set, and in scoring retention. Replaying the 09:16 UTC production stage through the real scorer takes global DEX TVL from $9.195B to $8.456B (-$738.6M, -8.0%). USDC loses $383.7M (7.5%, score 73 -> 74), USDT $334.0M (8.8%, 67 unchanged) and FRAX $22.9M (29.7%, 51 -> 50). No other coin or score changes. - v6.9 (2026-09-28): Admitted-only DEX volume with a coverage floor (DEC-19 as amended). A pool's provider 24h/7d reading is admitted when observed at most 72h ago (
DEX_VOLUME_OBSERVATION_MAX_AGE_SEC, decoupled from the unchanged 24h staged TVL/price freshness window); older readings arestaleand absent onesmissing, never decayed, zero-filled or imputed. Volume Activity is admitted volume / admitted TVL and is rated only when admitted pools cover at least 50% of the coin's retained scoring TVL (DEX_VOLUME_COVERAGE_MIN, floor inclusive); below itvolumeActivityand the LiquidityScore are NR (no renormalization). A complete measured zero still scores 0 activity. Totals staynullunless the window is complete; availability records publish the observed admitted-pool volume (partialGrossUsd) withadmittedTvlUsd,retainedTvlUsdandvolumeCoverage, including on__global__. Adapters and discovery parsers without a trailing-volume field now emit a missing reading instead of an ambiguous0, and pre-cutover registry zeros count as missing. Durability volume consistency uses coverage-qualified daily turnover. NR composites propagate to the Selector, Depeg Resolver and DEWS as unavailable inputs. See Measured volume availability. - v6.8 (2026-09-23): Three DEX-liquidity corrections shipped together.
aerodrome-slipstream-100bpandvelodrome-slipstream-100bpregain the documented 30bp+ 0.4x quality multiplier (the bucket had silently fallen through to generic 0.3x since 2026-09-16, a 25% underweight; a classifier contract test now fails when a fee-bearing bucket lacks a table entry). The PancakeSwap direct fetcher is declaredbounded-sample: its per-run response (head page plus two rotating tail pages) is not an exhaustive census, so it no longer vetoes staged Pancake rows on cycle-completion runs. And an implausible cross-source price now drops only the price — the trusted value row keeps its TVL and publishes unpriced — instead of the whole hybrid view being skipped asinvalid_price(same-source rows keep the whole-row skip); the attribution and weight-capping of surviving cross-source prices index_pricesis recorded as pricing v6.31. - v6.7 (2026-09-23): GeckoTerminal and CoinGecko Onchain pool rows are now admitted only when the tracked leg's USD price stays coherent with the pool's own pair ratio × the counter-leg's USD price (
POOL_PRICE_COHERENCE_POLICY,maxPairDivergenceBps = 500, inworker/src/cron/dex-liquidity/pool-price-coherence.ts). Rows carrying the provider broken-price signature — published leg USD prices with null/zero pair-ratio inputs — are rejected with reasonpool-pair-ratio-unavailable(a usable but conflicting ratio usespool-pair-price-incoherent) before they stage any TVL, price observation, or new-pool row. The pre-ship dry-run over all 483 guard-scope challenger rows rejects 24 of them (422 admitted; no threshold tuning): the two Sophon rows (~$429K USN / ~$105K sUSN) plus 21 further provider-broken GT rows, led by gtusdc-gauntlet (100% of its DEX TVL, its only pool), usdx-hex-trust (98%), ceur-celo (92%), yusd-yieldfi (72%), vchf-vnx (70%), usr-resolv (44%, its only and already dust/stale row), frax-frax (38%), and gho-aave (26%). USN retained TVL moves ~$3.86M -> ~$3.43M (Sophon chain TVL to zero) with pool count 7 -> 5, and sUSN ~$1.99M -> ~$1.89M with pool count 1 -> 0. Every rejected row is one whose own GT numbers disagree with each other; two of them (a sui USDB/USDC row and ethereum GHO/DMusd, where both USD legs are sane and only the pair-ratio field is broken) are flagged as misjudgment risks and re-admit automatically once the provider row is internally consistent. Zero volume and zero transactions are corroboration only, never an independent rejection. - v6.6 (2026-09-21): The DEX-liquidity card's "Concentration" verdict and the exit-route crowding bands now share one recorded threshold table — High at HHI >= 0.35, Medium at HHI >= 0.18, Low below — and v6.6 records the re-basing the 2026-09-16 card/exit-route consolidation shipped without a methodology entry (the pre-consolidation card table put High at >= 0.5 and Medium at >= 0.25, so
[0.35, 0.5)moved Medium -> High and[0.18, 0.25)moved Low -> Medium). A non-finite HHI now resolves to the broadest band instead of throwing at render time; the HHI computation itself is unchanged. - v6.5 (2026-09-18): Staged pool memory moves from a single row per pool per coin carrying one source to
dex_pool_registrykeyed by (stablecoin, pool, source); every lane records its own observation and the merge resolves one view per pool — value from the highest-trust observation refreshed within 24 hours (else the freshest), family from the value's source, price from the highest-trust priced observation refreshed within 24 hours, and token-pair identity from any observation within the 14-day horizon. - v6.4 (2026-09-10): Staged pool memory extended from a 24-hour horizon (rows deleted after 30 hours) to 14 days — confidence 1.0 through the first 24 hours, then linear to 0 at 336 hours, rows deleted after 15 days — with staged price observations still pinned to rows refreshed within 24 hours.
- Runtime/version source:
shared/lib/methodology-versions/registry.ts - Public changelog route:
/methodology/liquidity-score-changelog/ - Structured changelog:
shared/data/methodology-changelogs/liquidity-score/
DEX Liquidity Score
Hourly full publication admits recovered quotes at the next :16 instead of waiting for an even hour. Source requests and the :46 reuse path keep their existing cadence. Full generation and active-target writes run every hour; each uses the existing bounded persistence buffers and retention. Public history remains one reusable daily snapshot, not an hourly series. The reviewed DEX evidence maximum age remains four hours (DEX_LIQUIDITY_EVIDENCE_MAX_AGE_SEC), and measured-history high confidence remains three hours; operational cadence changes do not tighten those scoring bounds.
Published chain-label casing
Persisted DEX rows use the canonical internal chain id from canonicalExitRouteChain() (lowercase registry/alias keys). The chain_tvl_json keys, PoolEntry.chain, and top_pools_json chain values therefore use one canonical casing per chain; aggregate stages must not re-lowercase or substitute display names. Presentation adapters map those ids to CHAIN_META display names only.
Production runs the DEX source stage hourly and keeps the consumer's paired physical trigger shape. sync-dex-liquidity-stage loads external sources and writes the exact scoring input at 10 * * * *. sync-dex-liquidity consumes that stage at 16 * * * *, publishes DEX-implied prices hourly, and publishes the composite liquidity score (0-100), score history, and active measured-execution target inventory every hour. At 46 * * * * it reuses the exact current liquidity generation for the dependent Safety Score V9 input preparation without rewriting DEX price, liquidity, history, or target surfaces, except that the hour's source stage is published instead of skipped as a cadence reuse when :16 never consumed it — a ready-but-unconsumed generation is published as-is, and a terminal or never-started one is re-run for the exact slot first. Same-hour recovery is bounded and needs no extra trigger: when the consumed slot's stage is terminal (its dex_liquidity_scoring_stages row is failed, or its stage run recorded error with no live stage lease) the :16 consumer re-runs stageDexLiquidityScoring() inline for that exact source slot under the stage job's own lease and then publishes, recording stageRecovery in its run metadata; when the stage has not appeared at all by the readiness deadline it does the same instead of failing. A producer that is still live (active lease) is never overwritten and keeps the 90-second readiness wait, and a terminal slot whose stage lease is held — or whose stage never appeared while a producer is still live — fails the run with a machine-readable reason instead of waiting out the deadline. A missing current generation bootstraps with a full publication. The split invocation remains the only entrypoint: stageDexLiquidityScoring() followed by consumeDexLiquidityScoringStage().
The source stage's single :10 physical trigger and the consumer's paired :16 + :46 physical triggers qualify each invocation for Cloudflare's hourly Cron CPU class while preserving the half-hour Safety Score V9 preparation contract. Expensive source work and hourly publication occur only at :10 and :16; :46 is a current-generation reuse path. The Worker config caps these invocations at 300 seconds. Do not recombine the :16 + :46 pair into one twice-hourly expression: Cloudflare limits Cron expressions with intervals below one hour to 30 seconds of CPU time, which is insufficient for the complete publication graph. See Cloudflare Workers limits.
Cron result status semantics:
ok: all required source families succeeded and coverage is within normal range.degraded: one or more critical non-fatal source families failed (for example DeFiLlama yields/protocol coverage), coverage falls near the guardrail band, or malformed primary-pool input rejects at least $10,000 of TVL. The near and hard coverage/value guards are evaluated on every run, including runs with a critical source failure, sosourceCoverage.nearCoverageGuard,nearValueGuard, andhardCoverageGuardalways describe the trailing comparison instead of going dark on the runs that need it most; a critical failure already forcesdegradedand skipped persistence, so it still suppresses the hard abort that would otherwise replace that metadata with a thrown error.- throw/error: catastrophic source failure (for example DL+Curve hard failure) or an internal pool-processing invariant still aborts the run when no critical source failure is already forcing
degraded.
Primary-pool processing reports expected malformed input separately from policy skips. Rejections use the bounded reason codes invalid-pool-identity, invalid-pool-tvl, and invalid-pool-volume; each reason records its full rejected count and TVL plus at most 20 pool-id samples. The fixed $10,000 degraded threshold equals the pool scoring admission floor, so malformed dust remains telemetry while the loss of any otherwise score-eligible TVL is material. Unexpected exceptions are not converted into rejections.
The stage manifest and chunk tables are dex_liquidity_scoring_stages and dex_liquidity_scoring_stage_chunks. Schema-v1 records are newline JSON in chunks capped at 192 KiB; a scheduled consumer accepts the newest ready, unconsumed manifest at or before its preferred source slot whose chunk, record, and byte totals all match and whose source slot is no more than 55 minutes old. Direct callers without a preferred slot may also reload a matching consumed generation. Chunk writes and manifest finalization are retry-idempotent under ambiguous D1 commits. The two newest ready/consumed generations are retained, while older terminal generations and nonterminal failures older than two hours are pruned. Publication completes before the best-effort consumed marker, so failure to mark an already-published generation consumed does not invalidate its output.
The schema-v1 source header carries degradedSources across the stage boundary, so chain-scoped partial-source failures from the source run remain present in the consumer's run metadata.
Direct protocol-native API outages are tracked in failedSources / fallbackMode, but they do not by themselves flip the cron to degraded when the published coverage and value guardrails stay healthy. failedSources is reserved for providers that return no usable response; a provider with partial errors and usable output records a *-partial fallback signal plus source-warning diagnostics instead. That keeps the run-level status tied to material data loss rather than optional-source turbulence.
When DeFiLlama Protocols is unavailable, protocol TVL caps cannot be computed reliably. The cron still computes diagnostics and returns degraded, but it preserves the last source-complete public dataset instead of publishing capless secondary-source liquidity. Value guard comparisons use the latest source-complete guard baseline from cron metadata when the persisted __global__ row came from a source-incomplete run, so a recovered source-complete run does not fail merely because it returns from a capless degraded baseline to the normal capped range. Coverage and value guard baselines are the median of the trailing six productive published runs (the persisted __global__/published row plus the newest productive cron rows, falling back to that row alone when no history is loaded) instead of the single previous run: a 20% slide spread over six runs never crosses a 20% single-run bound, which is how the 2026-09-04 crawl collapse published as ok while coverage fell 288 → 232.
When DeFiLlama Yields is unavailable, the cron treats value/coverage guard failures as source-incomplete degradation instead of throwing before metadata can be written. The run returns degraded, skips persistence, and keeps the last successful public dataset authoritative until a source-complete run recovers.
Run metadata now includes failedSources, degradedSources (chain-scoped partial-failure telemetry such as pancakeswap-api:bsc, written when a source completes with one of its chains failed), fallbackMode signals, bounded poolRejections and poolRejectionMateriality, staged-pool merge counters (stagedPoolsMerged, stagedPoolsSkipped, stagedPoolsSkippedByExactIdentity, stagedPoolsSkippedByUniqueDerivedIdentity, stagedPoolsSkippedByOptionalWildcardIdentity, stagedPoolsSkippedByAuthoritativeProtocol), pool-registry counters (registryRowsRead, registryMultiSourcePools, registryFamilyBySource), the v6.92 dead-pool exclusion summary retainedDeadPoolExclusions (reason dead-pool-zero-trade-untracked-counter, threshold, pool count, pre-cap TVL, top ten coins), challenger publish counters, persistence skip state, inactive tracked-asset skip counts, publication-generation diagnostics (generationId, expected/candidate/current row counts), qualityDriftCandidates (pending/confirmed drift conditions with their pre-event baseline and consecutive-run count), qualityDriftRebaselined (lasting conditions accepted as the new level on that run), and detailed sourceCoverage values (currentCoverage, previousCoverage, minExpectedCoverage, nearCoverageGuard, hardCoverageGuard, currentGlobalTvl, previousGlobalTvl, minExpectedGlobalTvl, valueBaselineSource, valueBaselineGlobalTvl, ignoredPersistedGlobalTvl, nearValueGuard, currentTop10CoveredTvl, previousTop10CoveredTvl, currentTop10GuardTvl, previousTop10GuardTvl, nearMajorCoverageGuard, currentCoverageClasses, previousCoverageClasses, priceObservationCoins, weakCoverageCoins, coinTvlStepCount150, coinTvlStepCount25, coinTvlStepTop).
Since the Liquidity Score v6 Phase 0 instrumentation (2026-08-19), run metadata additionally
carries report-only observability with no formula effect: a fixed-key fallbackCounters object on
both the stage and consume runs counting every optimistic default and silent exclusion in the
scoring path (unmeasured-balance optimism, durability neutral defaults, the TVL-depth mcap
fallback, staged-pool defaults, retained-pool exclusions, Fluid and direct-API measurement-flag
defaults). Since v6.92 the stage run also counts stagedLiveVolumeBackfill (live-lane pools that adopted
the deduplicated registry view's reading) and stagedDeadPoolPriceObservationExcluded (staged price
observations withheld under the dead-pool floor). Half-hourly measured execution admits the fresh active target inventory through its
bounded whole-coin rotation, including new and interrupted routes that need their first proof.
An exact current published measured route remains eligible for the expiry-priority reservation;
inability to load that published route set fails closed and degrades the run.
The former daily shadow admission/quote evidence ledger and 240-character mxLedger* scalar
encoding were removed; shadow target and quote generation persistence remains for compatibility.
Current-row publication is generation-gated and active-set scoped. The cron may keep historical rows for inactive tracked assets, but the public dex_liquidity current table is rewritten from the current active tracked universe plus the __global__ aggregate only. Candidate rows are written to dex_liquidity_run_rows, the expected active row count is validated, and only then is the candidate generation mirrored into the public table and marked dex_liquidity_publication_generations.state = 'published'. Rows without a publication generation id remain readable for schema compatibility, while generation-tagged rows are consumed only when their generation is published. The separate pre-v5.9 API fallback that reconstructed methodology_version from updated_at was removed in v6.0; readers pass the stored version through unchanged.
At the two largest heap seams, the source-stage handoff encodes schema-v1 JSONL through one reusable 192-KiB byte buffer and writes each completed chunk with one direct conflict-idempotent D1 statement. Durable chunk/record/byte progress is reported every 24 chunks plus the final partial interval. Candidate publication separately buffers at most 15 rows per D1 transaction and packs those rows into at most five SQL statements, with three 29-bind rows per statement staying below D1's 100-bind ceiling. These bounds avoid retaining multi-chunk native bindings or the complete payload without weakening either generation fence.
The schema-v1 source handoff carries the already-loaded primary-price map into the scoring consumer, so price publication normally does not reparse the full stablecoin cache beside the decoded graph. A supplied map is already trust-filtered and may intentionally omit assets, so the consumer does not backfill missing entries from the broader cache before applying primary-relative publication guards.
Publication retention treats an unreferenced staged generation older than three hours as abandoned, deletes its private run rows in the same bounded oldest-first passes as terminal generations, and then removes the empty ledger. Publicly referenced generations remain protected, and ledger-only public references cannot consume the private-row cleanup candidate limit.
Persistence is heap-bounded without weakening that publication fence. Candidate rows stream through 15-row buffers packed into no more than five multi-row statements per D1 transaction; history and other scoring-side writes construct and execute at most 25 D1 statements at a time; later batches are not prepared until the prior batch resolves. The schema-v1 source handoff serializes and clears measured-target maps before the larger metric/pool graph, releases every consumed source map, uses direct-buffer UTF-8 encoding without a second line array or joined payload copy, and carries the already-loaded trust-filtered primary-price map into the scoring consumer. Price observations are derived one active asset at a time, staged in generation-keyed dex_price_run_rows, and exact-count validated before one atomic D1 batch checks an in-write current-generation fence and replaces the complete dex_prices table. The consumer then clears its primary-price and exact-observation graphs before challenger publication. Challenger payloads pack multiple projected pool rows into each statement up to D1's bind ceiling while consuming the retained-pool map entry by entry; after every payload row lands, one direct json_each-driven UPSERT atomically advances all complete asset snapshot pointers and derives has_rows from the durable payload. Superseded payload cleanup remains strictly after that pointer fence. A payload or pointer interruption therefore leaves the previous challenger snapshot set authoritative instead of exposing a mixed partial refresh. The stage remains intact across ambiguous D1 retries, is removed only after the public replacement is verified, and otherwise turns over after three hours through cleanup bounded to eight generations per run; cleanup protects the in-flight generation, active staged publication work, and the generation named by the public __global__ row. Depth-stability values are likewise written only to the current generation's private dex_liquidity_run_rows rows before one generation-guarded atomic update reaches the public table. A staging or final-batch error propagates to the cron and leaves each public price/depth surface wholly on its previous generation. Retention cleanup is best-effort after publication: failures degrade telemetry but do not invalidate an otherwise successful publish, and every pass reports its cutoff, deleted count, oldest remaining row, duration, and error. Consumed scoring-stage rows are deleted by the next successful stage cleanup; abandoned writing, ready, or failed stages become eligible after two hours, while the current generation stays protected. Consumed pool/score maps are cleared as their downstream stages complete, and progress advances through generation, price, challenger, history, and depth substages so a platform interruption is attributable. After measured targets have been adjusted onto retained pools and captured for target publication, the producer target maps are cleared before any proof-heavy join evidence is loaded; this preserves the same published inventories and standalone Liquidity Score and V9 inputs without retaining a duplicate target graph at the scoring peak. Current EVM target/profile JSON is read through joined target-ID keyset pages of at most 32 rows, and each raw page is released before the next one is requested. The EVM scorer retains schema-validated profiles in serialized form, materializes proof graphs only for the target currently undergoing history and consumer validation, and attaches only proof-free public projections to retained pools. The 30-day confidence-history reader uses stablecoin/date keyset pages of at most 512 rows and releases every consumed page before requesting the next one, preserving the complete durability input without materializing the full history table beside the assembled pool graph. The main public-table mirror and generation-state transition remain one final two-statement D1 batch after exact candidate coverage validation, so partial staging never becomes current and failed publication does not advance freshness. Superseded, failed, and rejected measured-execution generations are retained for four hours — an hour above the three-hour freshness ceiling, so a profile can never read fresh after its backing rows were pruned — while completed dex_liquidity_run_rows generations no longer referenced by the public table and abandoned price run rows are retained for three hours; all are then pruned oldest-first in bounded producer-owned passes. Generation-ledger rows are removed only after their data rows are gone; measured published generations, active/incomplete work, the current liquidity generation, and any target generation still referenced by a retained quote remain protected regardless of age. Staged discovery pools retain 30 hours for the complete 24-hour scoring lookback, while provider raw_json is nulled after four hours; both passes are bounded to 1,000 oldest rows. Public dex_liquidity_history remains unchanged at 365 days. The score-bearing loader preserves the complete three-hour measured-execution history window while reading proof-heavy EVM history in sequential 16-target batches and releasing raw target, quote, and history rows as soon as each validated object is constructed. Raw producer envelopes (raw_quote_payload_json) are persisted only for failed quotes, where they are the sole structured failure evidence; measured quotes carry their complete evidence in the validated profile's quoteProof, and the score-bearing evidence loader does not select the raw column.
Measured target inventories are prepared before proof-bearing quote evidence is loaded, but remain private until the candidate passes all source, coverage, value, and major-asset guards and its full liquidity generation has persisted. A rejected candidate or failed liquidity write preserves the previously published active and shadow target catalogs. Only bounded target descriptors survive to this publication step; producer maps and quote proofs are still released during scoring. The :46 reuse pass does not publish target catalogs, and shadow targets retain their daily publication cadence. After each EVM evidence family validates its targets, the scorer immediately releases that family's target descriptors, proof profiles, and internal diagnostics while retaining the proof-free public projection, physical-pool identity, and fail-closed gate consumed by P4. Evidence maps are then cleared before the next family loads. This bounds proof-heavy object lifetime without changing validation, activation policy, target coverage, or public score inputs.
Retained-route discovery applies its current-target, last-known-good, maturity, adapter, and tracked-asset checks from compact target/history metadata before parsing serialized EVM proof profiles. Only an absent eligible route materializes a full profile, and an accepted Curve packet reuses that parsed profile during packet validation. Current measured targets therefore do not pay a second proof-materialization pass solely to establish that no retained route is needed.
| Component | Weight | Source | How Computed |
|---|---|---|---|
| TVL Depth | <!-- GENERATED-START: liquidity-tvlDepth-weight -->30%<!-- GENERATED-END: liquidity-tvlDepth-weight --> | DeFiLlama Yields | Ratio-based log-scale: 35 * log10(depthRatio / 0.0007) where depthRatio = effectiveTvl / circulatingUsd. ~0.5%->30, ~1.5%->47, ~6%->67, ~14%->80, ~25%+->90+. Falls back to 35 * log10(tvl / 700_000) (parity with ratio formula at a $1B implied reference mcap) when circulatingUsd is unavailable. |
| Volume Activity | <!-- GENERATED-START: liquidity-volumeActivity-weight -->20%<!-- GENERATED-END: liquidity-volumeActivity-weight --> | DeFiLlama Yields | Log-scale V/T ratio: 38 * (log10(vtRatio) + 3). ~0.1%->0, ~0.3%->18, ~3.5%->59, ~19%->86, ~43%+->100. Admitted 24h volume / admitted TVL; rated when the window is complete or admitted pools cover ≥ 50% of retained TVL, otherwise null and the composite is NR (DEC-19, v6.9) |
| Pool Quality | <!-- GENERATED-START: liquidity-poolQuality-weight -->20%<!-- GENERATED-END: liquidity-poolQuality-weight --> | Curve API + DeFiLlama | Venue quality retention ratio: (qualityAdjustedTvl/totalTvlUsd - 0.15) / 0.65 * 100, rescaled from 15–80% range to 0–100 (see below). The scoring component uses mechanism and balance-health retention; pair quality affects effective TVL and pool stress. |
| Durability | <!-- GENERATED-START: liquidity-durability-weight -->20%<!-- GENERATED-END: liquidity-durability-weight --> | DeFiLlama Yields + History | <!-- GENERATED-START: liquidity-durability-tvlStability-weight -->35%<!-- GENERATED-END: liquidity-durability-tvlStability-weight --> TVL stability, <!-- GENERATED-START: liquidity-durability-volumeConsistency-weight -->25%<!-- GENERATED-END: liquidity-durability-volumeConsistency-weight --> volume consistency, <!-- GENERATED-START: liquidity-durability-maturity-weight -->25%<!-- GENERATED-END: liquidity-durability-maturity-weight --> maturity, <!-- GENERATED-START: liquidity-durability-organicFraction-weight -->15%<!-- GENERATED-END: liquidity-durability-organicFraction-weight --> organic fraction (sqrt curve) |
| Diversity | <!-- GENERATED-START: liquidity-pairDiversity-weight -->10%<!-- GENERATED-END: liquidity-pairDiversity-weight --> | DeFiLlama Yields | Pool count, diminishing returns: min(100, poolCount x 5) |
Primary scoring inputs are DeFiLlama Yields API (single request for all ~18K pools) + Curve Finance API (per-chain requests for A-factor, balance data, registry IDs, and metapool structure) + Uniswap V3 Subgraph (Ethereum, Base, Arbitrum, Polygon, and Celo, plus execution-only BSC) + eight score-capable direct protocol-native fetchers (Fluid, Balancer, Raydium, Orca, Meteora, PancakeSwap V3, Aerodrome Slipstream, Velodrome Slipstream), plus the target-only BSC Uniswap V3 recovery census. The six Uniswap V3 subgraphs are the complete bounded family with at most five requests in flight. BSC rows supply measured-execution candidates only: they cannot alter fee-quality enrichment or DEX price consensus, and their targets use the pinned official factory and QuoterV2 deployment but remain excluded from score eligibility pending production shadow evidence and a separate activation review.
The classic Aerodrome V2 pairs subgraph family was removed on 2026-09-21. Its configured Base deployment was a Slipstream (V3-schema) subgraph with no pairs field, so every run since the family landed answered HTTP 200 with a GraphQL error and zero entities: the family had published nothing while reporting as a healthy empty source. Deleting it is behaviour-neutral. Aerodrome Slipstream CL pools are unaffected — they arrive through the separate direct-API Sugar reader. Classic Aerodrome rows now take their pool type from the DeFiLlama label alone.
On 2026-09-27 the pinned Base subgraph IDs were replaced after every stage/publish run since 2026-09-22 16:10 UTC (when the failed source flag first made upstream failures visible in failedSources) recorded univ3-subgraph:base and uniswap-v4-subgraph:base. The old V3 Base pin resolved to a deployment serving the Messari subgraph-standard schema (liquidityPools, no pools field), so the native-schema query could never answer; it was replaced with the "Uniswap V3 Base" network subgraph, whose TVL-desc first page holds every tracked Base stablecoin pool above the $50K observation floor. Because that deployment answers a full 1000-pool page in a measured ~8s and the family's 15s per-chain signal covers every page of a chain, the Base V3 lane reads exactly one page (UNIV3_BASE_POOL_MAX_PAGES). The old V4 Base pin had lost its serving indexers (single indexer answering non-JSON after ~15s); it was replaced with the actively curated uniswap-v4-base-3 deployment (identical pool set, ~1.5-2.7s per page). On 2026-09-28 the Celo V3 pin was replaced as well, after every run since 2026-09-22 recorded univ3-subgraph:celo. The old pin (native-schema "Uniswap V3 Celo", deployment QmXfJmxY…) reports hasIndexingErrors: true, and its three serving indexers time out, answer HTTP 400, or refuse attestation (indexing_error) for any TVL-filtered or TVL-ordered pools page; only trivial unordered reads still answer, and even a 10-row page takes ~18s. No native-schema Celo deployment serves the production query, so the lane now reads the healthy Messari-standard "Uniswap V3 Celo" subgraph (8cLf29Kx…, deployment QmNi5byc…, no indexing errors, every pool with liquidity in one sub-second page) through buildUniV3MessariPoolQuery, and UNIV3_MESSARI_SCHEMA_CHAINS normalizes each liquidityPools row back to the native pool shape before the shared mapping runs: inputTokens must be the canonical ascending [token0, token1] pair, the fee tier is the FIXED_TRADING_FEE percentage in pips, and token1Price = 1.0001^tick × 10^(decimals0 − decimals1) (within 1 bp of the sqrtPrice spot, since the tick is its floor), with token0Price its inverse; unreadable rows are dropped. That deployment's USD valuations are wrong (USD₮ at ~$4.84 and USDC at $0 on 2026-09-28), so the query neither filters nor orders by totalValueLockedUSD (it pages totalLiquidity > 0 rows by id), and a pool with a USD-reference side is valued from inputTokenBalances in reference units at the tick spot before the same $10K floor (UNIV3_POOL_MIN_TVL_USD) and the $50K price-observation floor apply. Pools without a USD-reference side keep the deployment's TVL, which only gates fee enrichment and the execution candidate; Uni V3 measured-execution targets take their retained TVL from the DeFiLlama row, never from the candidate. The family still has six sources and at most five requests in flight, so the connection budget is unchanged, and Celo Uniswap V3 rows can again resolve a QuoterV2 execution candidate instead of falling to measured-execution:target-unresolved.
Uniswap-family subgraphs publish token0Price as token0 per token1 and token1Price as token1 per token0 (the upstream Uniswap/v3-subgraph GraphQL schema, and pair.token0Price = reserve0 / reserve1 in the V2 mapping). The Uni V3 price extraction follows that convention: when token1 is the USD reference, token1Price is token0's USD price, and vice versa. Any future constant-product leg must derive its denominator the same way (reserve0 * token1Price + reserve1); the inverted form silently misprices a non-unit-ratio pair.
The direct-API pool shape carries the same orientation: DexApiPool.price (worker/src/lib/dex-api-types.ts) is the amount of token[1] received per 1 token[0]. Every producer emits it that way — PancakeSwap's subgraph token1Price, the Aerodrome/Velodrome Sugar sqrt_ratio spot price, and the BSC Uniswap V3 recovery census — and the measured-execution inventory consumes it as spotToken1PerToken0.
To bound peak heap use, the source-stage invocation runs the serialized protocol-native phase first. Each provider result is reduced to tracked pools plus compact counts and exact-key evidence before the next provider starts; measured-execution targets and authoritative confirmation are then distilled, and provider-owned pool arrays are released before DeFiLlama, Curve, or subgraph graphs are loaded. The compact score-capable direct pool list remains available for direct-preference filtering and integration. DeFiLlama and Curve requests consume JSON bodies through timeout-covered helpers with a 30-second per-attempt budget; the full DeFiLlama pool graph first supplies fallback-project evidence and the shared yield cache, then is reduced to tracked-token rows and its response wrapper is released before Curve fetching begins. The defillama-protocols cache stores only the compact slug/category snapshot needed by the yield coverage audit. Raw Curve response trees are released after their lookup maps are built.
Once primary and direct pools have been projected into metrics and identity evidence, their consumed pool/enrichment/lookup graphs are released before the exact ordered graph is written to the D1 scoring stage. The staged-discovery merge derives identity cardinalities in a first pass, then processes and releases each D1 row in original order, preserving the existing confidence, authoritative-confirmation, dedupe, Map/Set insertion order, and pool iteration order without retaining a second full staged-entry graph. DexScreener and CoinGecko-ticker discovery run only in the isolated two-hour discovery job, which persists pools and price observations for this merge; the scoring consumer performs no contract or provider fanout. Curve API enrichment is scoped to Curve DeFiLlama rows on native-covered Curve API chains, so non-Curve pools that share a token-symbol pair with a Curve pool keep their own mechanism type, balance metadata, and TVL semantics. Secondary discovery still skips Curve pools on native-covered chains to avoid duplicate Curve API coverage, but can retain Curve pools on chains the native Curve API does not cover (for example Plasma) after the same TVL, price sanity, protocol-cap, and dedupe gates as other fallback pools.
The consumer prefers direct-API pools over overlapping DeFiLlama pools via a conservative pool-identity model (exact pool id first, derived token-shape match second) before score computation, but only after those direct-API pools pass the shared TVL sanity gates used elsewhere in the pipeline. Direct-source precedence normally requires positive 24h volume; the explicit exceptions are Aerodrome/Velodrome Slipstream pools and pools with a token priceUsdDependency, which can take precedence without volume telemetry. The execution split changes only heap ownership and scheduling; scoring inputs, iteration order, publication fences, and methodology remain unchanged.
For protocol families that already have a clean protocol-native direct fetch on that chain, staged discovery also needs authoritative exact-id confirmation before it can contribute liquidity. The confirmation scope matches the inventory the native source actually covers: classic v2 pools remain eligible through exact-id staged discovery because the concentrated-liquidity native fetchers do not enumerate them. GT/CG/DS rows therefore cannot invent pools inside a clean authoritative family, even when the source emitted non-degrading parser or pagination warnings; the guard deliberately fails open only when that family source is degraded or unavailable so staged discovery can still act as recovery coverage during an upstream incident. Confirmation reads only the raw provider census, never the tracked-token subset the compaction step leaves behind in result.pools, and a census that produced no exact identity at all confirms nothing and enforces nothing.
Veto authority is a declared property of each direct fetcher (censusScope), not an inference from the chains it lists. Only an exhaustive same-run census may reject a staged pool: Balancer, Raydium, and a contiguous complete Orca run. Orca is exhaustive only when its stored tail cursor is absent or equals this run's refreshed head cursor, the run reaches the end without a page-cap break, and no degraded/error condition occurred. Finishing a resumed tail cycle does not prove the skipped middle pages absent; cycleCompleted records rotation progress, not census authority. PancakeSwap, Meteora, Fluid, Aerodrome Slipstream, and Velodrome Slipstream are bounded-sample and never veto: their bounded or filtered captures cannot establish absence. Every direct census also drops pools under the $10K direct-source floor, so staged pools below that floor are never asked for confirmation. Exact, derived, and wildcard identity dedupe still run on every staged pool; withholding veto authority cannot double-count a pool a direct source already contributed.
Dead or explicitly blocked DEX ids are excluded before they can become pool contributions. The live runtime blocklist currently includes Retro variants and Bunni variants, and those blocked venues are also ignored again during retained-pool filtering, challenger publication, and dex_prices publication for defense in depth.
Direct API Data Sources
Protocol-native DEX sources are fetched first during sync-dex-liquidity-stage, before DeFiLlama/Curve loading and Uniswap V3/V4 subgraph enrichment. Results are normalized into a shared DexApiPool type (worker/src/lib/dex-api-common.ts), token-matched against the stablecoin contract registry via canonical chain + address first, and only fall back to chain-scoped unique symbols when the upstream token is addressless. Addressed unknown tokens are dropped instead of being reinterpreted by symbol. These matched pools are then merged with primary sources.
| Protocol | API Endpoint | Chains | Pool Types | Quality Multipliers | Fields Extracted |
|---|---|---|---|---|---|
| Fluid | GET https://api.fluid.instadapp.io/v2/:chainId/dexes/stats/tickers + official DexReservesResolver on Ethereum/Arbitrum/Base/Polygon | FLUID_CHAINS in worker/src/cron/dex-liquidity/fetch-fluid.ts | fluid-dex | 0.85x | TVL (liquidity_in_usd), one-sided USD volume (normalized from base_volume / target_volume), price (last_price), balances (collateral + debt real reserves), fee (getPoolFee) |
| Balancer | POST https://api-v3.balancer.fi/ (GraphQL poolGetPools + aggregatorPools amp sweep) | BALANCER_CHAIN_MAP in worker/src/cron/dex-liquidity/fetch-balancer.ts | balancer-stable, balancer-weighted | stable 0.85x, weighted 0.4x | Exact pool address (address), TVL (totalLiquidity), volume (volume24h), price (derived from balanceUSD / balance), balances (balance, balanceUSD, weight), rate-provider rates (priceRate), fees (swapFee), stable-math amp (aggregatorPools.amp, hook-free reviewed pools only) |
| Raydium | GET https://api-v3.raydium.io/pools/info/list | Solana | raydium-clmm, raydium-amm | clmm 0.85x, amm 0.4x | TVL (tvl), volume (day.volume), price (price), balances (mintAmountA/B), fees (feeRate) |
| Orca | GET https://api.orca.so/v2/solana/pools | Solana | orca-whirlpool | 0.85x | TVL (tvlUsdc), volume (stats.24h.volume), price (price), balances (tokenBalanceA/B), fees (feeRate) |
| Meteora | GET https://dlmm.datapi.meteora.ag/pools | Solana | meteora-dlmm | 0.85x | TVL (tvl), volume (volume.24h), price (current_price), balances (token_x_amount / token_y_amount), fees (base_fee_pct + dynamic_fee_pct) |
| PancakeSwap V3 | Graph gateway -> official PancakeSwap subgraphs | PANCAKESWAP_V3_SUBGRAPHS in worker/src/cron/dex-liquidity/fetch-pancakeswap.ts | pancakeswap-v3-* | 1bp 1.1x, 5bp 0.85x, 25bp 0.7x, 30bp 0.4x, 100bp 0.25x | TVL (totalValueLockedUSD), trailing 24h volume (sum of bounded poolHourDatas.volumeUSD), price (token1Price, token1 per token0 as explained above), balances (totalValueLockedToken0/1), fees (feeTier) |
| Uniswap V3 recovery | Fresh dex_pool_registry candidates + pinned-block Multicall verification | BSC | uniswap-v3-shadow | target-only | Up to 12 exact pools per run; official factory identity and getPool binding, ordered tokens, fee, slot state, decimals, balances, and pool-implied counter-token price; never enters scoring or price consensus before activation |
| Aerodrome Slipstream | Current Base Sugar view contract (all() + tokens()) via RPC | Base | aerodrome-slipstream-* | 1bp 1.1x, 5bp 0.85x, 30bp+ 0.4x | Factory-bound CL pages, TVL (reserve-derived from tracked token prices), price (sqrt_ratio Q64.96 via sqrtRatioToSpotPrice), balances (reserve0/1), fees (pool_fee) |
| Velodrome Slipstream | Current Optimism Sugar view contract (all() + tokens()) via RPC | Optimism | velodrome-slipstream-* | 1bp 1.1x, 5bp 0.85x, 30bp+ 0.4x | Factory-bound CL pages, TVL (reserve-derived from tracked token prices), price (sqrt_ratio Q64.96 via sqrtRatioToSpotPrice), balances (reserve0/1), fees (pool_fee) |
PancakeSwap and Orca return pending cursor updates rather than writing progress inside their fetchers. The source-stage owner acknowledges them only after registry writeback succeeds and the complete scoring stage is ready. Registry/stage failure or interruption before acknowledgement leaves the prior tail retryable; data-first replay is idempotent. Acknowledgement compares the captured cursor revision and run-start clock, with a per-attempt token preventing same-second stale attempts or cursor-cycle ABA overwrites. Read failures withhold acknowledgement. Failed head pages preserve the previous tail; a failed tail is retried. Malformed optional Raydium poolMeta falls back to ordinary AMM classification instead of aborting the provider run.
All direct fetchers now surface partial/total upstream failure explicitly to the cron, use circuit breakers (CIRCUIT_SOURCE.FLUID_DEX_API, BALANCER_API, RAYDIUM_API, ORCA_API, METEORA_API, PANCAKESWAP_API, AERODROME_SLIPSTREAM_API, VELODROME_SLIPSTREAM_API, UNISWAP_V3_BSC_SHADOW), and apply min TVL thresholds ($10K for liquidity inclusion or shadow target admission, $50K for score-capable price observations). The BSC Uniswap V3 recovery path performs one bounded D1 query, one block pin, at most two 60-call state Multicalls, and one factory-binding Multicall; it serializes with the other direct sources and its target-only rows neither establish authoritative liquidity precedence nor enter scoring. Runtime parsing no longer learns new token ownership from DeFiLlama or subgraph symbol strings, so the canonical tracked-token registry is immutable during a run. Each serialized provider result is normalized and filtered one pool at a time before the next provider starts, avoiding a second full normalized pool graph while preserving raw coverage evidence. Slipstream reads the current Sugar registry, resolves the live V2 and reviewed CL factory counts, begins at the CL boundary instead of scanning the preceding V2 inventory, and fails closed on incomplete pages or factory drift. all() rows are projected page by page to the nine fields consumed downstream, filtered to tracked-token pools, and token metadata is fetched in bounded custom-address batches that retain only address, symbol, and decimals. Staged Slipstream recovery also calls the reviewed factory's getPool(token0, token1, tickSpacing) and requires the candidate address; a candidate's own factory() assertion is insufficient. Factory lookup failures reject recovery. Fresh direct-API Slipstream writeback carries a producer-owned factory-review version; legacy Slipstream writeback without that version remains in inventory but cannot reenter scored metrics. Slipstream spot conversion preserves the human-unit price by applying the token-decimal scale without truncating tiny raw ratios first; this lets an 18-decimal tracked token paired with a strongly priced 6-decimal token derive its missing side, while pools with no priced anchor still fail closed. When both DL and a direct API cover the same physical pool, the direct API data is preferred only when the identity match is exact or uniquely derived and the direct source meets the precedence policy above (positive 24h volume or the explicit Slipstream/priceUsdDependency exceptions); ambiguous same-pair pools remain separate instead of being collapsed. The dedupe index now also reserves every authoritative direct-API exact pool id for later staged/fallback exact-match checks even when that direct row falls below the scoring floor, so discovery sources cannot re-add the same address with incompatible TVL semantics. Direct-API pools now use a conservative default maturity of 30 days unless the source provides stronger evidence. PancakeSwap subgraph fetches preserve valid zero-decimal token metadata, parse the raw body before surfacing a failure so HTML/plaintext upstream regressions are recorded as explicit invalid-json diagnostics instead of opaque parser crashes, and sum a bounded trailing window of official poolHourDatas rows instead of reading the latest UTC day bucket. Direct-API chain failures are first-class telemetry without changing circuit-breaker semantics: a provider reports the chains whose capture failed, so a source that still returns usable output from its other chains records a degradedSources entry (pancakeswap-api:bsc) alongside its *-partial fallback signal instead of leaving the failure in free text, and every configured protocol:chain the fetch phase attempted receives an explicit acceptedByProtocolChain entry including zeros, so a silently absent chain reads pancakeswap:bsc: 0 rather than disappearing from the map. PancakeSwap subgraph requests now let the retrying helper own the per-attempt 15-second budget and pass only the provider signal as parent: the previous pre-armed outer abort failed attempt 1 and rethrew straight past the retry loop, so the two retries never ran and one slow BSC head response hard-failed that chain every hour, while the provider signal still bounds the chain as a whole.
Every provider body the DEX source stage reads is now explicitly capped instead of inheriting the shared 16 MiB retry default. Caps were sized on 2026-09-23 by calling the same public endpoints with the repository's own query and page parameters: DeFiLlama Yields 11,816,252 B (17,188 pools) under a 16 MiB cap, DeFiLlama Protocols 8,911,702 B under 12 MiB, the largest Curve chain body 4,806,780 B under 8 MiB, Raydium pageSize=1000 pages 2.0-2.1 MB under 8 MiB, Meteora page_size=500 995 KB under 4 MiB, Orca size=200 672 KB under 4 MiB, Balancer's 1,000-row list page ~0.74 MB under 4 MiB, Fluid's largest ticker list 15 KB under 256 KiB, and the 1,000-row Uni V3 / Uniswap V4 / PancakeSwap subgraph pages under 8 MiB. The Graph gateway refused an uncredentialed measurement, so those three page caps are justified against the measured page budget of the same 1,000-row shape. An over-cap body is cancelled rather than parsed: fetchSubgraphEntities() reports failed: true with the machine-readable failureReason: "body-over-cap", readDexApiJson() returns "<context> response body exceeded <n> bytes", and Fluid records fluid <chain> response body exceeded <n> bytes, so the affected source degrades through its existing accounting and never publishes a short pool list. Full table and overflow contract: Response-Body Limits.
PancakeSwap and Orca refresh the highest-TVL head on every run and continue a bounded tail from dex_source_pagination_state. PancakeSwap keeps an independent offset cursor for Ethereum and Base; Orca keeps its opaque API cursor. The BSC PancakeSwap V3 subgraph was removed from the direct fetch on 2026-09-18 after persistent head-page timeouts; BSC pools continue through CoinGecko Onchain and discovery, outside the direct adapter's supportedChains. Completing a tail cycle resets the pending cursor to the first tail page. Orca retains the attempted far-tail cursor across transport, rate-limit, 5xx, and malformed-response failures, restarting from the refreshed head only for explicit cursor rejection (400/404). PancakeSwap retries the failed tail page rather than skipping it. Healthy budget truncation is pagination.state = "partial", while cycle completion remains distinct from same-run census completeness. Pending updates are acknowledged at the durable stage commit point described above; cursorPersistence in the stage-run metadata reports bounded acknowledgement failures, which leave the stored cursor retryable and degrade that stage run with reason pagination-cursor-acknowledgement-failed. Missing mandatory pagination tables fail the read rather than impersonating an empty initial cursor.
Since 2026-09-23 the PancakeSwap slot is declared censusScope: "bounded-sample": that BSC removal re-enabled census enforcement on otherwise clean Ethereum/Base runs, and a cycle-completion run's response holds only the head plus the last two tail pages, so enforcing its exact-key set vetoed every staged Pancake pool located in pages read on earlier runs. The rotating response therefore carries no veto authority; Balancer and Raydium retain theirs, while Orca retains authority only for the contiguous same-run census described above.
Balancer direct fetches now take exact identity from the API's address field. The GraphQL id remains the 32-byte vault pool id, but exact-address dedupe and authoritative staged-pool confirmation both key off the true pool address.
During the source-stage cron, the serialized direct API phase completes and releases provider-owned payloads before DeFiLlama/Curve loading and Uniswap V3/V4 subgraph enrichment begin, so those fetch families do not overlap inside Cloudflare's per-trigger connection budget. Fluid resolver enrichment and Slipstream Sugar reads use the scheduled runtime's configured chainRpcs map (Alchemy/dRPC when configured) instead of relying on module-level public RPC defaults.
Balancer, Raydium, Orca, and resolver-backed Fluid pools now preserve richer metadata through top_pools_json: measured balanceRatio, per-token balanceDetails, and normalized feeTier badges in basis points. Balancer weighted pools compare actual USD composition versus target token weights before deriving balance health; Raydium and Orca derive inventory balance from token balances plus per-token USD prices; Fluid derives inventory from the official DexReservesResolver by summing collateral and debt real reserves per token. Fluid pools on chains without that resolver deployment, or on any chain where token decimals cannot be resolved safely, fall back to neutral balance.
Large retained pools (> $100M TVL) must clear the $50K 24-hour volume floor with an admitted reading. Since v6.9 an unmeasured, absent or stale (older than the 72h admission window) reading does not clear it, and the vol/TVL > 50 sanity exclusion evaluates only admitted readings. After pool filtering and protocol-level TVL caps are applied, the scorer rebuilds every aggregate (total_tvl_usd, effective_tvl_usd, balance/organic/stress weights, protocol/chain breakdowns, and source-family mix) from the retained pool set before computing the final score. It summarizes the 24h and 7d volume windows (total_volume_24h_usd, total_volume_7d_usd, total_volume_7d_measured, volume_availability_json) over that same full retained set under the DEC-19 contract in Measured volume availability. The public API therefore renders any incomplete window as null beside its availability record instead of presenting partial or unknown coverage as $0. Filtered or capped pools cannot continue influencing the score through stale pre-filter aggregates. The top-asset recovery guard keeps raw top-10 covered TVL visible but discounts previous rows whose raw TVL was dominated by near-zero effective liquidity before applying near/hard guard thresholds. The strict cap now targets the inflation-prone secondary discovery families (cg_onchain, gecko_terminal, dexscreener, cg_tickers, horizon) rather than clipping direct_api pools by default, so legitimate protocol-native liquidity is less likely to be suppressed by stale DefiLlama protocol ceilings.
Known exclusions. BLOCKED_DEX_IDS in worker/src/lib/dex-cron-constants.ts is the one exclusion list. It covers dead venues (Retro, Bunni and its chain-scoped variants) and, since v6.91, NEAR Intents (near-intents), which is a non-AMM venue. NEAR Intents is an intent-settlement verifier (DefiLlama lists it as a bridge). GeckoTerminal and CoinGecko Onchain report each of its "pairs" with the intents.near contract's shared custody of the quote asset as reserve, so the TVL is not executable depth for the tracked stablecoin. Evidence from 2026-09-28: the FRAX / wNEAR pair reported $120.7M against 22.98M wNEAR held by intents.near (≈ $120M at $5.29). The contract held 3.38 FRAX, and the whole bridged FRAX supply on NEAR was about 237.7K. Blocked ids are rejected at every intake: the GeckoTerminal crawl, the CoinGecko Onchain admission policy shared by the token-pool crawl and the stale-pool refresh, and the DexScreener crawl, which drops them before its deployment-census count. They are also never stale-refresh candidates, and filterRetainedPools drops any remaining registry row (retainedExclusionBlockedDex). They therefore reach no coin or global TVL, pool count, exit route, challenger snapshot or DEX-implied price.
The large-pool floor reads the retained pool's scoring TVL, which for a staged registry row is the raw TVL after freshness-confidence decay. A stale row above $100M therefore passes once decay takes it below the threshold, and is excluded again as soon as a refresh restores full confidence. On 2026-09-28 this is how the FRAX / wNEAR NEAR Intents row moved: it was counted at a decayed ~$80M until the 04:09 UTC discovery run refreshed it to $120.7M with $3.27 of 24h volume. The 04:17 UTC run then excluded it under this floor (retainedExclusionLargePoolLowVolume 67 → 68, FRAX $157.3M → $77.2M) before v6.91 blocked the venue outright.
Dead-pool floor (v6.92). filterRetainedPools (worker/src/cron/dex-liquidity/scoring-helpers.ts) drops a retained pool when all of these hold: its scoring TVL is at least DEX_DEAD_POOL_TVL_MIN_USD ($1M, inclusive, in shared/lib/dex-volume-availability.ts); its admitted (in-window) 24h reading is 0; and the staged merge stamped that reading with the dead-pool signature. The merge (hasDeadPoolSignature in staging-merge.ts) signs a registry view when all of these hold: the resolver's volume row is a zero from a DEX_VOLUME_TRADE_VERIFIED_ZERO_SOURCES source (cg_onchain only: cgPoolVolume24hReading stores 0 only for an explicit zero volume with zero 24h buys and sells, and earlier CoinGecko Onchain zeros keep their v6.9 exempt provenance); the chain is one on which CoinGecko demonstrably indexes trades, meaning at least one cg_onchain row read by the same merge on that chain carries a positive 24h volume observed within the 72-hour admission window (collectTradeIndexedChains); the view's coin owns one leg through the pipeline's chain-address index (buildSymbolLookups().chainAddressToId, contracts plus traded contracts of active coins); and no other leg is in that index. CoinGecko publishes explicit zeros with zero trades for networks it lists but does not index: on 2026-09-28 /onchain/networks/hydration/pools returned no pools, all 15 Hydration cg_onchain rows were zero, and DeFiLlama reported $3.32M of weekly Hydration volume. Without the chain gate the real HOLLAR Omnipool, 2-Pool and aToken pools would be excluded permanently. Missing token addresses, an unmapped own leg, a missing, stale or positive reading, and GeckoTerminal, DeFiLlama or direct-API zeros never produce the signature. The large-pool floor runs first, so a zero-volume pool above $100M keeps counting under retainedExclusionLargePoolLowVolume. The same predicate (isDeadPool) withholds the staged price observation of a signed view at the merge clock, and published DEX-implied prices and challenger snapshots are rebuilt from the retained pools, so a screened pool carries no price weight anywhere. The signature rides on the internal volumeReading (never published) through the scoring stage, whose payload version is 3 since v6.92: a v2 stage has no signatures and is rejected, never scored without the floor under a 6.92 label.
The floor targets single-sided junk pools. An operator seeds a few dollars of a tracked stablecoin beside billions of an unlisted token, the seed fixes the pool price near $1, and the provider reports the unlisted side at that price as reserve. On 2026-09-28 eth_call balanceOf reads found $5.32 of USDC in Base AUSTRIA / USDC (reported $22.55M), $245 of USDT in Polygon UBS / USDT ($100.34M) and $0.03 of DAI in Base DAI / PTTO ($99.97M), each with zero 24h trades. Review spot checks of the replay's dropped pools found the same pattern on the remaining counters: Blast USD+ / USDB held 7.43M USD+ (a token whose totalSupply() returns 0 and which has no price) against 1.04 USDB, Celo USDN / USDGLO held 5.0M of an unpriced "USD Network" token against 0.001 USDGLO, and Base USDz / sUSDz held 1.28M USDz against 13.9 sUSDz. The counter-token gate keeps quiet stable-to-stable pairs (RLUSD / USDS, AUSD / USDe, savUSD / avUSD); dropping every trade-verified zero regardless of counter-token was rejected because it removes real pools and pushes savUSD, sUSN and mTBILL below the volume-coverage floor. A screened pool counts again on the first day CoinGecko reports a trade. Known residuals: real pools against stables Pharos does not catalog are screened while they do not trade (EURI / EUR on BSC, against an unlisted EUR token, is the one uncertain case in the 2026-09-28 replay); zero-volume pools under $1M, DeFiLlama-sourced zero pools (BMD-USDC, PORT3-USDT) and pools with one or two wash trades a day are not screened; a screened pool whose CoinGecko reading ages past 72 hours without a refresh returns as unmeasured TVL until the 14-day registry horizon drops it. Latent classes not seen in the replay: major non-stable counter-tokens (WETH, WBTC) get no protection, so a quiet WETH pool with a trade-verified zero is screened like junk; multi-asset pools are checked on the registry's base/quote pair only (a Balancer USDC.e / USDT / sDAI / sBAL3 pool whose base is uncataloged would be signed for USDT); and a cataloged coin's deployment that is missing from contracts/tradedContracts (USDe on Tempo appears only in its risk review) is treated as untracked. Admission-time rejection at discovery is a separate change.
Live-lane volume backfill (v6.92). When a live-lane pool (Aerodrome/Velodrome Slipstream through Sugar, or any DeFiLlama/direct-API entry) carries no volume reading and mergeStagedPools dedup-skips the registry view with the same coin and exact pool id, the live entry adopts the view's reading (value, observation clock and dead-pool signature), the same resolver choice a registry-only view already gets. Before v6.92 the stale-pool refresh wrote CoinGecko Onchain readings for Slipstream pools (PR #1233) that the resolver attached to the view, but the live entry won the dedup and kept its missing reading. A live pool that measured its own volume keeps it. Staged merge runs after the live-lane write-back snapshot, so an adopted reading is never written back. On the 2026-09-28 stage 23 live pools adopted a reading (20 positive), mostly DeFiLlama and direct-API pools; that alone rates moveUSD (NR -> 22, at exactly the 0.5 coverage floor) and moves msUSD 69 -> 70 and AUDD 48 -> 49. The eight Base country-flag Slipstream pools ($188.6M of USDC TVL) were already dropped through the registry-merge signature path on that stage; the backfill closes the same gap whenever the live lane wins the dedup.
dex_pool_registry is the handoff point for per-source pool observations: keyed by (stablecoin, pool, source), it holds one row per lane per pool — CoinGecko Onchain, GeckoTerminal, DexScreener, CoinGecko Tickers, and the live-lane write-back each record their own observation instead of contending for a single row. The source-stage cron validates every observation source-locally, groups survivors by (stablecoin, pool), and resolves one view per pool before merging: value from the highest-trust observation refreshed within the last 24 hours (else the freshest observation of any source, using the existing source-family trust order), family from the value's source, price from the highest-trust priced observation refreshed within 24 hours, and identity metadata (token pair) from the highest-trust observation carrying it within the 336-hour horizon, so derived dedupe is lane-symmetric. Rows refreshed within the last 14 days contribute — confidence 1.0 through the first 24 hours, then linear decay to 0 at 336 hours — and the merge gracefully falls back to primary-only scoring input when the registry table is absent or empty. Rows older than 24 hours still contribute decayed TVL but no price, and rows older than 72 hours no admitted volume: price observations stay pinned to rows refreshed within 24 hours, and the pool's volume reading comes from the highest-trust row with a reading refreshed within 24 hours, else the highest-trust row inside the 72-hour volume admission window, else the freshest row with any reading (classified stale), and is never scaled by the decay (DEC-19, v6.9). Network discovery remains isolated from source construction, while the second invocation separates the completed aggregation graph from all provider responses before proof-heavy scoring and publication; the post-merge phase keeps only the bounded direct-CEX orderbook telemetry probe. The :10 source stage also writes its own live-lane observations (DeFiLlama, direct-API, and subgraph pools with a trustworthy exact id) back into dex_pool_registry after the merge, so those pools survive a provider outage the way discovery-sourced pools do. The write-back set is snapshotted before the merge, so rows the merge itself backfilled are never re-stamped and a pool that stops being observed ages out on the same decay curve instead of staying fresh forever; rows the live lane already refreshed within the last 4 hours are left untouched (the no-op update writes zero rows). Because source is part of the key, a live-lane observation can never relabel or overwrite a discovery observation; a write-back failure is logged without failing the source stage because publication depends on the merge alone.
Staged rows with non-finite, negative, or impossible pool TVL above the discovery sanity ceiling are rejected before persistence and skipped again at scoring merge time. Secondary-source rows with a measured tracked-token price must also pass the same peg-aware DEX observation sanity gate used for price publication before their TVL can be staged or merged. Carbon DeFi chain-suffixed provider ids also normalize to the DefiLlama carbon-defi protocol cap. These gates prevent one malformed secondary-source reserve or token-price field from poisoning coin-level TVL, global TVL, or CPU-heavy downstream diagnostics.
Both GT-shaped admission paths — the GeckoTerminal crawl (crawlTokenPools) and the CoinGecko Onchain discovery stage — additionally apply the pool-price coherence gate before a row can stage, price, or publish. The owning policy, thresholds, and rejection vocabulary are documented under Discovery Cron.
Shared source-specific helpers now own the duplicate discovery/liquidity normalization rules:
- GeckoTerminal request construction, bounded pagination, pool parsing, and pool-type normalization:
worker/src/cron/dex-liquidity/geckoterminal-shared.ts - CoinGecko onchain parsing, fee-bucket classification, balance-ratio inference, and locked-liquidity parsing:
worker/src/cron/dex-liquidity/coingecko-onchain-shared.ts - CoinGecko tickers filtering, exchange aggregation, synthetic orderbook TVL, and price-observation gating:
worker/src/cron/dex-liquidity/coingecko-tickers-shared.ts - Direct EVM recovery queries, ERC-20 calls/decoding, and fixed-point conversion:
worker/src/cron/dex-liquidity/staged-pool-recovery.ts; callers retain protocol validation, block/retry choices, and failure handling.
GeckoTerminal and CoinGecko Onchain share the GT-shaped pool field projection. Volume parsing and createdAt defaults remain in their provider wrappers.
Data sources are split across three scheduled phases: discovery sources (CoinGecko Onchain, GeckoTerminal, DexScreener, CoinGecko Tickers) run on 6 */2 * * * and write dex_pool_registry; source loading and pool construction run hourly at 10 * * * * and write the bounded scoring-stage generation; the 16 * * * * consumer publishes both prices and liquidity scores every hour, while the retained :46 slot avoids publication rewrites.
See the Discovery Cron section below for the full discovery pipeline architecture.
Quality Multipliers (v2)
| Pool Type | Multiplier | Detection |
|---|---|---|
| Curve StableSwap A>=500 | 1.0x | registryId not containing crypto + A>=500 |
| Curve StableSwap A<500 | 0.85x | registryId not containing crypto + A<500 |
| Curve CryptoSwap | 0.5x | registryId containing crypto/twocrypto/tricrypto |
| Uniswap V3 1bp | 1.1x | fee tier <= 100 |
| Uniswap V3 5bp | 0.85x | fee tier <= 500 |
| Uniswap V3 30bp+ | 0.4x | fee tier > 500 |
| Fluid DEX | 0.85x | project contains fluid |
| Aerodrome Stable (sAMM) | 0.85x | direct-source label only; DeFiLlama rows never resolve here |
| Aerodrome Volatile (vAMM) | 0.4x | project contains aerodrome, no concentrated-liquidity tag |
| Balancer Stable | 0.85x | project contains balancer + stable pattern |
| Balancer Weighted | 0.4x | project contains balancer, non-stable |
| Raydium CLMM | 0.85x | direct API type, or DL poolMeta marked Concentrated |
| Raydium AMM | 0.4x | standard AMM, wider spreads |
| Orca Whirlpool | 0.85x | concentrated liquidity (direct API or DL) |
| Meteora DLMM | 0.85x | protocol contains meteora |
| PancakeSwap V3 1bp | 1.1x | protocol contains pancakeswap + fee tier <= 1 bp |
| PancakeSwap V3 5bp | 0.85x | protocol contains pancakeswap + fee tier <= 5 bp |
| PancakeSwap V3 25bp | 0.7x | protocol contains pancakeswap + fee tier <= 25 bp |
| PancakeSwap V3 30bp | 0.4x | protocol contains pancakeswap + fee tier <= 30 bp |
| PancakeSwap V3 100bp | 0.25x | protocol contains pancakeswap + fee tier > 30 bp |
| Aerodrome Slipstream 1bp | 1.1x | protocol contains aerodrome-slipstream + fee tier <= 1 bp |
| Aerodrome Slipstream 5bp | 0.85x | protocol contains aerodrome-slipstream + fee tier <= 5 bp |
| Aerodrome Slipstream 30bp+ | 0.4x | protocol contains aerodrome-slipstream + fee tier > 5 bp |
| Velodrome Slipstream 1bp | 1.1x | protocol contains velodrome-slipstream + fee tier <= 1 bp |
| Velodrome Slipstream 5bp | 0.85x | protocol contains velodrome-slipstream + fee tier <= 5 bp |
| Velodrome Slipstream 30bp+ | 0.4x | protocol contains velodrome-slipstream + fee tier > 5 bp |
| Generic AMM | 0.3x | fallback |
| Orderbook | 0.6x | CoinGecko tickers fallback (centralized exchange, no AMM) |
Direct concentrated-liquidity fetchers (PancakeSwap V3, Aerodrome/Velodrome Slipstream) derive their -1bp/-5bp/-25bp/-30bp/-100bp bucket from the fee the source actually decoded. When the upstream fee is absent or out of range — a missing or malformed subgraph feeTier, a Sugar pool_fee outside [1, 10000] bps — the row keeps feeRate: null and publishes the neutral <protocol>-unknown-fee bucket (CL_UNKNOWN_FEE_BUCKET in worker/src/cron/dex-liquidity/direct-source-helpers.ts) instead of a fabricated tier, so the bucket never contradicts the row's own null fee. That bucket carries no tier multiplier and falls through to the generic 0.3x weight; pool-shape family and measured-execution targeting are unchanged.
Pool Quality Adjustments
- Balance health: Continuous
Math.pow(balanceRatio, 1.5)instead of binary threshold - Pair quality: Co-token scored using Pharos governance classification (CeFi->1.0, DeFi->0.9, CeFi-Dep->0.8) + static map for volatile assets (WETH->0.65, WBTC->0.6, unknown->0.3). Known quote aliases such as
USD₮0,USDT0,aUSDC,aUSDT,USDbC, and.ebridged variants are normalized to canonical symbols before scoring. Composite Curve LP aliases such as3CrvandFRAXBPinherit the best score from their underlying stablecoin basket. Multi-asset pools use best co-token score - MetaPool TVL dedup: Uses
usdTotalExcludingBasePoolto prevent double-counting base pool liquidity across ~322 Curve metapools - Effective TVL:
poolTvl x mechanismMultiplier x balanceHealth x pairQuality, summed across all pools
For direct APIs, balance health is no longer uniformly neutral. Balancer, Raydium, Orca, and resolver-backed Fluid pools now contribute measured balance ratios when their APIs provide enough token-balance and pricing context. Fluid pools still default to 1.0 balance when the official resolver is unavailable or token decimals cannot be resolved safely.
Data Quality Filters
isBroken === trueCurve pools: skipped- Dead/rugged/deprecated protocols: excluded from
dexProjectsset and the explicit runtime blocklist (currently including Retro and Bunni variants) exposure === "single"pools (lending deposits, not DEX liquidity): skipped- CryptoSwap pools: correctly classified via
registryId - DL Raydium pools: classified from
poolMeta(Concentrated ->raydium-clmm) since v6.0, because DeFiLlama ships every Raydium pool under theraydium-ammproject slug; this lets the DL row and its direct-API CLMM twin share one pool-shape family and deduplicate instead of double-counting
Known Uncovered Venues
Venues that qualify for the score on the criteria above but that no configured provider indexes. Listed so a coverage gap is a recorded decision rather than a silent omission.
| Venue | Chain | Why uncovered | Revisit trigger |
|---|---|---|---|
Jupiter Lend DEX (jupiter-lend-dex) | Solana | A concentrated-liquidity AMM on Jupiter's shared liquidity layer, distinct from the jupiter-lend lending protocol and classed Dexs by DefiLlama. DefiLlama Yields ships no rows for it, and GeckoTerminal and DexScreener do not list it, so neither the dl lane nor discovery can see it. Jupiter publishes no REST endpoint — developers.jup.ag/docs/lend/dex/api.md is an explicit placeholder — so ingesting it means getProgramAccounts plus borsh decoding at IDL-derived offsets inside the source-stage cron, reopening the Solana on-chain lane that v6.0 retired and adding an RPC dependency inside the 6-connection budget. Protocol-wide TVL is ~$7.0M, so present score impact is small. | DefiLlama Yields adding jupiter-lend-dex pools, or Jupiter shipping the promised REST endpoints. Either collapses this to a cheap change. Raised as #880. |
| All chain DEX venues (provider gap) | XRP Ledger | 14 provider_inaccessible deployments / 12 coins (rlusd, usdc, ousg, tbill, eurcv...). CoinGecko Onchain exposes an xrpl network, but the registry registers no XRPL token-pool provider; the onchain pool API is EVM-contract-shaped and the chain's issued-currency order books need a native reader. | A public rippled DEX/AMM provider (largest single uncovered cluster). |
| All chain DEX venues (provider gap) | Stellar | 10 unsupported deployments. Horizon discovery covers classic-asset AMM pairs only; the Soroban census is intentionally bounded to the eight reviewed Spiko identities via Aquarius. CoinGecko Onchain exposes a stellar network but is not registered (same EVM-shape constraint). | Promoting Aquarius beyond supplemental or adding a Stellar Expert/AMM crawler. |
| All chain DEX venues (provider gap) | Starknet | 13 unsupported deployments; GeckoTerminal is the chain's only registered pool provider and its token-id padding path is unproven for these assets. CoinGecko Onchain exposes starknet-alpha, unregistered for the same reason. | A reviewed GeckoTerminal Starknet census or a native Starknet DEX provider. |
| All chain DEX venues (provider gap) | Hedera | 3 unsupported deployments; entity-id vs solidity-alias address handling is unresolved and no pool provider is registered. CoinGecko Onchain exposes hedera-hashgraph, unregistered. | An HTS token audit plus a mirrored-token DEX provider. |
| Tezos DEX venues beyond uUSD | Tezos | Only the TzKT uUSD census is registered; 5 unsupported rows remain. CoinGecko Onchain has no Tezos network (verified across the full /onchain/networks catalogue), so no CG mapping exists to add. | Youves/other Tezos AMMs shipping a queryable token-pool endpoint, or CG adding a Tezos network. |
| All chain DEX venues (provider gap) | Polkadot | 2 unsupported deployments; no registered provider, CoinGecko Onchain has no Polkadot network (verified), and DexScreener reports Polkadot as an unresolved chain. | A native Polkadot Asset Hub DEX provider or CG network addition. |
| Tempo Stablecoin DEX | Tempo | The enshrined orderbook exchange at 0xdec0000000000000000000000000000000000000 routes OUSD through pathUSD; it is distinct from the non-trading Fee AMM. Reviewed 2026-09-30: the GeckoTerminal OUSD pool endpoint returns 404 and the DexScreener lookup returns no pools. Pinned eth_call reads through https://rpc.tempo.xyz at block 41,988,508 (observed 2026-09-30 18:49:54 UTC; small quotes checked at 19:02:11 UTC) resolve the OUSD/pathUSD book: 0.01 OUSD quotes 0.009999 pathUSD, while the tested 0.1 OUSD and larger notionals through 25 million OUSD revert with the exchange's insufficient-liquidity error (0xbb55fd27). This snapshot establishes a book and a small quote, not deep executable liquidity. Tempo has CG/GT chain mappings but no DexScreener mapping or native orderbook reader. About 91% of OUSD supply is on Tempo; this is deployment materiality, not exchange liquidity. Base Uniswap and Solana Orca pools remain discoverable by exact contract. | A configured provider indexes the Tempo exchange, or a reviewed native orderbook integration supplies price, depth, and volume within the existing connection budget. |
CoinGecko Onchain Integration
CoinGecko Onchain is a discovery-stage source rather than a direct source-stage fetch. Its outputs are written into dex_pool_registry and later merged by sync-dex-liquidity-stage if the rows are fresh. Rows refreshed within 14 days count for scoring; rows older than 24 hours contribute decayed TVL but no price, and rows older than 72 hours no admitted volume. Pool parsing, fee-tier classification, balance-ratio inference, and locked-liquidity parsing are shared between discovery and liquidity through worker/src/cron/dex-liquidity/coingecko-onchain-shared.ts. CoinGecko Onchain and GeckoTerminal token crawls now read multiple bounded pages (3 x 20 rows max) before declaring discovery exhausted, which reduces false partial-coverage outcomes on fragmented assets.
Chain resolution is registry-backed in worker/src/lib/chain-registry.ts: the worker keeps one canonical internal chain id per deployment (bob, worldchain, plasma, etc.) and maps it to provider-specific network slugs (bob-network, world-chain, plasma, ...). When COINGECKO_API_KEY is configured, pool discovery uses CoinGecko /onchain for chains with a coingecko mapping and still runs GeckoTerminal for chains that only have a geckoTerminal mapping. This avoids the old all-or-nothing mode switch where enabling CoinGecko could silently drop GT-only chains.
Every canonical chain whose only pool providers were GeckoTerminal + DexScreener now carries a verified CoinGecko Onchain network id in CHAIN_META (shared/types/chain-identity.ts), verified live against the paginated /onchain/networks catalogue: plume → plume-network, hyperevm → hyperevm, monad → monad, plasma → plasma, linea → linea, sei → sei-network, katana → katana, scroll → scroll, worldchain → world-chain, unichain → unichain, mantle → mantle, manta → manta-pacific, mode → mode, taiko → taiko, blast → blast, bob → bob-network, megaeth → megaeth, sonic → sonic, zksync → zksync. None of the candidate chains lacked a CoinGecko network. Spot checks confirmed real token-pool rows for monad (USDe UniV4 pools) and katana (yUSD SushiSwap v3 pools); CoinGecko's pool index on plume returned 404 for both sampled deployment tokens, so GeckoTerminal and DexScreener remain load-bearing there until CoinGecko indexes more Plume pools. Adding these mappings also moves the chains out of GT_ONLY_CHAIN_MAP and registers the coingecko provider in DEX_DISCOVERY_PROVIDER_REGISTRY, so their deployments are no longer bounded-crawl misses when CoinGecko is the only responsive provider.
| Feature | GeckoTerminal (fallback) | CoinGecko Onchain (paid) |
|---|---|---|
| Rate limit | 30 req/min | ~240 req/min |
| Chain coverage | Registry-backed GT network slugs for canonical chains, including slug aliases such as bob-network, manta-pacific, and world-chain | Registry-backed CG network ids for chains with explicit CG support; GT-only chains still flow through GeckoTerminal in the same run |
| Balance data | Not available (defaults to 1.0) | Approximated from token prices |
| Fee tier | DEX-prefix lookup only | pool_fee_percentage field |
| Locked liquidity | Not available | locked_liquidity_percentage field |
The CG integration extracts three signals unavailable from GeckoTerminal:
- Balance ratio approximation: Computed from
base_token_price_usd/quote_token_price_usdfor stable pairs. Feeds intobalanceHealth,balanceRatioWeightedSum, and pool stress. - Fee tier classification:
pool_fee_percentageenables proper quality multipliers for non-Uniswap concentrated liquidity pools (PancakeSwap V3, SushiSwap V3, etc.). - Locked liquidity: Persisted for pool-quality context and API observability, but not currently included in the live durability score.
DexScreener Discovery
DexScreener runs in the isolated sync-dex-discovery cron and populates dex_pool_registry for later merge during scoring. The discovery router queries all tracked deployments when earlier CoinGecko/GeckoTerminal stages find no pool, and otherwise queries only chains those providers do not cover. This covers 30+ chains including Solana, Berachain, Monad, MegaETH, Plume, and other exotic chains without loading per-contract responses into the scoring isolate. One run-scoped state gates the provider once, records one aggregate outcome under dexscreener-liquidity for the full discovery run, and prevents per-coin failures from opening the source-wide circuit inside one incident.
DexScreener token-pool requests identify Pharos and request JSON. Because the public endpoint sits behind provider-side Cloudflare/WAF rules, HTTP 429 responses and WAF code 1015 latch a hard refusal for the rest of the run. Discovery metadata preserves the final HTTP status, content type, and bounded error detail so production can distinguish a provider refusal from an empty valid token-pool result. A discovery run with at least one successful request still records aggregate breaker success when a later request is refused; a zero-success refusal records failure. The scoring fallback applies the same stop-on-refusal behavior.
The discovery cron is intentionally best-effort rather than all-or-nothing. It runs with a 12-minute shared wall-clock budget, a 25-second per-coin cap, and short no-retry request timeouts for late-stage fallback sources so partial runs return status="degraded" with budgetExhausted=true instead of drifting into a hard timeout and leaving stale in-flight telemetry behind. Provider bodies are read through the bounded reader with explicit per-response caps — 2 MiB for DexScreener token-pair and CoinGecko onchain pool payloads, 512 KiB for depth=true tickers — so a mis-served or oversized response fails that coin's provider check instead of being buffered and parsed inside the isolate, and the sweep yields to the event loop once per coin so the slot fence heartbeat and child lease renewal keep flowing for the whole run. Tier-2 and tier-3 candidates are sharded by stablecoin id across their cadence windows, so the cron refreshes each lower-priority cohort on schedule without batching every eligible asset into one oversized run.
Address matching uses both canonical contracts and optional tradedContracts metadata. tradedContracts is reserved for wrapper / secondary-market token addresses that are meaningfully used for DEX discovery even when issuer metadata points to a different canonical deployment.
Quality gates:
- Pool TVL must exceed $1,000
- Pool must have 24h volume > 0 or TVL > $10,000
- Pools are accepted when the tracked token is either the base or quote asset
- Quote-side pools still require an explicit tracked-token USD derivation before they can contribute a DEX price observation
- Pools already discovered by the primary pipeline are deduplicated by exact or uniquely derived pool identity
- Generic quality multiplier (0.3x) unless the DEX ID matches a known protocol (same
GT_DEX_QUALITYlookup)
DexScreener pools are merged through the shared secondary-pool contribution path — no balance ratio data, neutral organic fraction default (0.5).
Stellar Horizon AMM Discovery
The isolated discovery cron queries Stellar's public Horizon GET /liquidity_pools?reserves=CODE:ISSUER endpoint after the generic provider stages. Horizon request starts are paced at least one second apart, response bodies are read through the bounded retry helper, and the per-request stage signal is capped at eight seconds inside the existing 25-second per-coin budget. The deployment census registers this provider as horizon only for classic Stellar asset identities that the endpoint can query.
Classic Stellar assets stored as case-preserving CODE-G... deployments are translated directly to Horizon's CODE:ISSUER filter; the legacy bare-G... EURCV deployment is combined with its tracked symbol to produce the same canonical query. Bare C... and CODE-C... Soroban contract-token deployments remain outside the Horizon provider scope and are materialized as unsupported-method census rows without sending a malformed query; Horizon's classic AMM index cannot discover Soroban-native liquidity for those identities. Valid pool rows preserve the exact Horizon pool id and reserve identities under source family horizon. A pool receives a USD price and TVL only when its counter-asset is another active tracked classic Stellar stable asset with a usable peg reference and the implied tracked-token price passes the shared plausibility gate; otherwise its value fields remain null and scoring rejects it while the pool still counts as observed census evidence. The request currently uses Horizon's 200-row page ceiling, so very broad assets such as USDC provide bounded discovery evidence rather than an exhaustive pool count.
CoinGecko Tickers Discovery (Orderbook DEXes)
CoinGecko Tickers runs in the isolated sync-dex-discovery cron. Synthetic orderbook pools enter scoring through dex_pool_registry; a coin with a geckoId is queried via CoinGecko's /coins/{id}/tickers endpoint with depth=true only when the earlier discovery stages found no pools or no usable price observation. This covers coins whose primary liquidity lives on orderbook exchanges not tracked by DeFiLlama or DexScreener (for example KAG and KAU on Kinesis Exchange) without adding time-budget-dependent synthetic books to already-covered DEX assets.
Ticker filtering: !is_stale && !is_anomaly, finite converted_last.usd, finite converted_volume.usd >= 1,000, and a non-empty exchange identifier. Only USD-equivalent quote assets are accepted (USD, USDT, USDC, DAI, C1USD, etc.). The depth=true body is read through the same bounded reader with a 512 KiB cap — CoinGecko serves at most 100 tickers per page and the heaviest tracked coin measures ~76 KiB — so an oversized tickers response is refused and logged instead of entering memory, and that coin simply gains no orderbook evidence. CoinGecko deprecated trust_score on March 3, 2026, so the ticker pipeline no longer depends on that field. Filtering, exchange aggregation, synthetic TVL construction, and orderbook price-observation gating are shared between discovery and liquidity through worker/src/cron/dex-liquidity/coingecko-tickers-shared.ts.
Per-exchange aggregation: all valid tickers from the same exchange are combined into one synthetic pool entry:
syntheticTvl = totalVolume × 3when CoinGecko depth fields are unavailable. Whendepth=truereturns 2% downside orderbook depth (cost_to_move_down_usd), Pharos usesmin(totalVolume × 3, cost_to_move_down_usd)so measured downside depth can reduce overstated volume-derived books without inflating scores on day one.poolType: "orderbook", quality multiplier 0.6xpriceUsd = volume-weighted averageacross accepted tickers on that exchange- Maturity is derived at scoring merge as days since first discovery, capped at 30 (
stagedPoolMaturityDays)
The 0.6x quality multiplier reflects that orderbook exchanges are legitimate but centralized (not fully on-chain), placing them between Aerodrome volatile (0.4x) and Balancer stable (0.85x).
These rows are explicitly marked synthetic in persisted pool metadata. Depth-informed rows also preserve the 2% downside/upside orderbook depth and orderbookTvlBasis metadata for top-pool diagnostics. They no longer present themselves as faux USDC pools; the quote side is labeled as an orderbook USD proxy so downstream consumers can distinguish centralized synthetic liquidity from measured AMM inventory.
Uses the shared secondary-pool contribution path used by GT/CG/staged fallback merges, so aggregate math and metadata propagation stay aligned across sources.
Direct CEX Orderbook Telemetry
The DEX liquidity cron also reads a tiny non-scoring direct orderbook canary for USDC and USDT from public Binance, Coinbase Exchange, and Kraken L2 endpoints. This telemetry computes 2% downside/upside depth, mid price, spread bps, and venue counts, then publishes only a compact summary in cron metadata under sourceCoverage.directCexOrderbookDepth.
This direct CEX lane is deliberately diagnostic for now:
- It does not change
liquidity_score - It does not create
dex_liquiditypool rows - It is bounded to major stablecoins and a few high-quality venues
- Failures are non-fatal and only mark the direct CEX telemetry source as failed
The lane exists to compare direct venue depth against CoinGecko depth-informed orderbook rows before any future scoring integration.
Exact-request execution certificates (local V10)
Safety Score methodology 10.0 adds a separate exact-request admission path, not another liquidity-score formula. worker/src/lib/exit-execution/orderbooks.ts reads Kraken's exact market metadata and sell-side bids, brackets the snapshot with venue time, rejects crossed/inverted/malformed books, and walks integer input/output units with minimum quantity/cost, lot rounding and conservative applicable taker fees. A partial final level is allowed; fees are charged once. Quote-currency USD value must come from the captured output reference, not a token symbol or presumed peg. Empty applicable-fee arrays do not mean zero fees: an exact reviewed fee-schedule content digest and applicable upper bound must be supplied and revalidated, otherwise the producer reports kraken-applicable-fee-unavailable.
REST depth is an observed prefix, never an exhaustive market. Its response digest identifies the bracketed snapshot but is not an exchange order-book sequence. A valid empty prefix is observed zero; a failed or invalid response is unavailable. Public depth does not prove an eligible account, deposits, withdrawals or a maximum bank-settlement time. Those required gates need same-run evidence; without them the route cannot score. Shared account/venue inventory and settlement failure domains cannot earn independent-backup credit.
The structural registry shared/data/safety-score-v9/exit-execution-model-reviews-v1.json starts empty. Existing routes, including USDC's scored routes, are not replaced or reclassified. When a reviewed record exists, computeStablecoinScores runs its observer serially before the existing payload bounds, increases actual route observation counts, and leaves retained pool counts, unsupported pool remainders, chain census, TVL and circulating supply unchanged. Bodies are fully consumed through the bounded body reader before another request starts; the existing abort signal is forwarded. No new trigger is introduced. Bitstamp/Kinesis and uncovered AMM families remain research-only: a discovered ticker, rate-bearing pool or target-resolution change alone cannot grant execution eligibility.
The strict wire schema and sole policy authority live in shared/types/exit-route.ts and semantic.exit.executionModels; admission is shared with the compiler/evaluator in shared/lib/safety-score-v9/exit-execution.ts. Each new model must contain the defining policy stress-grid request, exact input units, every actual output leg and its source clock, gates, reviewed deployment/code identity, settlement endpoint, source generation and resolved freshness budgets. No smaller-quote interpolation or legacy documented-capacity fallback is used for a certificate-backed route. The public breakdown removes private holder prerequisites, evidence identifiers and producer configuration while retaining request amounts and gate decisions.
For a read-only live diagnostic, run node --import tsx internal working notes; it records the actual observation clock and response hashes in an injectable diagnostic packet. It does not author canonical coin/review data or mutate an old capture. See redemption execution certificates for permissioned paths and settlement boundaries.
Pool Stress Index (0-100)
Per-pool stress metric: 35x(1-balanceRatio) + 25x(1-organicFraction) + 20xImmaturityPenalty + 20x(1-pairQuality). TVL-weighted average stored as avg_pool_stress.
Durability Score (0-100)
Per-stablecoin durability metric combining: TVL stability from 30-day CV (35%), volume consistency from 30-day CV (25%), oldest pool maturity (25%), and organic fee fraction with sqrt curve (15%). Locked liquidity removed — no reliable data source. Stored as durability_score.
Pool Quality Formula
Pool Quality measures the venue quality retention ratio: the fraction of total TVL that survives after applying mechanism and balance-health multipliers.
poolQuality = min(100, max(0, (qualityAdjustedTvl / totalTvlUsd - 0.15) / 0.65 * 100))
Where qualityAdjustedTvl applies mechanism and balance-health multipliers to raw TVL, and totalTvlUsd is the pre-adjustment sum across all pools. Pair quality is already reflected upstream in effectiveTvl and the pool-stress diagnostics, but it is not part of this retention-ratio component. The linear rescaling maps the 15–80% retention range to 0–100, so a pool set retaining 15% or less of its raw TVL after quality adjustment scores 0, and one retaining 80% or more scores 100.
Durability Sub-Component Weights
- 35% TVL stability —
1 - min(1, CV)over 30-day snapshots (CV = coefficient of variation) - 25% Volume consistency — same CV formula over 30-day daily 24h turnover (volume / TVL). Since v6.9 a recorded day counts when its window is complete (measured volume / retained TVL) or its admitted pools cover at least
DEX_VOLUME_COVERAGE_MINof retained TVL (partialGrossUsd / admittedTvlUsd); below the floor the day is skipped, never estimated. Legacy days without a record keep their stored volume over stored TVL. Fewer than 7 qualifying days in 30 still falls back to the neutral 50 (durabilityVolumeConsistencyDefault). - 25% Maturity — oldest pool age, capped at 365 days:
min(1, oldestDays / 365) × 100 - 15% Organic fraction —
sqrt(organicFraction) × 100(diminishing returns past 50%; 25% organic → 50 score, 50% → 71 score, 100% → 100 score) - Basis homogeneity (since v6.92): both 30-day series hold only the history rows of one TVL-measurement epoch.
LIQUIDITY_TVL_BASIS_BREAK_VERSIONSinshared/lib/dex-liquidity-evidence.tslists the releases that re-measured retained TVL in one step (6.91: NEAR Intents custody removed, global -8.0%; 6.92: dead-pool floor, global -4.7%);liquidityTvlBasisEpochmaps a persistedmethodology_versionto its epoch (numeric compare; a missing version is the earliest epoch). Each series uses the latest epoch with at least 7 samples, never a mix; if no epoch has 7, the component falls back to its neutral 50 as before. So for about a week after 6.92 the old-basis days alone are scored, then the new-basis days. With PAXG's replayed step held flat, a mixed series would read 0.59 TVL stability after 7 post-cutover days and 0.39 after 15 (USDT 0.75 / 0.89); the homogeneous series reads 1.0. Releases that only reweight pools, change volume admission (6.9) or tighten discovery admission (6.7, whose effect ages in over the 14-day registry horizon) are not breaks. The list is append-only: a future liquidity release that changes how retained TVL is measured must append its version toLIQUIDITY_TVL_BASIS_BREAK_VERSIONSin the same change that bumpsLIQUIDITY_METHODOLOGY_VERSION(shared/lib/__tests__/methodology-version.test.tschecks that every break is a released version at or below the current one).
Pool Identity (poolId)
Each PoolEntry carries a chain-scoped poolId. Chain aliases first resolve to the canonical Pharos chain ID; EVM addresses are lowercased, while case-sensitive non-EVM pool IDs and token mints retain their original case. Trustworthy on-chain/native IDs use chain:poolId; identity-poor retained rows use a fallback fingerprint over canonical chain, normalized protocol, and sorted chain-scoped token IDs. A Curve row joined to the native API by exact address or a unique full-token fingerprint retains that native chain/contract identity; symbol-only or ambiguous joins keep their fallback identity. Repeated primary aliases of the same proven Curve pool count once per stablecoin, and the retained exact identity excludes supplemental discovery duplicates even when a crawler exposes only two tokens of a multi-token pool. Native metapool-adjusted TVL continues to own the joined raw/effective liquidity amount. This identifies a physical pool across stablecoins without collapsing case-distinct Solana pools. A single pool (for example USDC/USDT on Raydium) may still appear under multiple stablecoin entries, and the scoped identity enables safe global deduplication.
Cross-Source Deduplication
DeFiLlama's yields API often uses opaque UUIDs as pool identifiers (for example 6b6de6c7-...), while CoinGecko/GeckoTerminal/DexScreener and direct protocol APIs usually expose on-chain pool addresses. The scorer therefore tracks a pool identity with two layers:
exactPoolKey:chain:poolIdwhen the id is trustworthy (EVM address, Uniswap V4 pool id, Solana-style address, or orderbook-native id)derivedMatchKey:chain + normalized protocol + sorted tokens + pool shape + fee bucket + stable/volatile flag
Dedup rules are intentionally conservative:
- exact ids always win when both sides expose the same real pool id
- derived matches only deduplicate when the match is unique on both sides
- direct-API vs DeFiLlama precedence also allows a narrowly scoped optional-metadata wildcard when the incoming identity-poor side is missing fee-tier and/or stable-flag metadata but still matches on chain, normalized protocol, token set, and pool-shape family
- staged discovery can use that same optional-metadata wildcard only when the staged incoming bucket and the known primary bucket are both unique, which lets one exact pool-id discovery row collapse against one DeFiLlama UUID row without merging parallel same-pair pools
- Balancer stablecoin pools get one extra fallback: if DeFiLlama tags a
balancer-v3pool as stablecoin-only but omits the stable subtype from its project metadata, the identity builder treats it as a stable-pair candidate for dedupe so it can still collapse against the exact Balancer direct-API pool instead of surviving as a faux weighted duplicate - ambiguous same-pair pools stay separate, so legitimate parallel pools are not collapsed
DEX price observations derive poolKey, derivedMatchKey, and identity confidence through the same producer helper. Exact identities still take precedence; when an exact pool id is unavailable, every producer labels the same derived identity as derived_unique, allowing the observation collapse to apply consistently across source families.
Token and pool identity share the same chain-aware canonicalizer. Addressed tokens must resolve by canonical chain + address; symbol fallback is allowed only for addressless tokens with one unique match on that chain. Route IDs, output asset keys, and correlation keys also retain the canonical chain-scoped pool/token identity, preventing the same address text on two chains, or case-distinct non-EVM identifiers, from being treated as one route or failure domain.
Staged-pool wildcard use is limited to unique incoming and known buckets. /status exposes the split directly via stagedPoolsSkippedByExactIdentity, stagedPoolsSkippedByUniqueDerivedIdentity, and stagedPoolsSkippedByOptionalWildcardIdentity.
Coverage Confidence
Every scored row now persists:
coverage_class:primary,mixed,fallback,legacy, orunobservedcoverage_confidence: current trust score for the row (0-1) derived from retained-pool evidence qualitysource_mix_json: compact source-family composition for the retained pool set
primary coverage now includes both pure-dl rows and pure-direct_api rows. fallback is reserved for rows built entirely from staged / DexScreener / CoinGecko-tickers style recovery sources.
Coverage confidence is no longer a fixed ladder by source family alone. The scorer now blends:
- protocol breadth and source-family breadth across the retained pool set
- measured-balance and measured-price TVL share
- organic measured TVL share
- penalties for synthetic and freshness-decayed TVL share
This keeps coverage_class stable for broad bucket semantics while making coverage_confidence more honest about partially measured rows.
Current rows also persist:
balance_measured_tvl_usdorganic_measured_tvl_usd
Top-pool JSON now also preserves per-pool measurement flags (tvlMeasured, volumeMeasured, balanceMeasured, maturityMeasured, priceMeasured, synthetic, decayed, capped) so downstream consumers can distinguish measured inventory from inferred fallback liquidity.
These measurement-denominator fields let the frontend weight balance/organic aggregates only by TVL that actually had measured inputs.
Measured volume availability (DEC-19; active since v6.9)
Findings D08-2/D08-3 (2026-09-27 review) showed that the producer republished age-decayed flow from rows up to 14 days old as measured 24h (and 7d) volume, and coerced absent pool volume to a fresh 0. The corrected contract shipped in two steps. First a reader release (CR-05) went out. Then the producer activation (CR-11 slice A) followed with liquidity methodology v6.9. Rows written before v6.9 remain legacy rows (no record, unknown completeness). They are not backfilled or restamped.
- Shared authority:
shared/lib/dex-volume-availability.tsowns the admission window (DEX_VOLUME_OBSERVATION_MAX_AGE_SEC) and coverage floor (DEX_VOLUME_COVERAGE_MIN) constants, window classification, storage reads, consumer interpretation, the volume-activity formula, and the composite rule. Wire vocabulary lives inshared/types/market.ts(DexVolumeCompleteness,DexVolumeAvailabilityReason,DexVolumeAvailability). - Producer readings: every retained pool carries its raw provider reading and observation clock (
volumeReading, producer-internal, never published). Live DeFiLlama and direct-API readings use the source stage's fetch clock. Staged registry rows use the refresh time of the resolver's volume row: the most trusted row with a usable reading refreshed within 24 hours, else the most trusted row inside the 72-hour admission window, else the freshest row with any reading. Staged confidence decay applies to TVL only, never to volume. Sources without trailing volume publishnull, never a measured0: the GeckoTerminal and DexScreener discovery parsers store an absent or unparseable 24h field asnulland keep an explicit0(CoinGecko onchain stores0only when the provider also reports zero 24h buys and sells). Registry rows refreshed beforeDEX_VOLUME_ZERO_PROVENANCE_SINCE_SEC(2026-09-28 07:29:14 UTC, the v6.9 Worker activation) were written by producers that coerced absent volume to0, so the resolver treats a zero from such a row as absent and keeps positive readings from it. CoinGecko onchain rows are exempt (DEX_VOLUME_ZERO_PROVENANCE_EXEMPT_SOURCES): a live sample of 600 pools found 1 absent field against 187 explicit zeros with zero trades, and the stale-first refresh re-reads those rows within about 20h; the rule is inert once those rows are older than 72h and the constant can then be removed. Volume rows dated after the run clock are never candidates. That covers on-chain Slipstream and Uniswap V3 BSC pool state, Fluid tickers with a malformed side, PancakeSwap pools whose hour-data batch failed, and Orca, Raydium, Meteora and Balancer rows without a usable 24h stat. - Pool eligibility: a pool reading is
measuredwhen it carries a finite non-negative value and an observation clock no older than the producer admission budgetDEX_VOLUME_OBSERVATION_MAX_AGE_SEC(72h;age <= budget, boundary inclusive, one second later is stale). The budget is deliberately wider than the 24h staged TVL/price freshness window (STAGED_POOL_FRESH_HOURS, unchanged) because discovery cadences leave many pools with a reading one to three days old; an admitted reading stays the provider's rolling 24h volume as of its own clock, not a 72h volume, so the admitted aggregate is an asynchronous observed-subset figure and every record publishesmaxObservationAgeSecwith the oldest/newest observation clocks. It isstalewhen the observation is older, andmissingwhen the value is absent or invalid, or no clock proves its window. A measured0is valid evidence. Scoring's eligibility pass (applyPoolVolumeEligibility) runs at the run's source clock before retention filters. Every downstreamvolumeUsd1d/volumeUsd7dis then the admitted value ornull,measurement.volumeMeasuredmarks the admitted reading, and each pool publishesvolumeObservation { status, observedAtSec }. - Window completeness over the complete retained contributing pool set (not the visible top ten):
complete(every pool measured; an empty set is vacuously complete at 0),partial(some measured),missing(none measured, at least one missing),stale(none measured, every observation aged),unknown(legacy row without a record, or an unreadable record). Every non-complete state carries a machine-readablereason. - Public fields (in place):
totalVolume24hUsd,totalVolume7dUsd, historyvolume24hand poolvolumeUsd1dare widened to nullable. The measured total is a number only for acompletewindow; otherwise it isnulland the additivevolume24hAvailability/volume7dAvailabilityrecord (history:volume24hAvailability) carriescompleteness,reason, a separately labelledpartialGrossUsd(observed volume over the admitted pools — a lower bound, never the complete statistic), pool counts, the observation-window clock (windowSec,asOfSec,maxObservationAgeSec, oldest/newest observation) and, since v6.9, the bound published with that statistic:admittedTvlUsd(retained scoring TVL of admitted pools),retainedTvlUsd(all retained scoring TVL, post protocol caps) andvolumeCoverage= admitted / retained (nullwhen the set carries no TVL). Records written before v6.9 omit the three coverage fields. Pools may carryvolumeObservation { status, observedAtSec }. Rows without a record omit these fields: consumers read them asunknowncompleteness and keep the historical number, never labelling it measured. - Storage: migration
0249_dex_volume_availability.sqladds nullablevolume_availability_jsontodex_liquidity,dex_liquidity_run_rowsanddex_liquidity_history.NULLmarks a legacy row. Since v6.9 every current, placeholder,__global__and daily-history row writes a record. The NOT NULL legacy volume columns hold the complete sum, else the admitted partial gross, else0, and are never published as measured unless the record sayscomplete.total_volume_7d_measuredmirrors a complete 7d window. Placeholder rows retain no pool, so they record the vacuous complete-zero window. A same-day history row that is carried forward keeps its own record. An unreadable record publishesnullwith reasonavailability-record-unreadable. There is no backfill: legacy completeness cannot be reconstructed from stored totals. - LiquidityScore (DEC-19, coverage-gated since v6.9): Volume Activity applies the existing log-scale formula to admitted 24h volume / admitted TVL; stale and missing pools enter neither the numerator nor the denominator and nothing is estimated, zero-filled or decayed for them. The component — and therefore the composite — is rated when the 24h window is
completeorvolumeCoverage >= DEX_VOLUME_COVERAGE_MIN(0.50, exported fromshared/lib/dex-volume-availability.ts; the exact floor is rated). A complete measured zero scores 0 activity under the full weight denominator. Below the floor (including a coin with no admitted reading,missing/stale, orunknowncompleteness) the component is unavailable (scoreComponents.volumeActivity: null) and the composite LiquidityScore NR (liquidity_score = NULL). No renormalization, no denominator shrink, and no earlier score is carried forward. The 7d window is display-only. Valid components remain displayable beside the NR component. Coverage is an evidence-sufficiency floor, not an unbiasedness proof: unobserved pools may trade differently from observed ones, which is why the floor, the admitted TVL and the observation clocks are published with the score. Publication coverage guards and same-day history reconciliation count NR rows as observed (a rated score, or a non-unobservedcoverage class), not as placeholders. - Downstream readers: the DEX API/history handlers, Depeg Resolver context (current and 30d volume baseline), public dataset snapshot and durability volume-consistency series apply the storage read; durability volume consistency takes coverage-qualified days only (see Durability Sub-Component Weights). Safety Score V9 does not consume the composite LiquidityScore or volume (its native capture carries only
updatedAtand exit-route evidence), so an NR composite reaches V9 as unavailable evidence rather than 0 or a carried-forward score. An NR composite also reaches other readers as unavailable, not as a low score: the Selector's trading profile requiresliquidityScore, so an NR coin is a coverage skip there (shared/lib/selector/exclusions.ts); the Depeg Resolver's legacy K5 thin-liquidity triggers (liquidityScore < 20/< 30) cannot fire for an NR coin, and its current and 30-day volume inputs arenullfor non-complete windows, so K6 wind-down and calm-catastrophic paths that need them cannot fire (shared/lib/depeg-resolver/resolution.ts,worker/src/cron/depeg-resolver/context.ts); and the DEWS liquidity signal is unavailable whileliquidityScoreisnull(worker/src/lib/dews/signal-families.ts). At the v6.9 cutover the captured-stage estimate is 25–55 newly NR coins (11 in the top 50) at 72h / 50%.
v6.9 deploy and rollback operations
- Deploy window: deploy (or roll back) outside roughly :08–:17. The scoring stage payload version changed to 2 (the D1 manifest
schema_versionstays 1). A stage written by the other Worker version between the :10 source stage and the :16 publication is rejected with a plain payload-version error, so that one :16sync-dex-liquidityrun errors and publishes nothing. This fails closed: the :46 tick defers, the charts slot continues, and V9 prep reuses the last accepted DEX publication. The next hourly stage recovers. - v6.92 (payload v3): the same rule applies to the 6.92 cutover. The scoring stage payload version moved to 3 because the consumer scores the dead-pool floor from signatures only the 6.92 staged merge writes; a v2 stage written by the 6.91 Worker (or a v3 stage read by a rolled-back one) is rejected with a payload-version error, so that one :16 run publishes nothing instead of a row labelled 6.92 scored without the floor. Deploy and roll back outside :08–:17.
- Rollback to a pre-v6.9 Worker: immediately after the rollback, before the next :16 publication, run
UPDATE dex_liquidity SET volume_availability_json = NULL;(do not drop the column; history rows need no change). The pre-v6.9 producer's upsert does not writevolume_availability_json, so without this step every current row keeps its frozen v6.9 record while the old producer rewrites the volume columns with zero-filled or decayed totals. The Release A readers then judge those totals against the stale record: a stalecompleterecord publishes the old producer's zero-filled total as measured, and a stalepartialrecord freezes the old partial gross and coverage indefinitely.NULLreturns every row to legacy (unknown completeness) semantics, which is what the old producer's totals are.
Storage
Stored in D1 dex_liquidity table (current checked-in schema lives in worker/migrations/0000_baseline.sql; the pre-squash lineage was created in migration 0009 and extended in 0010, 0012, 0024, 0036, and 0061) with per-stablecoin aggregate metrics, protocol/chain TVL breakdowns, top 10 pools as JSON columns, plus v2/v3 columns: avg_pool_stress, weighted_balance_ratio, organic_fraction, effective_tvl_usd, durability_score, score_components_json, locked_liquidity_pct, coverage_class, coverage_confidence, source_mix_json, balance_measured_tvl_usd, organic_measured_tvl_usd, and methodology_version. Stablecoins with no observed DEX presence store liquidity_score = NULL (NR semantics) and coverage_class = 'unobserved'.
Safety Score V9 consumes those evidence fields, plus aggregate dex_deployment_outcomes, through its exact input bridge. This does not change the standalone Liquidity Score. V9 Exit classifies aggregate rows as generic TVL proxy, synthetic/fallback, or unobserved and applies conservative evidence ceilings. Balance-measured aggregate TVL remains generic proxy evidence unless a separate exact route observation retains the invariant, fee, output identity, and executable capacity curve needed for reserve-based AMM simulation. Rows explicitly marked legacy remain neutral until current evidence is republished. The stronger measured-executable-depth and direct-orderbook-depth classes require dedicated, consumer-validated route producers; TVL alone is never labeled as executable slippage depth.
Pool, token, and deployment identities use chain-specific casing: EVM addresses remain case-insensitive, while non-EVM native identifiers preserve case. During the rollout from legacy lowercase non-EVM rows, a newer corrected staging or deployment-outcome row supersedes an older lowercase-equivalent row only when the stablecoin, chain, source identity, and native pool/token identity otherwise match; same-time or otherwise ambiguous case-distinct rows remain separate and fail closed rather than being guessed together.
Every active DEX publication row carries explicit route coverage, including zero-scoring-pool placeholders. A placeholder is published as known empty (populated, zero retained pools, zero observations) only when its exact current contracts plus tradedContracts deployment census is unique, no older than that coin's census freshness bound, provider-backed, entirely verified_no_pools, and no deployment result predates that deployment's latest attributed discovery attempt. That bound is sweep-aware rather than global: a footprint the discovery crawl finishes in one run keeps the two-dormant-window (two-day) limit exactly, while a footprint whose priced provider queries exceed the 25s per-coin budget is crawled in resumable windows and is therefore allowed its estimated full-sweep period plus half a sweep of slack. resolveDexDeploymentCensusMaxAgeSec() (worker/src/cron/dex-liquidity/deployment-census-coverage.ts) derives that per-coin value statically - no extra freshness persistence - by replaying the real window selector over the registry footprint to count the windows one sweep needs and pricing each window at the weekly t3 cohort cadence. The zero-pool maintenance queue refreshes these censuses faster, targeting an 18-hour full sweep without extending the existing freshness limits. Without this, the rotating tail of a windowed coin would report a stale or missing outcome forever even though discovery is on schedule. Discovery persists the selected deployments' attempt boundaries before network work without changing backoff counters, so an abort, budget discard, or result-persistence failure supersedes older empty evidence only inside that window. Failed bounded provider crawls also attempt to write an inaccessible outcome for each attempted deployment; an incomplete D1 persistence path retains only those attempt fences and therefore remains a discovery deferral rather than a provider outage. Timeout and 429 misses are retryable: they persist as a bounded-crawl reason and the scoring census treats them as a discovery deferral, not as “all provider queries failed.” A later GeckoTerminal page miss keeps any completed page-1 pools instead of discarding the token. The publication join rejects an older success even if either follow-up write fails. Missing, stale, superseded, malformed, inaccessible, unsupported, or observed-pool outcomes remain unknown with generation-bound census counts and reason codes. In particular, an observed pool that is lost before scoring is never converted into known-empty evidence. A persisted empty provider set is a snapshot of a registry fact, not an observation, so the live registry outranks it: an inaccessible row that claims no registered provider supports this chain while getDexDiscoveryProviders() resolves one today is counted as a superseded outcome awaiting the next crawl window, never as deploymentCensusUnsupportedMethod. Without that rule, every newly registered discovery provider (Aquarius Soroban, the supplemental GeckoTerminal networks) published a solved integration gap as a standing method limit for a full sweep period. npm run check:dex-census-provider-drift -- --rows=<d1-dump.json> lists the contradicted rows still waiting for that re-crawl.
Report-card deployment-supply coverage uses that same census-owned bound over the active registry's contracts plus tradedContracts, measured against each outcome's observed_at (not the DEX publication's update time). The independent four-hour quote/scoring freshness limit remains unchanged; an outcome beyond its census bound remains unknown in the supply join.
The Stellar Soroban census is intentionally bounded to the eight reviewed Spiko token identities served by the public Aquarius ticker index. Aquarius is registered as a supplemental provider, not a chain-exhaustive census: the 2026-09-04 check returned HTTP 200 JSON but no ticker matched any of the eight exact token identities, so a valid empty response is persisted as provider_inaccessible with Provider census is not exhaustive for this chain and cannot certify a known-empty footprint. The public Soroswap pools endpoint returned HTTP 403 without credentials when checked on 2026-09-04, so no chain-wide alternate is registered. Aquarius transport failures remain retryable and continue through the bounded-pending path; they must never be converted into a fabricated empty census.
Curve's address-grade DeFiLlama join treats identical coin sets as ambiguous only when more than one Curve API pool survives the shared $10K liquidity floor. Zero-TVL and dust duplicates remain address-indexed for identity evidence but cannot poison the unique fingerprint used to attach execution data to a retained pool. The native Curve census covers Ethereum, Base, Arbitrum, Polygon, Fraxtal, Sonic, Taiko, zkSync, Optimism, Avalanche, Fantom, Kava, and Gnosis (CURVE_NATIVE_DISCOVERY_CHAINS); the liquidity stage reads exactly that same chain set for scoring. Chains Curve's own API does not serve at all are covered instead by the pinned-factory on-chain capture described below (CURVE_STABLESWAP_FACTORY_DEPLOYMENTS, Plasma only), which never credits a discovery provider. A successful empty chain payload is retained as evidence that the deployment was checked rather than reported as provider-inaccessible.
The additive P4a producer also writes optional same-notional route observations into the existing score-details envelope. Capability matrix p4a.9 supports exact Raydium standard constant-product pools, canonical Uniswap V2 pools on Ethereum, canonical PancakeSwap V2 pools on BSC, Balancer weighted constant-mean pools, Curve plain StableSwap pools, Balancer stable-math pools (STABLE, COMPOSABLE_STABLE, META_STABLE), and validated Uniswap V3, hook-free Ethereum Uniswap V4, PancakeSwap V3, Base Aerodrome Slipstream QuoterV2, pinned Curve CryptoSwap get_dy, exact legacy Ethereum Curve 3pool StableSwap get_dy(int128,int128,uint256), or exact reviewed Ethereum Curve StableSwap-NG factory get_dy measurements. Reserve-based models require normalized balances, chain-scoped token identities, each token's own USD reference, fees, weights or amplification, and the tracked input index. Canonical Uniswap and PancakeSwap V2 candidates become exact models only after the producer pins a block, verifies the reviewed factory runtime hash, confirms that factory getPair(token0, token1) resolves the retained physical pool, and reads token order, reserves, and decimals at that same block. Classic Aerodrome volatile candidates are no longer produced: their only census was the deleted V2 pairs subgraph, which supplied the required isStable = false evidence, so no classic Aerodrome route enters the capability matrix. This is not a generic Solidly adapter; other forks and deployments on Avalanche, Linea, and Sonic remain outside score eligibility. The Raydium pool list carries no per-token USD price, so for standard constant-product pools an untracked counter asset's USD reference may instead be pool-implied (recorded as referencePriceSource: "pool-implied"): derived from the same response's spot price of token0 in token1 and the other token's direct reference, mirroring the display-price derivation; EVM V2 models use the equivalent same-block reserve ratio. If the tracked input itself has no trusted USD quote but exactly one other token does, the same reserve ratio implies the input (referencePriceSource: "pool-implied" on that leg); two unpriced legs still fail closed. Identity or balance failures still gate to incomplete-exact-capture. Curve reserve models attach only through an address-grade DeFiLlama join - exact pool address or unambiguous coin-set fingerprint (DeFiLlama yields rows carry UUID pool ids, so the fingerprint is the production path; identical coin sets fail closed to the symbol fallback, which never carries a reserve model) - and fail closed on CryptoSwap registries and on rate-bearing pools detected via a 1% per-coin USD price spread gate, both of which were measured overstating on-chain quotes when modeled as plain StableSwap. Pinned active CryptoSwap pools use the separate measured get_dy path and never inherit the StableSwap reserve model. The reviewed StableSwap exceptions are exact and deployment-specific: the legacy 3pool requires an atomic two-direction packet, while each active StableSwap-NG factory policy uses one reviewed route for that exact pool (USDG -> USDC or DUSD -> USDC). Each may supersede its existing reserve simulation only after three complete fresh cycles and three successful observations; an immature, partial, or invalid packet leaves the reserve route in place. Paused or swap-disabled Balancer pools survive only as P4 capability-gate rows; scoring excludes them before protocol caps, aggregate metrics, visible pools, challenger construction, and price observations. StableSwap amplification is stored in the plain paper convention (Ann = A * n^n): both the Curve API and the Balancer aggregator endpoint report the contract convention (Ann = A * n), so capture divides by n^(n-1) - verified against on-chain get_dy/queryBatchSwap quotes at pinned blocks. Balancer stable models take amp from the aggregator endpoint (which, queried without hook inclusion, only returns hook-free pools with reviewed rate providers), exclude the composable pool's own phantom BPT, and simulate on rate-scaled balances (balance * priceRate, reference price divided by the same rate) so the invariant sees on-chain scaled units; a missing amp or missing per-token price rate fails closed to shaped TVL evidence. It simulates bounded curves at a 200 bps maximum cost and a 300 second settlement horizon. The tracked input token must resolve to the stablecoin being scored, token identities must be distinct under the chain's casing rules, and modeled pool TVL (sum(balance * referencePriceUsd)) must stay within 0.5x-2x of the retained pool TVL. A missing or invalid model, including a TVL reconciliation outside those bounds, emits no executable observation and records an invalidExecutionModel:* unsupported reason; aggregate TVL is never substituted for executable depth.
When an exact AMM model resolves its output to a tracked stablecoin through an actual source-token or tracked-market USD reference, the route observation also carries that same output-token value together with its producer source identity and observation time. Peg-derived and pool-implied references are excluded, as are measured profiles whose target packets do not yet preserve the underlying price-source identity. This is evidence transport, not an independent price lookup or a par assumption: Safety Score V9 keeps an existing captured peg/NAV valuation authoritative and uses the exact route-carried value only when that output was otherwise unvalued. The captured peg/NAV record independently establishes the expected value for that fallback. USD-pegged outputs use an expected value of $1; non-USD outputs still require an authoritative peg reference, otherwise the route-carried valuation fails closed.
The tracked-market references those models consume are trust-gated at load (loadTrackedStablecoinMaps in worker/src/cron/dex-liquidity/orchestrator-phases/lookups.ts): a cached price enters the reference map only when its primary is depeg-authoritative or has fresh multi-source primary agreement, or — for navTokens — is a guarded NAV reference (isGuardedNavReferencePrice: a high-confidence protocol-redeem override observed within the depeg primary freshness window). A navToken whose NAV reference is missing or stale stays unpriced rather than falling back to a peg or generic reference print, which is what lets an otherwise-unpriced sUSN leg price from the ERC-4626 NAV override instead of a CoinGecko print.
Capability matrix p4a.9 makes completeness depend on the explicit count of retained pools that have a reviewed score-eligible execution capability. Generic shaped TVL remains visible in diagnostics but is excluded from that denominator because it cannot produce executable evidence. Reviewed exact-family failures, including unsupported CryptoSwap addresses or failed CryptoSwap measurements, rate-bearing Curve inputs, malformed exact captures, and paused or swap-disabled pools, remain in the scoring completeness denominator through an explicit capability gate and therefore keep exact-route scoring incomplete. Safety Score V9 gap accounting since methodology 9.2 uses the public route-selection bound instead (24 routes since 9.33): leftover construction gates and reviewed model limits do not keep incomplete-dex-route-coverage open once the budgeted score-eligible routes are observed. Older envelopes without the explicit capability count, reviewed deployment-specific StableSwap maturation contracts, and hook-free V4 identity checks fail closed until a new DEX-liquidity capture publishes p4a.9; the consumer does not infer completeness from legacy unsupported-reason strings.
Route-observation selection considers the complete filtered retained-pool graph rather than the public top-10-by-volume display list. The producer builds compact observations for that private candidate set, evaluates executable capacity at the actual $25M/200 bps V9 stress point, and only then applies the independent 24-route public payload bound. The selector guarantees the strongest executable-capacity route, the strongest exact reserve-model fallback, and an independent chain/protocol route when one exists. Capacity dominates evidence tier, so a $1K measured route cannot evict a fresh $24.6M exact route merely because measured evidence is nominally stronger. Remaining slots use maximums of 14 routes per chain, seven per protocol, and seven per adapter, with one output per physical pool preferred before extra outputs. These bounds scale with the payload so widening it admits genuinely new evidence: at the previous 10/6/3/3 setting the three anchor surfaces were saturated on concentration rather than on slot count, each publishing exactly six Ethereum, three Curve and three Uniswap-V3 routes. Concentration ratios are preserved rather than absolute counts, and common-mode risk continues to be modelled downstream where routes sharing a physical resource are grouped and only the strongest member is credited. A missing target or failed quote does not attach a measurement gate to a pool that already carries an independently complete exact AMM model; conflicting measured and exact evidence still fails closed. Omitted reviewed capability pools remain explicit payload-overflow diagnostics. If route-set churn cuts an asset's best stress capacity below half of a still-fresh prior route set that proved at least $100K, persistence preserves only that asset's prior route observations until the evidence expires or a non-collapsing generation confirms the change. Aggregate liquidity metrics, price observations, visible pools, and the standalone Liquidity Score continue to publish normally.
After each successful hourly liquidity publication, the D1-only dex-exit-route-turnover-watchdog compares every previously published coin's compact routeId -> evidenceKind set against its last confirmed baseline. A coin degrades the run only when its Jaccard distance reaches 0.5 or greater (one third of an equal-size set replaced) with at least two routes REMOVED (a coin that loses every published route qualifies regardless of how few it had), and the divergence sustains across two consecutive published generations against the held baseline: the first divergent generation opens a per-coin candidate and holds the pre-divergence baseline, and a candidate whose route set returns to (near) baseline in the next generation clears without alerting. The churn gate counts removals only because turnover is exit capacity disappearing: pure additions — a discovery-tier refresh reviving staged CoinGecko evidence past the 14-day confidence horizon, or a new venue listing — can grow or create a coin's whole published route set (Jaccard distance 1.0) without removing a single route, and one-for-one route substitutions on 1-2 route coins swap lanes without changing exit capacity. Those gains and swaps stay visible in run metadata without degrading the sentinel. A single-generation source blip — every CoinGecko-tickers route vanishing in one generation and returning in the next — therefore no longer pages, and 1↔2 route flaps on tiny route sets stay metadata-only. Confirmed alerts persist as pendingAlert until a later run observes no alerting coin, and metadata keeps added/removed route counts, same-route evidence-kind transitions, and open or cleared candidates so real losses remain visible. The compact per-coin baselines and candidate state are stored under dex-exit-route-turnover-watchdog:snapshot:v1 rather than relying on the three-hour publication-row retention window, so a missed producer cycle does not erase the comparison baseline; payloads written before the sustain window existed parse as candidate-free baselines, and a rerun against an already-compared generation skips neutrally so the sustain window counts published generations rather than watchdog runs. A missing baseline bootstraps without alerting. This telemetry does not change route selection, score inputs, scores, or grades.
The SunSwap V2 adapter (shadow-only since v5.96) was removed in v6.0 along with the rest of the Tron native measured-execution lane; its observations never became score-eligible.
Fully executable exact reserve-simulation capacity points also retain the realized execution cost from re-running the same invariant at the requested input and valuing its output with the captured token references. The projection is accepted only within the point's cost bound plus a narrow numerical tolerance. Zero capacity, an invalid recomputation, or partial capacity defined by bisection at the 200 bps request ceiling omits it and leaves V9 on the conservative fallback rather than mislabeling the bound as a realized cost. Curve reserve models apply the invariant to full input and deduct the fee from output, matching Curve StableSwap accounting. Ordinary source-only Curve models retain the documented 10 bps source-API fallback where the pools endpoint lacks pool-specific fee state; this is not a claimed per-pool upper bound, and a separately pinned get_dy measurement is required for proof-bearing execution cost. This additive V9 field does not affect aggregate liquidity or the standalone Liquidity Score.
Before ordinary whole-coin EVM cursor rotation, the measured lane may reserve one currently published score-bearing direction packet whose adapter-specific expiry is earliest. The reservation is capped at 20 estimated RPC requests, keeps the legacy Curve 3pool directions atomic, shares the 1,220-request admission ceiling, and does not advance the ordinary cursor. Every fresh target in the active inventory enters the remaining bounded whole-coin rotation, including targets with no prior published observation. Requiring an already measured route created a bootstrap deadlock: new or interrupted routes could never obtain their first proof. The inventory still admits only activated methods and deployments, and all quote, identity, freshness, and publication gates remain mandatory. The 2026-09-05 production inventory of 581 targets estimates 1,134 RPC requests for one rotation, below the unchanged 1,220 admission and 1,300 hard limits; larger inventories rotate behind the same deadline and cursor.
The isolated score-bearing sync-cl-exit-depth lane retains logical :00/:30 slots but physically runs at 5,35 * * * *, before the hourly DEX source stage and consumer. It loads only the latest active target generation, pins one block per chain, verifies reviewed QuoterV2 and factory bytecode, proves each pool through the factory's exact getPool binding, and records a $1,000 marginal quote plus the TVL-tiered $100,000/$1 million/$10 million/$25 million ladder with bounded refinement. The marginal quote always runs first; a five-minute pacing projection then stops starting further ladder/refinement stages when observed RPC throughput projects the remaining work past the soft deadline, marking uncovered targets with the same runtime-deadline-exceeded reason the eight-minute hard wall produces so the run publishes a partial generation and releases its lease early instead of holding D1-contending work for the full budget. Idempotent D1 reads and writes on this path — target and route loads, publication count readbacks, the admission-cursor state, and the native shadow collectors' retained-pool read, quote insert, and retention delete — retry transient D1 overload before reporting failure. PancakeSwap, Uniswap V3, and Aerodrome Slipstream target construction admit each direction only when the retained spot does not imply output worth more than 1.02x the input under independent token references; incoherent directions remain in retained DEX evidence but do not become measured-execution failures. Uniswap V3 target construction resolves each leg's USD reference independently and, when the counter asset is untracked and has no direct reference, pool-implies the output reference from the subgraph candidate's decimal-adjusted spot price times the input leg's direct reference (the same convention the Uni V3 price indexer consumes, mirroring the Raydium pool-implied derivation). Pool-implied references must remain representable by the measured-execution pipeline's 1e8 fixed-point price encoding; smaller values revert to target-unresolved and emit a structured diagnostic with the raw pair spot prices instead of creating a guaranteed quote-validation failure. Runtime favorable-output quote mismatches for untracked pool-implied counter assets still persist failed target rows and remain excluded from scoring, but they are diagnostic for cron health because the source spot and pinned quote block can drift after target publication. Identity failures still gate to target-unresolved. Tracked NAV tokens require a trusted live NAV at capture time and never fall back to a static fiat peg for quote sizing. Producers publish target and quote generations atomically in D1. The :16 hourly consumer joins only a fresh published quote generation from the exact :10 source-stage graph, publishes DEX prices, challenger snapshots, the full Liquidity Score, and the next active target generation every hour. The :46 invocation retains the hourly CPU-class trigger topology and V9 sequencing but performs no DEX source or scoring work. Score-ineligible EVM targets use separate shadow generations and one serial daily 08:10 UTC collection run; shadow failures cannot degrade score publication. Synthetic budget-deferred outcomes are omitted from D1 quote rows only after an exact target-count and target-ID-digest manifest is recorded, then reconstructed on read; any manifest mismatch fails closed. A mature fresh last-known-good profile may remain in the bounded route-only observation set when its physical pool rotates out of the current liquidity/display shortlist, but it never re-enters aggregate TVL, volume, visible pools, price consensus, target publication, or the standalone Liquidity Score. Last-known-good substitution is quote-level only: it reuses an older measured quote for a target identity the current read still contains (or that an older admissible cohort contributed), so it carries a route through budget-deferred, a missing current quote, or a rotated-out pool, but never through a pool the run's own capture input left at executionCapabilityGate: measured-execution: target-unresolved — with no target built for that pool there is no identity for a quote to bind to, and the pool stays gated until a later run's direct-API/price inputs resolve one again. QuoterV2 profiles must pass consumer validation of generation, identity, decimals, price, freshness, provenance, curve monotonicity, cost bracketing, and the 1.5x retained-TVL capacity ceiling. Score-eligible deployments are the owner-ratified Uniswap V3 cohorts on Ethereum, Polygon, Arbitrum, and Celo; PancakeSwap V3 on Base, BSC, and Ethereum; plus the reviewed Aerodrome Slipstream cohort on Base. For Aerodrome Slipstream alone, the P4 identity check accepts the retained source labels aerodrome and aerodrome-slipstream; the measured profile must still identify aerodrome-slipstream, and exact chain, physical-pool, token, generation, and proof validation remain mandatory.
That head start is an ordering assumption rather than a guarantee: the measured cohort is published on its own transport schedule and can land after the hourly stage's slot. The stage's route-observation clock is pinned to that slot (routeObservedAt is the run's syncStartSec), so the measured-evidence read is clock-bounded: loadDexMeasuredExecutionJoinEvidence passes that clock through loadLatestPublishedDexMeasuredQuoteEvidence, which then selects the newest quote cohort published at or before it (latestPublishedGenerationAtOrBefore, which admits a superseded generation) and applies the same ceiling to the history-generation scan (loadSupersededQuoteGenerationIds) in worker/src/cron/measured-execution/evidence-reader.ts. A cohort published after the clock can therefore neither become the read cohort nor enter that target's observation history, including last-known-good resolution. Without that bound the consumer reads a cohort that only became available after its own clock, and the future-history no-lookahead guard rejects every profile in it — dropping those coins' measured exit routes from publication for that generation and presenting upstream evidence rejection as published-route turnover to the exit-route turnover watchdog. The bound keeps the guard, its per-adapter freshness windows, and every alert threshold unchanged: while the lane is late, the run publishes the newest admitted cohort's still-fresh evidence instead of losing the route entirely.
Measured execution dispatch is a closed per-kind adapter table: each family supplies only its request projection, verifier patch, quote runner, and proof validator, while shared code applies budget stops and outcomes. Curve families likewise share one deployment-verification pipeline with declarative guard specifications; StableSwap-NG retains its stricter block-commitment, token-order, and decimals proof requirements rather than inheriting the legacy 3pool guard set.
Every Curve get_dy family shares one Multicall3 transport and one token-binding check, and the QuoterV2 and Uniswap V4 stages share one quote-plan transport and reverted-point projection. CryptoSwap keeps its own deployment verifier and quote preparation: its dependency addresses are discovered on chain rather than declared, its code hashes are collected as evidence for the reviewed-family decision instead of failing the read, the absent is_killed method is itself a proof, and its shadow cohort is quoted even when it is not score-eligible.
Uniswap V4 measurement is intentionally narrow. Since v5.993, the reviewed hook-free Ethereum deployment is score-eligible after three productive shadow generations and 129 successful quotes in the latest 130-direction production cohort. The source stage reads the official Ethereum V4 subgraph deployment, retaining PoolId, ordered currencies, fee, tick spacing, hook address, indexed in-range liquidity, spot prices, and raw indexed USD TVL. After the existing broad TVL-filtered scan drains, retained canonical V4 PoolIds from tracked primary and compact direct rows are requested through authenticated id_in queries without a TVL floor, in serial batches of at most 100, consuming each bounded response before opening the next request. Exact rows with finite zero, negative, sub-floor, or materially different indexed TVL remain identity evidence only; the diagnostic is never clamped, made absolute, or substituted for retained TVL. Missing, malformed, conflicting, or hash-inconsistent exact rows fail closed. A serial, project-filtered yields.llama.fi/poolsPro?project=uniswap-v4 identity lookup maps Ethereum main-list UUIDs to pool_old only when UUID, project, chain, and both token addresses agree; duplicate UUIDs fail closed. Its unchanged 10-second timeout, no retries, 4 MiB response limit, and 2,000-row limit apply after the existing DL response bodies are consumed. The extra endpoint supplies identity only: list TVL, volume, prices, and yield-cache UUIDs remain authoritative. This avoids treating rounded DL fee metadata (5 pips appears as 0.00%) as an exact fee.
Primary pool processing prefers an exact physical PoolId plus the retained row's canonical currency pair, even when fee metadata is absent or rounded. Exact admission requires current hash-proven PoolKey evidence, positive retained TVL, a zero hook, positive indexed active liquidity, independent token references, and the unchanged 1.02x favorable-output ceiling; indexed USD TVL is not an exact-identity affinity condition. Identity-poor token/fee fallback still requires exactly one collision-set candidate with positive indexed TVL within 2% of retained TVL. Hooked and zero-liquidity candidates remain in that collision set. Missing exact evidence, a currency/hash mismatch, multiple fallback candidates, or failed spot/reference guards remain target-unresolved; repairing lookup does not certify executable depth. At the pinned quote block, the producer verifies reviewed PoolManager, StateView, and Quoter runtime hashes, proves both views bind to that PoolManager, checks getSlot0(poolId) and getLiquidity(poolId), and decode-binds every quoteExactInputSingle call and result. Nonzero hooks, missing state, runtime drift, malformed or transport-failed quotes, and non-monotonic successful cost curves fail closed. Retained TVL still bounds capacity, while actual quote output and the unchanged notional/cost ladder determine executable depth. Ethereum remains active; the other registered V4 chains remain shadow. No per-pool allowlist or new shadow admission machinery is introduced. This admission change belongs to Safety Score v9.96, not the standalone Liquidity Score: aggregate retained TVL/volume and its v6.92 methodology remain unchanged.
Direct-API exact evidence enriches a retained row only after canonical chain/pool identity and normalized protocol match. Token-label compatibility removes a trailing provider display fee annotation (for example 0.05%) and uses the shared pool-symbol parser, so DeFiLlama's USDT-USDC and a direct provider's USDC / USDT do not lose the same physical pool's target or reserve packet merely because delimiters or order differ. This display normalization never supplies the target's fee: the exact execution packet remains authoritative. Different constituents and conflicting protocols still reject enrichment; enrichment does not add TVL or pool count. This producer repair does not admit a deployment or relax quote-proof, freshness, or capability gates.
Registered Uniswap V3 target enrichment likewise joins a complete chain-scoped pool address and canonical currency pair before considering display fee metadata. A missing or stale display fee cannot discard an unambiguous exact source packet; that packet supplies the executable fee. Fingerprints and address-like suffixes are not physical pool identities, and a different chain, different currency pair, absent source packet, or conflicting exact packets retain measured-execution:target-unresolved. This only repairs target construction: deployment activation, trusted input pricing, factory binding, quote proof, freshness, and maturity remain required before any route becomes score-eligible.
The legacy 3pool adapter uses a distinct
curve-stableswap-main-registry-get-dy-v1 profile. It admits only USDT and
USDC tracked inputs and creates both remaining 3pool stablecoins as output
targets using independent tracked reference prices; DAI remains output-only
until separately reviewed. At the pinned Ethereum block it verifies the exact
pool and main-registry runtime hashes, get_lp_token(pool), registry
get_coins(pool), pool coins(0..2), and all token decimals before any quote
can be published. Both output directions must validate against one block and
one quote generation before either measured direction replaces the reserve
simulation. Code, registry, token-order, decimal, stale-block, transport, or
quote failures fail closed without inventing a factory identity. An exact 0x
runtime-code response is semantic code absence and is never eligible for
last-known-good retention; an unavailable RPC response remains an operational
failure subject to the bounded freshness policy.
Reviewed Curve composite targets also value quote outputs independently of Curve pool metadata. In particular, the Avalanche NXUSD metapool values its avUSDC output with the tracked USDC reference; the Curve API's underlying-coin price is retained only with source metadata and cannot change measured cost or cost-bound results.
Address-grade plain factory-stable-ng pools that initially gate as
rate-bearing may instead contribute an exact reserve model only when the source
stage pins a fresh block, reads get_balances(), stored_rates(), A(),
fee(), offpeg_fee_multiplier(), and ordered coins(i) at that block, and
confirms the same header hash afterward.
The Curve API supplies the candidate identity/order and token USD references,
not executable balances or amplification. Each stored rate is normalized as
rate / 10^(36 - decimals): capture scales that token's balance by the factor
and divides its reference price by the same factor before applying the existing
paper-convention StableSwap invariant. The captured fee() / 1e10 is deducted
from output after the full-input invariant, and only an
offpeg_fee_multiplier() at or below 1e10 is accepted because larger values
make the fee trade- and imbalance-dependent. The candidate must have distinct
tokens and exactly one tracked input. A missing, base-only, stale, malformed,
dynamic-fee, mismatched-order, or hash-drifted read retains the original
curve-stableswap:rate-bearing-inputs gate. Legacy, metapool, CryptoSwap, and
other unreviewed Curve shapes are not widened.
A separate capture answers the case where the Curve API cannot serve the chain
at all. /v1/getPlatforms omits Plasma and /v1/getPools/all/plasma returns
ParamError: Invalid value for param "blockchainId", so a retained Plasma Curve
row has no source pool address to join and gates permanently at
curve-stableswap:exact-pool-join-unresolved with no exit route. For the
reviewed deployments in CURVE_STABLESWAP_FACTORY_DEPLOYMENTS
(cron/dex-liquidity/curve-stableswap-factory.ts) the pinned StableSwap-NG
factory becomes both the join and the sole state authority. This is a
deployment registry, not a chain toggle and not a generic Curve-fork adapter:
Plasma is its only entry, pinning factory
0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad with runtime hash
0xded1a5a5…f87f and pool blueprint 0xfc687efafed297b765edecf8179c32195597c2df
with runtime hash 0x620bf33f…5a17, both verified 2026-09-01 at Plasma block
31,321,392 over the public https://rpc.plasma.to endpoint that
buildChainRpcs() now carries. At one fresh block the stage verifies both
runtime hashes, reads pool_count() and every pool_list(i) and
get_coins(pool), and admits a pool only when exactly one indexed pool holds
the tracked stablecoin; a factory grown past the registry's maxIndexedPools
bound fails closed rather than joining from a truncated inventory, and two
matching pools gate ambiguous-token-identity instead of being broken apart on
TVL. State comes from the same factory (get_decimals, get_balances, get_A,
is_meta, get_implementation_address) plus the pool's own fee(),
offpeg_fee_multiplier(), and stored_rates(). The blueprint must match the
pinned implementation, is_meta must be false, and every stored rate must be
the base 10^(36 - decimals); a rate-bearing pool gates back to
rate-bearing-inputs and stays with the capture above. Amplification uses the
same n^(n-1) paper-convention divisor, and the fee carries the off-balance
maximum fee * offpeg_fee_multiplier / 1e10 — an upper bound on fee is a lower
bound on exit capacity. Reference prices come from the trusted stablecoin quote
map with the same unique-counter-asset pool-implied fallback the V2 capture
uses. The header hash is reread after the capture and any drift withdraws every
model the run published. Because the DeFiLlama row carries no pool address, the
factory index is the only join: nothing here can attach a model to a pool the
pinned factory does not itself index and attest.
Reviewed active StableSwap-NG factory routes use the separate
curve-stableswap-ng-factory-get-dy-v2 profile. USDG uses the Ethereum
USDG/USDC pool 0xc061caa073f3d95f80f8e5428d32d2d76f5e1622, factory
pool_list(563), and quotes USDG index 0 to USDC index 1. DUSD uses the
Ethereum DUSD/USDC pool 0x32e616f4f17d43f9a5cd9be0e294727187064cb3, factory
pool_list(580), and quotes DUSD index 1 to USDC index 0; this route uses
direct get_dy because DUSD's stored-rate and dynamic-fee behavior is not safe
to model as raw-balance StableSwap. At one explicitly finalized block the
producer requires the exact reviewed pool and StableSwap-NG factory runtime
hashes, factory registration, factory get_coins(pool) membership, pool
coins(0..1) order, and both token decimals before quoting
get_dy(int128,int128,uint256). It rereads that numeric header after the
identity calls and rejects a changed hash. The internal proof retains the block
number, hash, finalized commitment, and raw calls and returns; the public
profile exposes only proof-free block, factory, pool, and token provenance. This
allowlist does not enable other StableSwap-NG pools or generic Curve factory
discovery. A semantic hash, unproven or mismatched identity, order, decimal, or
quote failure cannot fall back through a retained measured profile; only an
operational transport failure may use a still-fresh last-known-good profile.
Reviewed Curve metapools use the shadow
curve-stableswap-ng-metapool-underlying-v1 profile only for explicitly pinned
physical deployments. The LUSD/3Crv route pins Ethereum pool
0xed279fdd11ca84beef15af5d39bb4d4bee23f0ca, legacy factory
pool_list(16), the factory-selected implementation, the 3pool base
relationship, direct and underlying token order, decimals, and runtime code
hashes before quoting LUSD to USDC through get_dy_underlying. The adapter
measures executable capacity at the common stress requests; it does not infer
capacity from the pool's reported TVL. The ten reviewed metapool routes (alUSD,
DOLA-FRAXBP, eUSD, GUSD, LUSD, MAI, MUSD, msUSD, OUSD, TUSD) publish
display-only measured depth behind the activation-pending capability gate:
the shared execution-capability registry keeps the profile shadow, and the
worker policies declare the same lifecycle — an admission contract test fails
while the two disagree. Identity, base-pool, price, freshness, or quote
failure retains the capability gate rather than falling back to a
reserve simulation. Curve may expose that one physical address through both
main and factory registry views. v6.3 collapses those same-address aliases
before fingerprint ambiguity is evaluated and keeps the address-key winner;
two distinct addresses with the same coin set remain ambiguous and fail closed.
Decision (2026-09-21, R3 metapool lifecycle). The worker composite policies were reverted to shadow to match the shared capability registry, rather than promoting the registry to active. The ten routes have no completed activation review, so admitting their measured depth into scoring would credit capacity that no reviewer signed off; no published score changed either way. Promotion stays an explicit, separately reviewed step.
Two additional StableSwap-NG shapes are collected as shadow-only measured
profiles. The exact Ethereum DOLA/sUSDe pool uses
curve-stableswap-ng-rate-bearing-get-dy-v1: the producer proves the factory
pool index, implementation runtime, direct coin order, [standard, ERC-4626]
asset types, the sUSDe provider runtime and USDe asset() binding, and equality
between convertToAssets(1e18) and the pool's pinned stored_rates() value
before calling the pool's own get_dy. The exact Ethereum USD1 metapool uses
curve-stableswap-ng-metapool-underlying-v1: it proves the factory pool index,
metapool implementation, direct USD1/base-LP order, exact base-pool runtime,
factory get_base_pool, is_meta, and the underlying USD1/USDC/USDT order and
decimals before calling get_dy_underlying for USD1 to USDC. Output USD value
comes from the tracked output asset's current reference; the retained
metapool-excluding-base TVL is only a capacity ceiling. Neither adapter derives
depth from TVL or reserve simulation, and both remain activation-pending
until current production generations, replay equivalence, drift, and explicit
activation review are complete. A missing provider, base relationship,
implementation, coin order, output reference, or exact retained target leaves
the original unsupported gate in place.
Measured capacity points may also retain the realized cost of the exact passing quote that defines their executable amount. This field is additive: legacy points remain valid without it. The repeated-cycle history still emits the pointwise-minimum capacity; it emits a realized cost only when every supporting cycle retained an exact passing quote at that same minimum amount, using the maximum observed cost across those cycles. A missing exact supporting quote falls back to the 200 bps request bound. This projection is consumed only by the V9 path and does not change aggregate liquidity or the standalone Liquidity Score.
The former Solana and Tron measured-execution lanes were removed in v6.0 after native score-facing consumers exceeded the Worker memory limit; they never became score-eligible. The native Orca and Raydium CLMM collectors below are isolated diagnostics, not restoration of the removed Jupiter/Trade API lanes. SunSwap still has no running native quote collector; Meteora remains deferred. Aggregate TVL, price, and visible-pool contributions are unchanged.
Base Aerodrome Slipstream targets come from the current Sugar RPC reader, which starts at the reviewed CL factory's live registry offset and preserves the exact pool address and signed int24 tick spacing separately from the pool's dynamic fee. The retained-row join uses an exact pool id first. A fingerprint-only row must have exactly one same-token physical target within 0.5% of its contemporaneous TVL; no match or multiple matches stays target-unresolved. Pancake pools outside the explicit pancakeswap-v3-* family are not attributed to the QuoterV2 adapter. The producer pins the reviewed Aerodrome factory and QuoterV2 runtimes and validates the signed factory and quote calldata. The deployment entered V9 route scoring on 2026-07-24 after complete repeated target rotation, clean endpoint/factory/pool-binding checks, current monotonic capacity curves within retained TVL, and independent historical-block quote reproduction. Consumer validation remains fail-closed when a fresh profile no longer matches the current target's identity, price, or TVL tolerances.
The 2026-09-21 Phase A review keeps deployment admission separate from provider visibility. The following candidate counts are unique assets among the 99 captured no-exact-capable-venue groups with matching public retained pools in generation dex-liquidity-1789992636; the bounded public list is not the complete private scoring graph. Counts are upper bounds on affected claim groups, not promised closures, and overlap across rows.
| Candidate cohort | Assets visible | Expected facts closed now | Admission evidence still required |
|---|---|---|---|
| Uniswap V4 BSC / Base | 5 / 5 | 0 / 0 | Shadow collecting; activation packet below |
| Uniswap V4 Arbitrum / Polygon | 2 / 2 | 0 / 0 | Shadow collecting; activation packet below |
| Uniswap V4 Blast / Tempo | 1 / 1 | 0 / 0 | Outside this ranked seven-cohort pass; deployment and activation evidence pending |
| Uniswap V3 Base / X Layer | 1 / 1 | 0 / 0 | Base remains shadow; X Layer now collects its exact pinned retained satUSD/USDT pool |
| Curve Etherlink | 3 | 0 | Three exact plain NG pool directions collect shadow get_dy; no reserve-model or score admission |
| Curve Base / Gnosis / Arbitrum / HyperEVM | 1 each | 0 | Exact invariant and deployment review, not a blanket Curve admission |
| Balancer Arbitrum / Monad / Avalanche / Hyperliquid / Base | 1 each | 0 | Exact pool invariant/version and complete existing reserve-model inputs; a protocol label does not prove eligibility |
| Uniswap V2 Tempo | 1 | 0 | Canonical fork-equivalence, official factory/runtime and pair binding, fee/reserve proof, activation packet |
No additional EVM cohort is activated by this review. Already-admitted Ethereum V3/V4, Celo/Polygon/Arbitrum V3, and canonical BSC Pancake deployments still require a valid per-pool packet; their missing packets are not repaired by widening the registry. For every proposed activation, retain official address provenance plus pinned runtime/factory proof, fork-equivalence, independent cross-check, drift and shadow evidence. After deploying collection/repair code, take two new captures with different producer generations at least three cycles/90 minutes apart; run four baseline/candidate replays and both neutral diffs (or reviewed declared movers), then observe the first accepted publication. The old campaign capture and a passing unit test are not activation authorization.
Phase A shadow deployment packet (2026-09-21)
All seven requested deployments passed sequential eth_getCode reads plus a pinned selector call. V4 calls Quoter.poolManager() (0xdc4c90d3) and V3 calls QuoterV2.factory() (0xc45a0155), requiring the returned binding to equal the official pin. Etherlink calls factory pool_count() (0x956aae3a, result 10); additional sequential pool-list/token reads pin indices 2, 4 and 8. Runtime hashes live in the deployment policies and are rechecked at quote time. This verifies deployments, not swap equivalence or scoring activation.
| Cohort | Official pins | Verification block | Affected assets | Expected facts closed |
|---|---|---|---|---|
| V4 BSC | PM 0x28e2ea090877bf75740558f6bfb36a5ffee9e9df; SV 0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4; Q 0x9f75dd27d6664c475b90e105573e550ff69437b0 | 123211047 | aid-gaib, iauon-ondo, idrt-rupiah-token, satusd-river, weusd-picwe | 0 |
| V4 Base | PM 0x498581ff718922c3f8e6a244956af099b2652b2b; SV 0xa3c0c9b65bad0b08107aa264b0f3db444b867a71; Q 0x0d5e0f971ed27fbff6c2837bf31316121532048d | 51608895 | aid-gaib, brlv-crown, gtusdcp-gauntlet, satusd-river, yousd-yield-optimizer | 0 |
| V4 Arbitrum | PM 0x360e68faccca8ca495c1b759fd9eee466db9fb32; SV 0x76fd297e2d437cd7f76d50f01afe6160f86e9990; Q 0x3972c00f7ed4885e145823eb7c655375d275a1c5 | 507495419 | aid-gaib, gtusdcp-gauntlet | 0 |
| V4 Polygon | PM 0x67366782805870060151383f4bbff9dab53e5cd6; SV 0x5ea1bd7974c8a611cbab0bdcafcb1d9cc9b3ba5a; Q 0xb3d5c3dfc3a7aebff71895a7191796bffc2c81b9 | 94201369 | idrt-rupiah-token, wusd-worldwide | 0 |
| V3 Base | F 0x33128a8fc17869897dce68ed026d694621f6fdfd; Q 0x3d4e44eb1374240ce5f1b871ab261cd16335b76a | 51608896 | satusd-river; already shadow | 0 |
| V3 X Layer | F 0x4b2ab38dbf28d31d467aa8993f6c2585981d6804; Q 0xd1b797d92d87b688193a2b976efc8d577d204343 | 71238105 | satusd-river | 0 |
| Curve Etherlink NG | F 0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad; views 0xc9459a955a885467f01ccc531c51dbcc957993c0 | 53950012 | mtbill-midas (index 2), mmev-midas (4), mre7yield-midas (8) | 0 |
Official sources: V4 deployments, V3 Base, V3 X Layer, Curve deployments. PM = PoolManager, SV = StateView, Q = quoter, F = factory.
Curve's API rejects Etherlink (Invalid blockchainId), and the V3 source registry has no X Layer subgraph. Their exact reviewed pool directions therefore attach from retained physical IDs and tracked contract identities, using current validated prices; quote-time factory membership, bytecode, token order and decimals still fail closed. No unsupported Curve API fetch or guessed subgraph is added. V4 UUID identity enrichment now binds chain as well as tokens, avoiding rounded-fee target loss on the four shadow chains.
Each row below is an unfilled activation packet, not a claim that those gates passed. Supply artifacts and owner review before changing the cohort eligibility configuration; keep measured-adapter-shadow and score eligibility false until then.
| Cohort | Fork-equivalence artifact | Independent pinned cross-check | Drift window | First production cycle |
|---|---|---|---|---|
| V4 BSC | Pending | Pending | Pending | Pending |
| V4 Base | Pending | Pending | Pending | Pending |
| V4 Arbitrum | Pending | Pending | Pending | Pending |
| V4 Polygon | Pending | Pending | Pending | Pending |
| V3 Base | Pending | Pending | Pending | Pending |
| V3 X Layer | Pending | Pending | Pending | Pending |
| Curve Etherlink NG (all three directions) | Pending | Pending | Pending | Pending |
The packet must contain exact target/PoolKey or factory-index bindings, same-block raw quotes and independently reproduced outputs, drift statistics across two post-deploy captures separated by at least three cycles/90 minutes, baseline/candidate replay and neutral-diff results, and the first accepted publication/terminal cron row. Collection is daily shadow, separate from active quote inventory. The active lane's approximately 990 RPC estimate changes by 0. A one-target-per-ranked-asset/chain projection of the newly collecting inventory estimates 91 additional shadow RPC requests (Base V3 was already collecting), below admission 1220/hard 1300; production inventory can differ and remains budget-admitted. No connection concurrency or trigger is added; check:cron-connections passes all 24 trigger groups, source peak 5/6.
Optimism Uniswap V3 is retired. Its subgraph source-stage lane and reviewed QuoterV2 deployment are no longer scheduled because the expected value is low relative to Worker-memory and consumer-health costs. Avalanche and Linea have candidate Uniswap deployments but no admitted retained-pool/source cohort and no equivalent evidence packet; Sonic has no reviewed official deployment. None are score-eligible.
Curve CryptoSwap is kept outside the plain StableSwap reserve model and uses direct on-chain get_dy measurements. Nineteen active Ethereum TwoCrypto pools are score-eligible: for the eight pinned-pool-code entries — crvUSD paired with WETH, WBTC, cbBTC, or tBTC — the producer pins and verifies each pool's runtime code, factory, factory-selected views implementation, immutable math dependency, and exact token order before quoting, while the eleven reviewed-deployment-family entries prove the same factory, views, and math dependency triple against the reviewed family at the pinned quote block without a per-pool bytecode pin. DeFiLlama rows that share an otherwise ambiguous token-set fingerprint become measured targets only when the Curve address candidates contain exactly one pool within 0.5% of the retained row's TVL; no match, multiple matches, a shadow-only address, or wider source drift remains gated. The resolver does not replace the retained DeFiLlama row's legacy TVL or Curve metadata join. Any code drift, dependency mismatch, broken-pool flag, unsupported token pair, or missing independent price fails closed. The formerly shadow-only reviewed CryptoSwap census (11 non-eligible entries across Ethereum, Base, Arbitrum, and Polygon) was removed in the Liquidity Score v6 Phase 1 cleanup (2026-08-19): the reviewed cohort now contains only its 19 active Ethereum policies (8 pinned-pool-code, 11 reviewed-deployment-family), and non-cohort CryptoSwap addresses simply remain gated. Quote transport follows the shared operational convention: a stalled or failed Multicall with no budget stop records rpc-failure, so it keeps last-known-good eligibility, while only a success: false pool result records the deterministic pool-revert.
The public CryptoSwap observation validator binds poolId directly to the quoted executionEndpoint.address and requires the two distinct input/output tokens to match the captured pool token set. It does not require the CL-only poolProvenance factory-lookup projection: get_dy executes on the physical pool, while QuoterV2 executes on a separate quoter. The existing producer-side cohort, runtime/dependency hashes, token-order, and quote-proof gates still run before public projection. This keeps validated direct-pool measurements from being rejected for a proof belonging to a different execution model; it does not activate any additional pool or adapter family.
The retained-pool join respects that same identity anchor. It compares a fixed endpoint bytecode hash for pinned-pool policies and ordinary reviewed deployments, including fail-closed rejection when their required pin is missing. Only an explicitly reviewed-deployment-family pool omits that fixed-pool-hash comparison; its exact cohort endpoint, readable captured runtime hash, quote-time dependency verification, ABI/token-bound proof, generations, and freshness still validate. Comparing these already admitted family profiles against an absent per-pool pin would wrongly turn successful quotes into deployment-code-mismatch; this producer repair does not admit a new deployment or waive any family proof.
Within the static 24-observation payload limit, P4 packs the first deterministic output from every selected physical pool before it emits any additional output from an already represented pool. routeObservationPayloadOverflow therefore remains fail-closed only when no representative observation for a selected reviewed capability pool can fit; omitting extra counter-asset outputs does not make that represented physical pool incomplete.
The two exact reviewed Curve StableSwap adapters deliberately require more maturity than the existing two-cycle measured-adapter floor. Selected profiles and histories use a three-hour window so five half-hour cycles survive normal scheduler jitter; since methodology v5.992 every measured adapter shares that three-hour ceiling. A retained last-known-good profile preserves its original quote block and timestamp, then expires to the reserve model. P4 reports high model confidence only when the legacy 3pool has three complete cycles and three successful observations in both directions, or an active reviewed StableSwap-NG singleton has three complete cycles and three successful observations. Until then the measured profiles remain diagnostic and the existing reserve simulation remains score-facing. An operational RPC/unavailable failure may retain a still-fresh last-known-good profile, while absent runtime code, registry or factory membership, token, decimal, or quote semantic drift remains an integrity barrier.
QuoterV2 failure semantics distinguish execution evidence from producer health. A Multicall inner revert confirmed by a serialized singleton retry is retained as a non-passing proof point that brackets executable capacity, including measured zero capacity when the $1,000 marginal quote reverts. RPC transport failures and successful calls with undecodable returndata remain operational failures and degrade the generation. The hook-free Uniswap V4 adapter recursively fragments a transport-failed eight-call quote batch inside the reserved request headroom; recovered sub-batches retain their direct results, while a terminal singleton transport failure remains operationally degraded.
EVM admission rotates whole stablecoin cohorts through the existing durable cursor before quote work begins. Its cohort estimate counts one block read per admitted chain, each deduplicated deployment's deterministic bytecode/configuration verification requests, separate pool-binding, probe-notional, and bounded-refinement Multicall batches, plus one serialized revert-confirmation request per Quoter target. It admits up to 1,220 estimated requests and reserves 80 of the hard 1,300-request ceiling only for adaptive batch fragmentation and other nondeterministic execution overhead. When a cohort does not fit, admission keeps its cursor immediately before that cohort for the next run while packing any later whole cohorts that still fit. Non-admitted active rows are represented as budget-deferred; their dense D1 rows are omitted only under the sparse manifest, and the deferral is healthy only when the next cursor is durably written and the active inventory can rotate completely within two half-hour runs. Attempted quote failures, an oversized single-coin cohort, cursor persistence failure, or a rotation longer than two half-hour runs remains degraded. If the hard runtime ceiling is nevertheless reached, only calls rejected by the shared budget are attributed to request-budget-exhausted; completed provider or execution failures retain their original reason. The score-ineligible EVM shadow lane collects once daily; shadow degradation is retained under nested metadata without changing active EVM health, an invocation error remains terminal, and a non-durable deferral remains degraded.
Exact DEX route coverage is complete only when the count of capability-denominator pools with at least one score-eligible observation equals the explicit capability-denominator count. Aggregate observation count is not the completeness measure because one pool may emit multiple observations, and generic shaped pools may remain unsupported diagnostics without entering the executable denominator. The mature exact 3pool measured packet therefore emits two observations but counts as one physical capability pool; each mature reviewed StableSwap-NG singleton emits one. Score-eligible exact routes now feed V9 Exit: complete coverage may certify the reviewed portfolio, while incomplete coverage remains bounded-unknown and cannot present the modeled subset as the holder's exhaustive DEX exit surface. The aggregate Liquidity Score is never substituted as same-notional execution capacity. The DEX envelope accepts only dex-amm and dex-orderbook route families. Measured observations carry their exact adapter profile identity so replay applies the adapter-specific freshness contract without symbol inference. Active replay rejects future observations; live profiles for every measured adapter expire after three hours — unchanged when score-bearing publication moved to hourly :16, so recovered quotes are admitted sooner without extending their validity — with the exact reviewed Curve StableSwap adapters reverting to the reserve simulation past that same ceiling. A profile that does cross the ceiling is no longer discarded by V9 Exit: it is derated through the reviewed staleObservationConfidenceFactor and stays in the capacity denominator, while a genuinely missing observation still fails closed. Unmeasured CLMM/DLMM pools, custom invariants without an activated adapter (Gyro and Fluid), hook-bearing Balancer pools, incomplete token-price models, aggregate TVL rows, and narrow CEX diagnostics remain explicitly unsupported or diagnostic-only. Earlier activation-boundary and legacy-aggregate wording is historical only.
Only active registry adapters enter the capability denominator. The removed native-measured-exact capability remains absent (matrix version p4a.9); Orca and Raydium CLMM are registered exclusively as measured-adapter-shadow, with activation-pending target gates and no V1 measured targets.
Native Solana shadow lanes (2026-09-21): solana/whirlpool-shadow.ts runs Orca, then Raydium CLMM, after the active EVM phase settles on the existing physical 5,35 * * * * trigger. Both select fresh (24-hour), case-preserving identities from current published dex_liquidity.top_pools_json, fenced to the __global__ publication generation. Stored JSON retains poolId even though the API response projection removes it; discovery-only registry rows cannot enter these lanes. Raydium selection requires the raydium-clmm pool shape, including direct raydium and DeFiLlama raydium-amm project identities, then proves the CLMM program owner on chain. Physical pools are deduplicated and TVL-ordered, with separate durable cursors, at most four eligible quote attempts per family, and separate 45-second abort budgets. Unpriced assets, adaptive-fee Whirlpools, and unsupported/Token-2022 mints are skipped during discovery before consuming a quote slot; skippedIneligible retains reason counts. Each family runs every RPC serially. Orca discovers the pool and mints, then rereads the pool with its bounded directional tick arrays in one bank snapshot.
The fixed exact-in probe reuses the measured lane's $1,000 marginal notional, converted using the existing trusted tracked-price policy and on-chain decimals. The tracked input mint must match the native pool; both mints must be initialized legacy SPL mints. Adaptive fees and Token-2022 pools fail closed, as do changed mint identities, stale snapshot slots, wrong owners, incomplete traversal, or exhausted tick arrays. The captured slot 449058549 Orca fixture reproduces raw input 1000000000 to output 999660000.
solana/raydium-clmm-quote.ts uses native BigInt Q64.64 Raydium tick factors, exact-in input/fee ceiling and output floor rounding, and signed liquidity crossings. It decodes packed PoolState, AmmConfig and 60-tick TickArrayState accounts, discovers nonadjacent arrays from the pool bitmap and positive/negative bitmap-extension banks, and derives tick-array PDAs with signed big-endian indices. Following pool/mint discovery, a serialized pool/bitmap census finds addresses; the final single bank snapshot rereads the pool, config, bitmap extension, both mints, and at most three directional arrays (eight accounts maximum). All quote-input slots must agree. Missing bitmap-selected arrays, zero/exhausted liquidity, Token-2022 transfer-fee-capable mints, dynamic/output-fee modes, and limit-order state reject rather than return partial or approximate quotes. The same-slot JUPUSD fixture reproduces 1000000000 → 999152114, matching the recorded Jupiter/reference output at 0 bps. The native model has no SDK dependency: 15,231 source bytes / 7,420 minified ESM bytes (model only, excluding imported shared helpers). Neither module size nor the frozen fixtures prove deployed Worker memory safety.
Migration 0243_native_shadow_quote_families.sql must precede Worker rollout. Both collectors write dex_native_shadow_quotes_v2, preserving the pool, tracked asset, RPC slot, quote timestamp, input/output mints, decimal-string amounts, notional, input price/decimals, profile, and model version (orca-whirlpool-native-v1 or raydium-clmm-native-v1). SQL constrains the profile to the two admitted shadow families and fixes score_eligible=0 and capability_id=measured-adapter-shadow. There is no V1 target/quote generation or scoring-reader path. Same-slot quotes are idempotent; seven-day evidence retention drains at most 256 expired rows per family pass. The Orca-only 0242 store remains for prior-Worker rollback; its four diagnostic rows are intentionally not copied, and removal requires a separate cleanup rollout. sync-cl-exit-depth retains active EVM status and nests attempts, persisted rows, failures, budget exhaustion, skippedIneligible, and durationMs under orcaShadow and raydiumShadow; rejected family invocations also retain elapsed duration.
Solana Phase B activation checklist: Orca and Raydium CLMM collection are enabled, but score activation remains OFF; Meteora has no admitted runtime adapter. Review dex_native_shadow_quotes_v2 by pool, slot, notional and model version alongside both family diagnostics. These marginal-only rows begin a shadow packet; they do not establish a capacity ladder, cohort-wide same-slot independent agreement, full replay provenance, or score eligibility.
- Bind each retained physical pool, program owner, input/output mint, token program, decimals, fee configuration, and complete directional tick/bin accounts to one recorded RPC
context.slot. Bind adaptive-fee time and transfer-fee epoch to the same snapshot. Missing, stale, wrong-owner, partial, or changed account sets must reject the quote, not substitute shaped TVL or partial-fill capacity. - Reproduce exact-in output against the same physical pool's independent quote or transaction simulation at the same slot. Solana
minContextSlotis a lower bound, not a historical-slot selector. A matching amount at different slots is diagnostic only; record both slots and leave admissible drift unset. A local frozen-bank replay requires the program and all invoked dependencies as well as the pool accounts. - Complete a cohort-scoped shadow packet across representative directions and notionals, including exhausted tick/bin ranges, transfer fees, adaptive fees, and malformed/stale state. Do not interpret a DLMM insufficient-bin-liquidity exception as a proven zero unless complete traversal and independent execution establish it.
- Measure each deployed native pass's RPC/body/runtime/memory costs and the complete consumer graph before any score activation. The direct-source slot is already
5/6and cannot absorb another fetch-heavy lane. Serialized Orca and Raydium phases preserve active measured execution's3/6peak; require a passingnpm run check:cron-connections. Desktop SDK or module-only bundle measurements are not Worker memory proof; the prior128 MiBconsumer failure remains an activation blocker. - Before score activation, retain two production captures from different generations at least three producer cycles / 90 minutes apart, replay baseline and candidate against both, and review both diffs and the complete mover manifest under the equivalence protocol. Keep lifecycle OFF until this packet and exact deployment identities are approved.
- After deployment, inspect the first full target/quote/consumer/publication cycle: exact joins, admitted physical-pool denominator, freshness, successful quote history, completeness, memory, and producer health. A successful deploy alone does not close
EXIT_DEX_COVERAGE; require the first-cycle evidence and a fresh V9 replay.
Current rows expose exitRouteObservations and exitRouteObservationCoverage; daily history stores the bounded summary prospectively in dex_liquidity_history.exit_route_summary_json. Existing history is not backfilled or claimed to reconstruct old route capacity. The first isolated complete generation, dex-liquidity-1783905029, published 360 asset rows: 7 populated, 173 unsupported, and 180 unknown, with 21 exact observations. That generation predates the per-pool completeness counter, so current calibration preserves the observations but treats its DEX coverage as incomplete and activation-ineligible. The one-off all-active calibration table produced during that analysis was never read by any runtime or build path and has been deleted; git history is its archive.
Both dex_liquidity and dex_liquidity_history also carry methodology_version (migration 0036), stamped at publication time from shared/lib/methodology-versions/registry.ts; since v6.0 the API readers pass the stored value through without any reconstruction fallback. Historical rows also persist coverage_class, coverage_confidence, and source_mix_json. Legacy pre-0061 rows are backfilled as coverage_class = 'legacy' and coverage_confidence = 0.5.
Detail-page consumers should treat unobserved history as explicit absence-of-direct-market evidence, not as a measured zero-liquidity market chart. The stablecoin detail page now renders a dedicated unobserved-history state for those rows instead of plotting a zero-value TVL area chart.
Discovery and merge staging tables are documented in the Discovery Cron section below.
Only independent tracked-market output prices may be pinned as a fallback valuation on exact AMM observations. Curve API source-token-usd prices can support the reserve simulation but cannot substitute for missing independent output valuation in Safety Score.
Owner-accepted freshness tradeoff (2026-09-12): the 14-day staged confidence horizon continues to apply to scored TVL, including live-lane primary writeback. Since v6.9 it no longer applies to volume: a staged reading older than the 72-hour admission window is stale and never enters a measured sum (DEC-19). Provider mismeasurement or a drained pool omitted from a later census can therefore contribute decaying stale TVL until expiry; primary dl/direct_api fallback retains its existing protocol-cap treatment. The $10bn staged-pool TVL sanity limit remains. This accepted inventory-continuity risk does not waive exact identity or independent valuation validation, and price evidence still expires after 24 hours.
Staged V2 pair verification distinguishes transport from semantics (2026-09-23). A request-level, pinned-block, or wall-budget failure observed nothing about the pool, so it gates constant-product-v2:transport-unavailable — separate from incomplete-exact-capture, which asserts an observed capture was incomplete — letting operators tell a provider brownout from a data refusal. No stale reserves are reused after either. Each verification request carries one network-only retry per URL, and the whole loop is wall-bounded by the earlier of the stage slot's controlled deadline and a five-minute cap, checked before each batch is issued. CoinGecko-tickers staged rows keep their raw payload for the 24-hour staged price window instead of the generic four-hour TTL: that payload carries the orderbook-depth evidence behind direct-orderbook-depth routes, and clearing it after four hours made every cg-tickers route blink off until the tier rotation revisited the coin, while the staged row stayed mergeable for fourteen days.
Discovery Cron
Census scope and pagination completeness are run-scoped evidence claims, never defaults inferred from a provider's declared chains or from a missing next-page marker — rule R6 (ADR-33 in architecture.md), enforced by the declared censusScope and by fetchPagedTokenPools()'s complete flag described below.
worker/src/cron/dex-discovery/orchestrator.ts runs every 2 hours (6 */2 * * *) and is responsible for pool discovery only. Scored TVL publishes hourly; discovery data is merged during the hourly source-stage pool construction.
- Architecture: three dedicated cron phases feed discovery through publication:
- Source-stage cron:
sync-dex-liquidity-stagehourly at10 * * * *. - Price publication:
sync-dex-liquidityhourly at16 * * * *, consuming only the exact source slot six minutes earlier and waiting up to 90 seconds when that stage is still finalizing. It never substitutes an older ready stage. When that slot's stage is terminal (failed generation, or an errored stage run with no live lease) or has not appeared by the readiness deadline, the same tick re-runs the stage for that exact slot under the stage job's lease and publishes, recordingstageRecoveryin its metadata. - Liquidity score/history and active measured-target publication: hourly at
:16, including recovered measured quote evidence. - Half-hour V9 bridge: the retained
:46consumer reuses the exact current DEX generation without rewriting DEX surfaces, except that an hour whose stage was never consumed — ready, terminal, or never started — is published from that stage instead of skipped as a cadence reuse. - Discovery cron:
syncDexDiscovery()every 2 hours (6 */2 * * *). - Discovery writes normalized candidates to
dex_pool_registry; the source stage consumes and merges them, then writesdex_liquidity_scoring_stages/dex_liquidity_scoring_stage_chunks.
- Source-stage cron:
- Discovery progress: provider-stage progress includes both the provider and stablecoin in the stage key (
crawl-curve:usdn-smardex, for example), so abandonment reconciliation retains the last provider/coin boundary after clearing the in-flight row. - Discovery registry schema:
dex_pool_registryincludespool_id,stablecoin_id,source,chain,protocol,dex_id,symbol,tvl_usd,volume_24h,quality_multiplier,pool_type,fee_tier,balance_ratio,is_stable,base_token,quote_token,quote_symbol,price_usd,locked_liq_pct,raw_json,discovered_at,refreshed_at; PK is(stablecoin_id, pool_id, source)with one index onrefreshed_at. The predecessordex_pool_stagingremains until a separate cleanup migration; migration 0241 backfills it into the registry withINSERT OR IGNORE. - Pool-price coherence gate: before either GT-shaped crawl admits a row,
evaluatePoolPriceCoherence()— the single owning, frozen policy (POOL_PRICE_COHERENCE_POLICY,maxPairDivergenceBps = 500) and rejection vocabulary (POOL_PRICE_COHERENCE_REJECT_REASONS) inworker/src/cron/dex-liquidity/pool-price-coherence.ts— requires the tracked leg's USD price to agree with the pool's own pair ratio × the counter-leg's USD price. The provider broken-price signature (leg USD prices published while every pair-ratio input — both pair-ratio fields and both native-currency prices — is null or0.0) is rejected outright with reasonpool-pair-ratio-unavailable; a usable but conflicting ratio is rejected withpool-pair-price-incoherentand its divergence in bps. Rejection drops the whole row before staging, price observation, or new-pool publication, because the same break also invalidatesreserve_in_usd(TVL is dropped, not just the price). Rejections are counted per machine-readable reason and emitted as one warn summary per crawl run ([dex-liquidity] <sourceLabel> rejected incoherent pool prices by reason: …,[dex-discovery] CG onchain …). Zero volume and zero transactions are never policy inputs — pool prices derive from reserves and quiet pools are legitimate — and a payload that omits every pair-ratio field is admitted unchecked, so the guard stays inert for transports that do not carry the fields. - Discovery meta schema:
dex_discovery_metastoresstablecoin_id(PK),consecutive_misses, the coin-level cadence timestamplast_crawl_at,last_hit_at, and the rollout markerdeployment_fence_attribution_at. Exact attempt attribution lives on eachdex_deployment_outcomes.last_attempt_at; the coin timestamp remains the compatibility fence when the marker is absent or does not match it. - Supplemental census providers: After Horizon, the discovery lane runs Aquarius, TzKT, Balanced on ICON, Kava x/swap, and the Cosmos stage (Osmosis then Noble) serially. Aquarius is limited to the eight allowlisted Spiko Soroban identities, Balanced covers only its bnUSD venue, and Kava covers only native USDX in the x/swap module; completed empty results from those three non-exhaustive censuses remain
provider_inaccessible. TzKT enumerates holders for the registered Tezos uUSD deployment, but admits only reviewed Youves pool addresses and reviewed quote contract/token-ID pairs. Aliases and USD-like metadata symbols do not establish pool or dollar-value identity. The reviewed crawler writes a producer-owned identity version inraw_json; legacy Tezos staging without that version contributes no metrics until a reviewed crawl refreshes it. Unreviewed originated holders keep the census degraded, so they cannot produceverified_no_pools; completed sub-floor reviewed pools remain an honest empty result. - Cosmos census stage (
worker/src/cron/dex-discovery/crawl-cosmos-pools.ts, providersosmosis-sqsandnoble-swap): two chains behind one serial stage, both plain public HTTPS GETs on port 443 with no key and at most one in-flight request.- Osmosis issues one denom-filtered read of Osmosis' own sidecar query server per tracked deployment (
GET https://sqsprod.osmosis.zone/pools?filter[denom]=<denom>). It is the only public Osmosis surface that answers "which pools hold this denom" without downloading the whole book:/osmosis/gamm/v1beta1/pools_with_filteranswers501 Not Implementedandall-poolsis ~2.4 MB per call. The sidecar indexes every pool module on the chain (balancer, stableswap, concentrated, CosmWasm), so the provider is registered exhaustive and a completed empty response may certifyverified_no_pools.liquidity_capis the sidecar's own USD valuation and suppliestvl_usd; whenliquidity_cap_errornames a leg it cannot price, the cap still counts every priceable leg — including the tracked stablecoin's — so it stays a usable lower bound. Only pools at or above the shared $10K retained-pool floor are staged and counted as observed; below-floor pools are a completed-empty census at the scoring threshold, not a degraded response. A returned pool whose own denom list,token0/token1, orpool_assetscannot corroborate the tracked denom degrades the check instead of being dropped into a verified-empty census. Cosmos denoms carry no decimals in the response, so no price observation is emitted. - Noble issues one read of the app-chain's first-party
swapmodule per coin (GET https://api.noble.xyz/noble/swap/v1/pools), answering every tracked Noble deployment from that single response. Noble was investigated before wiring (2026-09-01): it is a permissioned app-chain with no CosmWasm and no third-party AMM, but it does host that first-party StableSwap module, so the honest outcome is a real query rather than a standing not-applicable ruling. That module is the chain's whole DEX surface, so the provider is exhaustive. Noble reports reserves in base units and prices nothing, so pools are staged with a null TVL exactly as the Kavax/swapadapter does. - Both providers are registered by chain and denom shape (
ibc/<64 uppercase hex>, a plain lowercase bank denom, or afactory/...denom). MANTRA's Cosmos IBC denom shares that shape but not the chain and is never routed to either index, matching the DEX-scoped GeckoTerminal resolver's MANTRA EVM-only rule.
- Osmosis issues one denom-filtered read of Osmosis' own sidecar query server per tracked deployment (
- Deployment outcome schema:
dex_deployment_outcomesstores one exact stablecoin/chain/contract row asobserved_pools,verified_no_pools, orprovider_inaccessible, including the provider set, reason, observation time, per-deploymentlast_attempt_at, pool count, and optional owned waiver. The provider set is derived per deployment bygetDexDiscoveryProviders(), and the discovery crawl queries exactly the providers that set names. In addition to the general chain registry, the DEX-scoped GeckoTerminal resolver covers Starknet (starknet-alpha), Stacks, Hedera, and Injective, plus only 20-byte0xdeployments on MANTRA EVM; Cosmos IBC denoms sharing themantrarepo chain id remain unsupported rather than being sent to the wrong network. Starknet token queries use GeckoTerminal's 64-hex-digit felt form, Hedera0.0.Nentity ids use their 20-byte long-zero Solidity form, and Injective EVM, Peggy, IBC, and token-factory denoms use GeckoTerminal's provider-native identities. Provider-native token ids are URL-encoded once in the token-pools path while persisted census rows retain the registry address. The Curve discovery stage is scoped toCURVE_NATIVE_DISCOVERY_CHAINS, the same registered chains that credit Curve as a provider, so a Curve result can always be attributed to a named provider; its getPools requests run one chain at a time with an 8 MiB response ceiling. Ethereum's full census exceeded the former 4 MiB ceiling; serial reads keep the aggregate in-flight response-byte allowance unchanged. The liquidity stage reads Curve on exactly the same registered chain set, so every Curve pool it scores also has an attributable discovery provider. A no-pool result is written only after a provider completes that exact token query and is usable only while that deployment's attempt fence has not superseded it. Before network work, the writer reconciles any unmatched legacy coin fence, advanceslast_attempt_atonly for the selected rotating window, and atomically marks the coin fence as attributed. Missing, mismatched, or legacy attribution stays fail-closed; failed result persistence supersedes only attempted deployments, while untouched rows keep their prior evidence. Failed provider crawls materialize inaccessible outcomes for the attempted footprint when D1 is available. The canonical registry owns current inaccessible deployments; full-footprint gaps require explicit, expiring waivers while adapters or provider mappings are evaluated. - Pagination-completeness contract:
fetchPagedTokenPools()(worker/src/lib/paged-token-pools.ts) returns{rows, complete, cappedAtMaxPages, failedAfterRows}instead of a bare array.completeis a run-scoped contiguity claim — a page shorter than the requested page size, read this run. A scan stopped by the page cap, or by a later-page 429/timeout, keeps its rows and reportscomplete: false; the absence of a next-page marker is never completeness. CoinGecko Onchain, GeckoTerminal, and Horizon are registeredscope: "paginated-exhaustive"and may only certifyverified_no_poolsfor a check carryingpaginationComplete: true(classifyDexDeploymentOutcomes); a GeckoTerminal crawl that lost a later page reportsdegraded. CoinGecko's documented plan boundary is page 10 — a live paid key still served rows on page 11, so neither that boundary nor the page cap is an inventory end. Horizon follows_links.next.hrefsequentially under a three-page budget. Meteora requests the honouredpage_size(its ignoredlimitspelling produced 10-row pages) and stops on the provider's ownpages/current_page, keeping a bounded head of the ~126K-pool inventory and thecensusScope: "bounded-sample"marker that denies it veto authority. - Tiered priority:
- Refresh: coins with admitted supplemental liquidity in the published source mix, and zero-pool footprints with a supported census provider, receive maintenance opportunities before their evidence expires. This includes verified-empty and incomplete censuses: weekly discovery cannot keep their two-day evidence current. An 18-hour full-sweep target (six hours before expiry) is divided across the existing estimated deployment-window count, rounded down to the existing two-hour tick (minimum one tick). This queue runs before new discovery and resumes the existing cursor; native-only footprints with retained pools keep their weekly discovery cadence.
- T1: coins with 0 pools (or effectively eligible baseline), every run.
- T2: 1–4 pools or 1 chain, every 84th run (one week at the two-hour cron cadence).
- T3:
>=5pools on>=2chains, every 84th run (one week), sharded by stablecoin id. - Global scheduling is tier-first (
refresh -> T1 -> T2 -> T3 -> dormant), with staleness used only as the tie-breaker inside a tier.
- Exponential backoff (applied as a tier floor from
consecutiveMisses; effective tier ismax(baseTier, backoffTier)):- 0–2 misses: no backoff override (base tier from pool/chain counts determines placement)
- 3–5: floor T2
- 6–9: floor T3
- 10+: dormant (daily gate)
- Any discovery hit resets
consecutiveMissesto 0, removing the backoff floor; the coin's tier is then recomputed from its pool/chain counts on the next run. - Verified-empty census cadence hold: a crawl that finds zero pools always increments
consecutiveMisses, so a footprint whose correct answer is "no DEX pools anywhere" used to accrue misses forever and decay to dormant — whose 24-hour-plus per-window cadence is slower than the sweep-aware census freshness bound below, turning a correct zero-pool answer into a permanently stale census. A coin whose current census answers every provider-supported tracked deployment asverified_no_pools(noobserved_pools, no provider-supportedprovider_inaccessible, no missing row) therefore stops at the T3 floor instead of falling to dormant. That hold remains the base-tier diagnostic; the zero-pool maintenance queue now schedules refreshes before the two-day single-window freshness bound instead of relying on the weekly T3 cadence. Chains with no registered discovery provider are excluded from the test because the census already carries them as a standing unsupported remainder rather than an unanswered deployment.readDiscoveryCensusSummaries()aggregates the census in one grouped read per run,hasVerifiedEmptyCensus()applies the test, and cron metadata reportscensusCadenceHolds— the number of coins the hold kept above dormant this run. - Remapped unsupported-scope re-attempt: when a provider mapping lands after a census row was written as "No registered token-pool provider supports this chain", the stale row is a pre-coverage artifact the backoff ladder can starve for weeks (the freshness bound is priced at the T3 cadence).
readDiscoveryCensusSummariesnow counts unsupported rows on chains the registry serves (remappedUnsupportedCount), andhasRemappedUnsupportedCensusRow(orchestrator.ts) promotes such coins to therefreshtier — front of queue, no cohort gate — until the crawl overwrites the row. Rows on genuinely provider-less chains are excluded, so permanent scope limits (XRPL, Stellar, Tezos beyond uUSD) do not trigger extra crawls. The consume side already published these rows asdeploymentCensusSupersededOutcome; this closes the loop on the write side.
- Chain-aware source routing: discovery only queries chains with defined entries in a stablecoin’s
contractsplus optionaltradedContractsmetadata; this avoids unnecessary API calls against un-deployed chains while preserving wrapper/secondary-market discovery addresses. - Resumable deployment windows: each coin crawl is bounded by a 25s per-coin budget shared by all provider stages, and the stages run to completion in order, so a footprint whose paced provider queries exceed that budget would let the first stage consume it and permanently starve every chain only a later stage can serve.
selectDiscoveryTargetWindow()(worker/src/cron/dex-discovery/target-window.ts) prices each deployment at every registered serial provider's pacing floor plus request allowance, and when the footprint does not fit it hands the crawl one window at a time, resuming after the last deployment a provider actually reached on the previous run. The resume markers live in onekv_configrow (discovery_target_cursors); an unknown or missing marker restarts the rotation at the first deployment. Footprints that fit the budget are crawled whole, exactly as before. Deployments outside the current window are not classified, so they keep their previous census row instead of being downgraded to a bounded-crawl deferral, and cron metadata reportswindowedCoinspluswindowedDeploymentsDeferred. - Maintenance capacity: refresh uses the existing serial provider sequence, 25-second per-coin window, 12-minute run budget, and finalization reserve; no trigger or concurrent connection is added. The 18-hour sweep is a scheduling target, not a promise under provider failure, budget exhaustion, or footprints too large for the existing ticks. Deferred or failed observations still expire after 24 hours; they are never relabeled fresh. Monitor the
refreshtier count,budgetExhausted, and deployment-window deferrals after rollout. - Stale-first pool refresh (
worker/src/cron/dex-discovery/refresh-stale-pools.ts): coin cohorts are revisited weekly, so a retained CoinGecko-onchain row outlives its 24h volume reading long before its coin is crawled again, and Sugar-backed Aerodrome (Base) / Velodrome (Optimism) Slipstreamdirect_apirows never carry 24h volume. Before the cohort queue, each run readsdex_pool_registryfor (a)cg_onchain/gecko_terminalattributions inside the 14-day horizon whose newest same-stablecoin CG-family reading is older than 20 hours (24h minus two discovery ticks, so a refreshed pool is re-read before it leaves the window) and (b) retained Slipstreamdirect_apirows without such a reading. Candidates collapse to one physical pool id and are ordered stale-first: no in-window reading before merely due, then retained TVL descending. Attributions to a stablecoin with no active deployment on the pool's chain (frozen or retired coins) are dropped before packing, since no pool leg can match (untrackedPools). A pool the provider answered for without a refresh (not returned, unparseable, id mismatch, untracked legs, or refused by admission) is backed off: it is skipped until 6 hours after that attempt, doubling per consecutive miss up to 4 days, minus half a discovery tick of cron-jitter slack. The per-pool map (misses,at,reason) lives in onekv_configrow,dex_stale_pool_refresh_backoff, restricted to the pools still due and written at most once per run; a refresh, or a newer CG-family reading written by another path after the miss, clears the entry, while transport failures and pools not reached leave it unchanged. The due set is not capped by the request budget (the 2026-09-28 horizon held about 7,700 CG-family and Slipstream ids, roughly 0.9 MB if every one were backed off), so a row that would exceedbackoffMaxRowBytes(1 MB, half of D1's 2 MB value limit) keeps the highest-TVL entries and drops the rest, counted inbackoffPruned; pruned pools are simply attempted on the next run. A malformed row keeps its valid entries (an unparseable one keeps none) and is rewritten as valid JSON. A failed backoff write only logs a warning (dex_discovery.stale_pool_refresh_backoff_write_failed) and never fails the pass; until the next successful write, runs fall back to re-attempting those pools every run. The remaining candidates are packed into per-network CoinGecko onchain/pools/multirequests of 30 addresses (lower-priority same-network pools fill a request opened by a higher-priority one), capped at 125 requests and a 150-second slice of the run budget; anything not reached stays due for the next run. Returned pools pass the same admission policy as the token-pool crawl (admitCgOnchainPool: tracked leg, $1k TVL floor, pair-price coherence, price plausibility, 50x turnover ceiling) and are written ascg_onchainrows for the samechain:addresspool id throughupsertStagedPools, so the monotonicrefreshed_atguard applies and the registry resolver sees a same-id CG reading beside the Slipstreamdirect_apirow. Both CG onchain discovery paths store 24h volume throughcgPoolVolume24hReading()(worker/src/lib/coingecko-onchain.ts): a positive reading as-is,0only when the provider publishes an explicit zero volume and zero 24h buys and sells, otherwisenull(absent or null volume field, or a zero beside nonzero or unpublished trade counts). A 2026-09-28 read-only sample of the 600 largest stale/Slipstream pools found every published zero paired with zero trades (187 pools, $1.55B, identical across/pools/multi, the single-pool endpoint and public GeckoTerminal) and one null volume field. Refreshed pools are not added to the run's known-pool set, so cohort crawls, miss counters, and deployment outcomes are unchanged. The pass shares the CoinGecko onchain key, circuit, and 250 ms pacing, runs one request at a time, stops after three consecutive provider failures or when the circuit opens, and recordsstalePoolRefreshrun metadata (outcome,poolsDue(backed-off pools included),poolsSelected,requests,refreshed,failed,failures,backedOff,backedOffTvl,backoffPruned,deferred,rowsWritten,unsupportedPools,untrackedPools,stalePoolsRemaining,staleTvlRemaining).failuressplitsfailedby the furthest stage each attempted pool reached:transport(failed request, or a pool missing from a schema-degraded answer),notReturned,parseFailed,poolIdMismatch,untrackedToken,blockedDex,minTvl,incoherentPrice,implausiblePrice,turnoverCeiling, andinvalidRow(admitted but refused by theupsertStagedPoolsid/TVL-ceiling check, so never counted as refreshed).staleTvlRemainingsums the largest retained attribution TVL of each pool still without an in-window reading after the run, backed-off pools included, so it is an inventory measure rather than coverage. A 2026-09-28 live replay of the due set (897 pools) found no transport, parse, id-key or deployment-index defect: 466 pools failed pair-price coherence, 333 sat under the $1k TVL floor, 69 belonged only to inactive coins, 4 hit the price-plausibility or turnover gates, 1 was not returned, and 2 admitted pools exceeded the staged TVL ceiling (previously miscounted as refreshed), which is why about 900 pools had been re-requested and counted as failed every run. A registry read/write failure marks the rundegraded(dex-discovery-stale-pool-refresh-failed) without stopping cohort crawling. Rows on chains without a CoinGecko onchain network remain on the cohort cadence. - Freshness confidence decay: staged pool effective TVL is multiplied by
1for ages up to 24h, then linearly to0at 336h (14 days); rows past the horizon are excluded from scoring merge, and registry rows are deleted after 15 days. Price observations come only from rows refreshed within 24h. - Staged pool defaults:
organic_fraction = 0.5,balanceRatio = 1.0,lockedLiquidity = null,maturity = min(daysSinceDiscovered, 30),isStableinferred from normalizedquoteSymbol. - Source order and transport:
CG Onchain -> GeckoTerminal -> DexScreener -> CG Tickers -> Curve -> Horizon -> Aquarius -> TzKT -> Balanced -> Kava x/swap -> Cosmos (Osmosis, Noble)(the Curve stage contributes census evidence only), executed sequentially with one active fetch at a time, except Curve, which fans out to at most two chains (2connections). - Failure telemetry: cron metadata records both
failedCoinsandfailedCoinErrors; DexScreener malformed-pair or ordinary per-target errors are downgraded to warnings so a single bad fallback payload does not fail the whole coin crawl. DexScreener discovery records one aggregate breaker outcome per run. A hard 429/1015 provider refusal suppresses later DexScreener requests in that run and is retained in bounded run metadata; any earlier successful request heals the aggregate run outcome, while a zero-success refusal records failure and leaves subsequent runs under the normal circuit probe interval.
Global Deduped Aggregates (__global__)
A sentinel row with stablecoin_id = '__global__' stores cross-stablecoin aggregates where each physical pool is counted only once (deduped by poolId). This prevents double-counting when a pool contains multiple tracked stablecoins (e.g., a USDT/USDC pool would otherwise add its full TVL to both USDT and USDC rows).
The __global__ row contains deduped total_tvl_usd, total_volume_24h_usd, total_volume_7d_usd, total_volume_7d_measured, volume_availability_json, pool_count, chain_count, protocol_tvl_json, and chain_tvl_json. Volume windows are summarized over the poolId-deduped pool set, with the highest-TVL occurrence owning each pool's reading, under the same DEC-19 contract as per-coin rows. The public API returns totalVolume24hUsd / totalVolume7dUsd as null whenever any deduped pool lacks an admitted reading; the availability records then publish the observed volume over the admitted deduped pools (partialGrossUsd) together with its deduped-TVL volumeCoverage (admittedTvlUsd / retainedTvlUsd, before the global protocol-TVL clamp), never labelled a complete total. Score-related fields (liquidity_score, concentration_hhi, etc.) are NULL.
The frontend reads __global__ for overview stats (total DEX TVL, 24h volume, protocol/chain breakdown bars) instead of naively summing per-stablecoin values. The constant DEX_GLOBAL_KEY (shared/types/index.ts) provides the key.
The liquidity overview's Protocol TVL Breakdown legend is capped at 10 entries total: the top 9 protocols render individually, and the remainder is grouped into Other.
Additional Liquidity Metrics
- Concentration HHI: Herfindahl-Hirschman Index computed from the full retained pool set after filtering/caps but before top-10 display truncation. Range 0-1 (1.0 = single pool). Stored as
concentration_hhi. - Depth Stability: Coefficient of variation of daily TVL over 30-day rolling window, inverted to 0-1 scale. Requires >=7 days of data. Stored as
depth_stability. - TVL Trends: 24h and 7d percentage changes computed from daily history snapshots, but only when a baseline exists within a tolerance window (
12hfor 24h,36hfor 7d) and that snapshot hascoverage_confidence >= 0.5. Otherwise the API returnsnull. - Depth Stability / Volume Consistency inputs: durability history uses only snapshots with
coverage_confidence >= 0.75; fewer than 7 confident rows fall back to neutral durability defaults. - Daily Snapshots: One snapshot per active stablecoin per day in
dex_liquidity_history(migration 0010, confidence fields added in 0061). A run reuses today's snapshot only when its active-ID set is exact, has no duplicate identities, and its scored-ID set covers the incoming active scored IDs. If coverage expands or the active universe changes, the writer preserves richer same-day scored rows, overlays new observations, and replaces the UTC date through one bounded atomic D1 batch (DELETEplus multi-row inserts), so a failed replacement leaves the prior date state intact. Successful DEX liquidity persistence also prunes history to the public 365-day window.
The published "Concentration" verdict on the DEX-liquidity card and the exit-route crowding bands share one recorded threshold table (methodology v6.6, effective 2026-09-21): HHI >= 0.35 renders High ("Crowded exits"), 0.18 <= HHI < 0.35 renders Medium ("Visible route concentration"), and HHI < 0.18 renders Low ("Broad route diversity"). A non-finite HHI renders the broadest band rather than throwing. The canonical table lives in shared/lib/classification/liquidity-concentration.ts (re-exported from shared/lib/classification.ts); consumers import it, never re-type it. v6.6 records the re-basing the 2026-09-16 card/exit-route consolidation shipped (the pre-consolidation card table put High at >= 0.5 and Medium at >= 0.25).
DEX Price Cross-Validation
dex_prices table (migration 0011) stores DEX-implied USD prices extracted from multiple DEX sources. It is updated hourly by the :16 sync-dex-liquidity consumer.
Price observation sources:
| Source | Tier | Chains | Method | Filter |
|---|---|---|---|---|
| Curve StableSwap | 1 (1.0) | CURVE_CHAINS in worker/src/cron/dex-liquidity/constants.ts | Curve Finance API usdPrice per coin | TVL >= $50K, balance ratio >= 0.3 |
| Fallback indexed Curve pools | lower | Chains without native Curve API coverage, currently including Plasma when indexed by fallback pool providers | GeckoTerminal / CoinGecko Onchain token-pool prices | TVL >= $50K for price observations, peg-aware price sanity against the shared validation engine, and skipped on native-covered Curve API chains to avoid duplicates |
| Uniswap V3 | 1 (1.0) | Ethereum, Base, Arbitrum, Polygon, Celo | Subgraph token0Price/token1Price relative to USD reference tokens (Celo: Messari tick-derived spot) | TVL >= $50K, one side must be USDC/USDT/DAI/etc. (after alias normalization such as USD₮0 -> USDT), peg-aware price sanity against the shared validation engine |
| Fluid | 1 (1.0) | FLUID_CHAINS in worker/src/cron/dex-liquidity/fetch-fluid.ts | Direct API last_price (base/target ratio) | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Balancer | 1 (1.0) | BALANCER_CHAIN_MAP in worker/src/cron/dex-liquidity/fetch-balancer.ts | Derived from balanceUSD / balance per token | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Raydium | 1 (1.0) | Solana | Direct API price field (base/quote ratio) | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Orca | 1 (1.0) | Solana | Direct API price field (base/quote ratio) | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Meteora | 1 (1.0) | Solana | Direct API current_price | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| PancakeSwap V3 | 1 (1.0) | PANCAKESWAP_V3_SUBGRAPHS in worker/src/cron/dex-liquidity/fetch-pancakeswap.ts | Subgraph token1Price, token1 per token0 (see the direct-API orientation explanation above) | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Aerodrome Slipstream | 1 (1.0) | Base | Sugar view sqrt_ratio via sqrtRatioToSpotPrice | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Velodrome Slipstream | 1 (1.0) | Optimism | Sugar view sqrt_ratio via sqrtRatioToSpotPrice | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| DexScreener | lower | 30+ chains (universal fallback) | Token pools API priceUsd | Pair liquidity >= $50K for price observations, >= $1K for pool discovery, peg-aware price sanity against the shared validation engine |
Price extraction pipeline:
- Collect price observations from all source families during data fetching phase
- Merge all observations into a single map keyed by stablecoin ID
- Run pool dedupe, retention filters, and protocol-level TVL caps for the main liquidity scoring surface
- Rebuild DEX price observations only from retained pools that still carry a usable stablecoin
priceand finite positive effective price-evidence TVL; protocol source rows without usable weighted observations are omitted, never assigned price zero. - Collapse any remaining duplicate retained observations of the same physical pool so one pool only carries weight once
- Compute source-family-confidence-weighted median per stablecoin from that retained priced-pool surface
- Compare with primary price from D1 cache to compute
deviation_from_primary_bps - Store in
dex_priceswith one aggregated JSON entry per protocol inprice_sources_json - Publish qualifying challenger pools from the full retained pool set into
dex_price_challenger_snapshotsanddex_price_challengers - Retire any pre-existing
dex_pricesrows whose stablecoin has no observations in the latest successful scoring run, so the table reflects current DEX coverage rather than last-seen coverage
Publication is fail-closed against a missing primary. Every guard in computeDexPrices() — the pre-median outlier filter, deviation_from_primary_bps, and the display-ratio band — is anchored to the stablecoin's primary price from the trusted cache map, so when that map holds no usable primary the row is withheld before staging instead of publishing with the guards silently bypassed. Withheld rows carry machine-readable reason primary-missing in the persistence diagnostics (withheldByStablecoin, bounded like the peg-impossible rejection sample and paired with truncatedWithheldStablecoins when the sample overflows) and raise the DEX publication summary log line to warn level.
Raw pre-retention discovery observations no longer write directly into dex_prices. If a pool is skipped as a duplicate or dropped by retained-pool quality filters, it cannot keep influencing dexPriceUsd or price_sources_json.
DEX observation validation now loads the current FX / gold / silver references once per cron entrypoint and passes them through the scoring and discovery paths. In normal operation this means:
- fiat pegs validate against live FX references, not only hardcoded fallback ranges
- gold/silver pegs validate against live spot references, scaled by
commodityOuncesfor fractional tokens
The primary-pricing bridge now reads dex_prices.price_sources_json as a per-protocol aggregate (fluid, balancer, curve, uniswap-v3, uniswap-v4, raydium, orca, etc.) rather than as repeated individual pool rows. Those aggregates are rebuilt from the same retained pool surface used by challenger publication and UI liquidity detail, so skipped discovery rows cannot bypass retained-pool admission just because they emitted an early price observation. Individual pool challenge reads instead come from the dedicated challenger tables published from the full retained pool set, so consensus promotion, depeg confirmation, and UI top-pool display no longer share the same storage shape. When a promoted per-protocol bridge source is actually admitted for an asset, the overlapping dex-promoted aggregate is withheld from primary consensus so the same DEX observation family cannot self-confirm. If promoted protocol candidates are rejected for registry, freshness, TVL, or corroboration reasons, a valid aggregate dex-promoted source can still enter as the soft DEX fallback. A lone promoted DEX protocol is admitted only when no non-DEX source exists, or when a hard market/oracle/protocol source agrees inside the live threshold. Two or more promoted DEX protocols are admitted as candidate sources; consensus then determines agreement.
Every source family now uses the same minimum liquidity rule for DEX prices: a pool must contribute at least $50K of liquidity at observation time. For staged discovery rows, the floor is applied after freshness confidence decay. For retained-pool publication, the same floor is reapplied before writing dex_price_usd or price_sources_json, while lower-TVL retained pools can still contribute to liquidity scoring when they pass the scoring gates.
DEX price median weighting uses canonical source families rather than the normalized protocol label: DeFiLlama and direct API observations carry 1.0x, CoinGecko Onchain and GeckoTerminal carry 0.85x, and DexScreener, CoinGecko tickers, plus Horizon carry 0.55x. This prevents fallback rows from gaining primary-source median weight solely by claiming a high-trust protocol name.
Confirmation gate in detectDepegEvents():
- When primary price shows depeg (>=100bps), check DEX price
- Only trusted DEX rows are used for depeg suppression/confirmation: freshness within
DEX_FRESHNESS_SEC(currently 75 minutes for the hourly price producer) and aggregate source TVL>= $1M - If a trusted DEX price shows coin at peg (<100bps): suppress new depeg event (likely false positive)
- If DEX unavailable, stale, or confirms depeg: open event normally
- DEX evidence participates in new-event suppression, pending/extreme confirmation, same-direction peak support, and corroborated recovery paths; existing events are not auto-closed by a single contradictory DEX row
- ~80-100 stablecoins covered by multi-source observations; remainder fall through to primary-only detection
API exposure:
/api/dex-liquidity: addsdexPriceUsd,dexDeviationBps,priceSourceCount,priceSourceTvl,priceSources,coverageClass,coverageConfidence, coverage-confidence-derivedliquidityEvidenceClass,hasMeasuredLiquidityEvidence,trendworthy,sourceMix,balanceMeasuredTvlUsd,organicMeasuredTvlUsd, and exactdeploymentCoverageoutcome rows/api/dex-liquidity: adds aWarningheader when the latestsync-dex-liquidityrun was degraded or failed and the endpoint is serving the last successful dataset; quality drift in an otherwiseokrun emits a warning only when the finding is dataset-wide. Drift flags that name a single coin (major-tvl-cliff:<id>,watchlist-pool-drop:<id>) never reach the global header or the__global__row; they surface only on the affected coin's row.failedSources, near-guard proximity, and unscoped pipeline-counter flags stay global/api/dex-liquidity-history: now returnsliquidityEvidenceClass,hasMeasuredLiquidityEvidence, andtrendworthyso history consumers can separate baseline-worthy periods from informational low-confidence snapshots/api/dex-liquidity:tvlChange24h/tvlChange7dcompare against the history row nearest 24h / 7d ago (confidence >= 0.5, positive TVL, 12h / 36h tolerance) with no methodology check. A liquidity methodology release that re-measures retained TVL (6.91, 6.92) therefore shows as a TVL change for about seven days. Known limitation: TVL-basis cutovers are guarded only in the digest (methodology-basis-change) and in the 30-day stability series; guarding the trend readers without blinding every coin would need a persisted per-coin marker of whether the coin's TVL changed at the break/api/peg-summary: adds optionaldexPriceCheckper coin when the row passes a UI trust gate (fresh within 60 minutes and aggregate source TVL>= $250K)
Frontend:
dex-liquidity-card.tsx: labels the detail moduleDEX market liquidity, describes its score as an aggregate market measurement rather than a single-route execution test, and shows source freshness beside the scoredex-liquidity-card.tsx: shows DEX-implied price section when available plus coverage badges (Primary,Mixed,Fallback,NR)dex-liquidity-card.tsx: surfaces whether liquidity is measured, partially measured, or only observed without measured pool balancesdex-liquidity-card.tsx: forunobservedrows, the detail page now says no direct-token DEX market is observed and renders an explicit unobserved-history state instead of hiding history or plotting placeholder zeros as a market chartdex-liquidity-card.tsxand the detail distribution section distinguish unsupported or valid-empty coverage from source failures; unavailable and retained-stale data remain visible with source notices and retry actions instead of disappearing/liquidity: shows coverage badges and a separate unrated/unobserved section instead of silently dropping NR assets/liquiditysearch uses two-way URL synchronization, so browser Back/Forward restores the visibleqinput as well as the result set/liquidity: the advisory banner renders user-facing copy (for exampleSome liquidity data is being re-verifiedfor drift advisories) and keeps the raw producer advisory string — includingqualityDriftFlags=...— available only as atitletooltip, instead of printing the machine tokens in the banner body- Detail and overview liquidity surfaces now attach contextual methodology hints to the score label,
Effective TVL, and key summary stats, with score-card footer links back to/methodology/#liquidity-methodology peg-heatmap.tsx: amber "!" badge on tiles where DEX disagrees with primary
Operator metadata:
sync-dex-liquiditycron metadata now records run-over-run drift and evidence-gap diagnostics including:qualityDriftSeverity/qualityDriftFlags, plusqualityDriftCandidatesfor pending and confirmed conditions andqualityDriftRebaselinedfor lasting conditions accepted as the new level on this runcoinsWithoutMeasuredBalances,coinsGtOnly,coinsCrawlerOnly- per-source-family retained pool counts, measured TVL, and price-observation coin counts
- protocol-cap breakdowns by top protocol and top affected stablecoin
- watchlist deltas for major assets such as USDC, USDT, DAI, USDS, and USDe, measured as published pool count against published pool count so the delta cannot compare a curated subset against the full set
majorTvlCliffsand amajor-tvl-cliff:<id>flag athighseverity for any coin that was among the previous run's ten largest by TVL, held at least $5M, and landed below 60% of that value. The publication guards (hardValueGuard,hardMajorCoverageGuard, coverage floor) are aggregate: on 2026-08-20 one coin shed ~91% of its measured TVL to a partial pool inventory while every aggregate stayed inside its bound, and the next daily digest published the hole as news (see digest-pipeline.md). The run still publishes — a real drain must reach the primary dataset — but the cliff is now visible to operators and refused as digest evidence.- a drift flag is reported only once the same condition holds for two consecutive productive runs against the same pre-event baseline (the last run whose value was not itself flagged), and it stays reported until the value recovers against that baseline or is accepted as the new level. The pre-event baseline lives in
sourceCoverage.qualityDriftCandidates, so publishing the collapsed value cannot silence its own flag — the failure mode that made 13 of 19 flagged production runs clear within two hours. - a confirmed condition is reported on consecutive runs 2 through
DRIFT_REBASELINE_RUNS(6, about five hourly publications). The run that finds it holding a seventh consecutive time accepts it as the new level: the candidate is dropped, no flag is emitted, and the run recordssourceCoverage.qualityDriftRebaselined: [{ flag, baselineValue, acceptedValue, runs }](runsincludes the accepting run). This applies to every drift candidate —major-tvl-cliff:<id>,watchlist-pool-drop:<id>,price-observation-drop,staged-merge-drop,measured-balance-drop, andweak-coverage-rise. Before 2026-09-28 a lasting, explained step kept its sticky pre-event baseline and warned indefinitely (major-tvl-cliff:frax-fraxheld from 05:17 UTC after FRAX's NEAR Intents contribution stopped counting). Because the candidate is gone, the next run measures against the accepted value, so a further step or a repeat of the same hole after recovery is detected and confirmed from scratch. Acceptance changes no score or publication; the rebaseline evidence stays in run metadata for operators.
- Drift baselines use the latest prior
okordegradedrun with a complete persisted summary; failed, persistence-skipped, and empty-metadata runs cannot create synthetic zero baselines and cannot advance a pending confirmation. - Aerodrome Slipstream can recover a fresh exact-address candidate from staged GeckoTerminal, CoinGecko on-chain, or DexScreener discovery when the broad Sugar crawl misses it. Recovery is generic and bounded to 12 candidates, requires both tokens to be tracked, reuses the staging producer's normalized fee tier, and verifies the pinned factory, token order, tick spacing, slot price, decimals, and balances in serialized Multicall batches before the pool enters the existing Slipstream family. This is the BtcUSD/Base observation path; no per-asset route is authored.
- Deployment-window pricing sums every registered serial provider stage and interleaves provider-signature cohorts before cursor rotation. This prevents a window sized only for its first provider from expiring before later GeckoTerminal, DexScreener, Curve, or Horizon checks while preserving the 25-second per-coin and 12-minute run budgets.