fix(indexer): USDT-quoted Charts Price (USD) and 24h OHLC stay empty #1258

Closed
opened 2026-09-12 13:39:20 +00:00 by PlasticDigits · 2 comments

Summary

On /charts for the factory cLUNC / USDT pair, the Price (USD) candlestick pane and the 24h Open / High / Low / Close (USD) tiles show no price (— / empty chart), while Price Change % and Recent trades still populate.

This is the closed #522 / P522-4 fail-closed path hitting a quote the catalog never priced: bridged registry USDT (18 decimals) is not in P522-Q, so ingest stamps price_usd NULL. Candles omit NULL-USD bars (#543). Charts OHLC binds *_usd via formatPairStatsUsdOhlc (— when missing). price_change_pct and tape Price stay human quote-per-base, so they still render.

Do not treat this as open #1221 (bridge + CMM seed — the pair already trades). Do not fold into open #1257 (Swap USTR→USDT You Receive scale). Do not reopen closed #522 (USTC/LUNC/UST1/USTR catalog), #543 (invertUsd vs 1/x), #568 (hub rewrite), #680 (?price= invert completeness), or #113 (empty-state UX). Those tickets did not add USDT as a quote USD handle.

Reported columbus-5 pair: terra17l7eqc5j8vkm09up55etfggpr6y92ka6p03yc765mt3nerqcnhdsl6l7jq (confirm legs on LCD). Registry USDT: terra1z0xe7t5ymmltg4vju8tghkq0pewy4et548ta23nlu9zxtl950uyqkv8mv4. Wrap cLUNC: terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg.

Current codebase

P522-Q (indexer/src/indexer/pair_price_usd.rs):

