Fix pair Price (USD) / chart / tape: scale decimals and convert to USD #522

Closed
opened 2026-08-15 11:39:17 +00:00 by PlasticDigits · 37 comments
PlasticDigits commented 2026-08-15 11:39:17 +00:00 (Migrated from gitlab.com)

Summary

Trade/Charts Price (USD) headline, candle OHLC, pair stats, and other USD surfaces treat indexer swap_events.price as dollars. That field is raw quote-per-base (return/offer integers) with no decimal scaling and no oracle conversion, so mixed-decimal pairs (USTR 18 vs UST1 6) show 79.7181T and same-decimal UST1/cUSTC shows ~206 instead of ~$1.

Environment

  • Network: columbus-5 (dex.cl8y.com)
  • Component: indexer (orient_swap_leg / candles / pair stats) + frontend (tapeLastPriceUsd / PriceChart)
  • After #508 Path A seed + test swaps

Steps to reproduce

  1. Open https://dex.cl8y.com/trade (or /charts) for UST1/cUSTC.
  2. Read Price (USD) and the candle Y-axis / Open–Close stats.
  3. Repeat for UST1/USTR.

Pairs (columbus-5):

Pair Address
UST1/cUSTC terra1ceprjsxp86ggftf5e38wwt34l83e5gq7penkdnv4wsatkwcs8v6qccw55f
UST1/USTR terra16vxrhpvpcucu05y0nr862vf9hnqeh274uaff4s7hz4n0ea74006qf5hgqy

Example last prints (hybrid pool-only, cl8ydeploy):

  • UST1→cUSTC FA9582AB… → 24.102109 cUSTC
  • cUSTC→UST1 DD50849A… → 0.116651 UST1
  • UST1→USTR 1748D35D… → 9.297 USTR

Expected behavior

