Agent navigation — Grep the heading you need: Methodology Versioning · DEX Liquidity Score · Discovery Cron · DEX Price Cross-Validation.
Methodology Versioning
- Current methodology version:
v6.3 - Runtime/version source:
shared/lib/methodology-versions/liquidity-score.ts - Public changelog route:
/methodology/liquidity-score-changelog/ - Structured changelog:
shared/data/methodology-changelogs/liquidity-score/
DEX Liquidity Score
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 on even UTC hours only. 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. 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 occurs only at :10, hourly price publication at :16, and two-hour liquidity publication at even-hour :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.- throw/error: catastrophic source failure (for example DL+Curve hard failure) or an internal pool-processing invariant still aborts the run.
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.
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.
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, fallbackMode signals, bounded poolRejections and poolRejectionMateriality, staged-pool merge counters (stagedPoolsMerged, stagedPoolsSkipped, stagedPoolsSkippedByExactIdentity, stagedPoolsSkippedByUniqueDerivedIdentity, stagedPoolsSkippedByOptionalWildcardIdentity, stagedPoolsSkippedByAuthoritativeProtocol), challenger publish counters, persistence skip state, inactive tracked-asset skip counts, publication-generation diagnostics (generationId, expected/candidate/current row counts), and detailed sourceCoverage values (currentCoverage, previousCoverage, minExpectedCoverage, nearCoverageGuard, currentGlobalTvl, previousGlobalTvl, minExpectedGlobalTvl, valueBaselineSource, valueBaselineGlobalTvl, ignoredPersistedGlobalTvl, nearValueGuard, currentTop10CoveredTvl, previousTop10CoveredTvl, currentTop10GuardTvl, previousTop10GuardTvl, nearMajorCoverageGuard, currentCoverageClasses, previousCoverageClasses, priceObservationCoins, weakCoverageCoins).
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); and a per-cohort shadowAdmissionReport beside it classifying each shadow policy cohort
as no-eligible-source-row or eligible-source-rejected (with gate reason) at target-publication
time. The same publication and quote runs also emit a durable evidence ledger into
worker_producer_history.metadata_json as bounded chunked scalars (mxLedgerV/mxLedgerKind
"A"|"B"/mxLedgerCycle/mxLedgerParts/mxLedger0..n): Record A (06:16 shadow target
publication — per-cohort eligible/rejected/published counts and the target generation id) and
Record B (08:10 shadow quote sync — per-cohort measured/failed/budget-deferred counts plus
ladder monotonicity and cost-bound-consistency violations computed at emission). Records are
written on productive and zero-output runs alike, survive the outer handler merges, and are joined
at retrieval (shared/lib/measured-execution-ledger.ts) to derive the third state,
target-produced-no-quote. The 3-hour target/quote tables remain a rolling
window; activation evidence is evaluated from this ledger,
never from those tables.
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 published before proof-bearing quote evidence is loaded. 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 | 30% | 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 | 20% | DeFiLlama Yields | Log-scale V/T ratio: 38 * (log10(vtRatio) + 3). ~0.1%->0, ~0.3%->18, ~3.5%->59, ~19%->86, ~43%+->100 |
| Pool Quality | 20% | 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 | 20% | DeFiLlama Yields + History | 35% TVL stability, 25% volume consistency, 25% maturity, 15% organic fraction (sqrt curve) |
| Diversity | 10% | 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) + the classic Aerodrome subgraph + 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.
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 also requires measured non-zero 24h volume, which lets pool-state-only sources such as Slipstream expand coverage without replacing stronger overlapping DeFiLlama rows when authoritative volume telemetry is absent. 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: PancakeSwap v3/v4 claims are checked against its concentrated-liquidity inventory, while classic v2 pools remain eligible through exact-id staged discovery because that native fetcher does 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 a provider whose response is an exhaustive census of its protocol on its declared chains may reject a staged pool: Balancer, PancakeSwap, Raydium, and Orca. Meteora, Fluid, Aerodrome Slipstream, and Velodrome Slipstream are declared bounded-sample and never veto — Meteora's API ignores the requested page size and answers ~10 of the ~123K pools it advertises, while Fluid and the two Sugar/Slipstream crawls emit only tracked, two-sided, priceable pools, so an omission there is a coverage hole rather than evidence of absence. Every direct census also drops pools under the $10K direct-source floor before it can be read, so staged pools below that floor are outside every census's reach and are never asked for confirmation. Identity dedupe is unaffected by all of this: exact, derived, and wildcard dedupe still run on every staged pool, so relaxing the veto 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 UniV3/Aerodrome 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 matches are deduplicated against DL via exact or uniquely derived pool identities and merged into the pool graph before the generation is handed to the scoring consumer. Source family: direct_api.
| 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 (token0Price), balances (totalValueLockedToken0/1), fees (feeTier) |
| Uniswap V3 recovery | Fresh dex_pool_staging 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) |
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. 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 carries measured non-zero 24h volume; 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.
PancakeSwap and Orca pagination now 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 BSC, Ethereum, and Base; Orca keeps its opaque API cursor. Completing a tail cycle resets its cursor to the first tail page while preserving the newly refreshed head. Orca retains the attempted far-tail cursor across transport, rate-limit, 5xx, and malformed-response failures; it restarts from the refreshed head only when the API explicitly rejects the cursor with 400/404. Healthy budget truncation is represented as pagination.state = "partial" with the next cursor instead of an opaque fetch error. Cursor writes return a bounded persistence class in source/run metadata: write-failed degrades the source and leaves the stored cursor retryable, while missing-table remains an explicit non-degrading rollout-compatibility state. Rejection/error samples are bounded before entering cron metadata.
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 UniV3/Aerodrome 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 must clear the minimum 24-hour volume floor even when a source marks volume as unmeasured; the unmeasured flag remains diagnostic and no longer bypasses the anti-poisoning guard. After pool filtering and protocol-level TVL caps are applied, the scorer rebuilds every aggregate (total_tvl_usd, total_volume_24h_usd, total_volume_7d_usd, total_volume_7d_measured, effective_tvl_usd, balance/organic/stress weights, protocol/chain breakdowns, and source-family mix) from the retained pool set before computing the final score. total_volume_7d_usd remains the numeric sum of measured 7-day pool volumes, while total_volume_7d_measured = 0 marks rows where at least one retained pool lacks a 7-day volume source; the public API renders that aggregate as null 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.
dex_pool_staging is the handoff point for discovery-refresh rows (CoinGecko Onchain, GeckoTerminal, DexScreener, CoinGecko Tickers). The source-stage cron consumes rows refreshed within the last 24 hours and gracefully falls back to primary-only scoring input when the staging table is absent or empty. 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.
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.
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
Data sources are split across three scheduled phases: discovery sources (CoinGecko Onchain, GeckoTerminal, DexScreener, CoinGecko Tickers) run on 6 */2 * * * and write dex_pool_staging; source loading and pool construction run hourly at 10 * * * * and write the bounded scoring-stage generation; the 16 * * * * consumer publishes prices hourly and liquidity scores on even UTC hours, 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 | project contains aerodrome + isStable flag |
| Aerodrome Volatile (vAMM) | 0.4x | project contains aerodrome, non-stable |
| 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) |
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. |
CoinGecko Onchain Integration
CoinGecko Onchain is a discovery-stage source rather than a direct source-stage fetch. Its outputs are written into dex_pool_staging and later merged by sync-dex-liquidity-stage if the rows are fresh. 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.
| 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_staging 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. 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_staging; 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.). 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.
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 volume snapshots
- 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)
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. 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
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.
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 t3 cohort cadence (ten two-hour discovery runs, so 20 hours per window); the six largest footprints currently sweep in 60-120 hours and are allowed 90-180 hours. 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.
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, and Kava (CURVE_NATIVE_DISCOVERY_CHAINS); the liquidity stage additionally reads Optimism, Avalanche, Fantom, and Gnosis for scoring only. 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, classic Aerodrome volatile pools on Base, 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 candidates come only from the existing Base Aerodrome census. Retained rows resolve the exact address first; a DeFiLlama UUID may fall back only to one unique same-chain, same-family token fingerprint, while a collision fails closed. The selected candidate then requires, at one pinned block, reviewed factory and implementation runtimes, exact factory getPool(token0, token1, false) binding, pool.stable() = false, an unpaused factory, and the pool's dynamic fee. 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.
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 even-hour liquidity publication, the D1-only dex-exit-route-turnover-watchdog compares every previously published coin's compact routeId -> evidenceKind set with the new generation. It reports degraded when any coin reaches Jaccard distance 0.5 or greater (one third of an equal-size set replaced), and metadata includes added/removed route counts plus same-route evidence-kind transitions. The prior compact set is 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. 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. All remaining targets retain the existing whole-coin ranking and fairness behavior.
The isolated score-bearing sync-cl-exit-depth lane runs at 0,30 * * * *, 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. 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 and challenger snapshots each hour, and publishes the full Liquidity Score plus the next active target generation every two hours. 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. 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. The Solana and Tron native measured-execution lanes and the Fluid measured overlay were removed in v6.0; retained Raydium, Orca, SunSwap, and Fluid pools resolve as shaped evidence outside the strict exact-route denominator.
Uniswap V4 measurement is intentionally narrow. Since v5.993, the reviewed hook-free Ethereum cohort is score-eligible after three productive shadow generations and 129 successful quotes in the latest 130-direction production cohort. The source stage serially reads the official Ethereum V4 subgraph deployment, retains the exact PoolId, ordered currencies, fee, tick spacing, hook address, indexed in-range liquidity, and TVL, and joins a DeFiLlama UUID row only when exactly one same-pair/fee V4 candidate exists within 2% TVL. Hooked and zero-liquidity candidates remain in the collision set, but only one exact hook-free candidate with positive indexed in-range liquidity can become a target. Each direction also applies the existing 1.02x favorable-output ceiling to the indexed spot and independent token references before admission, so a deterministically incoherent direction remains target-unresolved rather than becoming a producer failure. At the pinned quote block, the producer verifies reviewed PoolManager, StateView, and Quoter runtime hashes, proves that both view contracts bind to that PoolManager, checks getSlot0(poolId) and getLiquidity(poolId), and decode-binds every quoteExactInputSingle call and result. Nonzero hooks, other chains, missing state, runtime drift, malformed or transport-failed quotes, and non-monotonic successful cost curves fail closed; aggregate TVL is never substituted for executable depth. The separate shadow generation and comparison machinery remains available for future V4 cohorts and rollback review.
The historical SunSwap activation description in this section is superseded twice over: v5.96 returned the adapter to shadow, and v6.0 removed the lane entirely.
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 legacy Curve metapools use the active
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. 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.
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 Solana and Tron native measured-execution lanes were removed in v6.0. The Solana lane captured case-sensitive target and quote generations for retained Raydium CLMM and Orca Whirlpool pools (official Raydium Trade API quotes with pinned pool-account replay for the reviewed wM/USDC direction, Jupiter Whirlpool exact-in quotes for Orca, an adaptive-fee HYUSD/USDC Orca reservation), and the Tron lane collected SunSwap V2 direct-route proofs through SUN's Smart Router with an on-chain V2 Router fallback. Neither lane ever became score-eligible: the wM/USDC and SunSwap activations were paused after production scoring consumers exceeded the Worker memory limit, and every other native target stayed activation-pending shadow evidence. v6.0 deletes the producers, registries, schemas, and persistence wrappers outright; retained Raydium, Orca, and SunSwap pools now resolve as shaped evidence outside the strict exact-route denominator, and their 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 preceding SunSwap activation history is retained for incident context only; the lane itself was removed in v6.0.
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.
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 — deliberately one publication cycle WIDER than the two-hour score-bearing cadence, so a single delayed or degraded even-hour :16 publication cannot expire every measured profile at once — 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.
Since v6.0 the Solana and Tron native lanes are removed (they had been shadow-only since v5.96); only active registry adapters enter the capability denominator, and the unreachable native-measured-exact entry was dropped from the route-source capability matrix (matrix version remains p4a.9).
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/liquidity-score.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.
Discovery Cron
worker/src/cron/dex-discovery/orchestrator.ts runs every 2 hours (6 */2 * * *) and is responsible for pool discovery only. Scored TVL publishes every two hours; 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. - Liquidity score/history and active measured-target publication: every two hours at even-hour
:16; odd-hour:16reuses the current score generation while refreshing prices. - Half-hour V9 bridge: the retained
:46consumer reuses the exact current DEX generation without rewriting DEX surfaces. - Discovery cron:
syncDexDiscovery()every 2 hours (6 */2 * * *). - Discovery writes normalized candidates to
dex_pool_staging; the source stage consumes and merges them, then writesdex_liquidity_scoring_stages/dex_liquidity_scoring_stage_chunks.
- Source-stage cron:
- Discovery staging schema:
dex_pool_stagingincludespool_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(pool_id, stablecoin_id). - 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's token-holder/reserve census is chain-wide for the registered Tezos uUSD deployment and may certifyverified_no_pools. - 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 nested getPools requests run at most two chains at a time with a 4 MiB response ceiling. The liquidity stage reads Curve on a wider chain set for scoring; those extra chains are deliberately not crawled for deployment outcomes. 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. - Tiered priority:
- T1: coins with 0 pools (or effectively eligible baseline), every run.
- T2: 1–4 pools or 1 chain, every 3rd run.
- T3:
>=5pools on>=2chains, every 10th run. - Global scheduling is tier-first (
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. T3 is exactly the cadence the census bound is priced at, so the coin's own correct answer can no longer age itself out of the reviewed scope. 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.
- 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. - Freshness confidence decay: staged pool effective TVL is multiplied by
max(0.5, 1 - ageHours / 48); rows older than 24h are excluded from scoring merge. - 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, pool_count, chain_count, protocol_tvl_json, and chain_tvl_json. 24h and 7d volumes are deduped by poolId the same way TVL is. The public API returns totalVolume7dUsd: null for the global row when any selected deduped pool lacks measured 7-day volume. 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.
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 | 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 |
| Aerodrome | 1 (1.0) | Base | Subgraph token0Price/token1Price + reserveUSD | TVL >= $50K, balance ratio >= 0.3, 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 token0Price/token1Price | 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
price - 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
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; high-severity quality drift in an otherwiseokrun now also emits a warning/api/dex-liquidity-history: now returnsliquidityEvidenceClass,hasMeasuredLiquidityEvidence, andtrendworthyso history consumers can separate baseline-worthy periods from informational low-confidence snapshots/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- 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/qualityDriftFlagscoinsWithoutMeasuredBalances,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
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.
- Drift baselines use the latest prior
okordegradedrun with a complete persisted summary; failed, persistence-skipped, and empty-metadata runs cannot create synthetic zero baselines. - 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.