fix(charts): blank price chart for ALPHA pairs #1315

Closed
opened 2026-09-22 05:02:59 +00:00 by PlasticDigits · 2 comments

Summary

On /charts, factory pairs that include ALPHA (terra1x6e64es6yhauhvs3prvpdg2gkqdtfru840wgnhs935x8axr7zxkqzysuxz, 6 decimals, token registry) show the empty price pane (“No chart data for this interval yet”) even when the pair has trades.

Candle ingest only writes OHLC when price_usd is a positive factory USD mark of 1 asset_0. That mark is human_quote_per_base * usd_per_human_quote, and the quote is asset_1. quote_usd_kind prices UST1, USTC/cUSTC, LUNC/cLUNC, USTR, and the pinned USDT CW20. ALPHA and CL8Y are not catalog quotes, so a pair whose asset_1 is ALPHA (or CL8Y, on CL8Y/ALPHA) stores no candles. The dApp then drops the empty USD series and mounts PriceChartEmptyState.

Do not peg ALPHA (or CL8Y) to $1. Do not reopen closed #1258 (USDT identity pin), #543 (human numbers must not land in USD columns), #522 (USTC/LUNC/UST1/USTR catalog), #113 (empty-state copy; that state already ships), or #568 (hub rewrite of existing USD history). #1250 is the ALPHA code-id migrate, not this chart.

Current codebase

Indexer:

  • indexer/src/indexer/pair_price_usd.rs — quote_usd_kind returns None for ALPHA and CL8Y. price_usd_for_human_quote_per_base_pinned therefore returns None when asset_1 is ALPHA.
  • indexer/src/indexer/parser.rs — swap price_usd is computed only from the quote asset (asset_1).
  • indexer/src/indexer/candle_builder.rs — update_candles_for_swap returns before any write when price_usd is missing or non-positive, so *_human OHLC is not stored either.
  • indexer/src/indexer/swap_orientation.rs — asset_0 is base, asset_1 is quote; human price is quote-per-base. Factory order follows chain asset_infos, not “stable is quote”.

Frontend:

  • frontend-dapp/src/components/charts/priceChartCandles.ts — bars without finite positive USD are dropped (indexerCandlesToFactoryPoints). No human-on-USD-axis fallback.
  • frontend-dapp/src/components/charts/PriceChart.tsx — chartPoints.length === 0 mounts PriceChartEmptyState.
  • frontend-dapp/src/utils/tokenRegistry.ts — ALPHA symbol, 6 decimals, contract above.

Known factory markets from #558: UST1/ALPHA (catalog leg exists) and CL8Y/ALPHA (neither leg is a USD catalog quote). Terraport ALPHA/LUNC and ALPHA/USTC addresses in communityTaxMigratePairs.ts are other-venue inventory, not this factory chart.

Given / When / Then

Given a factory pair that includes ALPHA and has at least one indexed swap with a positive human quote-per-base price,

When that pair is opened on /charts at the default interval,

Then the price pane shows candlesticks, not PriceChartEmptyState.

Given the other leg is a catalog USD quote (UST1, USTC/cUSTC/uusd, LUNC/cLUNC/uluna, USTR, or the pinned USDT CW20),

When candles are built,

Then price_usd is USD of 1 human ALPHA, derived from that catalog leg, whichever asset_infos slot the catalog asset occupies.

Given both legs are outside the catalog (CL8Y/ALPHA),

When candles are built,

Then human quote-per-base OHLC is stored and the chart draws that series, labeled as quote-per-base rather than USD. Human values are not copied into USD columns.

Given the pair has no swaps, or every price is non-positive,

When /charts loads,

Then the existing empty state remains.

Expected vs actual

Expected: an ALPHA factory pair with trades shows a price series on /charts. USD when a catalog leg can mark it; human quote-per-base when neither leg is in the catalog.

Actual: ingest skips the candle when asset_1 has no USD handle, the candles API returns no usable USD rows, and the pane is the dashed empty state.

Acceptance criteria

  • UST1/ALPHA (and any ALPHA pair with a catalog quote leg) produces positive price_usd of 1 human ALPHA after a swap, independent of which slot ALPHA occupies.
  • CL8Y/ALPHA persists human OHLC and /charts renders those bars instead of the empty state.
  • USD columns stay null or omitted when no catalog leg exists. No ALPHA peg and no new quote_usd_kind("ALPHA") arm.
  • Non-positive and overflow prices still skip the candle write.
  • A CW20 whose symbol is ALPHA but whose contract is not the registry address does not gain a special mark.
  • Empty-state copy and behavior for pairs with no trades are unchanged.

