fix: /cg/tickers liquidity_in_usd is 24h volume — plus other CG/CMC listing field mislabels #685

Closed
opened 2026-08-27 04:49:56 +00:00 by PlasticDigits · 15 comments
PlasticDigits commented 2026-08-27 04:49:56 +00:00 (Migrated from gitlab.com)

Follow-up on closed docs audit #224 (do not reopen #224). Exchange form child: #643. Catalog: #639. True TVL already exists as pair_liquidity_usd / protocol_pair_tvl (#569 / #655). GeckoTerminal event reserves are #684 — different crawler (/gt/*).

Summary

CoinGecko /cg/tickers and several sibling CMC/CG listing fields do not mean what their names or CG_CMC_COMPLIANCE.md say. The worst is liquidity_in_usd: aggregators treat it as pool TVL; we emit 24h volume_usd. Docs still describe it as pool liquidity (or "0" until an oracle exists). That can inflate exchange “liquidity” to equal volume and leak into Llama/GT if anyone copies the field.

Bundle field truthfulness for /cg/* + /cmc/* in one issue: TVL, fake ticker bid/ask, freeze flag, CMC id types, gem leak, symbol-key collisions, and docs that invert trade side / limits. Do not split “rename the comment” vs “wire pair_liquidity_usd” vs “fix the markdown table.”

Current codebase

Surface What the name / docs claim What the code does
GET /cg/tickers → liquidity_in_usd Pool liquidity in USD; notes say "0" unless an oracle is configured (CG_CMC_COMPLIANCE.md § tickers). indexer/src/api/cg.rs sets it from stats.volume_usd ("0" if NULL). Rust comment already admits: “Mislabeled 24h volume_usd (legacy aggregator field).”
True pair TVL (unused here) protocol_pair_tvl + pair_liquidity_usd stamp (#569 / #655). Overview total_liquidity_usd explicitly not CG liquidity_in_usd. aggregator_snapshot.rs never JOINs pair_liquidity_usd.
/cg/tickers bid / ask Best bid/ask (listing tickers). last_price * 0.999 / * 1.001 via f64 (always 20 bps wide). Not the hybrid book.
/cmc/summary highest_bid / lowest_ask Same. Same * 0.999 / * 1.001 in cmc.rs.
/cg/orderbook + /cmc/orderbook Hybrid-simulated depth (#220). Real curve + resting limits. Tickers ignore this.
/cmc/ticker isFrozen 0 active / 1 frozen. Always "0". Pair code_id_frozen (#585) is unused.
/cmc/ticker base_id / quote_id Docs + example: numeric CMC unified IDs (0). Code emits CW20 contract or native denom strings. assets.cmc_id exists and is used only on /cmc/assets.
/cg/pairs, /cg/tickers, /cmc/* gems Listing forms: do not submit gems (coingecko-exchange.md, L639-2). /gt/* 404s gems. get_all_pairs — no gem / ALPHA / USTRIX / SpaceUSD filter. GT form even tells the vendor to ignore gems in /cg/pairs.
ticker_id = {symbol}_{symbol} Unique market id. find_pair_by_ticker or_insert — first pair wins. Two UST1/… markets or spoofed symbols collide; orderbook/trades resolve to the wrong pool_id. /cmc/assets HashMap keyed by symbol overwrites.
/cg/historical_trades side Docs: buy = offer is base (buying the target). Code: is_buy = offer_asset_id == asset_1 (offer quote = buy base). CEX-standard. Docs are inverted.
Trades limit Docs: max 200. Handler clamp 500; utoipa says 500.

Invariants already warn operators: docs/indexer-invariants.md, docs/DEFILLAMA.md, docs/runbooks/overview-global-stats-brin.md — “CG liquidity_in_usd is mislabeled 24h volume; never publish as Llama TVL.” That is a workaround, not a fix.

Why a new implementation is needed

  1. Listing crawlers will treat liquidity_in_usd as TVL. CoinGecko’s exchange ticker schema uses that name for pool USD. Serving 24h volume makes a quiet book look deep after one large print, and a deep idle pool look empty.
  2. Docs actively disagree with code (TVL vs volume vs "0"; buy vs sell; trade limit; CMC ids). #224 aligned timestamps/wrappers; it did not fix these field semantics.
  3. We already have the honest number. pair_liquidity_usd is the same P522-Q / hub catalog as Protocol Total liquidity. Wiring it is a JOIN on the stamp, not a new oracle.
  4. Fake 10 bps bid/ask on tickers contradict /cg/orderbook on the same host. A crawler that prefers ticker bid/ask over the book will invent a 0.2% market that does not exist.
  5. Gems on /cg/pairs fight #562 / #646 / listing-form policy. Soft-launch pairs become “official” CG markets.
  6. Do not reopen #224. That ticket is the closed spec-matrix pass. This is implementation + doc truth.

Constraints / guardrails

  1. Keep the CoinGecko field name liquidity_in_usd. Do not rename it in JSON (crawlers are keyed). Change the value to AMM v2 TVL from pair_liquidity_usd / protocol_pair_tvl. Unpriced / stale / overflow / same-asset → "0" or omit only if the Kujira/CG schema allows — prefer "0" for this required string field. Never $1 UST1, never 2.5× USTR, never vFDUSD, never book escrow, never CG volume.
  2. GET stays a rollup read. Do not live-join pair_reserves ⋈ oracles on /cg/tickers (same rule as #655). Stamp already refreshed with protocol TVL / hub.
  3. Do not “fix” Llama or /gt/events here. Llama = #631 on-chain Pool {}. GT historical reserves = #684.
  4. Headline volumes stay consolidated swap totals (#216 / L10). cl8y_extensions stay additive/optional. last_price stays human quote-per-base (H10), not price_usd.
  5. Bid/ask on tickers: if you replace the 10 bps toy spread, use hybrid book best (mirror or the same sim as /cg/orderbook) or reserve-implied mid ± fee_bps. Do not LCD N+1 per ticker on the cached list path. If best bid/ask cannot be computed cheaply, document the remaining approximation and stop claiming it is the book.
  6. f64 mid * 0.999 is banned for the replacement (precision + fake width).
  7. Gems / ALPHA / USTRIX / SpaceUSD: same exclude list as /gt (L639-2 / #562). 404 / omit from /cg/pairs, tickers, CMC summary/ticker/assets as applicable. Frozen pairs may stay listed with isFrozen=1.
  8. isFrozen: 1 when code_id_frozen (or pair inactive). Do not invent a pause bit from missing volume.
  9. CMC base_id / quote_id: pick one and make docs + OpenAPI + JSON agree. Preferred: numeric assets.cmc_id when set, else 0, and put contract/denom in a documented extension — or keep contract strings and rewrite the compliance table/example (today they claim numbers). Do not ship both silently.
  10. Symbol collisions: do not silently map EMBER_LUNC (or two UST1 pairs) to the first insert. Prefer pool_id disambiguation in lookup, or skip duplicate ticker_ids and log. HashMap-by-symbol on /cmc/assets must not drop the economic token for a gem spoof.
  11. 60s aggregator cache (#288) stays. Invalid limit/offset still 400. No raw SQL from query strings.
  12. Additive JSON. Do not remove liquidity_in_usd, cl8y_extensions, or pool_id. Do not wrap /cg/tickers in { tickers: [...] } unless product explicitly accepts a crawler break (Kujira wrap is Partial today — leave it).
  13. No emails, handles, or integrator chat in commits or docs.

Relevant files

File Role
indexer/src/api/cg.rs Tickers liquidity_in_usd, fake bid/ask, pairs list, trades side
indexer/src/api/cmc.rs Summary bid/ask, ticker ids / isFrozen, assets map
indexer/src/api/aggregator_snapshot.rs Shared load — JOIN pair_liquidity_usd, gem filter
indexer/src/api/mod.rs find_pair_by_ticker collision
indexer/src/db/queries/pair_liquidity_usd.rs Stamp to reuse
indexer/src/indexer/protocol_tvl.rs protocol_pair_tvl — do not fork
indexer/src/indexer/defillama.rs COLUMBUS5_GEM_ADDRESSES — share with GT
indexer/src/api/gt.rs EXCLUDED_CW20 / is_excluded_cw20 — keep lockstep
indexer/tests/api_cg.rs, api_cmc.rs, api_aggregator_batch.rs Assertions today may lock in volume-as-liquidity
docs/CG_CMC_COMPLIANCE.md Field table + notes + trade side + limit
docs/indexer-invariants.md Replace “still mislabeled” once fixed
docs/DEFILLAMA.md, docs/runbooks/overview-global-stats-brin.md Cross-links
docs/listings/forms/coingecko-exchange.md Gem / API notes
scripts/qa/ + Makefile make verify-issue-NNN (do not overload verify-issue-224)
  1. load_aggregator_pairs: LEFT JOIN pair_liquidity_usd. Map to ticker liquidity_in_usd as the human USD string. Unpriced → "0". Stop copying volume_usd.
  2. Ticker bid/ask: from mirrored best bid/ask (same units as last_price) when the book snapshot is fresh; else omit-equivalent (last_price both sides) or reserve mid ± fee_bps — pick one, test it. Kill to_f64() * 0.999.
  3. isFrozen: code_id_frozen / is_active.
  4. CMC ids + docs in the same MR so the table matches JSON.
  5. Gem filter shared with /gt (one helper). /cmc/assets skip excluded CW20s; last-write-wins on symbol must prefer the permanent economic pin if a collision remains.
  6. Ticker map: on duplicate SYMBOL_SYMBOL, do not or_insert the first forever — prefer factory-listed non-gem, else skip key and require pool_id.
  7. Docs: liquidity_in_usd = AMM TVL stamp; buy = offer quote; trades limit 500; drop “0 unless oracle.”
  8. make verify-issue-NNN. Coolify indexer redeploy. No wasm migrate. No dApp change.

Acceptance criteria

  • AC1. /cg/tickers liquidity_in_usd equals pair_liquidity_usd / protocol_pair_tvl for a priced pair (e.g. UST1/cUSTC), not 24h volume_usd.
  • AC2. Unpriced pair → "0" (not volume, not $1 UST1, not 2.5× USTR).
  • AC3. A pair with large 24h volume and tiny reserves shows small liquidity_in_usd and large base_volume / target_volume.
  • AC4. Docs + OpenAPI + code comments agree. Invariants no longer say the field is mislabeled volume.
  • AC5. Ticker bid ≤ last_price ≤ ask when a book/reserve mid exists; width is not hardcoded 20 bps unless that is the documented fallback. No f64 * 0.999.
  • AC6. /cmc/summary bid/ask follow the same rule as CG tickers.
  • AC7. Frozen pair (code_id_frozen) → /cmc/ticker isFrozen = "1" (or 1 — match existing string type).
  • AC8. CMC base_id / quote_id match the rewritten spec (numbers or contracts — not both).
  • AC9. Gem / ALPHA / USTRIX / SpaceUSD omitted from /cg/pairs, /cg/tickers, /cmc/summary, /cmc/ticker. /gt exclude list stays lockstep.
  • AC10. Duplicate ticker_id does not silently bind orderbook/trades to the wrong pair.
  • AC11. historical_trades docs match code (buy = offer quote / buy base). Limit docs = 500.
  • AC12. GET does not scan swap_events or live-sum reserves for TVL (EXPLAIN / test). #288 cache + pagination unchanged.
  • AC13. make verify-issue-NNN + compliance doc grep. #224 stays closed.

Test plan (all paths)

ID Path Expect
T1 Priced UST1/cUSTC (or UST1/USTR) ticker liquidity_in_usd = stamp TVL, ≠ volume_usd
T2 High volume, dust reserves TVL small; volumes large
T3 Unpriced / stale / hub-down "0"
T4 Idle priced pool (volume 0, reserves > 0) TVL > 0, volumes 0
T5 One-sided catalog (2× priced leg) Same as #569
T6 /cmc/summary same pair No liquidity_in_usd field (CMC schema); bid/ask not 20 bps toy if book exists
T7 Hybrid book with rest bid/ask Ticker bid/ask match book best (or documented fallback)
T8 Empty book, usable reserves Mid from reserves ± fee or documented fallback — not close * 0.999
T9 Frozen pair isFrozen=1; still listed unless gem
T10 Gem pair in DB Absent from CG/CMC lists; /gt/pair still 404
T11 Two pairs, same symbols, different contracts Lookup does not return the wrong pool_id
T12 /cmc/assets gem + real token same symbol Economic pin kept
T13 /cmc/ticker ids Match AC8
T14 Buy: offer = quote In buy[]; sell: offer = base in sell[]
T15 limit=200 / limit=500 / limit=501 on trades 200/500/clamp; docs say 500
T16 offset>10000 tickers 400
T17 Cache: two GETs in 60s Same TVL stamp (not a second live compute)
T18 /cg/pairs page still has pool_id Unchanged shape

Test plan (attack, hack, abuse)

ID Vector Expect
A1 Volume-as-TVL cheat (leave volume_usd in liquidity_in_usd) T1/T3 fail the MR
A2 $1 UST1 / 2.5× USTR Stamp uses hub catalog only
A3 Wash volume to inflate “liquidity” Volumes move; TVL does not
A4 Donate to pair CW20 TVL stays internal RESERVES (stamp), not balanceOf
A5 Symbol spoof / gem ticker CL8Y_UST1 Collision rule; gems omitted
A6 SQL / proto ticker_id 400; existing segment check
A7 N+1 LCD on /cg/tickers Forbidden; cache-miss = SQL stamps only
A8 Cache poison (stale TVL after refresh) TTL 60s max; next miss reads new stamp
A9 Frozen pair reported live isFrozen=1
A10 Gem leak after filter typo (case / missing pin) Lockstep test vs COLUMBUS5_GEM_ADDRESSES + GT list
A11 XSS / huge numeric string in stamp String field only; no HTML
A12 DoS limit=1000 tickers with live book sim per row If bid/ask uses book, batch from mirror — not 1000 LCD calls

Verification criteria

make setup-indexer-postgres   # if needed
make verify-issue-NNN         # api_cg + api_cmc + snapshot tests + compliance grep

Live (after indexer redeploy), no chat logs:

curl -sS "https://indexer.dex.cl8y.com/cg/tickers?limit=5"
# For a known priced pair: liquidity_in_usd ≈ GET /api/v1/pairs/{pool_id} liquidity_usd
# and ≠ that pair's 24h volume_usd.
  • T1–T18 and A1–A12 automated where possible.
  • CG_CMC_COMPLIANCE.md field table matches JSON.
  • Invariants/Llama runbook no longer call the field “mislabeled volume.”
  • /gt/events unchanged except shared gem helper if extracted.
  • #224 remains closed; #684 remains the GT reserve ticket.

Out of scope

  • GeckoTerminal per-event reserves (#684)
  • DeFiLlama adapters (#631)
  • Wrapping /cg/tickers in { tickers: [...] } (Kujira wrap)
  • CoinGecko Partners form submit (#643)
  • Protocol UI / /pool TVL (already #569 / #655)
  • Renaming liquidity_in_usd or inventing liquidity_in_usd_real
## Parent / related Follow-up on closed docs audit [#224](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/224) (**do not reopen** `#224`). Exchange form child: [#643](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/643). Catalog: [#639](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/639). True TVL already exists as `pair_liquidity_usd` / `protocol_pair_tvl` ([#569](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/569) / [#655](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/655)). GeckoTerminal **event** reserves are [#684](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/684) — different crawler (`/gt/*`). ## Summary CoinGecko `/cg/tickers` and several sibling CMC/CG listing fields **do not mean what their names or `CG_CMC_COMPLIANCE.md` say**. The worst is `liquidity_in_usd`: aggregators treat it as **pool TVL**; we emit **24h `volume_usd`**. Docs still describe it as pool liquidity (or `"0"` until an oracle exists). That can inflate exchange “liquidity” to equal volume and leak into Llama/GT if anyone copies the field. Bundle **field truthfulness** for `/cg/*` + `/cmc/*` in **one** issue: TVL, fake ticker bid/ask, freeze flag, CMC id types, gem leak, symbol-key collisions, and docs that invert trade side / limits. Do **not** split “rename the comment” vs “wire `pair_liquidity_usd`” vs “fix the markdown table.” ## Current codebase | Surface | What the name / docs claim | What the code does | |---------|----------------------------|--------------------| | **`GET /cg/tickers` → `liquidity_in_usd`** | Pool liquidity in USD; notes say `"0"` unless an oracle is configured ([`CG_CMC_COMPLIANCE.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/CG_CMC_COMPLIANCE.md) § tickers). | [`indexer/src/api/cg.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/api/cg.rs) sets it from `stats.volume_usd` (`"0"` if NULL). Rust comment already admits: *“Mislabeled 24h `volume_usd` (legacy aggregator field).”* | | **True pair TVL (unused here)** | `protocol_pair_tvl` + `pair_liquidity_usd` stamp ([#569](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/569) / [#655](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/655)). Overview `total_liquidity_usd` explicitly **not** CG `liquidity_in_usd`. | [`aggregator_snapshot.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/api/aggregator_snapshot.rs) never JOINs `pair_liquidity_usd`. | | **`/cg/tickers` `bid` / `ask`** | Best bid/ask (listing tickers). | `last_price * 0.999` / `* 1.001` via `f64` (always 20 bps wide). **Not** the hybrid book. | | **`/cmc/summary` `highest_bid` / `lowest_ask`** | Same. | Same `* 0.999` / `* 1.001` in [`cmc.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/api/cmc.rs). | | **`/cg/orderbook` + `/cmc/orderbook`** | Hybrid-simulated depth ([#220](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/220)). | Real curve + resting limits. **Tickers ignore this.** | | **`/cmc/ticker` `isFrozen`** | `0` active / `1` frozen. | Always `"0"`. Pair `code_id_frozen` ([#585](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/585)) is unused. | | **`/cmc/ticker` `base_id` / `quote_id`** | Docs + example: **numeric** CMC unified IDs (`0`). | Code emits **CW20 contract or native denom** strings. `assets.cmc_id` exists and is used only on `/cmc/assets`. | | **`/cg/pairs`, `/cg/tickers`, `/cmc/*` gems** | Listing forms: do not submit gems ([`coingecko-exchange.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/listings/forms/coingecko-exchange.md), **L639-2**). `/gt/*` 404s gems. | `get_all_pairs` — **no** gem / ALPHA / USTRIX / SpaceUSD filter. GT form even tells the vendor to ignore gems **in `/cg/pairs`**. | | **`ticker_id` = `{symbol}_{symbol}`** | Unique market id. | [`find_pair_by_ticker`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/indexer/src/api/mod.rs) `or_insert` — **first pair wins**. Two UST1/… markets or spoofed symbols collide; orderbook/trades resolve to the wrong `pool_id`. `/cmc/assets` HashMap keyed by symbol overwrites. | | **`/cg/historical_trades` side** | Docs: buy = offer is **base** (buying the **target**). | Code: `is_buy = offer_asset_id == asset_1` (offer **quote** = buy **base**). CEX-standard. **Docs are inverted.** | | **Trades `limit`** | Docs: max **200**. | Handler clamp **500**; utoipa says 500. | Invariants already warn operators: [`docs/indexer-invariants.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/indexer-invariants.md), [`docs/DEFILLAMA.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/DEFILLAMA.md), [`docs/runbooks/overview-global-stats-brin.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/overview-global-stats-brin.md) — “CG `liquidity_in_usd` is mislabeled 24h volume; never publish as Llama TVL.” That is a **workaround**, not a fix. ## Why a new implementation is needed 1. **Listing crawlers will treat `liquidity_in_usd` as TVL.** CoinGecko’s exchange ticker schema uses that name for pool USD. Serving 24h volume makes a quiet book look deep after one large print, and a deep idle pool look empty. 2. **Docs actively disagree with code** (TVL vs volume vs `"0"`; buy vs sell; trade limit; CMC ids). `#224` aligned timestamps/wrappers; it did **not** fix these field semantics. 3. **We already have the honest number.** `pair_liquidity_usd` is the same P522-Q / hub catalog as Protocol **Total liquidity**. Wiring it is a JOIN on the stamp, not a new oracle. 4. **Fake 10 bps bid/ask** on tickers contradict `/cg/orderbook` on the same host. A crawler that prefers ticker bid/ask over the book will invent a 0.2% market that does not exist. 5. **Gems on `/cg/pairs`** fight `#562` / `#646` / listing-form policy. Soft-launch pairs become “official” CG markets. 6. **Do not reopen `#224`.** That ticket is the closed spec-matrix pass. This is **implementation + doc truth**. ## Constraints / guardrails 1. **Keep the CoinGecko field name `liquidity_in_usd`.** Do not rename it in JSON (crawlers are keyed). Change the **value** to AMM v2 TVL from `pair_liquidity_usd` / `protocol_pair_tvl`. Unpriced / stale / overflow / same-asset → `"0"` or omit only if the Kujira/CG schema allows — prefer `"0"` for this required string field. Never `$1` UST1, never `2.5×` USTR, never vFDUSD, never book escrow, never CG volume. 2. **GET stays a rollup read.** Do **not** live-join `pair_reserves ⋈` oracles on `/cg/tickers` (same rule as `#655`). Stamp already refreshed with protocol TVL / hub. 3. **Do not “fix” Llama or `/gt/events` here.** Llama = `#631` on-chain `Pool {}`. GT historical reserves = `#684`. 4. **Headline volumes stay consolidated swap totals** (`#216` / **L10**). `cl8y_extensions` stay additive/optional. `last_price` stays **human quote-per-base** (**H10**), not `price_usd`. 5. **Bid/ask on tickers:** if you replace the 10 bps toy spread, use **hybrid book best** (mirror or the same sim as `/cg/orderbook`) or reserve-implied mid ± `fee_bps`. Do **not** LCD N+1 per ticker on the cached list path. If best bid/ask cannot be computed cheaply, document the remaining approximation and stop claiming it is the book. 6. **`f64` mid * 0.999 is banned** for the replacement (precision + fake width). 7. **Gems / ALPHA / USTRIX / SpaceUSD:** same exclude list as `/gt` (**L639-2** / `#562`). 404 / omit from `/cg/pairs`, tickers, CMC summary/ticker/assets as applicable. Frozen pairs may stay listed with `isFrozen=1`. 8. **`isFrozen`:** `1` when `code_id_frozen` (or pair inactive). Do not invent a pause bit from missing volume. 9. **CMC `base_id` / `quote_id`:** pick **one** and make docs + OpenAPI + JSON agree. Preferred: numeric `assets.cmc_id` when set, else `0`, and put contract/denom in a documented extension — **or** keep contract strings and rewrite the compliance table/example (today they claim numbers). Do not ship both silently. 10. **Symbol collisions:** do not silently map `EMBER_LUNC` (or two UST1 pairs) to the first insert. Prefer `pool_id` disambiguation in lookup, or skip duplicate `ticker_id`s and log. HashMap-by-symbol on `/cmc/assets` must not drop the economic token for a gem spoof. 11. **60s aggregator cache** ([#288](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/288)) stays. Invalid `limit`/`offset` still **400**. No raw SQL from query strings. 12. **Additive JSON.** Do not remove `liquidity_in_usd`, `cl8y_extensions`, or `pool_id`. Do not wrap `/cg/tickers` in `{ tickers: [...] }` unless product explicitly accepts a crawler break (Kujira wrap is **Partial** today — leave it). 13. **No emails, handles, or integrator chat** in commits or docs. ## Relevant files | File | Role | |------|------| | `indexer/src/api/cg.rs` | Tickers `liquidity_in_usd`, fake bid/ask, pairs list, trades side | | `indexer/src/api/cmc.rs` | Summary bid/ask, ticker ids / `isFrozen`, assets map | | `indexer/src/api/aggregator_snapshot.rs` | Shared load — JOIN `pair_liquidity_usd`, gem filter | | `indexer/src/api/mod.rs` | `find_pair_by_ticker` collision | | `indexer/src/db/queries/pair_liquidity_usd.rs` | Stamp to reuse | | `indexer/src/indexer/protocol_tvl.rs` | `protocol_pair_tvl` — do not fork | | `indexer/src/indexer/defillama.rs` | `COLUMBUS5_GEM_ADDRESSES` — share with GT | | `indexer/src/api/gt.rs` | `EXCLUDED_CW20` / `is_excluded_cw20` — keep lockstep | | `indexer/tests/api_cg.rs`, `api_cmc.rs`, `api_aggregator_batch.rs` | Assertions today may lock in volume-as-liquidity | | `docs/CG_CMC_COMPLIANCE.md` | Field table + notes + trade side + limit | | `docs/indexer-invariants.md` | Replace “still mislabeled” once fixed | | `docs/DEFILLAMA.md`, `docs/runbooks/overview-global-stats-brin.md` | Cross-links | | `docs/listings/forms/coingecko-exchange.md` | Gem / API notes | | `scripts/qa/` + `Makefile` | `make verify-issue-NNN` (do not overload `verify-issue-224`) | ## Recommended direction 1. **`load_aggregator_pairs`:** LEFT JOIN `pair_liquidity_usd`. Map to ticker `liquidity_in_usd` as the human USD string. Unpriced → `"0"`. Stop copying `volume_usd`. 2. **Ticker bid/ask:** from mirrored best bid/ask (same units as `last_price`) when the book snapshot is fresh; else omit-equivalent (`last_price` both sides) **or** reserve mid ± `fee_bps` — pick one, test it. Kill `to_f64() * 0.999`. 3. **`isFrozen`:** `code_id_frozen` / `is_active`. 4. **CMC ids + docs** in the same MR so the table matches JSON. 5. **Gem filter** shared with `/gt` (one helper). `/cmc/assets` skip excluded CW20s; last-write-wins on symbol must prefer the permanent economic pin if a collision remains. 6. **Ticker map:** on duplicate `SYMBOL_SYMBOL`, do not `or_insert` the first forever — prefer factory-listed non-gem, else skip key and require `pool_id`. 7. **Docs:** `liquidity_in_usd` = AMM TVL stamp; buy = offer quote; trades limit 500; drop “0 unless oracle.” 8. **`make verify-issue-NNN`.** Coolify indexer redeploy. No wasm migrate. No dApp change. ## Acceptance criteria - [ ] **AC1.** `/cg/tickers` `liquidity_in_usd` equals `pair_liquidity_usd` / `protocol_pair_tvl` for a priced pair (e.g. UST1/cUSTC), **not** 24h `volume_usd`. - [ ] **AC2.** Unpriced pair → `"0"` (not volume, not `$1` UST1, not `2.5×` USTR). - [ ] **AC3.** A pair with large 24h volume and tiny reserves shows **small** `liquidity_in_usd` and **large** `base_volume` / `target_volume`. - [ ] **AC4.** Docs + OpenAPI + code comments agree. Invariants no longer say the field is mislabeled volume. - [ ] **AC5.** Ticker `bid` ≤ `last_price` ≤ `ask` when a book/reserve mid exists; width is not hardcoded 20 bps unless that is the documented fallback. No `f64 * 0.999`. - [ ] **AC6.** `/cmc/summary` bid/ask follow the same rule as CG tickers. - [ ] **AC7.** Frozen pair (`code_id_frozen`) → `/cmc/ticker` `isFrozen` = `"1"` (or `1` — match existing string type). - [ ] **AC8.** CMC `base_id` / `quote_id` match the rewritten spec (numbers **or** contracts — not both). - [ ] **AC9.** Gem / ALPHA / USTRIX / SpaceUSD omitted from `/cg/pairs`, `/cg/tickers`, `/cmc/summary`, `/cmc/ticker`. `/gt` exclude list stays lockstep. - [ ] **AC10.** Duplicate `ticker_id` does not silently bind orderbook/trades to the wrong pair. - [ ] **AC11.** `historical_trades` docs match code (buy = offer quote / buy base). Limit docs = **500**. - [ ] **AC12.** GET does not scan `swap_events` or live-sum reserves for TVL (`EXPLAIN` / test). `#288` cache + pagination unchanged. - [ ] **AC13.** `make verify-issue-NNN` + compliance doc grep. `#224` stays closed. ## Test plan (all paths) | ID | Path | Expect | |----|------|--------| | T1 | Priced UST1/cUSTC (or UST1/USTR) ticker | `liquidity_in_usd` = stamp TVL, ≠ `volume_usd` | | T2 | High volume, dust reserves | TVL small; volumes large | | T3 | Unpriced / stale / hub-down | `"0"` | | T4 | Idle priced pool (volume 0, reserves > 0) | TVL > 0, volumes 0 | | T5 | One-sided catalog (2× priced leg) | Same as `#569` | | T6 | `/cmc/summary` same pair | No `liquidity_in_usd` field (CMC schema); bid/ask not 20 bps toy if book exists | | T7 | Hybrid book with rest bid/ask | Ticker bid/ask match book best (or documented fallback) | | T8 | Empty book, usable reserves | Mid from reserves ± fee **or** documented fallback — not `close * 0.999` | | T9 | Frozen pair | `isFrozen=1`; still listed unless gem | | T10 | Gem pair in DB | Absent from CG/CMC lists; `/gt/pair` still 404 | | T11 | Two pairs, same symbols, different contracts | Lookup does not return the wrong `pool_id` | | T12 | `/cmc/assets` gem + real token same symbol | Economic pin kept | | T13 | `/cmc/ticker` ids | Match AC8 | | T14 | Buy: offer = quote | In `buy[]`; sell: offer = base in `sell[]` | | T15 | `limit=200` / `limit=500` / `limit=501` on trades | 200/500/clamp; docs say 500 | | T16 | `offset>10000` tickers | **400** | | T17 | Cache: two GETs in 60s | Same TVL stamp (not a second live compute) | | T18 | `/cg/pairs` page still has `pool_id` | Unchanged shape | ## Test plan (attack, hack, abuse) | ID | Vector | Expect | |----|--------|--------| | A1 | **Volume-as-TVL cheat** (leave `volume_usd` in `liquidity_in_usd`) | T1/T3 fail the MR | | A2 | **`$1` UST1 / `2.5×` USTR** | Stamp uses hub catalog only | | A3 | **Wash volume** to inflate “liquidity” | Volumes move; TVL does not | | A4 | **Donate** to pair CW20 | TVL stays internal `RESERVES` (stamp), not `balanceOf` | | A5 | **Symbol spoof / gem ticker** `CL8Y_UST1` | Collision rule; gems omitted | | A6 | **SQL / proto `ticker_id`** | **400**; existing segment check | | A7 | **N+1 LCD** on `/cg/tickers` | Forbidden; cache-miss = SQL stamps only | | A8 | **Cache poison** (stale TVL after refresh) | TTL 60s max; next miss reads new stamp | | A9 | **Frozen pair reported live** | `isFrozen=1` | | A10 | **Gem leak** after filter typo (case / missing pin) | Lockstep test vs `COLUMBUS5_GEM_ADDRESSES` + GT list | | A11 | **XSS / huge numeric string** in stamp | String field only; no HTML | | A12 | **DoS** `limit=1000` tickers with live book sim per row | If bid/ask uses book, batch from mirror — not 1000 LCD calls | ## Verification criteria ```bash make setup-indexer-postgres # if needed make verify-issue-NNN # api_cg + api_cmc + snapshot tests + compliance grep ``` Live (after indexer redeploy), no chat logs: ```bash curl -sS "https://indexer.dex.cl8y.com/cg/tickers?limit=5" # For a known priced pair: liquidity_in_usd ≈ GET /api/v1/pairs/{pool_id} liquidity_usd # and ≠ that pair's 24h volume_usd. ``` - [ ] T1–T18 and A1–A12 automated where possible. - [ ] `CG_CMC_COMPLIANCE.md` field table matches JSON. - [ ] Invariants/Llama runbook no longer call the field “mislabeled volume.” - [ ] `/gt/events` unchanged except shared gem helper if extracted. - [ ] `#224` remains closed; `#684` remains the GT reserve ticket. ## Out of scope - GeckoTerminal per-event reserves ([#684](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/684)) - DeFiLlama adapters ([#631](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/631)) - Wrapping `/cg/tickers` in `{ tickers: [...] }` (Kujira wrap) - CoinGecko Partners form submit ([#643](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/643)) - Protocol UI / `/pool` TVL (already `#569` / `#655`) - Renaming `liquidity_in_usd` or inventing `liquidity_in_usd_real`
PlasticDigits commented 2026-08-27 04:50:01 +00:00 (Migrated from gitlab.com)

marked as related to #224

marked as related to #224
PlasticDigits commented 2026-08-27 04:50:02 +00:00 (Migrated from gitlab.com)

marked as related to #643

marked as related to #643
PlasticDigits commented 2026-08-27 04:50:03 +00:00 (Migrated from gitlab.com)

marked as related to #569

marked as related to #569
PlasticDigits commented 2026-08-27 04:50:04 +00:00 (Migrated from gitlab.com)

marked as related to #655

marked as related to #655
PlasticDigits commented 2026-08-27 04:50:04 +00:00 (Migrated from gitlab.com)

marked as related to #684

marked as related to #684
PlasticDigits commented 2026-08-27 04:50:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #643

mentioned in issue #643
PlasticDigits commented 2026-08-27 07:03:58 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1188

mentioned in merge request !1188
PlasticDigits commented 2026-08-27 07:04:56 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1189

mentioned in merge request !1189
PlasticDigits commented 2026-08-27 07:07:33 +00:00 (Migrated from gitlab.com)

mentioned in commit 4fd3d2fd19

mentioned in commit 4fd3d2fd191b8d8088d0d42513d2a6612709f106
PlasticDigits commented 2026-08-27 07:08:26 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1190

mentioned in merge request !1190
PlasticDigits commented 2026-08-27 07:40:57 +00:00 (Migrated from gitlab.com)

mentioned in commit cf1c0a63e6

mentioned in commit cf1c0a63e63d1031d4852f0a04b5f6ab4a93906b
PlasticDigits commented 2026-08-27 07:41:02 +00:00 (Migrated from gitlab.com)

mentioned in commit 906b6e7f0d

mentioned in commit 906b6e7f0d2f8819342e4d3a16008dc493d4ec1b
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-27 07:41:02 +00:00
PlasticDigits commented 2026-08-27 07:43:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #688

mentioned in issue #688
PlasticDigits commented 2026-08-27 07:43:03 +00:00 (Migrated from gitlab.com)

marked as related to #688

marked as related to #688
PlasticDigits commented 2026-08-27 07:43:22 +00:00 (Migrated from gitlab.com)

Merged as !1190 onto main (906b6e7f). Local AC1–AC13 passed (make verify-issue-685). Conflicts vs !1187/!1189 resolved: Makefile kept verify-issue-684/685/686/687; gt.rs uses shared listing_exclude::is_excluded_cw20 (no duplicate helper).

Leftovers (do not reopen unless a merged invariant is wrong): Coolify indexer redeploy (no wasm/dApp). Live check: /cg/tickers liquidity_in_usd ≈ pair liquidity_usd stamp and ≠ 24h volume. Tracked on #688.

CI was failed (ci_quota_exceeded); not waited.

Non-blocking: list path can still emit duplicate SYMBOL_SYMBOL rows with different pool_id (orderbook/trades 404); bid/ask is reserve mid ± fee_bps, not hybrid book best (documented L685-3).

Merged as !1190 onto `main` (`906b6e7f`). Local AC1–AC13 passed (`make verify-issue-685`). Conflicts vs !1187/!1189 resolved: Makefile kept verify-issue-684/685/686/687; `gt.rs` uses shared `listing_exclude::is_excluded_cw20` (no duplicate helper). **Leftovers (do not reopen unless a merged invariant is wrong):** Coolify indexer redeploy (no wasm/dApp). Live check: `/cg/tickers` `liquidity_in_usd` ≈ pair `liquidity_usd` stamp and ≠ 24h volume. Tracked on #688. CI was `failed` (`ci_quota_exceeded`); not waited. Non-blocking: list path can still emit duplicate `SYMBOL_SYMBOL` rows with different `pool_id` (orderbook/trades 404); bid/ask is reserve mid ± `fee_bps`, not hybrid book best (documented L685-3).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#685
No description provided.