Methodology Versioning
- Current methodology version:
v5.98 - Runtime/version source:
shared/lib/liquidity-score-version.ts - Public changelog route:
/methodology/liquidity-score-changelog/ - Structured changelog:
shared/data/methodology-changelogs/liquidity-score/
DEX Liquidity Score
Production uses two generation-fenced invocations every 30 minutes. sync-dex-liquidity-stage runs at 10,40 * * * *, loads external sources, constructs the ordered pool graph, and writes the exact scoring input to bounded D1 chunks. sync-dex-liquidity runs at 16,46 * * * *, prefers the stage from exactly six minutes earlier, can reuse that exact stage when retrying after consumption, and otherwise falls back to the newest ready, unconsumed stage within 55 minutes. It computes the composite liquidity score (0-100) per stablecoin, publishes all existing liquidity/price/history surfaces, and then yields to the serial stablecoin-charts job. This bounded fallback ensures a producer that finishes after its first consumer attempt is retried in the next cycle. The split invocation is the only entrypoint: stageDexLiquidityScoring() followed by consumeDexLiquidityScoringStage().
The 10,40 and 16,46 expressions above are logical cadence identities. Cloudflare receives them as paired hourly physical triggers (10 + 40, and 16 + 46) that dispatch to the same half-hourly slot keys. This preserves 30-minute freshness and idempotency while qualifying each expensive DEX invocation for Cloudflare's hourly Cron CPU class; the Worker config caps those invocations at 300 seconds. Do not recombine either 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 source or 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).
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'. Readers accept legacy rows with no generation id during rollout, but generation-tagged rows are consumed only when their generation is published.
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 28-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 five producer target maps are cleared before any proof-heavy join evidence is loaded; this preserves the same published inventories and V8/V9 inputs without retaining a duplicate target graph at the scoring peak. Current EVM, Solana, and Tron 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; native joins likewise avoid attaching redundant internal profiles. 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, completed dex_liquidity_run_rows generations no longer referenced by the public table, and abandoned price run rows are retained for three hours and 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 two-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; native quote loaders use the same incremental release boundary. 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, Solana, or Tron 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 (4 chains: Ethereum, Base, Arbitrum, and Polygon) + the classic Aerodrome subgraph + eight score-capable direct protocol-native fetchers (Fluid, Balancer, Raydium, Orca, Meteora, PancakeSwap V3, Aerodrome Slipstream, Velodrome Slipstream), plus a ninth SunSwap V2 census used only for exact-execution target construction.
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. Target-only Fluid copies are hydrated from the canonical tracked-contract metadata after this reduction because the official ticker rows identify tokens by address but omit usable symbols and decimals; exact target construction is still limited to chains with a pinned reviewed resolver deployment. SunSwap census rows never enter scoring, price consensus, or direct-source precedence. 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 now also needs authoritative exact-id confirmation before it can contribute liquidity. That means GT/CG/DS rows cannot invent new Balancer, Fluid, Raydium, Orca, Meteora, PancakeSwap, Aerodrome, or Velodrome pools when the native fetch completed without degradation, even if the source emitted non-degrading parser or pagination warnings; the guard deliberately fails open only when that direct source is degraded or unavailable so staged discovery can still act as recovery coverage during an upstream incident.
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 | Ethereum, Arbitrum, Base, Polygon, BSC, Plasma | 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) | 16 mapped chains (Ethereum, Arbitrum, Base, Polygon, Optimism, Gnosis, Avalanche, Sonic, Fantom, Fraxtal, Mode, Polygon zkEVM, Plasma, Monad, HyperEVM, X Layer) | 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 | BSC, Ethereum, Base | 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) |
| SunSwap V2 | GET https://open.sun.io/apiv2/pools/scan | Tron | sunswap-v2 | target-only | Exact pool/token identities, normalized balances, TVL, volume, token prices, and reviewed 0.3% fee; retained only for exact-execution target construction |
| 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, SUNSWAP_API, AERODROME_SLIPSTREAM_API, VELODROME_SLIPSTREAM_API), and apply min TVL thresholds ($10K for liquidity inclusion or shadow target admission, $50K for score-capable price observations). SunScan currently accepts at most 100 rows per request; the V2 cursor scan is bounded to 60 pages and reports a resumable degraded result if that cap is reached. 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, effective_tvl_usd, balance/organic/stress weights, protocol/chain breakdowns, and source-family mix) from the retained pool set before computing the final score. 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) 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 on 10,40 * * * * and write the bounded scoring-stage generation; scoring/publication on 16,46 * * * * prefers that generation and can consume the prior cycle's still-ready generation when the preferred producer finishes late.
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 | concentrated liquidity (direct API or DL) |
| 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
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. 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).
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 defaults to 30 days unless later refreshed through repeated discovery
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 more than two dormant discovery windows old, provider-backed, entirely verified_no_pools, and at least as new as the coin's latest discovery attempt. Discovery persists that attempt boundary before network work without changing backoff counters, so an abort, budget discard, or result-persistence failure supersedes older empty evidence. Failed bounded provider crawls also attempt to write an inaccessible outcome for each deployment; an incomplete D1 persistence path retains only the attempt fence and therefore remains a discovery deferral rather than a provider outage. 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.
Curve's address-grade DeFiLlama join treats identical coin sets as ambiguous only when more than one Curve API pool survives the shared $10K liquidity floor. Zero-TVL and dust duplicates remain address-indexed for identity evidence but cannot poison the unique fingerprint used to attach execution data to a retained pool. The native Curve census covers Ethereum, Base, Arbitrum, Polygon, Fraxtal, Sonic, Taiko, zkSync, Optimism, Avalanche, Fantom, and Kava; 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.8 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, 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. 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 as executable depth.
Capability matrix p4a.8 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 denominator through an explicit capability gate and therefore keep coverage incomplete. Older envelopes without the explicit capability count and the reviewed deployment-specific StableSwap maturation contracts fail closed until a new DEX-liquidity capture publishes p4a.8; 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 ten-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 six routes per chain, three per protocol, and three per adapter, with one output per physical pool preferred before extra outputs. 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 V8 scoring continue to publish normally.
Since v5.96, the SunSwap V2 adapter is shadow-only. Target and quote collection continue for revalidation, but SunSwap observations are score-ineligible and cannot satisfy exact-route completeness or displace active EVM/Curve routes.
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 V8 scoring.
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 sync-cl-exit-depth lane runs at 0,30 * * * *, before the DEX source stage and scoring consumer. It loads the latest published retained-pool 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. 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. The producer publishes target and quote generations atomically in D1. The 16,46 scoring consumer joins only a fresh published quote generation from the exact 10,40 source-stage graph, projects a compact public profile, and publishes the next target generation after scoring; raw calldata, returndata, and failure evidence remain in D1. 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 V8 liquidity scoring. 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, and Arbitrum; 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. SunSwap V2 collection remains enabled, but the Tron native registry keeps it shadow and score-ineligible; missing, failed, stale, malformed, multi-hop-only, or identity-drifted evidence stays target-level fail-closed. Fluid target construction uses the post-compaction canonical token metadata and only the pinned Ethereum, Arbitrum, Base, and Polygon resolver deployments; other Fluid chains and identity-poor discovery fingerprints remain target-unresolved. Fluid resolver measurements retain activation-pending under their own matrix and shadow requirements.
Uniswap V4 measurement is intentionally narrower and shadow-only. 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. Valid profiles retain activation-pending until independent production generations and an explicit activation review are complete.
The v5.96 containment above supersedes the historical SunSwap activation description in this section: SunSwap collection remains enabled, but its current registry policy is shadow and score-ineligible.
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.
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.
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 V8 scoring.
The same isolated job captures a separate case-sensitive Solana target and quote generation for retained Raydium CLMM and Orca Whirlpool pools. Raydium quotes use the official Trade API and are accepted only when its single route returns the requested pool, input/output mints, exact raw input, exact output threshold, and CLMM lastPoolPriceX64 state inside a bounded before/after Solana RPC slot window. The single reviewed wM-to-USDC Raydium direction additionally reads the current pool account serially, requires the canonical program owner and mint ordering, captures its liquidity and Q64.64 sqrt price, and accepts the route only when integer single-segment replay with the provider-returned fee reproduces both raw output and post-swap sqrt price. A tick crossing, state decode/owner/mint mismatch, fee mismatch, or replay mismatch fails closed; this is a pinned-direction proof rather than a generic Raydium CLMM model. Orca quotes use Jupiter's exact-in engine with onlyDirectRoutes, the current Whirlpool filter, and zero slippage; the consumer requires one 100% route whose ammKey, label, mints, raw amounts, and top-level contextSlot match the target and slot window. Historical persisted proofs using Jupiter's former Orca V2 label remain schema-readable, but new quotes must prove Whirlpool. The adaptive-fee HYUSD/USDC Orca reservation remains shadow-only until an equivalent full on-state Whirlpool replay exists. Quote and RPC bodies are byte-bounded before buffering and parsing; overflow cancels the body and produces a bounded failure rather than retaining an upstream payload. A best-route response for another physical pool is a failed exact quote, never substituted evidence. The serialized Solana producer preserves 12 cursor-rotated targets per half-hour run and reserves two serialized, exact-identity priorities: HYUSD/USDC Orca and the reviewed wM/USDC Raydium route. Each binds target ID, stablecoin, adapter, protocol, pool type, physical pool, mints, decimals, and tracked output identity; missing or drifted identities degrade the lane rather than falling back to a lookalike. Both priorities remain shadow-only. The wM policy keeps its complete pool-account capture and replay proof, but its score eligibility is paused after the first post-activation scoring consumers exceeded the Worker memory limit. All generic Raydium CLMM, Orca Whirlpool, Meteora, and unlisted native targets remain evidence-only with activation-pending. A current operational budget-deferred, bounded transport, slot, or provider-availability failure may use the newest still-fresh exact profile for the same target; malformed, semantic, stale, or identity-mismatched evidence never does. These native profiles are route-only and do not alter aggregate liquidity, price consensus, direct-source precedence, visible pool selection, target publication, or V8 liquidity scoring.
Tron SunSwap V2 uses a third native target/quote generation. The producer verifies the canonical V2 factory and pair runtime hashes, proves factory.getPair(tokenIn, tokenOut), and reads exact pair token order and reserves. It first requests a direct SUNSWAP_V2 route from SUN's documented Smart Router calculation service (https://rot.endjgfsv.link/swap/routerUniversal). Because that service documents and returns only the three highest-output paths, a successful response whose candidates are all multi-hop may fall back to the documented on-chain V2 Router only after verifying its pinned runtime hash, exact factory binding, and getAmountsOut result for the two-token path. A malformed or invalid direct Smart Router candidate never enables the fallback. In either path, the raw output must exactly equal the reviewed 0.3% constant-product result from the independently read pair reserves; Smart Router evidence also requires an equal minimum output and zero referral. TronGrid's JSON-RPC endpoint rejects numeric historical block quantities for eth_call, so all state reads explicitly use latest and are enclosed by before/after eth_blockNumber reads with a bounded 64-block window; this is a race-detecting bracket, not a same-block pin. When independently sampled reserves and the router output disagree, the adapter retries the complete proof capture once so a swap landing between those latest reads can converge on one coherent state; the retry re-verifies identity, runtimes, reserves, route, output, and block window, and a second mismatch still fails closed with exact integer equality. The serialized lane admits the complete current target inventory per half-hour generation because the native consumer reads one published generation rather than combining a multi-run rotation. It still waits one second before each Smart Router request, stops the admitted tail after the first 429, resumes through the durable cursor, and preserves the seven-minute producer deadline with 20 seconds of final-request headroom. TronGrid and Smart Router response bodies are byte-bounded before buffering and parsing, and any missing or mismatched retained SunSwap target is explicitly gated target-unresolved rather than silently omitted. Any identity, hash, route, output, or block-window mismatch fails the quote. The current SunSwap V2 policy is shadow and score-ineligible; target and quote collection continue for revalidation, but valid profiles retain activation-pending. Missing, stale, failed, or unproved direct evidence remains target-level fail-closed. SunSwap census rows and measurements cannot affect aggregate liquidity, price consensus, direct-source precedence, visible pool selection, target publication, P4 capacity or completeness, or V8 liquidity scoring.
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; current registry state is shadow-only, not active-policy score admission.
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. The reviewed wM/USDC Raydium direction remains shadow-only after the first post-activation scoring consumers exceeded the Worker memory limit. 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. Eight Ethereum TwoCrypto crvUSD pools paired with WETH, WBTC, cbBTC, or tBTC are score-eligible: the producer pins and verifies each pool's runtime code, factory, factory-selected views implementation, immutable math dependency, and exact token order before quoting. 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 remaining reviewed CryptoSwap census stays shadow-only until its generation and transfer semantics receive equivalent validation.
Within the unchanged static ten-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. Their selected profiles and histories use a two-hour window so three half-hour cycles survive normal scheduler jitter; all other measured adapters keep the one-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 rows publish as budget-deferred so the quote generation still exactly covers its target generation; that deliberate deferral is healthy only when the next cursor is durably written and the current 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 the one-hour evidence window 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. Solana and Tron SunSwap remain activation-gated shadow evidence under native registry policy. Native degradation is retained under nested metadata and laneStatuses without changing active EVM health; an invocation error remains terminal, and a non-durable deferral, including a Tron rate-limit tail, 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. Before adapter-specific maturity, measured profiles remain diagnostic and the existing reserve simulation remains score-facing. Partial model coverage remains useful shadow evidence but cannot present the modeled subset as the holder's exhaustive DEX exit surface. Production report cards continue to use the legacy aggregate path until a deployed p4a.8 capture and replay satisfy the activation boundary; incomplete modeled coverage retains the aggregate DEX floor rather than allowing a modeled subset to replace it. 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 existing adapters expire after twice the 30-minute producer interval (1 hour), while both exact reviewed Curve StableSwap adapters use their documented two-hour packet ceiling before reverting to the reserve simulation. 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.
Since v5.96, both Solana native profiles and Tron SunSwap are shadow evidence; only active registry adapters enter the capability denominator.
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 fixed all-active decision table is committed at shared/data/safety-score-v9/exit-route-calibration-v1.json.
Both dex_liquidity and dex_liquidity_history also carry methodology_version (migration 0036), reconstructed from commit-history version windows in shared/lib/liquidity-score-version.ts. 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 continues on the 30-minute cadence; discovery data is merged during source-stage pool construction.
- Architecture: three dedicated cron phases feed discovery through publication:
- Source-stage cron:
sync-dex-liquidity-stageevery 30 minutes (10,40 * * * *). - Scoring/publication cron:
sync-dex-liquidityevery 30 minutes (16,46 * * * *), preferring the source slot six minutes earlier and otherwise consuming the newest ready stage within 55 minutes before the serial charts job. - 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,last_crawl_at,last_hit_at. - 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, pool count, and optional owned waiver. The provider set is derived from the registry (getDexDiscoveryProviders()), and the discovery crawl queries exactly the providers that set names: the Curve discovery stage is scoped toCURVE_NATIVE_DISCOVERY_CHAINS, the same eight chains that credit Curve as a provider, so a Curve result can always be attributed to a named provider. 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 it has not been superseded bydex_discovery_meta.last_crawl_at. Each attempted crawl advances that fence before network work without incrementing misses. Failed provider crawls materialize inaccessible outcomes for the attempted footprint when D1 is available; incomplete result persistence retains the fence and remains discovery-deferral evidence. 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.
- 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. - 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, executed sequentially with one active fetch at a time (1connection). - 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 overrides partial success, is retained in bounded run metadata, suppresses later DexScreener requests in that run, 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, pool_count, chain_count, protocol_tvl_json, and chain_tvl_json. 24h and 7d volumes are deduped by poolId the same way TVL is. 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 every 30 minutes by the 16,46 sync-dex-liquidity scoring/publication consumer.
Price observation sources:
| Source | Tier | Chains | Method | Filter |
|---|---|---|---|---|
| Curve StableSwap | 1 (1.0) | Ethereum, Base, Arbitrum, Polygon | 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 | 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) | Ethereum, Arbitrum, Base, Polygon, BSC, Plasma | Direct API last_price (base/target ratio) | TVL >= $50K, peg-aware price sanity against the shared validation engine |
| Balancer | 1 (1.0) | 16 mapped chains (Ethereum, Arbitrum, Base, Polygon, Optimism, Gnosis, Avalanche, Sonic, Fantom, Fraxtal, Mode, Polygon zkEVM, Plasma, Monad, HyperEVM, X Layer) | 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) | BSC, Ethereum, Base | 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 plus CoinGecko tickers 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 35 minutes) 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
- Drift baselines use the latest prior
okordegradedrun with a complete persisted summary; failed, persistence-skipped, and empty-metadata runs cannot create synthetic zero baselines.