Test plan

  • Indexer unit tests in pair_price_usd / candle_builder: ALPHA as asset_1 with UST1 as asset_0 writes USD candles; ALPHA as asset_0 with UST1 as asset_1 still does; CL8Y/ALPHA writes human OHLC and does not write USD; symbol ALPHA on another contract does not price.
  • Frontend: priceChartCandles (or PriceChart) fixture with human-only rows for an unpriced quote renders points; all-invalid USD without a human series still shows the empty state.
  • Do not require a live chain or a deployed indexer for the regression.

Attack / abuse tests

  • Spoofed symbol ALPHA (wrong CW20) must not receive the registry token’s mark or a catalog bypass.
  • Non-positive, zero, overflow, and non-finite human prices must not create candles or reach setData.
  • Client fixtures with NaN, Infinity, empty OHLC strings, and non-numeric fields stay dropped.
  • Human quote-per-base must not be written into USD OHLC fields.

Verification

  • Unit tests above pass.
  • For a fixture swap on UST1/ALPHA, candle USD matches human_alpha_price * ust1_usd (not a peg).
  • For a fixture swap on CL8Y/ALPHA, /charts is not PriceChartEmptyState and the axis is not labeled as USD.
  • A no-trade pair still shows the empty state.

First-pass model recommendation

Recommendation: grok-high

Rationale: The blank pane comes from the indexer USD-candle skip (candle_builder.rs, pair_price_usd.rs, parser.rs) plus the frontend drop of non-USD bars (priceChartCandles.ts, PriceChart.tsx). Fixing it changes how OHLC is stored for unpriced quotes and has to keep human values out of USD columns. That is more than one subsystem, more than three production files, and a pricing-invariant change, so a first pass needs the stronger model. Verify with the indexer orientation tests and the chart fixture tests above.

