fix(indexer): USDT-quoted Charts Price (USD) and 24h OHLC stay empty #1258
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#1258
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
On
/chartsfor 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_usdNULL. Candles omit NULL-USD bars (#543). Charts OHLC binds*_usdviaformatPairStatsUsdOhlc(—when missing).price_change_pctand 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):hub_prices.ust1— not$1uusdulunahub_prices.ustrNone—quote_usd_kind("USDT")has no armNone(#580)None(fee marks #683 are not pairprice_usd)price_usd_for_human_quote_per_base→quote_usd_kind(symbol)→ unknown →None. Candle OHLC is factory USD fromprice_usdonly (P522-4).HubQuoteUsd.economicis fee-only (EFee-4); volume / TVL / pair charts must not silently start reading it for USDT.Frontend:
ChartsPage24h High/Low/Open/Close →resolveDisplayPairStatsUsdOhlc→formatPairStatsUsdOhlc(—on null / non-positive).price_change_pct(human open→close) even when*_usdis null — matches the split symptom.PriceChartdrops bars without USD; empty / all-dropped → existing empty state (#113), not a silent canvas.classifyQuoteSymbolhas noUSDTarm; client fallback cannot invent dollars when indexerprice_usdis missing.?price=cLUNC(#680) still needs factory USD beforeinvertUsd. 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
symbol=USDTstaysNone. Do not match ticker substring. Do not treat IBCibc/…USDC (#1220) or native bank denoms as USDT.QuoteUsdKind::Peg1/hub_prices.ust1. That handle is UST1 DEX mark, not Tether. Do not$1UST1. Do not2.5×USTR.*USDToracle symbols are venue tickers, not Terra CW20 USDT.GET /oracle/historyonto pair candles (C568-7). No new/oracle/price/usdtunless product explicitly wants a feed; a contract-pinned $1 advisory peg for this listed USDT is acceptable and simpler than a CEX poll.price_usdremains USD of 1 humanasset_0. If USDT isasset_1,usd_per_human_quote ≈ 1soprice_usd ≈human USDT-per-cLUNC. If USDT isasset_0, quote is cLUNC → already LUNC-catalogued; then this bug would not match the empty-USD report — confirm orientation, do not invert in storage.1e-12or 18-dec raw as dollars). After catalog + ingest, bars with USD must plot; remaining NULL rows still drop.UPDATEhistorical non-NULLprice_usd. NULL-only backfill / candle rebuild for this pair is allowed. Idle marks may use the new quote USD going forward.invertUsd(price_usd / human), never1/xof USD.?price=allowlist remains the two pair legs. Charts vs Trade invert storage isolation stays.T/M/Bstill forbidden on OHLC (S564-7).last_pricestays human (H10). Do not switch aggregators to USD.Relevant files
indexer/src/indexer/pair_price_usd.rsQuoteUsdKind,quote_usd_kind/quote_usd_kind_for_identity,usd_per_human_quote,price_usd_for_human_quote_per_base, volume helperindexer/src/indexer/pair_price_usd.rstestsfrontend-dapp/src/utils/pairPriceUsd.tsusdtkind for legacy fallback only; prefer indexerprice_usdfrontend-dapp/src/utils/__tests__/pairPriceUsd.test.tsfrontend-dapp/src/utils/tokenRegistry.tsdocs/indexer-invariants.mdskills/AGENTS_INDEXER_PAIR_PRICE_USD.mdscripts/qa/verify-issue-522.shverify-issue-*for this ticketindexer/src/db/queries/candles.rsprice_usd(no formula change)Maybe:
hub_usd.rsonly if implementers reject $1 and insist on a DEX-reserve USDT mark — then it must be a new handle, notust1. Default is pin+$1, not hub.Recommended direction
QuoteUsdKind::Usdt(name as you like).usd_per_human_quote=1when kind is Usdt. Wire only inquote_usd_kind_for_identitywhenis_cw20andcontract_addressequals the pinned registry USDT (casefold). SymbolUSDTwith empty/wrong contract →None. Do not add a barequote_usd_kind("USDT")arm that prices spoofs.quote_usd_kind("USDT", None)returningNoneor document that identity-gated helper is the only ingest path (prefer the latter tests onquote_usd_kind_for_identity).swap_events.price_usdfor pairs whose quote (or priced quote leg) is that USDT contract; rebuild candles. Do not rewrite UST1/cUSTC history.price_usdpresent, Charts/Trade unchanged. LegacyclassifyQuoteSymbolmay map USDT → $1 only as old-indexer fallback; production path prefers the field. Still never pass human tape Price into USD tiles.Acceptance criteria
/charts/{pair}(with or without?price=cLUNCas a valid leg).—), while tape Price stays human and % remains consistent with the displayed USD (or factory human when not inverted).Also:
GET /pairs/{addr}/statsopen_price_usd/high_usd/low_usd/close_price_usdare positive decimals for that pair after ingest/backfill.GET /candlesrows include factory USD OHLC; lightweight-charts is not the #113 empty state solely because USDT was unpriced.?price=cLUNCvs?price=USDT(or invert pill) usesinvertUsd; Last and last candle agree in both orientations; Y-axis not0.00for sub-cent cLUNC USD; inverted view is not1/xof USD.symbol=USDTother contract: still NULL USD. VFDUSD/FDUSD/CL8Y unchanged.pricealready scaled (P522-1); USD ≈ human USDT-per-cLUNC when USDT is quote at $1. No79T/ compactTon OHLC.make verify-issue-522stays green.Test plan (functional paths)
quote_usd_kind_for_identityregistry USDT CW20 pinSome(Usdt);usd_per_human_quote= 1terra1NoneVFDUSD/FDUSD/CL8YNoneprice_usdNUMERIC(38,18)volume_usd_for_swapwith USDT quote leg 18-decT*_humanstill quote-per-baseresolveDisplayPairStatsUsdOhlcwith USDT*_usdformatPairStatsUsdOhlcis$…, not—?price=other legVitest:
pairPriceUsd.test.tsUSDT pin + spoof. Indexer:cargo test --lib pair_price_usd+swap_price_human_usd.make verify-issue-522+ newverify-issue-*.Test plan (attack, hack, and abuse)
symbol=USDTCW20 / native denom spoof$1?price=javascript:…/ HTML / overlonginnerHTMLprice_usd0/-1/NaN/InfinitysetDataNaN (#226 / #543 H1)hub_prices.ust1or CEX FDUSDprice_usdin the backfillshowGems=1USDT-named gemVerification criteria
make verify-issue-(indexer catalog + spoof + 6/18 USD + docs/skill grep).make verify-issue-522,make verify-issue-543,make verify-issue-556stay green.cd indexer && cargo test --lib pair_price_usd -- --quietpairPriceUsd.test.ts,chartsPairStats.test.ts(—still for true null;$for USDT fixture)./charts/{cLUNC-USDT}— candles + 24h USD OHLC populated; tape still human;%still shown; invert/?price=both legs; UST1/cUSTC hero unchanged.Out of scope
/hub-pricestickerusdt.last_priceto 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 withpair_price_usdunit tests,verify-issue-522, and/chartsUSDT pair smoke — not by guessing$1in the UI alone./agent implement
cl8y-agent-control: queued
implementjob6a0e5797-e7b9-4b0e-88e8-f9b3682c233f(not executed; no Hetzner VM).