Price (USD) (headline, chart, pair Open/Close, and any other USD label) is the USD price of 1 human unit of pair base (asset_0).

  1. Scale raw quote-per-base by 10^(decimals_quote − decimals_base) so the tape is human quote per base.
  2. Convert to USD with the existing ticker oracle (#515): cUSTC ≈ USTC USD; USTR ≈ 2.5× USTC; UST1 = $1.

For the seeded pools (USTC ~$0.004928 at seed):

Pair Human mid (approx) USD of 1 UST1 (approx)
UST1/cUSTC ~203 cUSTC / UST1 ~$1.00 (last print ~$1.02 after 180 bps)
UST1/USTR ~81 USTR / UST1 ~$1.00 (last print ~$0.98 after 180 bps)

Trades table / limit-order price may stay human quote-per-base, but any control labeled USD (headline, chart, escrow USD notional) must use the converted value.

Actual behavior

Pair Shown as Price (USD) What it actually is
UST1/cUSTC ~206 Last print 24.102109 / 0.116651 = 206.62 cUSTC per UST1 (6/6, no USD)
UST1/USTR 79.7181T Last print 9297047794755092035 / 116624 ≈ 7.97e13 raw (×10^12 vs human ~79.72 USTR/UST1)

Pool mids are ~202.92 cUSTC/UST1 and ~81.17 USTR/UST1. Compact format turns 7.97e13 into 79.7181T.

Root cause

#466 orientation (quote per base, independent of offer leg) is correct, but orient_swap_leg divides raw on-chain amounts and never applies assets.decimals:

indexer/src/indexer/swap_orientation.rs
price = return_amount / offer_amount   # integers, no 10^(d1-d0)

Candles copy that price (candle_builder.rs). The UI then treats the newest tape price as USD:

  • TradePage.tsx / ChartsPage.tsx: tapeLastPriceUsd={trades[0].price}
  • PriceChart.tsx / chartHeadlinePrice.ts: headline + series
  • LimitOrdersPage.tsx / limitOrderPriceReference.ts: tapeHeadlineUsd for escrow USD notional
  • Pair stats Open/Close and CG/CMC last_price are the same unscaled series

assets.decimals is already stored (UST1/cUSTC = 6, USTR = 18).

Fix (suggested)

Indexer (source of truth)

  • Scale orient_swap_leg (or insert path) by 10^(decimals_1 − decimals_0).
  • Persist USD of 1 human base at index time (or expose a derived field) using #515 oracles so candles/stats/tape share one number.
  • Backfill existing swap_events.price + candles for the two #508 pairs (and any other mixed-decimal pair).
  • Keep #466 orientation tests; add 6-vs-18 and 6-vs-6 fixtures (UST1/USTR last print → ~79.72 human, ~$0.98–$1.00 USD).

Frontend (do not pass raw trades[].price as USD)

  • tapeLastPriceUsd / tapeHeadlineUsd must be the USD field (or client-side: human scale × quote USD).
  • Audit every USD label: Price (USD) headline, chart OHLC, Charts Open/Close, limit USD notional, portfolio/trader USD if any.
  • Trades table: show human quote-per-base (or USD) consistently; do not compact-format raw 1e13 as T.

Mode

  • v2 (pool-only swap)
  • limit order (USD notional / limit USD preview also wrong if they use tape headline)
  • hybrid (same tape/candle path)

Severity

  • blocks production
  • major UX / incorrect accounting display

Live charts currently imply UST1 is $206 or $79 trillion.

Evidence

  • Relevant files: indexer/src/indexer/swap_orientation.rs, indexer/src/indexer/parser.rs, indexer/src/indexer/candle_builder.rs, frontend-dapp/src/pages/TradePage.tsx, frontend-dapp/src/pages/ChartsPage.tsx, frontend-dapp/src/components/charts/PriceChart.tsx, frontend-dapp/src/components/charts/chartHeadlinePrice.ts, frontend-dapp/src/utils/limitOrderPriceReference.ts
  • Related: #466 (orientation), #508 (UST1 secondary AMM pairs), #515 (USTC/LUNC oracle)

Acceptance

  • UST1/cUSTC Price (USD) and chart ≈ $1 (not ~206)
  • UST1/USTR Price (USD) and chart ≈ $1 (not 79.7181T)
  • Same USD number on Trade, Charts, pair Open/Close, and any other USD price surface
  • Limit-order USD notional uses the same USD (not raw tape)
  • Mixed-decimal + same-decimal indexer tests; frontend headline tests with 18/6 fixture
  • Existing columbus-5 prints backfilled (or candles rebuilt) so history is not permanently T-scaled
## Summary Trade/Charts **Price (USD)** headline, candle OHLC, pair stats, and other USD surfaces treat indexer `swap_events.price` as dollars. That field is **raw quote-per-base** (`return/offer` integers) with **no decimal scaling** and **no oracle conversion**, so mixed-decimal pairs (USTR 18 vs UST1 6) show `79.7181T` and same-decimal UST1/cUSTC shows `~206` instead of ~$1. ## Environment - Network: columbus-5 (`dex.cl8y.com`) - Component: indexer (`orient_swap_leg` / candles / pair stats) + frontend (`tapeLastPriceUsd` / `PriceChart`) - After [#508](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/508) Path A seed + test swaps ## Steps to reproduce 1. Open https://dex.cl8y.com/trade (or `/charts`) for **UST1/cUSTC**. 2. Read **Price (USD)** and the candle Y-axis / Open–Close stats. 3. Repeat for **UST1/USTR**. Pairs (columbus-5): | Pair | Address | |------|---------| | UST1/cUSTC | `terra1ceprjsxp86ggftf5e38wwt34l83e5gq7penkdnv4wsatkwcs8v6qccw55f` | | UST1/USTR | `terra16vxrhpvpcucu05y0nr862vf9hnqeh274uaff4s7hz4n0ea74006qf5hgqy` | Example last prints (hybrid pool-only, `cl8ydeploy`): - UST1→cUSTC `FA9582AB…` → 24.102109 cUSTC - cUSTC→UST1 `DD50849A…` → 0.116651 UST1 - UST1→USTR `1748D35D…` → 9.297 USTR ## Expected behavior **Price (USD)** (headline, chart, pair Open/Close, and any other USD label) is the **USD price of 1 human unit of pair base (`asset_0`)**. 1. Scale raw quote-per-base by `10^(decimals_quote − decimals_base)` so the tape is **human** quote per base. 2. Convert to USD with the existing ticker oracle ([#515](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/515)): cUSTC ≈ USTC USD; USTR ≈ 2.5× USTC; UST1 = $1. For the seeded pools (USTC ~$0.004928 at seed): | Pair | Human mid (approx) | USD of 1 UST1 (approx) | |------|--------------------|------------------------| | UST1/cUSTC | ~203 cUSTC / UST1 | **~$1.00** (last print ~$1.02 after 180 bps) | | UST1/USTR | ~81 USTR / UST1 | **~$1.00** (last print ~$0.98 after 180 bps) | Trades table / limit-order **price** may stay human quote-per-base, but any control labeled USD (headline, chart, escrow USD notional) must use the converted value. ## Actual behavior | Pair | Shown as Price (USD) | What it actually is | |------|----------------------|---------------------| | UST1/cUSTC | **~206** | Last print `24.102109 / 0.116651` = 206.62 **cUSTC per UST1** (6/6, no USD) | | UST1/USTR | **79.7181T** | Last print `9297047794755092035 / 116624` ≈ **7.97e13** raw (×10^12 vs human ~79.72 USTR/UST1) | Pool mids are ~202.92 cUSTC/UST1 and ~81.17 USTR/UST1. Compact format turns `7.97e13` into `79.7181T`. ## Root cause [#466](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/466) orientation (quote per base, independent of offer leg) is correct, but `orient_swap_leg` divides **raw on-chain amounts** and never applies `assets.decimals`: ```text indexer/src/indexer/swap_orientation.rs price = return_amount / offer_amount # integers, no 10^(d1-d0) ``` Candles copy that price (`candle_builder.rs`). The UI then treats the newest tape `price` as USD: - `TradePage.tsx` / `ChartsPage.tsx`: `tapeLastPriceUsd={trades[0].price}` - `PriceChart.tsx` / `chartHeadlinePrice.ts`: headline + series - `LimitOrdersPage.tsx` / `limitOrderPriceReference.ts`: `tapeHeadlineUsd` for escrow USD notional - Pair stats Open/Close and CG/CMC `last_price` are the same unscaled series `assets.decimals` is already stored (UST1/cUSTC = 6, USTR = 18). ## Fix (suggested) **Indexer (source of truth)** - Scale `orient_swap_leg` (or insert path) by `10^(decimals_1 − decimals_0)`. - Persist **USD of 1 human base** at index time (or expose a derived field) using [#515](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/515) oracles so candles/stats/tape share one number. - Backfill existing `swap_events.price` + candles for the two #508 pairs (and any other mixed-decimal pair). - Keep #466 orientation tests; add 6-vs-18 and 6-vs-6 fixtures (UST1/USTR last print → ~79.72 human, ~$0.98–$1.00 USD). **Frontend (do not pass raw `trades[].price` as USD)** - `tapeLastPriceUsd` / `tapeHeadlineUsd` must be the USD field (or client-side: human scale × quote USD). - Audit every USD label: Price (USD) headline, chart OHLC, Charts Open/Close, limit USD notional, portfolio/trader USD if any. - Trades table: show human quote-per-base (or USD) consistently; do not compact-format raw 1e13 as `T`. ## Mode - [x] v2 (pool-only swap) - [ ] limit order (USD notional / limit USD preview also wrong if they use tape headline) - [x] hybrid (same tape/candle path) ## Severity - [ ] blocks production - [x] major UX / incorrect accounting display Live charts currently imply UST1 is $206 or $79 trillion. ## Evidence - Relevant files: `indexer/src/indexer/swap_orientation.rs`, `indexer/src/indexer/parser.rs`, `indexer/src/indexer/candle_builder.rs`, `frontend-dapp/src/pages/TradePage.tsx`, `frontend-dapp/src/pages/ChartsPage.tsx`, `frontend-dapp/src/components/charts/PriceChart.tsx`, `frontend-dapp/src/components/charts/chartHeadlinePrice.ts`, `frontend-dapp/src/utils/limitOrderPriceReference.ts` - Related: #466 (orientation), #508 (UST1 secondary AMM pairs), #515 (USTC/LUNC oracle) ## Acceptance - [ ] UST1/cUSTC Price (USD) and chart ≈ **$1** (not ~206) - [ ] UST1/USTR Price (USD) and chart ≈ **$1** (not 79.7181T) - [ ] Same USD number on Trade, Charts, pair Open/Close, and any other USD price surface - [ ] Limit-order USD notional uses the same USD (not raw tape) - [ ] Mixed-decimal + same-decimal indexer tests; frontend headline tests with 18/6 fixture - [ ] Existing columbus-5 prints backfilled (or candles rebuilt) so history is not permanently `T`-scaled
PlasticDigits commented 2026-08-15 11:39:18 +00:00 (Migrated from gitlab.com)

marked as related to #466

marked as related to #466
PlasticDigits commented 2026-08-15 11:39:18 +00:00 (Migrated from gitlab.com)

marked as related to #508

marked as related to #508
PlasticDigits commented 2026-08-15 11:39:19 +00:00 (Migrated from gitlab.com)

marked as related to #515

marked as related to #515
PlasticDigits commented 2026-08-15 11:52:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 7edc70d4cd

mentioned in commit 7edc70d4cda5bf740c67fd6cb75e66245c34f36b
PlasticDigits commented 2026-08-15 11:52:33 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1055

mentioned in merge request !1055
PlasticDigits commented 2026-08-15 11:59:54 +00:00 (Migrated from gitlab.com)

Merge review (!1055) — residual gaps

Local sanity: make verify-issue-522 5/5 PASS (docs, indexer lib pair_price/swap_orientation, integration human+USD + #466, frontend tape/headline/formatPairPrice). GitLab CI on !1055 did not run tests — all jobs failed with ci_quota_exceeded (no runner).

Code review vs acceptance: PASS_WITH_GAPS (code complete; live/ops still open).

Acceptance still open (ops / live)

  • Live dex.cl8y.com visual confirm — UST1/cUSTC and UST1/USTR Price (USD) ≈ $1 on Trade / Charts / pair Open–Close. Needs indexer migrate + restart then frontend deploy.
  • Prod backfill quality — migration rebuilds candles; price_usd is NULL if oracle_prices is empty at migrate time. Confirm USTC oracle is populated before migrate.
  • Limit-order USD notional — wired to the same resolveTapeLastPriceUsd helper; not live-verified on columbus-5.
  • Deploy order — ship indexer migration + new frontend together. Old UI + migrated indexer can still label human quote-per-base as USD on 6/6 pairs.

Non-blocking

  • Issue text exponent 10^(decimals_quote − decimals_base) is inverted; MR correctly uses 10^(decimals_base − decimals_quote).
  • AGENTS.md on the MR branch has a duplicated verify-issue-522 line / skill bullet (docs-only). Will clean after merge if still present.
  • CI quota is an infra problem, not a test failure.

Merging !1055 without waiting for CI per review request.

## Merge review (!1055) — residual gaps Local sanity: `make verify-issue-522` **5/5 PASS** (docs, indexer lib pair_price/swap_orientation, integration human+USD + #466, frontend tape/headline/formatPairPrice). GitLab CI on !1055 did **not** run tests — all jobs failed with `ci_quota_exceeded` (no runner). Code review vs acceptance: **PASS_WITH_GAPS** (code complete; live/ops still open). ### Acceptance still open (ops / live) - **Live dex.cl8y.com visual confirm** — UST1/cUSTC and UST1/USTR Price (USD) ≈ $1 on Trade / Charts / pair Open–Close. Needs indexer **migrate + restart** then frontend deploy. - **Prod backfill quality** — migration rebuilds candles; `price_usd` is NULL if `oracle_prices` is empty at migrate time. Confirm USTC oracle is populated before migrate. - **Limit-order USD notional** — wired to the same `resolveTapeLastPriceUsd` helper; not live-verified on columbus-5. - **Deploy order** — ship indexer migration + new frontend together. Old UI + migrated indexer can still label human quote-per-base as USD on 6/6 pairs. ### Non-blocking - Issue text exponent `10^(decimals_quote − decimals_base)` is inverted; MR correctly uses `10^(decimals_base − decimals_quote)`. - `AGENTS.md` on the MR branch has a duplicated `verify-issue-522` line / skill bullet (docs-only). Will clean after merge if still present. - CI quota is an infra problem, not a test failure. Merging !1055 without waiting for CI per review request.
PlasticDigits commented 2026-08-15 12:00:01 +00:00 (Migrated from gitlab.com)

mentioned in commit 8ff700ee20

mentioned in commit 8ff700ee20c7ddd2ac17eea8368316b5ab3522e8
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-15 12:00:02 +00:00
PlasticDigits commented 2026-08-15 12:01:22 +00:00 (Migrated from gitlab.com)

mentioned in commit 1d5360e031

mentioned in commit 1d5360e0310d3b0c7bc40743ccd203a9c4aeb69b
PlasticDigits commented 2026-08-15 12:19:14 +00:00 (Migrated from gitlab.com)

Live check (columbus-5 Coolify) — 2026-08-15T12:19Z

Indexer https://indexer.dex.cl8y.com and frontend https://dex.cl8y.com are already on the #522 build. No further Coolify rebuild needed for this issue.

Indexer

  • /health ok. USTC oracle fresh (0.0049405, sources kucoin/mexc/average @ 12:18Z).
  • Trades expose price_usd. Stats expose *_usd. Candles OHLC are USD of 1 UST1 (not raw / not T).
Pair Human price (quote/base) price_usd Candle close
UST1/cUSTC last 206.62 cUSTC/UST1 $1.021 $1.021
UST1/USTR last 82.76 USTR/UST1 (not 79.72T) $1.022 $1.022

Frontend

  • HTML Last-Modified: Sat, 15 Aug 2026 12:10:06 GMT
  • Chunk pairPriceUsd-BiLCzYSz.js present; Trade / Charts / Limits wire tapeLastPriceUsd / open_price_usd / close_price_usd / tapeHeadlineUsd.

Manual eyeball on /trade and /charts still useful; API + bundle already match ~$1.

## Live check (columbus-5 Coolify) — 2026-08-15T12:19Z Indexer `https://indexer.dex.cl8y.com` and frontend `https://dex.cl8y.com` are already on the #522 build. No further Coolify rebuild needed for this issue. ### Indexer - `/health` ok. USTC oracle fresh (`0.0049405`, sources kucoin/mexc/average @ 12:18Z). - Trades expose `price_usd`. Stats expose `*_usd`. Candles OHLC are USD of 1 UST1 (not raw / not `T`). | Pair | Human `price` (quote/base) | `price_usd` | Candle close | |------|----------------------------|-------------|--------------| | UST1/cUSTC last | 206.62 cUSTC/UST1 | **$1.021** | **$1.021** | | UST1/USTR last | 82.76 USTR/UST1 (not 79.72T) | **$1.022** | **$1.022** | ### Frontend - HTML `Last-Modified: Sat, 15 Aug 2026 12:10:06 GMT` - Chunk `pairPriceUsd-BiLCzYSz.js` present; Trade / Charts / Limits wire `tapeLastPriceUsd` / `open_price_usd` / `close_price_usd` / `tapeHeadlineUsd`. Manual eyeball on `/trade` and `/charts` still useful; API + bundle already match ~$1.
PlasticDigits commented 2026-08-15 12:19:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #524

mentioned in issue #524
PlasticDigits commented 2026-08-15 12:19:48 +00:00 (Migrated from gitlab.com)

marked as related to #524

marked as related to #524
PlasticDigits commented 2026-08-15 12:20:36 +00:00 (Migrated from gitlab.com)

mentioned in issue PlasticDigits2/ustr-cmm#13

mentioned in issue PlasticDigits2/ustr-cmm#13
PlasticDigits commented 2026-08-15 12:27:02 +00:00 (Migrated from gitlab.com)

Ops complete on columbus-5. Issue already closed by !1055.

Coolify indexer + frontend serve #522 (price_usd ~$1 on UST1/cUSTC and UST1/USTR). Live check: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/522#note_3690136730

Ops complete on columbus-5. Issue already closed by !1055. Coolify indexer + frontend serve #522 (`price_usd` ~$1 on UST1/cUSTC and UST1/USTR). Live check: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/522#note_3690136730
PlasticDigits commented 2026-08-16 07:14:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #530

mentioned in issue #530
PlasticDigits commented 2026-08-17 03:51:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #543

mentioned in issue #543
PlasticDigits commented 2026-08-17 03:51:36 +00:00 (Migrated from gitlab.com)

marked as related to #543

marked as related to #543
PlasticDigits commented 2026-08-17 03:52:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #544

mentioned in issue #544
PlasticDigits commented 2026-08-17 03:52:30 +00:00 (Migrated from gitlab.com)

marked as related to #544

marked as related to #544
PlasticDigits commented 2026-08-17 10:29:11 +00:00 (Migrated from gitlab.com)

mentioned in issue #548

mentioned in issue #548
PlasticDigits commented 2026-08-17 10:29:12 +00:00 (Migrated from gitlab.com)

marked as related to #548

marked as related to #548
PlasticDigits commented 2026-08-17 10:35:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #550

mentioned in issue #550
PlasticDigits commented 2026-08-17 10:36:02 +00:00 (Migrated from gitlab.com)

marked as related to #550

marked as related to #550
leonardocolucci commented 2026-08-17 13:07:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #551

mentioned in issue #551
PlasticDigits commented 2026-08-18 00:28:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #556

mentioned in issue #556
PlasticDigits commented 2026-08-18 00:29:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #557

mentioned in issue #557
PlasticDigits commented 2026-08-18 12:12:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #564

mentioned in issue #564
PlasticDigits commented 2026-08-18 12:13:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #565

mentioned in issue #565
PlasticDigits commented 2026-08-19 00:57:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #568

mentioned in issue #568
PlasticDigits commented 2026-08-19 00:57:41 +00:00 (Migrated from gitlab.com)

marked as related to #568

marked as related to #568
PlasticDigits commented 2026-08-19 01:02:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #569

mentioned in issue #569
PlasticDigits commented 2026-08-19 12:11:28 +00:00 (Migrated from gitlab.com)

mentioned in issue #580

mentioned in issue #580
PlasticDigits commented 2026-08-19 12:11:32 +00:00 (Migrated from gitlab.com)

marked as related to #580

marked as related to #580
PlasticDigits commented 2026-08-21 00:21:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #586

mentioned in issue #586
PlasticDigits commented 2026-08-22 12:26:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #597

mentioned in issue #597
PlasticDigits commented 2026-08-27 01:00:18 +00:00 (Migrated from gitlab.com)

mentioned in issue #682

mentioned in issue #682
PlasticDigits commented 2026-08-27 01:00:28 +00:00 (Migrated from gitlab.com)

mentioned in issue #683

mentioned in issue #683
PlasticDigits commented 2026-08-28 05:22:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #692

mentioned in issue #692
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#522
No description provided.