## Summary On `/charts`, factory pairs that include **ALPHA** (`terra1x6e64es6yhauhvs3prvpdg2gkqdtfru840wgnhs935x8axr7zxkqzysuxz`, 6 decimals, token registry) show the empty price pane (“No chart data for this interval yet”) even when the pair has trades. Candle ingest only writes OHLC when `price_usd` is a positive factory USD mark of 1 `asset_0`. That mark is `human_quote_per_base * usd_per_human_quote`, and the quote is `asset_1`. `quote_usd_kind` prices UST1, USTC/cUSTC, LUNC/cLUNC, USTR, and the pinned USDT CW20. **ALPHA and CL8Y are not catalog quotes**, so a pair whose `asset_1` is ALPHA (or CL8Y, on CL8Y/ALPHA) stores no candles. The dApp then drops the empty USD series and mounts `PriceChartEmptyState`. Do not peg ALPHA (or CL8Y) to $1. Do not reopen closed #1258 (USDT identity pin), #543 (human numbers must not land in USD columns), #522 (USTC/LUNC/UST1/USTR catalog), #113 (empty-state copy; that state already ships), or #568 (hub rewrite of existing USD history). #1250 is the ALPHA code-id migrate, not this chart. ## Current codebase Indexer: - `indexer/src/indexer/pair_price_usd.rs` — `quote_usd_kind` returns `None` for `ALPHA` and `CL8Y`. `price_usd_for_human_quote_per_base_pinned` therefore returns `None` when `asset_1` is ALPHA. - `indexer/src/indexer/parser.rs` — swap `price_usd` is computed only from the quote asset (`asset_1`). - `indexer/src/indexer/candle_builder.rs` — `update_candles_for_swap` returns before any write when `price_usd` is missing or non-positive, so `*_human` OHLC is not stored either. - `indexer/src/indexer/swap_orientation.rs` — `asset_0` is base, `asset_1` is quote; human price is quote-per-base. Factory order follows chain `asset_infos`, not “stable is quote”. Frontend: - `frontend-dapp/src/components/charts/priceChartCandles.ts` — bars without finite positive USD are dropped (`indexerCandlesToFactoryPoints`). No human-on-USD-axis fallback. - `frontend-dapp/src/components/charts/PriceChart.tsx` — `chartPoints.length === 0` mounts `PriceChartEmptyState`. - `frontend-dapp/src/utils/tokenRegistry.ts` — ALPHA symbol, 6 decimals, contract above. Known factory markets from #558: UST1/ALPHA (catalog leg exists) and CL8Y/ALPHA (neither leg is a USD catalog quote). Terraport ALPHA/LUNC and ALPHA/USTC addresses in `communityTaxMigratePairs.ts` are other-venue inventory, not this factory chart. ## Given / When / Then Given a factory pair that includes ALPHA and has at least one indexed swap with a positive human quote-per-base price, When that pair is opened on `/charts` at the default interval, Then the price pane shows candlesticks, not `PriceChartEmptyState`. Given the other leg is a catalog USD quote (UST1, USTC/cUSTC/`uusd`, LUNC/cLUNC/`uluna`, USTR, or the pinned USDT CW20), When candles are built, Then `price_usd` is USD of 1 human ALPHA, derived from that catalog leg, whichever `asset_infos` slot the catalog asset occupies. Given both legs are outside the catalog (CL8Y/ALPHA), When candles are built, Then human quote-per-base OHLC is stored and the chart draws that series, labeled as quote-per-base rather than USD. Human values are not copied into USD columns. Given the pair has no swaps, or every price is non-positive, When `/charts` loads, Then the existing empty state remains. ## Expected vs actual Expected: an ALPHA factory pair with trades shows a price series on `/charts`. USD when a catalog leg can mark it; human quote-per-base when neither leg is in the catalog. Actual: ingest skips the candle when `asset_1` has no USD handle, the candles API returns no usable USD rows, and the pane is the dashed empty state. ## Acceptance criteria - [ ] UST1/ALPHA (and any ALPHA pair with a catalog quote leg) produces positive `price_usd` of 1 human ALPHA after a swap, independent of which slot ALPHA occupies. - [ ] CL8Y/ALPHA persists human OHLC and `/charts` renders those bars instead of the empty state. - [ ] USD columns stay null or omitted when no catalog leg exists. No ALPHA peg and no new `quote_usd_kind("ALPHA")` arm. - [ ] Non-positive and overflow prices still skip the candle write. - [ ] A CW20 whose symbol is ALPHA but whose contract is not the registry address does not gain a special mark. - [ ] Empty-state copy and behavior for pairs with no trades are unchanged. ## Test plan - Indexer unit tests in `pair_price_usd` / `candle_builder`: ALPHA as `asset_1` with UST1 as `asset_0` writes USD candles; ALPHA as `asset_0` with UST1 as `asset_1` still does; CL8Y/ALPHA writes human OHLC and does not write USD; symbol `ALPHA` on another contract does not price. - Frontend: `priceChartCandles` (or `PriceChart`) fixture with human-only rows for an unpriced quote renders points; all-invalid USD without a human series still shows the empty state. - Do not require a live chain or a deployed indexer for the regression. ## Attack / abuse tests - Spoofed symbol `ALPHA` (wrong CW20) must not receive the registry token’s mark or a catalog bypass. - Non-positive, zero, overflow, and non-finite human prices must not create candles or reach `setData`. - Client fixtures with `NaN`, `Infinity`, empty OHLC strings, and non-numeric fields stay dropped. - Human quote-per-base must not be written into USD OHLC fields. ## Verification - Unit tests above pass. - For a fixture swap on UST1/ALPHA, candle USD matches `human_alpha_price * ust1_usd` (not a peg). - For a fixture swap on CL8Y/ALPHA, `/charts` is not `PriceChartEmptyState` and the axis is not labeled as USD. - A no-trade pair still shows the empty state. ## First-pass model recommendation Recommendation: grok-high Rationale: The blank pane comes from the indexer USD-candle skip (`candle_builder.rs`, `pair_price_usd.rs`, `parser.rs`) plus the frontend drop of non-USD bars (`priceChartCandles.ts`, `PriceChart.tsx`). Fixing it changes how OHLC is stored for unpriced quotes and has to keep human values out of USD columns. That is more than one subsystem, more than three production files, and a pricing-invariant change, so a first pass needs the stronger model. Verify with the indexer orientation tests and the chart fixture tests above.
Author
Owner

cl8y-agent-control: queued design_author job 5edf0349-fd52-4437-ae40-04a413d6ea20 (not executed; no Hetzner VM).

cl8y-agent-control: queued `design_author` job `5edf0349-fd52-4437-ae40-04a413d6ea20` (not executed; no Hetzner VM).
Author
Owner

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.
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#1315
No description provided.