fix: Price (USD) candles must match Last headline (invertUsd, not 1/x; sub-cent scale) #543
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#543
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
/tradeand/charts, the Last headline (tape USD of the displayed base) and the Price (USD) candlestick series disagree after #524.Observed on columbus-5:
asset_0, default invert)0.0122588(correct USD of 1 USTR)~1.06(UST1-ish /1 / price_usd)~$0.95(UST1) — headline and candles then agree0.0049(correct USD of 1 cUSTC)~0.93(1 / ~1.07)~$1.07for UST1asset_0, no default invert)0.000047(correct USD of 1 cLUNC)0.00(default lightweight-charts 2-dp)1.0(UST1) but chart~21260(1 / 0.000047)1 USTR ≈ $0.012258. The chart must never show ~$1 (or1/usd) as if that were USTR, nor21260as if that were USD of 1 UST1.This is one product surface: display USD of the displayed base on both the headline and the candle pane, including Y-axis precision. Do not split invert-math vs scale into separate issues.
Related: #524 (UI invert; T524-4 one orientation), #522 (factory
price_usd= USD of 1asset_0), #515 (USTC/LUNC oracles), #151 (non-negative USD scale), #226 (finite candles), #149 (Last prefers tape).Current codebase
Factory pairs keep a fixed
asset_0/asset_1. Indexer math is factory-oriented; #524 inverts only in the dApp.swap_events.price= human quote per base.price_usd= USD of 1 humanasset_0=price × USD(1 human quote). Quote catalog: UST1=$1, cUSTC=#515 USTC, USTR=2.5×USTC, cLUNC=#515 LUNC.rebuild_candles_from_swapswrites OHLC asCOALESCE(se.price_usd, se.price)into a singlecandles.open/high/low/close. There is no parallel human OHLC onGET /candles. Ifprice_usdis NULL, the USD-labeled chart plots the human ratio.resolveDisplayTapeLastPriceUsd→ when inverted,invertUsd(factoryUsd, humanPrice)=price_usd / human_quote_per_base(USD of 1 display base). Else factoryprice_usd.resolveTradeChartHeadlineUsdprefers that tape string over last candle close.PriceChartmaps indexer rows withindexerCandlesToChartPointsthenapplyChartDisplayInvert→invertOhlc= naive1/x+ high/low swap. That is correct for a human quote-per-base series, not for a USD-of-asset_0 series.1 / $1.06 ≈ $0.94, not$0.012USTR.1 / $0.000047 ≈ 21260, not$1UST1.PriceChartLightweightCanvassets candlestick colors +#151autoscale clamp. It does not setpriceFormat.precision/minMove. TradingView lightweight-charts defaults to 2 decimal places, so$0.000047prints as0.00.defaultDisplayInverted= UST1 isasset_0and the other leg is not. UST1/USTR and UST1/cUSTC default to USTR/UST1 and cUSTC/UST1. cLUNC/UST1 (cLUNC =asset_0) stays factory-oriented until the pill is clicked.setData(notseries.update()from the oldest bar) — #336 / T524-6.Why the three reports are the same bug
invertUsd(other-token USD). Candles use1 / price_usd(still ~$1). Switch-side turns invert off, so Last and candles both show factory UST1 USD and appear to “fix” the chart.0.000047); candles are the same magnitude but the axis shows0.00. Switch-side inverts: Last usesinvertUsd→~$1UST1; candles use1/x→~21260(human cLUNC per UST1), not dollars.P522-2:price_usd / priceis USD of 1 human quote. That is the invert the headline already implements. Candles must use the same identity, not1 / price_usd.Why this is needed
Traders read Last and the candle pane as one Price (USD) control (T524-4). Today they disagree by 1–2 orders of magnitude (USTR/cUSTC) or five (cLUNC). That is not a cosmetic invert: it is a false market.
0.00) until invert, then looks like a 20k print — both wrong as USD of the displayed base.#524tests lock ininvertOhlcas1/x(priceChartCandles.test.ts“reciprocates…”). That helper is valid for human book/limit prices, not for the USD candle series.Constraints / guardrails
#466/#522swap_events.price/price_usdor CG/CMClast_price. Invert stays a display of factory series. Additive candle fields (human OHLC alongside USD) are allowed if required for per-barinvertUsd; do not replace factory USD with a reciprocal in storage.place_limit_order/ market submit convert-on-submit. This issue is chart + headline + axis only.price_usdwhen not inverted; when inverted =invertUsd(price_usd, human_quote_per_base)=price_usd / price. Never1 / price_usdon a USD series. Never plot human quote-per-base on a control labeled Price (USD) (P522-5).price_usd. If a bar has no USD, drop it from the USD series (or computehuman × quote_catalogthe same way the indexer does). Do not fall back to raw/humanpriceon the USD axis (cLUNC21260case).≤ 0/ non-finite inputs. After invert, swap high/low sohigh ≥ low. Never passNaN/Infinityinto lightweight-charts.priceFormat(precision /minMove) from the visible USD magnitude so$0.000047and$0.012258are readable. Do not keep a fixed 2-dp format on a USD pane. CompactformatNumT/Kmust not be used as a price-axis formatter (P522-5).setData(T524-6 / #336).asset_0still defaults to the other token; cLUNC/UST1 stays factory until the pill is used. Do not substring-matchcUSTC./limitsstandalone stays out of scope (no silent invert).Relevant files
Must touch
frontend-dapp/src/utils/tradePairDisplayOrientation.ts— keepinvertUsdas the USD invert; do not reuseinvertOhlcfor USD candlesfrontend-dapp/src/components/charts/priceChartCandles.ts—applyChartDisplayInvertmust take factory USD and human OHLC (or precomputed display USD)frontend-dapp/src/components/charts/PriceChart.tsx— wire display series; Last already getsresolveDisplayTapeLastPriceUsdfrontend-dapp/src/components/charts/PriceChartLightweightCanvas.tsx— adaptive USDpriceFormat; keep#151clampfrontend-dapp/src/components/charts/priceChartPriceScale.ts— if precision helpers live herefrontend-dapp/src/utils/pairPriceUsd.ts—resolveDisplayTapeLastPriceUsdis the reference formulafrontend-dapp/src/types/index.ts—IndexerCandlehuman and/or*_usdfields if the API growsfrontend-dapp/src/services/indexer/client.ts—getCandlesmappingindexer/src/db/queries/candles.rs—rebuild_candles_from_swaps/CandleRowif additive human (or usd) OHLC is requiredindexercandle HTTP handler + migrations (only if additive columns)docs/frontend.md§ Trade pair display invert + price chart invariantsdocs/indexer-invariants.mdrow Pair price human + USD #522 (API additive note only)skills/AGENTS_FRONTEND_TRADE_PAIR_INVERT.md,skills/AGENTS_FRONTEND_PRICE_CHART.md,skills/AGENTS_INDEXER_PAIR_PRICE_USD.mdscripts/qa/verify-issue-524.shand a newscripts/qa/verify-issue-<iid>.shDo not treat as invert-USD
invertOhlc/displayPriceToFactoryToken1PerToken0for limit/book human prices (those stay1/xof quote-per-base)last_price(stays human quote-per-base)Regression tests to update (they currently encode
1/xon candle USD)frontend-dapp/src/components/charts/__tests__/priceChartCandles.test.ts(applyChartDisplayInvert reciprocates…)frontend-dapp/src/utils/__tests__/tradePairDisplayOrientation.test.ts(invertOhlcvsinvertUsd— keep both; document which series each applies to)frontend-dapp/src/components/charts/__tests__/PriceChart.test.tsx(#524 invert describe)frontend-dapp/src/components/charts/__tests__/priceChartLightweightRealLibrary.charts.test.tsRecommended direction
invertUsd, notinvertOhlc. For each bar:display_usd = inverted ? factory_usd / human_quote_per_base : factory_usd. Apply independently to open/close; for high/low, invert each then swap sohigh ≥ low(same as today).open/high/low/closeas factory USD (COALESCE(price_usd, NULL)— not human fallback) and addopen_human/high_human/low_human/close_humanfromse.price. Rebuild affected pairs. Frontend maps with#226finite guards on both.human × quote_catalogsoprice_usdis populated (already the indexer identity).minMove=10^floor(log10(min_visible))clamped to a sane range, precision = digits needed for thatminMove). cLUNC (1e-5) and USTR (1e-2) must not share a 2-dp format. Update on invert and on pair/interval change viaapplyOptions(do not recreate the chart — #148).resolveTradeChartHeadlineUsd(tape, chartPoints)oncechartPointsare display USD).#524ticket/book invert as-is. Only the USD candle path changes formula.Acceptance criteria
USTR/UST1): Last and candle last-value / last close are USD of 1 USTR (same order as0.012…, not~1.0).UST1/USTR): Last and candles are USD of 1 UST1 (~$0.95–$1.10), not1/0.012 ≈ 81.~$0.004–0.006class), not~0.93. Switch-side: both UST1 USD (~$1), not1/0.0049.~$0.000047class). Y-axis / last-value must not render0.00as the only digits (at least 5–6 decimal places or equivalentminMove).~$1), not~21260.asset_0./tradeand/chartsshare the same series math and pill state (sessionStorage key perpairAddrunchanged).setDatafor historical rewrite.NaN/Infinity/ negative axis.#524playbook states USD candles useinvertUsd;invertOhlcis human quote-per-base only.make verify-issue-524still passes. Newmake verify-issue-<iid>covers A1–A9 unit paths.Test plan (all paths)
Unit (no chain)
invertUsdvsinvertOhlcfixtures: factory USD1.06+ human86.48→ display0.012258…;1/1.06must fail the USD assertion. Inverse: factory USD0.000047+ human0.000047→ display1.0;1/0.000047must fail.applyChartDisplayInvert(new signature): OHLC USD + human → display USD; high/low swap; drop≤0/ non-finite on either series; times unchanged.21260.resolveDisplayTapeLastPriceUsdand last inverted candle close agree on the same print (shared fixture).priceFormat: inputs1.06,0.012258,0.000047produce precision/minMovethat distinguish those values (not all0.00/ not all1.06).PriceChart/PriceChartLightweightCanvas: invert toggle callssetData(not historicalupdate);applyOptionsupdatespriceFormatwithoutcreateChartagain.asset_0only; cLUNC/UST1 starts factory-oriented.*.charts.test.ts): inverted USTR-scale series last close ≈0.012, not~1; cLUNC-scale last-value visible ≠0.00.Indexer (if additive candle fields)
rebuild_candles_from_swapswrites USD fromprice_usdonly (no silent human fallback into the USD columns). Human columns fromse.price. Integration: UST1/USTR bar has USD ~1 and human ~80–90; cLUNC/UST1 bar has USD ~5e-5and human ~5e-5(UST1 per cLUNC).#522lib +swap_price_human_usdtests still pass (make verify-issue-522).Manual / LocalTerra / mainnet smoke
/trade+/chartsfor UST1/USTR, UST1/cUSTC, cLUNC/UST1: record Last vs chart last-value both orientations and all intervals (1m…1w).make verify-issue-524).Test plan (attack, hack, and abuse)
These are display bugs; abuse is false price, XSS, and chart DoS — not custody. Still required.
open/price_usd=0,-1,NaN,Infinity,1e309,""NaN/InfinityintosetData; no thrownCannot update oldest data0or USD0on invert (n/0,1/0)Infinitylast-value; headline stays on tape or hides1e-18,1e18) with tiny/huge USDaria-label(existing #524)innerHTMLof indexer stringscl8y-dex-trade-pair-invert:<otherPair>)pairAddronly; pair B never reads pair A; invalid values ignoredgetCandlesfor pair A after switch to B__proto__pollution affecting invertprice_usdNULL on all swaps)21260as dollarstoken1/token0(T524-2). A wrong USD candle must not change on-chain price.Verification criteria
Done when:
make verify-issue-<iid>exits 0 (new script: unit paths A1–A9 + H1–H3 + docs/skill grep).make verify-issue-524andmake verify-issue-522still exit 0.bash scripts/with-node.sh --cwd frontend-dapp -- npm test -- --runforpriceChartCandles,tradePairDisplayOrientation,pairPriceUsd,PriceChart,priceChartLightweightSeriesSync, and charts real-library invert cases./tradeand/chartsfor UST1/USTR, UST1/cUSTC, cLUNC/UST1: Last and chart last-value match in both pill orientations; cLUNC factory view is not0.00; inverted cLUNC is not21260.docs/frontend.mdinvert section states USD candles useinvertUsd; playbooks cross-link. No claim that1/xinverts USD-of-UST1 into USD-of-USTR.Mode
Severity
marked as related to #524
marked as related to #522
marked as related to #151
marked as related to #226
mentioned in merge request !1069
mentioned in commit
f53e3bf768mentioned in commit
64c6dd9623mentioned in commit
d7aa1c07e1Merged via !1069 onto
main(f53e3bf7). Sequential stack is now7fdc3a44(!1069 + !1070 + !1071).Sanity check on
main:make verify-issue-543— 8/8 pass (unit A1–A9 / H1–H3 paths, docs/skill grep, indexercandle_human_usd+candle_skip_zero_price). USD candles useinvertUsd/invertUsdNumber, not1/x. No merge-conflict leftovers.Conflicts: none on GitLab (first merge). Later !1070/!1071 needed Makefile/
AGENTS.mdtarget unions only.Remaining (not a merge blocker): production/QA indexer must apply
indexer/migrations/20260817000000_candle_human_ohlc.sql(adds*_human, TRUNCATEscandles, rebuilds fromswap_events). Until then inverted charts can look empty (bars without*_humanare dropped). Manual columbus-5 Last-vs-candle smoke (UST1/USTR, UST1/cUSTC, cLUNC/UST1, both pills) is still unchecked. Tracked in a post-merge follow-up.mentioned in issue #545
marked as related to #545
mentioned in issue #547
mentioned in issue #556
mentioned in merge request !1083
mentioned in issue #564
mentioned in issue #568
marked as related to #568
mentioned in issue #680
mentioned in issue #705
mentioned in issue #717