Quote USD handle
UST1 hub_prices.ust1 — not $1
USTC / cUSTC / uusd #515 USTC oracle
LUNC / cLUNC / uluna #515 LUNC oracle
USTR hub_prices.ustr
USDT (registry CW20) None — quote_usd_kind("USDT") has no arm
VFDUSD / FDUSD stay None (#580)
CL8Y / gems stay None (fee marks #683 are not pair price_usd)

price_usd_for_human_quote_per_base → quote_usd_kind(symbol) → unknown → None. Candle OHLC is factory USD from price_usd only (P522-4). HubQuoteUsd.economic is fee-only (EFee-4); volume / TVL / pair charts must not silently start reading it for USDT.

Frontend:

  • ChartsPage 24h High/Low/Open/Close → resolveDisplayPairStatsUsdOhlc → formatPairStatsUsdOhlc (— on null / non-positive).
  • Non-inverted Price Change % is indexer price_change_pct (human open→close) even when *_usd is null — matches the split symptom.
  • PriceChart drops bars without USD; empty / all-dropped → existing empty state (#113), not a silent canvas.
  • Tape Price is human (#557 / P522-5). classifyQuoteSymbol has no USDT arm; client fallback cannot invent dollars when indexer price_usd is missing.
  • ?price=cLUNC (#680) still needs factory USD before invertUsd. Empty factory USD → empty display OHLC/candles after invert too.

P522-1 already scales mixed decimals: human = raw × 10^(decimals_base − decimals_quote). cLUNC 6 vs USDT 18. Do not double-scale on the tape that already looks sane.

Why this is needed

Retail Charts for the new USDT/cLUNC market looks broken next to a live tape. Traders read Last / candles / 24h OHLC as one Price (USD) control. Leaving USDT unpriced is honest for gems; it is not honest for the listed Tether CW20 that the tokenlist and Swap already treat as a product asset.

Constraints / guardrails

  • Identity by contract (A1). Price only the pinned registry USDT CW20 (columbus-5 address above; LocalTerra from deploy/env, not the mainnet bech32). A different CW20 with symbol=USDT stays None. Do not match ticker substring. Do not treat IBC ibc/… USDC (#1220) or native bank denoms as USDT.
  • Do not reuse QuoteUsdKind::Peg1 / hub_prices.ust1. That handle is UST1 DEX mark, not Tether. Do not $1 UST1. Do not 2.5× USTR.
  • Do not map VFDUSD / FDUSD / Venus into this path (#580). CEX *USDT oracle symbols are venue tickers, not Terra CW20 USDT.
  • Do not client-stitch CoinGecko or GET /oracle/history onto pair candles (C568-7). No new /oracle/price/usdt unless product explicitly wants a feed; a contract-pinned $1 advisory peg for this listed USDT is acceptable and simpler than a CEX poll.
  • P522-2 meaning unchanged: price_usd remains USD of 1 human asset_0. If USDT is asset_1, usd_per_human_quote ≈ 1 so price_usd ≈ human USDT-per-cLUNC. If USDT is asset_0, quote is cLUNC → already LUNC-catalogued; then this bug would not match the empty-USD report — confirm orientation, do not invert in storage.
  • P522-4 / #543: still no human fallback onto the USD axis (do not plot ~1e-12 or 18-dec raw as dollars). After catalog + ingest, bars with USD must plot; remaining NULL rows still drop.
  • #568: do not UPDATE historical non-NULL price_usd. NULL-only backfill / candle rebuild for this pair is allowed. Idle marks may use the new quote USD going forward.
  • #680 / #524: UI invert stays invertUsd (price_usd / human), never 1/x of USD. ?price= allowlist remains the two pair legs. Charts vs Trade invert storage isolation stays.
  • Decimals: USDT 18, cLUNC 6. Volume notional must humanize the USDT leg with 18, not 6 (#564 class). Compact T/M/B still forbidden on OHLC (S564-7).
  • CG/CMC last_price stays human (H10). Do not switch aggregators to USD.
  • Gems (#562), U1 mint/redeem copy (#508), factory wasm, wrap-mapper, CMM custody (#1221), and Swap route-solve (#1257) stay out of this ticket.
  • No Coolify/host/queue notes on this issue.

Relevant files

Path Why
indexer/src/indexer/pair_price_usd.rs QuoteUsdKind, quote_usd_kind / quote_usd_kind_for_identity, usd_per_human_quote, price_usd_for_human_quote_per_base, volume helper
indexer/src/indexer/pair_price_usd.rs tests Pin USDT; spoof USDT; VFDUSD still none; 6/18 human × ~$1
frontend-dapp/src/utils/pairPriceUsd.ts Optional usdt kind for legacy fallback only; prefer indexer price_usd
frontend-dapp/src/utils/__tests__/pairPriceUsd.test.ts USDT quote → ~$1 of display base; unknown USDT spoof stays null
frontend-dapp/src/utils/tokenRegistry.ts Registry pin (read; do not dual-source decimals from tokenlist JSON)
docs/indexer-invariants.md P522-Q row: USDT contract-pinned advisory $1
skills/AGENTS_INDEXER_PAIR_PRICE_USD.md Catalog table + A1
scripts/qa/verify-issue-522.sh Keep green; add verify-issue-* for this ticket
indexer/src/db/queries/candles.rs Rebuild from new price_usd (no formula change)

Maybe: hub_usd.rs only if implementers reject $1 and insist on a DEX-reserve USDT mark — then it must be a new handle, not ust1. Default is pin+$1, not hub.

  1. Add QuoteUsdKind::Usdt (name as you like). usd_per_human_quote = 1 when kind is Usdt. Wire only in quote_usd_kind_for_identity when is_cw20 and contract_address equals the pinned registry USDT (casefold). Symbol USDT with empty/wrong contract → None. Do not add a bare quote_usd_kind("USDT") arm that prices spoofs.
  2. Keep quote_usd_kind("USDT", None) returning None or document that identity-gated helper is the only ingest path (prefer the latter tests on quote_usd_kind_for_identity).
  3. NULL-only backfill swap_events.price_usd for pairs whose quote (or priced quote leg) is that USDT contract; rebuild candles. Do not rewrite UST1/cUSTC history.
  4. Frontend: if price_usd present, Charts/Trade unchanged. Legacy classifyQuoteSymbol may map USDT → $1 only as old-indexer fallback; production path prefers the field. Still never pass human tape Price into USD tiles.
  5. Docs/skills: P522-Q table gains USDT (contract pin, advisory $1, not Peg1).

Acceptance criteria

  • Given the columbus-5 cLUNC/USDT factory pair with recent swaps and a populated tape.
  • When a trader opens /charts/{pair} (with or without ?price=cLUNC as a valid leg).
  • Then Price (USD) candles render finite OHLC of the displayed base, and 24h Open/High/Low/Close (USD) show dollar amounts (not —), while tape Price stays human and % remains consistent with the displayed USD (or factory human when not inverted).

Also:

  • AC1. Indexer GET /pairs/{addr}/stats open_price_usd / high_usd / low_usd / close_price_usd are positive decimals for that pair after ingest/backfill.
  • AC2. GET /candles rows include factory USD OHLC; lightweight-charts is not the #113 empty state solely because USDT was unpriced.
  • AC3. ?price=cLUNC vs ?price=USDT (or invert pill) uses invertUsd; Last and last candle agree in both orientations; Y-axis not 0.00 for sub-cent cLUNC USD; inverted view is not 1/x of USD.
  • AC4. Spoof CW20 symbol=USDT other contract: still NULL USD. VFDUSD/FDUSD/CL8Y unchanged.
  • AC5. Mixed 6/18: human price already scaled (P522-1); USD ≈ human USDT-per-cLUNC when USDT is quote at $1. No 79T / compact T on OHLC.
  • AC6. UST1/cUSTC and cLUNC/UST1 charts still match #522 / #543 / #556 (hub, not $1 UST1).
  • AC7. make verify-issue-522 stays green.

Test plan (functional paths)

# Path Expect
T1 quote_usd_kind_for_identity registry USDT CW20 pin Some(Usdt); usd_per_human_quote = 1
T2 Same symbol, other terra1 None
T3 VFDUSD / FDUSD / CL8Y still None
T4 Human 6/18 USDT-per-cLUNC × $1 → price_usd ≈ human; fits NUMERIC(38,18)
T5 volume_usd_for_swap with USDT quote leg 18-dec humanize 18; not 6-dec T
T6 Candle rebuild fixture: previous NULL USD, new stamp USD OHLC present; *_human still quote-per-base
T7 resolveDisplayPairStatsUsdOhlc with USDT *_usd formatPairStatsUsdOhlc is $…, not —
T8 Invert ?price= other leg invertUsd; high≥low; % from display open/close
T9 UST1 quote still hub, not $1 existing #556 fixtures
T10 Charts empty pair with no swaps existing empty / no-trades copy, not a false USD

Vitest: pairPriceUsd.test.ts USDT pin + spoof. Indexer: cargo test --lib pair_price_usd + swap_price_human_usd. make verify-issue-522 + new verify-issue-*.

Test plan (attack, hack, and abuse)

ID Vector Expect
H1 Hostile symbol=USDT CW20 / native denom spoof Unpriced; no $1
H2 ?price=javascript:… / HTML / overlong Existing #680 ignore; no innerHTML
H3 Candle price_usd 0 / -1 / NaN / Infinity Drop row; no setData NaN (#226 / #543 H1)
H4 Plot human 18-dec raw as USD when stamp still NULL Forbidden (P522-4 / P522-5)
H5 Peg USDT via hub_prices.ust1 or CEX FDUSD Forbidden
H6 Rewrite historical UST1/cUSTC price_usd in the backfill Forbidden (#568)
H7 XSS in USDT symbol / pair pill Text nodes only
H8 Limit/market submit using Charts USD as on-chain price Unchanged factory human (T524-2)
H9 showGems=1 USDT-named gem Production hide / not catalog-priced
H10 Second factory USDT ticker collision Unique-symbol / contract identity; only the pin prices

Verification criteria

  • New make verify-issue- (indexer catalog + spoof + 6/18 USD + docs/skill grep). make verify-issue-522, make verify-issue-543, make verify-issue-556 stay green.
  • cd indexer && cargo test --lib pair_price_usd -- --quiet
  • Frontend: pairPriceUsd.test.ts, chartsPairStats.test.ts (— still for true null; $ for USDT fixture).
  • Manual: /charts/{cLUNC-USDT} — candles + 24h USD OHLC populated; tape still human; % still shown; invert/?price= both legs; UST1/cUSTC hero unchanged.

Out of scope

  • Bridging more USDT or reseeding CMM LP (#1221).
  • Swap USTR→USDT route-solve billions (#1257).
  • Factory native-IBC USDC (#1220).
  • New CoinGecko USDT oracle microservice.
  • Protocol hub card fifth cell / GET /hub-prices ticker usdt.
  • Changing CG/CMC last_price to USD.

First-pass model recommendation

Recommendation: grok-high

Rationale: P522-Q catalog plus identity gating, mixed 6/18 ingest, NULL-only price_usd/candle backfill, and Charts OHLC/invert consumers. That is more than three production files and crosses indexer ingest and the Charts USD display path (price integrity, not a local formatter). Comparable DEX history is #522 / #556 (catalog + hub), not a single RCA or test-helper PR. Composer’s “one subsystem, known local edit, deterministic tests, no cross-cutting protocol/catalog change” bar fails. Verify with pair_price_usd unit tests, verify-issue-522, and /charts USDT pair smoke — not by guessing $1 in the UI alone.

## Summary On `/charts` for the factory **cLUNC / USDT** pair, the Price (USD) candlestick pane and the 24h **Open / High / Low / Close (USD)** tiles show no price (`—` / empty chart), while **Price Change %** and **Recent trades** still populate. This is the closed #522 / **P522-4** fail-closed path hitting a quote the catalog never priced: bridged registry **USDT** (18 decimals) is not in P522-Q, so ingest stamps `price_usd` NULL. Candles omit NULL-USD bars (#543). Charts OHLC binds `*_usd` via `formatPairStatsUsdOhlc` (`—` when missing). `price_change_pct` and tape **Price** stay human quote-per-base, so they still render. Do not treat this as open #1221 (bridge + CMM seed — the pair already trades). Do not fold into open #1257 (Swap USTR→USDT You Receive scale). Do not reopen closed #522 (USTC/LUNC/UST1/USTR catalog), #543 (invertUsd vs `1/x`), #568 (hub rewrite), #680 (`?price=` invert completeness), or #113 (empty-state UX). Those tickets did not add USDT as a quote USD handle. Reported columbus-5 pair: `terra17l7eqc5j8vkm09up55etfggpr6y92ka6p03yc765mt3nerqcnhdsl6l7jq` (confirm legs on LCD). Registry USDT: `terra1z0xe7t5ymmltg4vju8tghkq0pewy4et548ta23nlu9zxtl950uyqkv8mv4`. Wrap cLUNC: `terra1437qslye72t7qmmahn4t5chz50r8a62g45phwkquwpyu2l62u6ksqssgdg`. ## Current codebase P522-Q (`indexer/src/indexer/pair_price_usd.rs`): | Quote | USD handle | | --- | --- | | UST1 | `hub_prices.ust1` — **not** `$1` | | USTC / cUSTC / `uusd` | #515 USTC oracle | | LUNC / cLUNC / `uluna` | #515 LUNC oracle | | USTR | `hub_prices.ustr` | | **USDT** (registry CW20) | **`None`** — `quote_usd_kind("USDT")` has no arm | | VFDUSD / FDUSD | stay `None` (#580) | | CL8Y / gems | stay `None` (fee marks #683 are **not** pair `price_usd`) | `price_usd_for_human_quote_per_base` → `quote_usd_kind(symbol)` → unknown → `None`. Candle OHLC is factory USD from `price_usd` only (P522-4). `HubQuoteUsd.economic` is fee-only (EFee-4); volume / TVL / pair charts must not silently start reading it for USDT. Frontend: - `ChartsPage` 24h High/Low/Open/Close → `resolveDisplayPairStatsUsdOhlc` → `formatPairStatsUsdOhlc` (`—` on null / non-positive). - Non-inverted **Price Change %** is indexer `price_change_pct` (human open→close) even when `*_usd` is null — matches the split symptom. - `PriceChart` drops bars without USD; empty / all-dropped → existing empty state (#113), not a silent canvas. - Tape **Price** is human (#557 / P522-5). `classifyQuoteSymbol` has no `USDT` arm; client fallback cannot invent dollars when indexer `price_usd` is missing. - `?price=cLUNC` (#680) still needs factory USD before `invertUsd`. Empty factory USD → empty display OHLC/candles after invert too. P522-1 already scales mixed decimals: `human = raw × 10^(decimals_base − decimals_quote)`. cLUNC 6 vs USDT 18. Do not double-scale on the tape that already looks sane. ## Why this is needed Retail Charts for the new USDT/cLUNC market looks broken next to a live tape. Traders read Last / candles / 24h OHLC as one Price (USD) control. Leaving USDT unpriced is honest for gems; it is not honest for the listed Tether CW20 that the tokenlist and Swap already treat as a product asset. ## Constraints / guardrails - **Identity by contract (A1).** Price only the pinned registry USDT CW20 (columbus-5 address above; LocalTerra from deploy/env, not the mainnet bech32). A different CW20 with `symbol=USDT` stays `None`. Do not match ticker substring. Do not treat IBC `ibc/…` USDC (#1220) or native bank denoms as USDT. - **Do not reuse `QuoteUsdKind::Peg1` / `hub_prices.ust1`.** That handle is UST1 DEX mark, not Tether. Do not `$1` UST1. Do not `2.5×` USTR. - **Do not map VFDUSD / FDUSD / Venus** into this path (#580). CEX `*USDT` oracle symbols are venue tickers, not Terra CW20 USDT. - **Do not** client-stitch CoinGecko or `GET /oracle/history` onto pair candles (C568-7). No new `/oracle/price/usdt` unless product explicitly wants a feed; a contract-pinned **$1 advisory peg** for this listed USDT is acceptable and simpler than a CEX poll. - **P522-2 meaning unchanged:** `price_usd` remains USD of 1 human `asset_0`. If USDT is `asset_1`, `usd_per_human_quote ≈ 1` so `price_usd ≈` human USDT-per-cLUNC. If USDT is `asset_0`, quote is cLUNC → already LUNC-catalogued; then this bug would not match the empty-USD report — confirm orientation, do not invert in storage. - **P522-4 / #543:** still no human fallback onto the USD axis (do not plot ~`1e-12` or 18-dec raw as dollars). After catalog + ingest, bars with USD must plot; remaining NULL rows still drop. - **#568:** do not `UPDATE` historical non-NULL `price_usd`. NULL-only backfill / candle rebuild for this pair is allowed. Idle marks may use the new quote USD going forward. - **#680 / #524:** UI invert stays `invertUsd` (`price_usd / human`), never `1/x` of USD. `?price=` allowlist remains the two pair legs. Charts vs Trade invert storage isolation stays. - **Decimals:** USDT 18, cLUNC 6. Volume notional must humanize the USDT leg with 18, not 6 (#564 class). Compact `T`/`M`/`B` still forbidden on OHLC (S564-7). - **CG/CMC `last_price` stays human** (H10). Do not switch aggregators to USD. - Gems (#562), U1 mint/redeem copy (#508), factory wasm, wrap-mapper, CMM custody (#1221), and Swap route-solve (#1257) stay out of this ticket. - No Coolify/host/queue notes on this issue. ## Relevant files | Path | Why | | --- | --- | | `indexer/src/indexer/pair_price_usd.rs` | `QuoteUsdKind`, `quote_usd_kind` / `quote_usd_kind_for_identity`, `usd_per_human_quote`, `price_usd_for_human_quote_per_base`, volume helper | | `indexer/src/indexer/pair_price_usd.rs` tests | Pin USDT; spoof USDT; VFDUSD still none; 6/18 human × ~$1 | | `frontend-dapp/src/utils/pairPriceUsd.ts` | Optional `usdt` kind for **legacy** fallback only; prefer indexer `price_usd` | | `frontend-dapp/src/utils/__tests__/pairPriceUsd.test.ts` | USDT quote → ~$1 of display base; unknown USDT spoof stays null | | `frontend-dapp/src/utils/tokenRegistry.ts` | Registry pin (read; do not dual-source decimals from tokenlist JSON) | | `docs/indexer-invariants.md` | P522-Q row: USDT contract-pinned advisory $1 | | `skills/AGENTS_INDEXER_PAIR_PRICE_USD.md` | Catalog table + A1 | | `scripts/qa/verify-issue-522.sh` | Keep green; add `verify-issue-*` for this ticket | | `indexer/src/db/queries/candles.rs` | Rebuild from new `price_usd` (no formula change) | Maybe: `hub_usd.rs` only if implementers reject $1 and insist on a DEX-reserve USDT mark — then it must be a **new** handle, not `ust1`. Default is pin+$1, not hub. ## Recommended direction 1. Add `QuoteUsdKind::Usdt` (name as you like). `usd_per_human_quote` = `1` when kind is Usdt. Wire **only** in `quote_usd_kind_for_identity` when `is_cw20` and `contract_address` equals the pinned registry USDT (casefold). Symbol `USDT` with empty/wrong contract → `None`. Do not add a bare `quote_usd_kind("USDT")` arm that prices spoofs. 2. Keep `quote_usd_kind("USDT", None)` returning `None` **or** document that identity-gated helper is the only ingest path (prefer the latter tests on `quote_usd_kind_for_identity`). 3. NULL-only backfill `swap_events.price_usd` for pairs whose quote (or priced quote leg) is that USDT contract; rebuild candles. Do not rewrite UST1/cUSTC history. 4. Frontend: if `price_usd` present, Charts/Trade unchanged. Legacy `classifyQuoteSymbol` may map USDT → $1 **only** as old-indexer fallback; production path prefers the field. Still never pass human tape Price into USD tiles. 5. Docs/skills: P522-Q table gains USDT (contract pin, advisory $1, not Peg1). ## Acceptance criteria - **Given** the columbus-5 cLUNC/USDT factory pair with recent swaps and a populated tape. - **When** a trader opens `/charts/{pair}` (with or without `?price=cLUNC` as a valid leg). - **Then** Price (USD) candles render finite OHLC of the displayed base, and 24h Open/High/Low/Close (USD) show dollar amounts (not `—`), while tape Price stays human and % remains consistent with the displayed USD (or factory human when not inverted). Also: - AC1. Indexer `GET /pairs/{addr}/stats` `open_price_usd` / `high_usd` / `low_usd` / `close_price_usd` are positive decimals for that pair after ingest/backfill. - AC2. `GET /candles` rows include factory USD OHLC; lightweight-charts is not the #113 empty state solely because USDT was unpriced. - AC3. `?price=cLUNC` vs `?price=USDT` (or invert pill) uses `invertUsd`; Last and last candle agree in both orientations; Y-axis not `0.00` for sub-cent cLUNC USD; inverted view is not `1/x` of USD. - AC4. Spoof CW20 `symbol=USDT` other contract: still NULL USD. VFDUSD/FDUSD/CL8Y unchanged. - AC5. Mixed 6/18: human `price` already scaled (P522-1); USD ≈ human USDT-per-cLUNC when USDT is quote at $1. No `79T` / compact `T` on OHLC. - AC6. UST1/cUSTC and cLUNC/UST1 charts still match #522 / #543 / #556 (hub, not $1 UST1). - AC7. `make verify-issue-522` stays green. ## Test plan (functional paths) | # | Path | Expect | | --- | --- | --- | | T1 | `quote_usd_kind_for_identity` registry USDT CW20 pin | `Some(Usdt)`; `usd_per_human_quote` = 1 | | T2 | Same symbol, other `terra1` | `None` | | T3 | `VFDUSD` / `FDUSD` / `CL8Y` | still `None` | | T4 | Human 6/18 USDT-per-cLUNC × $1 → `price_usd` | ≈ human; fits `NUMERIC(38,18)` | | T5 | `volume_usd_for_swap` with USDT quote leg 18-dec | humanize 18; not 6-dec `T` | | T6 | Candle rebuild fixture: previous NULL USD, new stamp | USD OHLC present; `*_human` still quote-per-base | | T7 | `resolveDisplayPairStatsUsdOhlc` with USDT `*_usd` | `formatPairStatsUsdOhlc` is `$…`, not `—` | | T8 | Invert `?price=` other leg | invertUsd; high≥low; % from display open/close | | T9 | UST1 quote still hub, not $1 | existing #556 fixtures | | T10 | Charts empty pair with no swaps | existing empty / no-trades copy, not a false USD | Vitest: `pairPriceUsd.test.ts` USDT pin + spoof. Indexer: `cargo test --lib pair_price_usd` + `swap_price_human_usd`. `make verify-issue-522` + new `verify-issue-*`. ## Test plan (attack, hack, and abuse) | ID | Vector | Expect | | --- | --- | --- | | H1 | Hostile `symbol=USDT` CW20 / native denom spoof | Unpriced; no `$1` | | H2 | `?price=javascript:…` / HTML / overlong | Existing #680 ignore; no `innerHTML` | | H3 | Candle `price_usd` `0` / `-1` / `NaN` / `Infinity` | Drop row; no `setData` NaN (#226 / #543 H1) | | H4 | Plot human 18-dec raw as USD when stamp still NULL | Forbidden (P522-4 / P522-5) | | H5 | Peg USDT via `hub_prices.ust1` or CEX FDUSD | Forbidden | | H6 | Rewrite historical UST1/cUSTC `price_usd` in the backfill | Forbidden (#568) | | H7 | XSS in USDT symbol / pair pill | Text nodes only | | H8 | Limit/market submit using Charts USD as on-chain price | Unchanged factory human (T524-2) | | H9 | `showGems=1` USDT-named gem | Production hide / not catalog-priced | | H10 | Second factory USDT ticker collision | Unique-symbol / contract identity; only the pin prices | ## Verification criteria - New `make verify-issue-` (indexer catalog + spoof + 6/18 USD + docs/skill grep). `make verify-issue-522`, `make verify-issue-543`, `make verify-issue-556` stay green. - `cd indexer && cargo test --lib pair_price_usd -- --quiet` - Frontend: `pairPriceUsd.test.ts`, `chartsPairStats.test.ts` (`—` still for true null; `$` for USDT fixture). - Manual: `/charts/{cLUNC-USDT}` — candles + 24h USD OHLC populated; tape still human; `%` still shown; invert/`?price=` both legs; UST1/cUSTC hero unchanged. ## Out of scope - Bridging more USDT or reseeding CMM LP (#1221). - Swap USTR→USDT route-solve billions (#1257). - Factory native-IBC USDC (#1220). - New CoinGecko USDT oracle microservice. - Protocol hub card fifth cell / GET `/hub-prices` ticker `usdt`. - Changing CG/CMC `last_price` to USD. ## First-pass model recommendation Recommendation: grok-high Rationale: P522-Q catalog plus identity gating, mixed 6/18 ingest, NULL-only `price_usd`/candle backfill, and Charts OHLC/invert consumers. That is more than three production files and crosses indexer ingest and the Charts USD display path (price integrity, not a local formatter). Comparable DEX history is #522 / #556 (catalog + hub), not a single RCA or test-helper PR. Composer’s “one subsystem, known local edit, deterministic tests, no cross-cutting protocol/catalog change” bar fails. Verify with `pair_price_usd` unit tests, `verify-issue-522`, and `/charts` USDT pair smoke — not by guessing `$1` in the UI alone.
Author
Owner

/agent implement

/agent implement
Author
Owner

cl8y-agent-control: queued implement job 6a0e5797-e7b9-4b0e-88e8-f9b3682c233f (not executed; no Hetzner VM).

cl8y-agent-control: queued `implement` job `6a0e5797-e7b9-4b0e-88e8-f9b3682c233f` (not executed; no Hetzner VM).
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#1258
No description provided.