feat: DEX hub USD for cUSTC/UST1/USTR (largest liquidity) + Protocol USTR #556

Closed
opened 2026-08-18 00:28:34 +00:00 by PlasticDigits · 39 comments
PlasticDigits commented 2026-08-18 00:28:34 +00:00 (Migrated from gitlab.com)

Summary

Replace the hardcoded P522-Q UST1 = $1 and USTR = 2.5 × USTC pegs with DEX-derived hub USD, ranked by largest USD liquidity. Anchor 1 cUSTC = 1 USTC from the existing #515 CEX USTC oracle, then price UST1 from the deepest cUSTC/UST1 pool, then USTR (and other economic tokens) from their deepest pair against an already-priced hub.

Show USTR (and the other hubs) on /protocol as a DEX reference card — not as a fourth CEX oracle ticker.

Related: #522 (pair price_usd + catalog), #515 / #550 (CEX ustc | lunc | vfdusd only), #524 / #543 (UI invert of factory USD), #548 (volume_usd ingest), #508 (UST1 secondary AMM seed peg). Tape Amount in / Amount out / Price humanization is #557 (raw formatNum).

Current codebase

Quote USD catalog (P522-Q)

Indexer pair_price_usd.rs and dApp pairPriceUsd.ts convert human quote-per-base to USD of 1 human asset_0 using a static catalog:

Quote USD handle today
UST1 hardcoded $1 (QuoteUsdKind::Peg1)
USTC / cUSTC / uusd #515 USTC CEX oracle
LUNC / cLUNC / uluna #515 LUNC CEX oracle
USTR 2.5 × USTC (USTR_PER_USTC)
anything else NULL (do not invent)

That catalog is also volume_usd ingest (#548 C8 / X4). Tests in pair_price_usd.rs assert UST1/cUSTC last print ≈ $1 because quote is cUSTC × oracle, and UST1/USTR ≈ $1 because 79.72 × 2.5 × USTC — not because the pool was marked to a DEX USTR price.

Charts / pair APIs

  • GET /api/v1/pairs/{addr}/trades — price is human quote-per-base; price_usd is USD of factory asset_0 via P522-Q.
  • GET /api/v1/pairs/{addr}/candles — OHLC is factory USD from price_usd only (#543); additive *_human for invert.
  • /charts and /trade headline Price (USD) uses resolveTapeLastPriceUsd / invert (#524). Default invert on UST1-as-base pairs shows the other token’s USD, which today falls back to the same peg catalog when invertUsd cannot run.
  • CG/CMC last_price stays human quote-per-base (P522-5) — do not silently switch aggregators to USD.

Protocol page

ProtocolPage.tsx after #550: Global stats → one CEX oracle card with tabs USTC / LUNC / vFDUSD only (PROTOCOL_ORACLE_TICKERS). Copy: “CEX reference prices. Not settlement.” There is no USTR, no UST1, no cUSTC DEX mark.

GET /api/v1/oracle/price/{ticker} is CEX-only (ustc | lunc | vfdusd). Unknown ticker (including ustr) is 400. Overview exposes ustc_price_usd only.

Liquidity already mirrored

pair_reserves (book-snapshot loop, #322) has raw reserve_0 / reserve_1 per factory pair. Nothing ranks “largest USD pool for token T” or publishes a hub USD table. GET /api/v1/tokens has no price_usd.

Why this is wrong in production

  • UST1 is not a dollar stable. It is a window-minted asset (#506) that trades vs cUSTC. Charting UST1/USTR as $1 of UST1 (or USTR as 2.5 × USTC) hides the actual pool mark.
  • cUSTC should track USTC 1:1 (wrap). That part of the catalog is the correct anchor.
  • Most economic pairs are vs cUSTC or UST1. Pricing those quotes as $1 (UST1) or 2.5× USTC (USTR) makes Charts Price (USD), candles, pair Open/Close, and any downstream USD notional disagree with the book.
  • Protocol is the census/reference page. Users looking for USTR/USD have nowhere to read a DEX mark; stuffing USTR into the CEX ticker allowlist would also be a lie (there is no CEX USTR feed).

Why the new implementation is needed

  1. Honest marks. Charts and pair APIs must show USD of USTR / cUSTC / UST1 from the deepest real pool, not launch-seed pegs.
  2. Composable hub graph. Once cUSTC is anchored to the USTC oracle and UST1 is marked from cUSTC/UST1, every other economic token (USTR, CL8Y, cLUNC, gems that actually pair vs a hub) can inherit USD from its largest-liquidity hub pair.
  3. Protocol completeness. Show USTR (and the hub set) next to CEX USTC/LUNC/vFDUSD, clearly labeled as DEX reference.

Constraints / guardrails

  1. Advisory only (X5 / P550-11). DEX hub USD is not settlement, TWAP, limit crossing, or the /ust1 window rate. Short “DEX reference” label (#489) — no essays.
  2. Do not add ustr (or ust1 / custc) to CEX OracleTicker. Keep X1–X2: /oracle/price catalog stays ustc | lunc | vfdusd. ustr on that path stays 400. New DEX prices use a different route (and/or additive overview fields).
  3. Do not use vFDUSD/FDUSD to convert DEX marks or volume_usd (P550-10). UST1 window ≠ CEX FDUSD ≠ pool UST1.
  4. 1 cUSTC = 1 USTC from #515 ustc oracle (wrap assumption). Native uusd same. If the USTC oracle is down, cUSTC/UST1/USTR USD is NULL (—), not $0 and not a leftover $1 peg.
  5. Largest liquidity = USD TVL of a factory pair, not raw reserve integers (18-dec USTR vs 6-dec cUSTC would rank wrong). Humanize each reserve with that asset’s decimals, convert both legs to USD, rank by sum (or documented equivalent). Ignore dust below a documented floor (recommend ≥ $100 USD TVL, configurable) so a 1-unit spoof pool cannot win.
  6. Identity is contract/denom, not symbol (A1). Native gem with symbol=USTR must not price. Hub CW20s must match tokenlist / known hub addresses (cUSTC, UST1, USTR), not ticker string. Factory provenance (#311) already gates pair insert — do not price unlisted clones.
  7. Stale / missing pair_reserves: degrade — skip that pair in ranking (same spirit as book-snapshot degrade-not-error). Do not treat zero reserves as infinite price.
  8. Deterministic ties: highest USD TVL wins; exact tie → stable key (pair contract address lexicographic). Document it.
  9. No circular quotes. Bootstrap order is fixed: USTC oracle → cUSTC → UST1 from cUSTC/UST1 → USTR from deepest pair vs already priced cUSTC or UST1. Do not price UST1 from UST1/USTR using the old 2.5× peg.
  10. Do not scan swap_events on every GET. Materialize hub USD on a cadence (book-snapshot and/or volume aggregator, ≤ same order as snapshot interval). Cache reads O(1). Cache-miss must not walk all trades.
  11. CG/CMC last_price stays human quote-per-base (P522-5). Pair price field stays human. Only price_usd / candles USD / Protocol DEX card / overview hub fields change.
  12. Do not change /ust1 window math, wrap-mapper fees, limit validate_limit_order_price, or ops script USTR_PER_USTC used only to size UST1/USTR LP (rebalance-mint-ust1-lp.sh) — that seed is not a display oracle.
  13. UI invert (#524 / #543) stays frontend. Indexer still stores USD of factory asset_0. After hub USD is correct, invert continues to use invertUsd(price_usd, human) — do not 1/x USD candles.
  14. Allowlist any new ticker path. javascript:, ../, HTML, unknown → 400 (API) or default/omit (UI). Never interpolate raw query input into SQL or fetch URLs.
  15. NUMERIC(38,18) / non-finite: reuse fits_numeric_38_18 and X6. No NaN/Inf in DB or JSON.

Relevant files

File Role
indexer/src/indexer/pair_price_usd.rs P522-Q Peg1 / Ustr / usd_per_human_quote / volume_usd_for_swap
indexer/src/indexer/swap_orientation.rs Human quote-per-base + price_usd at ingest
indexer/src/db/queries/candles.rs Factory USD candles
indexer/src/db/queries/pair_reserves.rs Raw reserves for TVL ranking
indexer/src/indexer/book_snapshot.rs Reserve refresh cadence
indexer/src/api/oracle.rs CEX catalog — do not add ustr here
indexer/src/indexer/oracle.rs CEX poll loop
indexer/src/api/overview.rs Additive hub USD fields (optional but useful)
indexer/src/api/tokens.rs Token list currently has no price
indexer/src/api/pairs.rs Trades / candles / pair stats *_usd
indexer/tests/swap_price_human_usd.rs Ingest USD tests
indexer/tests/volume_usd_catalog.rs Volume catalog
indexer/tests/api_oracle.rs CEX ticker 400 regressions
frontend-dapp/src/utils/pairPriceUsd.ts Client catalog + tape USD
frontend-dapp/src/utils/protocolOracleTicker.ts CEX allowlist
frontend-dapp/src/pages/ProtocolPage.tsx Layout
frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx CEX card — keep separate
frontend-dapp/src/pages/ChartsPage.tsx Price (USD) + candles
frontend-dapp/src/pages/TradePage.tsx Trade headline USD
docs/indexer-invariants.md P522-Q / X4 rows
skills/AGENTS_INDEXER_PAIR_PRICE_USD.md Playbook
skills/AGENTS_FRONTEND_PROTOCOL_STATS.md Protocol layout
docs/runbooks/indexer-external-oracle.md CEX vs DEX split
tokenlist/ Hub CW20 addresses

1. Hub USD resolver (indexer)

New module (name bikeshed-ok: hub_usd.rs / dex_token_usd.rs):

  1. Anchor: usd(cUSTC) = usd(uusd) = ustc_oracle.
  2. UST1: among factory pairs whose legs are exactly hub cUSTC + hub UST1 (address/denom match), with fresh reserves and TVL ≥ floor, pick max USD TVL. usd(UST1) = human_quote_per_base × usd(other_leg) using the spot from reserves (constant-product reserve_quote_human / reserve_base_human after decimal scale) — not the last swap print alone (prints can be dust). Last print may be a sanity check / fallback if reserves are missing and the pair still has a recent price_usd — document which; prefer reserves.
  3. USTR: same, among pairs with hub USTR + (cUSTC or UST1). Prefer the max-TVL pair. Convert via the already-resolved hub leg.
  4. General economic token (phase 1b, same issue): for a token T, consider factory pairs where the other leg is cUSTC or UST1 (and optionally cLUNC/LUNC using LUNC oracle). Rank by USD TVL; skip unknown other-leg. Phase 1 must ship cUSTC, UST1, USTR correctly; other tokens may reuse the same helper.

Persist a small token_usd (or hub_prices) snapshot: asset_id, price_usd, source_pair_id, tvl_usd, updated_at. Refresh on book-snapshot success (and on oracle tick so cUSTC moves with USTC without waiting for a reserve change).

2. Wire into P522 ingest

Replace QuoteUsdKind::Peg1 and ::Ustr lookups with token_usd for that quote asset (still classify so unknown gems stay NULL). volume_usd_for_swap uses the same table. Backfill recent swap_events.price_usd + candles (idempotent migration / job) from stored human price × new quote USD as-of backfill time (advisory; document).

Keep USTR_PER_USTC out of ingest. Client pairPriceUsd.ts fallback for pre-upgrade indexers may keep a documented legacy path; new indexer must send price_usd from hub table.

3. APIs

Pick one primary read (additive JSON):

  • GET /api/v1/hub-prices → { metadata, prices: [{ ticker, asset, price_usd, source_pair, tvl_usd, updated_at }] } with allowlisted tickers custc | ust1 | ustr (and maybe clunc).
  • Additive on GET /api/v1/overview: custc_price_usd, ust1_price_usd, ustr_price_usd (null when unresolved). Keep ustc_price_usd as the CEX USTC ticker.

Do not break existing overview field names.

4. Protocol UI

Add a DEX hub prices card (data-testid="protocol-dex-hub-prices") between Global stats and the CEX oracle card or immediately after the CEX card — pick one, document in docs/frontend.md. Show cUSTC, UST1, USTR (compact $ + formatPairPrice, never formatNum T). Optional: source pair shortened + explorer (token-identity style, #541). Label DEX reference — not CEX, not settlement.

Do not add USTR as a fourth tab on ProtocolOracleCard. Do not change ?ticker= CEX allowlist.

5. Charts / Trade audit

After ingest uses hub USD:

  • UST1/cUSTC Price (USD) of 1 UST1 ≈ human(cUSTC per UST1) × USTC oracle (pool mark), not $1 unless the pool is actually there.
  • Default invert (#524): USD of 1 cUSTC ≈ USTC oracle (~$0.00x), USD of 1 USTR ≈ pool USTR mark (today’s order of $0.01 class if 2.5× still happens to be near the pool — must track the pool if it diverges).
  • Regression: if USTR/UST1 pool trades at 100 USTR per UST1 while UST1 is 200 cUSTC, USTR USD must be usd(UST1)/100, not 2.5 × ustc.

Acceptance criteria

  • H1. usd(cUSTC) = ustc_oracle (and native USTC). Oracle down → NULL, not peg.
  • H2. usd(UST1) comes from the largest USD-TVL factory cUSTC/UST1 pool (hub addresses), via humanized reserves. Not $1.
  • H3. usd(USTR) comes from the largest USD-TVL factory pair vs cUSTC or UST1. Not 2.5 × USTC.
  • H4. Charts/Trade Price (USD) and candle USD for those pairs match the hub formula (factory asset_0 USD in API; invert still UI-only).
  • H5. /protocol shows USTR (and cUSTC, UST1) DEX marks. CEX tabs remain exactly ustc | lunc | vfdusd.
  • H6. GET /api/v1/oracle/price/ustr (and ust1, custc) remain 400.
  • H7. volume_usd ingest uses hub USD for UST1/USTR quotes; still not vFDUSD.
  • H8. Dust / stale / unlisted / symbol-spoof pairs cannot become the source.
  • H9. Skills + docs/indexer-invariants.md P522-Q updated; make verify-issue-<iid> exists.
  • H10. CG/CMC last_price unchanged (human).

Test plan — functional paths

Indexer unit / integration (make setup-indexer-postgres)

  • cUSTC tracks USTC oracle 1:1; oracle missing → no hub USD.
  • Two cUSTC/UST1 pools: deeper USD TVL wins; raw 18-dec vs 6-dec cannot invert the rank.
  • UST1 USD = reserve ratio × cUSTC USD (orientation both ways: UST1 as asset_0 vs asset_1).
  • USTR from UST1/USTR vs from a thinner cUSTC/USTR — max TVL wins; USTR USD uses the winning hub leg.
  • Tie: identical TVL → lexicographic pair address, stable across refreshes.
  • Below dust floor → pair ignored; if all ignored → NULL.
  • Stale pair_reserves skipped.
  • Ingest: UST1/cUSTC price_usd uses hub UST1 (when UST1 is quote) / hub cUSTC (when cUSTC is quote) — not Peg1.
  • Ingest: UST1/USTR price_usd uses hub USTR when USTR is quote — not 2.5 × ustc.
  • volume_usd_for_swap on USTR offer uses hub USTR.
  • LUNC-quoted volume still uses LUNC oracle.
  • Unknown gem quote still NULL.
  • Overview additive fields present; 60s cache still O(1) (no 30d swap_events scan).
  • Hub-prices API: allowlisted tickers; unknown 400; metadata says DEX not CEX.
  • Existing api_oracle tests: catalog length 3; ustr 400; fdusd 400.

Frontend RTL

  • Protocol: DEX hub card renders cUSTC / UST1 / USTR; CEX card still three tabs; getOraclePrice('ustr') is not called.
  • Null hub prices → —, not $0 / $1 / 2.5×.
  • pairPriceUsd / chart headline: UST1/USTR inverted Last is hub USTR, not $1.
  • formatPairPrice not formatNum compact T on hub USD.
  • Outage banner still covers overview + CEX oracle; include hub-prices query in outage detection if it is on the page.

Charts / Trade (mocked indexer)

  • UST1/cUSTC factory USD ≈ pool × oracle; invert ≈ cUSTC USD ≈ oracle.
  • UST1/USTR invert ≈ USTR hub USD.
  • Candle invert still invertUsd, not 1/x of USD (#543).

Manual / LocalTerra

  • After make setup-cloud-localterra, Protocol shows hub marks; Charts UST1/cUSTC and UST1/USTR Last vs a hand-computed reserve ratio × oracle (tolerance documented, e.g. 1%).
  • Mainnet smoke: dex.cl8y.com/protocol USTR visible; /oracle/price/ustr still 400.

Test plan — attack, hack, and abuse

Vector Expected
Thin-pool takeover — attacker creates a factory-listed pair with 1 wei of USTR vs UST1 and hopes to become “largest” Dust floor + USD TVL (not raw) → ignored. If they actually provide the most USD, that is the market — document; do not add a secret whitelist of pair addresses beyond hub token identity.
Symbol spoof — native symbol=USTR or clone CW20 ticker A1 + hub contract allowlist → not a source; not priced as USTR.
Unlisted / foreign pair Factory provenance already skips insert; resolver must only read pairs rows (never LCD-discovered orphans).
Reserve spoof via LCD Resolver reads indexed pair_reserves only.
Stale snapshot / halted indexer Skip stale; NULL marks; UI —. Do not freeze last $1 peg.
Oracle manipulation (CEX USTC) Inherited; still advisory. Do not mix vFDUSD.
Circular graph / self-pair Reject same-asset legs; bootstrap order prevents USTR→UST1→USTR using unpriced nodes.
Division by zero / empty pool Unusable reserves (reserve_*=0) skipped (same as route-solver).
Numeric overflow fits_numeric_38_18 → NULL, no 500.
Ticker injection ?ticker=javascript:… still CEX-only allowlist → ustc. Hub API path traversal / extra underscores → 400. No SQL string paste.
DoS No per-request walk of all swaps or unbounded pair table without indexes; cached snapshot; GET caps unchanged.
Wash volume vs liquidity Ranking is reserves TVL, not 24h volume (volume is washable; still document residual: attacker can add real USD LP).
UI spoofing Protocol CEX card USTR must not appear as a CEX source row or KuCoin/MEXC line.
Integrator confusion Metadata + docs: CEX vs DEX endpoints; do not restore bare /oracle/price numeric body.
Settlement confusion Copy + skills: not used for swap/limit math.

Verification criteria

make verify-issue-<iid>
# expected contents:
# - indexer lib + integration tests for hub USD + ingest + API 400s
# - frontend RTL Protocol hub card + pairPriceUsd
# - docs/skills/invariants/AGENTS.md crosslinks
# - grep: USTR_PER_USTC not used in pair_price_usd.rs ingest
# - grep: OracleTicker::ALL still length 3
# - make verify-issue-515 && make verify-issue-550 && make verify-issue-522 && make verify-issue-543 && make verify-issue-524

Cloud Agent: make setup-indexer-postgres for indexer tests; full Charts/Protocol against chain needs make setup-cloud-localterra. Do not report skip without provisioning.

Out of scope

  • Tape Amount in / Amount out / Price raw formatNum (#557).
  • Charts leaderboard total_volume (#553).
  • Portfolio/Trader P&L raw units (#551).
  • Changing ops LP seed USTR_PER_USTC in rebalance scripts.
  • On-chain TWAP, UST1 mint/redeem window, CEX poll symbols.
## Summary Replace the hardcoded P522-Q **UST1 = $1** and **USTR = 2.5 × USTC** pegs with **DEX-derived hub USD**, ranked by **largest USD liquidity**. Anchor **1 cUSTC = 1 USTC** from the existing #515 CEX USTC oracle, then price **UST1** from the deepest **cUSTC/UST1** pool, then **USTR** (and other economic tokens) from their deepest pair against an already-priced hub. Show **USTR** (and the other hubs) on `/protocol` as a **DEX reference** card — not as a fourth CEX oracle ticker. Related: [#522](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/522) (pair `price_usd` + catalog), [#515](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/515) / [#550](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/550) (CEX `ustc` \| `lunc` \| `vfdusd` only), [#524](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/524) / [#543](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/543) (UI invert of factory USD), [#548](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/548) (`volume_usd` ingest), [#508](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/508) (UST1 secondary AMM seed peg). Tape **Amount in / Amount out / Price** humanization is **[#557](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/557)** (raw `formatNum`). ## Current codebase ### Quote USD catalog (P522-Q) Indexer [`pair_price_usd.rs`](indexer/src/indexer/pair_price_usd.rs) and dApp [`pairPriceUsd.ts`](frontend-dapp/src/utils/pairPriceUsd.ts) convert human quote-per-base to **USD of 1 human `asset_0`** using a **static catalog**: | Quote | USD handle today | |-------|------------------| | UST1 | **hardcoded `$1`** (`QuoteUsdKind::Peg1`) | | USTC / cUSTC / `uusd` | #515 USTC CEX oracle | | LUNC / cLUNC / `uluna` | #515 LUNC CEX oracle | | USTR | **`2.5 ×` USTC** (`USTR_PER_USTC`) | | anything else | `NULL` (do not invent) | That catalog is also **`volume_usd` ingest** ([#548](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/548) **C8** / **X4**). Tests in `pair_price_usd.rs` assert UST1/cUSTC last print ≈ $1 **because** quote is cUSTC × oracle, and UST1/USTR ≈ $1 **because** 79.72 × 2.5 × USTC — not because the pool was marked to a DEX USTR price. ### Charts / pair APIs - `GET /api/v1/pairs/{addr}/trades` — `price` is human quote-per-base; `price_usd` is USD of factory `asset_0` via P522-Q. - `GET /api/v1/pairs/{addr}/candles` — OHLC is factory USD from `price_usd` only ([#543](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/543)); additive `*_human` for invert. - `/charts` and `/trade` headline **Price (USD)** uses `resolveTapeLastPriceUsd` / invert ([#524](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/524)). Default invert on UST1-as-base pairs shows the **other** token’s USD, which today falls back to the **same peg catalog** when `invertUsd` cannot run. - CG/CMC `last_price` stays **human** quote-per-base (**P522-5**) — do not silently switch aggregators to USD. ### Protocol page [`ProtocolPage.tsx`](frontend-dapp/src/pages/ProtocolPage.tsx) after [#550](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/550): Global stats → **one CEX oracle card** with tabs **USTC / LUNC / vFDUSD** only (`PROTOCOL_ORACLE_TICKERS`). Copy: “CEX reference prices. Not settlement.” There is **no USTR**, **no UST1**, **no cUSTC** DEX mark. `GET /api/v1/oracle/price/{ticker}` is **CEX-only** (`ustc` \| `lunc` \| `vfdusd`). Unknown ticker (including `ustr`) is **400**. Overview exposes `ustc_price_usd` only. ### Liquidity already mirrored [`pair_reserves`](indexer/src/db/queries/pair_reserves.rs) (book-snapshot loop, [#322](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/322)) has raw `reserve_0` / `reserve_1` per factory pair. Nothing ranks “largest USD pool for token T” or publishes a hub USD table. `GET /api/v1/tokens` has **no** `price_usd`. ### Why this is wrong in production - UST1 is **not** a dollar stable. It is a window-minted asset ([#506](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/506)) that trades vs cUSTC. Charting UST1/USTR as `$1` of UST1 (or USTR as `2.5 × USTC`) hides the **actual pool mark**. - cUSTC **should** track USTC 1:1 (wrap). That part of the catalog is the correct **anchor**. - Most economic pairs are vs **cUSTC or UST1**. Pricing those quotes as `$1` (UST1) or `2.5× USTC` (USTR) makes Charts **Price (USD)**, candles, pair Open/Close, and any downstream USD notional **disagree with the book**. - Protocol is the census/reference page. Users looking for **USTR/USD** have nowhere to read a DEX mark; stuffing USTR into the CEX ticker allowlist would also be a lie (there is no CEX USTR feed). ## Why the new implementation is needed 1. **Honest marks.** Charts and pair APIs must show USD of USTR / cUSTC / UST1 from **the deepest real pool**, not launch-seed pegs. 2. **Composable hub graph.** Once cUSTC is anchored to the USTC oracle and UST1 is marked from cUSTC/UST1, every other economic token (USTR, CL8Y, cLUNC, gems that actually pair vs a hub) can inherit USD from **its largest-liquidity hub pair**. 3. **Protocol completeness.** Show USTR (and the hub set) next to CEX USTC/LUNC/vFDUSD, clearly labeled as **DEX reference**. ## Constraints / guardrails 1. **Advisory only (X5 / P550-11).** DEX hub USD is **not** settlement, TWAP, limit crossing, or the `/ust1` window rate. Short “DEX reference” label ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489)) — no essays. 2. **Do not add `ustr` (or `ust1` / `custc`) to CEX `OracleTicker`.** Keep **X1–X2**: `/oracle/price` catalog stays `ustc` \| `lunc` \| `vfdusd`. `ustr` on that path stays **400**. New DEX prices use a **different** route (and/or additive overview fields). 3. **Do not use vFDUSD/FDUSD to convert DEX marks or `volume_usd` (P550-10).** UST1 window ≠ CEX FDUSD ≠ pool UST1. 4. **1 cUSTC = 1 USTC** from #515 `ustc` oracle (wrap assumption). Native `uusd` same. If the USTC oracle is down, cUSTC/UST1/USTR USD is **NULL** (`—`), not `$0` and not a leftover `$1` peg. 5. **Largest liquidity = USD TVL of a factory pair**, not raw reserve integers (18-dec USTR vs 6-dec cUSTC would rank wrong). Humanize each reserve with **that asset’s decimals**, convert both legs to USD, rank by **sum** (or documented equivalent). Ignore dust below a documented floor (recommend **≥ $100** USD TVL, configurable) so a 1-unit spoof pool cannot win. 6. **Identity is contract/denom, not symbol (A1).** Native gem with `symbol=USTR` must not price. Hub CW20s must match **tokenlist / known hub addresses** (cUSTC, UST1, USTR), not ticker string. Factory provenance ([#311](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/311)) already gates pair insert — do not price unlisted clones. 7. **Stale / missing `pair_reserves`:** degrade — skip that pair in ranking (same spirit as book-snapshot degrade-not-error). Do not treat zero reserves as infinite price. 8. **Deterministic ties:** highest USD TVL wins; exact tie → stable key (pair contract address lexicographic). Document it. 9. **No circular quotes.** Bootstrap order is fixed: USTC oracle → cUSTC → UST1 from cUSTC/UST1 → USTR from deepest pair vs **already priced** cUSTC or UST1. Do not price UST1 from UST1/USTR using the old 2.5× peg. 10. **Do not scan `swap_events` on every GET.** Materialize hub USD on a cadence (book-snapshot and/or volume aggregator, ≤ same order as snapshot interval). Cache reads O(1). Cache-miss must not walk all trades. 11. **CG/CMC `last_price` stays human quote-per-base (P522-5).** Pair `price` field stays human. Only `price_usd` / candles USD / Protocol DEX card / overview hub fields change. 12. **Do not change** `/ust1` window math, wrap-mapper fees, limit `validate_limit_order_price`, or ops script `USTR_PER_USTC` used only to **size** UST1/USTR LP ([`rebalance-mint-ust1-lp.sh`](scripts/rebalance-mint-ust1-lp.sh)) — that seed is not a display oracle. 13. **UI invert (#524 / #543) stays frontend.** Indexer still stores USD of factory `asset_0`. After hub USD is correct, invert continues to use `invertUsd(price_usd, human)` — do not `1/x` USD candles. 14. **Allowlist any new ticker path.** `javascript:`, `../`, HTML, unknown → 400 (API) or default/omit (UI). Never interpolate raw query input into SQL or fetch URLs. 15. **NUMERIC(38,18) / non-finite:** reuse `fits_numeric_38_18` and X6. No NaN/Inf in DB or JSON. ## Relevant files | File | Role | |------|------| | [`indexer/src/indexer/pair_price_usd.rs`](indexer/src/indexer/pair_price_usd.rs) | P522-Q `Peg1` / `Ustr` / `usd_per_human_quote` / `volume_usd_for_swap` | | [`indexer/src/indexer/swap_orientation.rs`](indexer/src/indexer/swap_orientation.rs) | Human quote-per-base + `price_usd` at ingest | | [`indexer/src/db/queries/candles.rs`](indexer/src/db/queries/candles.rs) | Factory USD candles | | [`indexer/src/db/queries/pair_reserves.rs`](indexer/src/db/queries/pair_reserves.rs) | Raw reserves for TVL ranking | | [`indexer/src/indexer/book_snapshot.rs`](indexer/src/indexer/book_snapshot.rs) | Reserve refresh cadence | | [`indexer/src/api/oracle.rs`](indexer/src/api/oracle.rs) | CEX catalog — **do not** add `ustr` here | | [`indexer/src/indexer/oracle.rs`](indexer/src/indexer/oracle.rs) | CEX poll loop | | [`indexer/src/api/overview.rs`](indexer/src/api/overview.rs) | Additive hub USD fields (optional but useful) | | [`indexer/src/api/tokens.rs`](indexer/src/api/tokens.rs) | Token list currently has no price | | [`indexer/src/api/pairs.rs`](indexer/src/api/pairs.rs) | Trades / candles / pair stats `*_usd` | | [`indexer/tests/swap_price_human_usd.rs`](indexer/tests/swap_price_human_usd.rs) | Ingest USD tests | | [`indexer/tests/volume_usd_catalog.rs`](indexer/tests/volume_usd_catalog.rs) | Volume catalog | | [`indexer/tests/api_oracle.rs`](indexer/tests/api_oracle.rs) | CEX ticker 400 regressions | | [`frontend-dapp/src/utils/pairPriceUsd.ts`](frontend-dapp/src/utils/pairPriceUsd.ts) | Client catalog + tape USD | | [`frontend-dapp/src/utils/protocolOracleTicker.ts`](frontend-dapp/src/utils/protocolOracleTicker.ts) | CEX allowlist | | [`frontend-dapp/src/pages/ProtocolPage.tsx`](frontend-dapp/src/pages/ProtocolPage.tsx) | Layout | | [`frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx`](frontend-dapp/src/components/protocol/ProtocolOracleCard.tsx) | CEX card — keep separate | | [`frontend-dapp/src/pages/ChartsPage.tsx`](frontend-dapp/src/pages/ChartsPage.tsx) | Price (USD) + candles | | [`frontend-dapp/src/pages/TradePage.tsx`](frontend-dapp/src/pages/TradePage.tsx) | Trade headline USD | | [`docs/indexer-invariants.md`](docs/indexer-invariants.md) | P522-Q / X4 rows | | [`skills/AGENTS_INDEXER_PAIR_PRICE_USD.md`](skills/AGENTS_INDEXER_PAIR_PRICE_USD.md) | Playbook | | [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](skills/AGENTS_FRONTEND_PROTOCOL_STATS.md) | Protocol layout | | [`docs/runbooks/indexer-external-oracle.md`](docs/runbooks/indexer-external-oracle.md) | CEX vs DEX split | | [`tokenlist/`](tokenlist/) | Hub CW20 addresses | ## Recommended direction ### 1. Hub USD resolver (indexer) New module (name bikeshed-ok: `hub_usd.rs` / `dex_token_usd.rs`): 1. **Anchor:** `usd(cUSTC) = usd(uusd) = ustc_oracle`. 2. **UST1:** among factory pairs whose legs are **exactly** hub cUSTC + hub UST1 (address/denom match), with fresh reserves and TVL ≥ floor, pick **max USD TVL**. `usd(UST1) = human_quote_per_base × usd(other_leg)` using the **spot from reserves** (constant-product `reserve_quote_human / reserve_base_human` after decimal scale) — **not** the last swap print alone (prints can be dust). Last print may be a sanity check / fallback if reserves are missing **and** the pair still has a recent `price_usd` — document which; prefer reserves. 3. **USTR:** same, among pairs with hub USTR + (cUSTC **or** UST1). Prefer the max-TVL pair. Convert via the already-resolved hub leg. 4. **General economic token (phase 1b, same issue):** for a token T, consider factory pairs where the other leg is cUSTC or UST1 (and optionally cLUNC/LUNC using LUNC oracle). Rank by USD TVL; skip unknown other-leg. Phase 1 **must** ship cUSTC, UST1, USTR correctly; other tokens may reuse the same helper. Persist a small `token_usd` (or `hub_prices`) snapshot: `asset_id`, `price_usd`, `source_pair_id`, `tvl_usd`, `updated_at`. Refresh on book-snapshot success (and on oracle tick so cUSTC moves with USTC without waiting for a reserve change). ### 2. Wire into P522 ingest Replace `QuoteUsdKind::Peg1` and `::Ustr` **lookups** with `token_usd` for that quote asset (still classify so unknown gems stay NULL). `volume_usd_for_swap` uses the same table. Backfill recent `swap_events.price_usd` + candles (idempotent migration / job) from stored human `price` × new quote USD **as-of backfill time** (advisory; document). Keep `USTR_PER_USTC` **out** of ingest. Client `pairPriceUsd.ts` fallback for pre-upgrade indexers may keep a documented legacy path; new indexer must send `price_usd` from hub table. ### 3. APIs Pick **one** primary read (additive JSON): - `GET /api/v1/hub-prices` → `{ metadata, prices: [{ ticker, asset, price_usd, source_pair, tvl_usd, updated_at }] }` with allowlisted tickers `custc` \| `ust1` \| `ustr` (and maybe `clunc`). - Additive on `GET /api/v1/overview`: `custc_price_usd`, `ust1_price_usd`, `ustr_price_usd` (null when unresolved). Keep `ustc_price_usd` as the **CEX** USTC ticker. Do **not** break existing overview field names. ### 4. Protocol UI Add a **DEX hub prices** card (`data-testid="protocol-dex-hub-prices"`) **between** Global stats and the CEX oracle card **or** immediately after the CEX card — pick one, document in `docs/frontend.md`. Show **cUSTC, UST1, USTR** (compact `$` + `formatPairPrice`, never `formatNum` `T`). Optional: source pair shortened + explorer (token-identity style, [#541](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/541)). Label **DEX reference — not CEX, not settlement**. Do **not** add USTR as a fourth tab on `ProtocolOracleCard`. Do **not** change `?ticker=` CEX allowlist. ### 5. Charts / Trade audit After ingest uses hub USD: - UST1/cUSTC **Price (USD)** of 1 UST1 ≈ human(cUSTC per UST1) × USTC oracle (pool mark), **not** `$1` unless the pool is actually there. - Default invert ([#524](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/524)): USD of 1 cUSTC ≈ USTC oracle (~`$0.00x`), USD of 1 USTR ≈ pool USTR mark (today’s order of **`$0.01` class** if 2.5× still happens to be near the pool — **must track the pool** if it diverges). - Regression: if USTR/UST1 pool trades at 100 USTR per UST1 while UST1 is 200 cUSTC, USTR USD must be `usd(UST1)/100`, **not** `2.5 × ustc`. ## Acceptance criteria - [ ] **H1.** `usd(cUSTC) = ustc_oracle` (and native USTC). Oracle down → NULL, not peg. - [ ] **H2.** `usd(UST1)` comes from the **largest USD-TVL** factory cUSTC/UST1 pool (hub addresses), via humanized reserves. Not `$1`. - [ ] **H3.** `usd(USTR)` comes from the **largest USD-TVL** factory pair vs cUSTC or UST1. Not `2.5 × USTC`. - [ ] **H4.** Charts/Trade **Price (USD)** and candle USD for those pairs match the hub formula (factory `asset_0` USD in API; invert still UI-only). - [ ] **H5.** `/protocol` shows USTR (and cUSTC, UST1) DEX marks. CEX tabs remain exactly `ustc` \| `lunc` \| `vfdusd`. - [ ] **H6.** `GET /api/v1/oracle/price/ustr` (and `ust1`, `custc`) remain **400**. - [ ] **H7.** `volume_usd` ingest uses hub USD for UST1/USTR quotes; still **not** vFDUSD. - [ ] **H8.** Dust / stale / unlisted / symbol-spoof pairs cannot become the source. - [ ] **H9.** Skills + `docs/indexer-invariants.md` P522-Q updated; `make verify-issue-<iid>` exists. - [ ] **H10.** CG/CMC `last_price` unchanged (human). ## Test plan — functional paths ### Indexer unit / integration (`make setup-indexer-postgres`) - [ ] cUSTC tracks USTC oracle 1:1; oracle missing → no hub USD. - [ ] Two cUSTC/UST1 pools: deeper USD TVL wins; raw 18-dec vs 6-dec cannot invert the rank. - [ ] UST1 USD = reserve ratio × cUSTC USD (orientation both ways: UST1 as `asset_0` vs `asset_1`). - [ ] USTR from UST1/USTR vs from a thinner cUSTC/USTR — max TVL wins; USTR USD uses the winning hub leg. - [ ] Tie: identical TVL → lexicographic pair address, stable across refreshes. - [ ] Below dust floor → pair ignored; if all ignored → NULL. - [ ] Stale `pair_reserves` skipped. - [ ] Ingest: UST1/cUSTC `price_usd` uses hub UST1 (when UST1 is quote) / hub cUSTC (when cUSTC is quote) — not Peg1. - [ ] Ingest: UST1/USTR `price_usd` uses hub USTR when USTR is quote — **not** `2.5 × ustc`. - [ ] `volume_usd_for_swap` on USTR offer uses hub USTR. - [ ] LUNC-quoted volume still uses LUNC oracle. - [ ] Unknown gem quote still NULL. - [ ] Overview additive fields present; 60s cache still O(1) (no 30d `swap_events` scan). - [ ] Hub-prices API: allowlisted tickers; unknown 400; metadata says DEX not CEX. - [ ] Existing `api_oracle` tests: catalog length 3; `ustr` 400; `fdusd` 400. ### Frontend RTL - [ ] Protocol: DEX hub card renders cUSTC / UST1 / USTR; CEX card still three tabs; `getOraclePrice('ustr')` is **not** called. - [ ] Null hub prices → `—`, not `$0` / `$1` / `2.5×`. - [ ] `pairPriceUsd` / chart headline: UST1/USTR inverted Last is hub USTR, not `$1`. - [ ] `formatPairPrice` not `formatNum` compact `T` on hub USD. - [ ] Outage banner still covers overview + CEX oracle; include hub-prices query in outage detection if it is on the page. ### Charts / Trade (mocked indexer) - [ ] UST1/cUSTC factory USD ≈ pool × oracle; invert ≈ cUSTC USD ≈ oracle. - [ ] UST1/USTR invert ≈ USTR hub USD. - [ ] Candle invert still `invertUsd`, not `1/x` of USD ([#543](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/543)). ### Manual / LocalTerra - [ ] After `make setup-cloud-localterra`, Protocol shows hub marks; Charts UST1/cUSTC and UST1/USTR Last vs a hand-computed reserve ratio × oracle (tolerance documented, e.g. 1%). - [ ] Mainnet smoke: `dex.cl8y.com/protocol` USTR visible; `/oracle/price/ustr` still 400. ## Test plan — attack, hack, and abuse | Vector | Expected | |--------|----------| | **Thin-pool takeover** — attacker creates a factory-listed pair with 1 wei of USTR vs UST1 and hopes to become “largest” | Dust floor + USD TVL (not raw) → ignored. If they actually provide the most USD, that **is** the market — document; do not add a secret whitelist of pair addresses beyond hub token identity. | | **Symbol spoof** — native `symbol=USTR` or clone CW20 ticker | A1 + hub **contract** allowlist → not a source; not priced as USTR. | | **Unlisted / foreign pair** | Factory provenance already skips insert; resolver must only read `pairs` rows (never LCD-discovered orphans). | | **Reserve spoof via LCD** | Resolver reads **indexed** `pair_reserves` only. | | **Stale snapshot / halted indexer** | Skip stale; NULL marks; UI `—`. Do not freeze last `$1` peg. | | **Oracle manipulation (CEX USTC)** | Inherited; still advisory. Do not mix vFDUSD. | | **Circular graph / self-pair** | Reject same-asset legs; bootstrap order prevents USTR→UST1→USTR using unpriced nodes. | | **Division by zero / empty pool** | Unusable reserves (`reserve_*=0`) skipped (same as route-solver). | | **Numeric overflow** | `fits_numeric_38_18` → NULL, no 500. | | **Ticker injection** | `?ticker=javascript:…` still CEX-only allowlist → `ustc`. Hub API path traversal / extra underscores → 400. No SQL string paste. | | **DoS** | No per-request walk of all swaps or unbounded pair table without indexes; cached snapshot; GET caps unchanged. | | **Wash volume vs liquidity** | Ranking is **reserves TVL**, not 24h volume (volume is washable; still document residual: attacker can add real USD LP). | | **UI spoofing Protocol CEX card** | USTR must not appear as a CEX source row or KuCoin/MEXC line. | | **Integrator confusion** | Metadata + docs: CEX vs DEX endpoints; do not restore bare `/oracle/price` numeric body. | | **Settlement confusion** | Copy + skills: not used for swap/limit math. | ## Verification criteria ```bash make verify-issue-<iid> # expected contents: # - indexer lib + integration tests for hub USD + ingest + API 400s # - frontend RTL Protocol hub card + pairPriceUsd # - docs/skills/invariants/AGENTS.md crosslinks # - grep: USTR_PER_USTC not used in pair_price_usd.rs ingest # - grep: OracleTicker::ALL still length 3 # - make verify-issue-515 && make verify-issue-550 && make verify-issue-522 && make verify-issue-543 && make verify-issue-524 ``` Cloud Agent: `make setup-indexer-postgres` for indexer tests; full Charts/Protocol against chain needs `make setup-cloud-localterra`. Do not report skip without provisioning. ## Out of scope - Tape **Amount in / Amount out / Price** raw `formatNum` ([#557](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/557)). - Charts leaderboard `total_volume` ([#553](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/553)). - Portfolio/Trader P&amp;L raw units ([#551](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/551)). - Changing ops LP seed `USTR_PER_USTC` in rebalance scripts. - On-chain TWAP, UST1 mint/redeem window, CEX poll symbols.
PlasticDigits commented 2026-08-18 00:28:52 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-08-18 00:29:12 +00:00 (Migrated from gitlab.com)

marked as related to #557

marked as related to #557
PlasticDigits commented 2026-08-18 00:29:14 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-08-18 00:29:15 +00:00 (Migrated from gitlab.com)

mentioned in issue #557

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

mentioned in merge request !1082

mentioned in merge request !1082
PlasticDigits commented 2026-08-18 02:30:14 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1083

mentioned in merge request !1083
PlasticDigits commented 2026-08-18 03:08:54 +00:00 (Migrated from gitlab.com)

mentioned in commit f6a06fcd1e

mentioned in commit f6a06fcd1e5ffe35a6c4685c5e96e5267e43ab9a
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-18 03:08:55 +00:00
PlasticDigits commented 2026-08-18 03:11:25 +00:00 (Migrated from gitlab.com)

Merged in !1083. H3 documented as requested from the !1082 note: USTR is set by the market (largest USD-TVL vs priced cUSTC/UST1), not a fixed 2.5 × USTC peg. Launch 2.5× remains ops LP seed only (USTR_PER_USTC).

Shipped: hub_prices ingest + GET /api/v1/hub-prices; Protocol DEX card; CEX /oracle/price/ustr|ust1|custc stay 400; volume_usd uses hub for UST1/USTR; refresh does not freeze last $1.

Problem remaining: portfolio/trader Total realized P&L USD (#551) still uses the pre-#556 client catalog (quoteTokenUsd pegs) instead of hub prices. Indexer Charts/Trade price_usd is hub; the P&L header is not. Follow-up after !1080–!1084.

Still open ops/manual: indexer migrate hub_prices; Coolify; dex.cl8y.com/protocol USTR visible; LocalTerra reserve-ratio hand check. CI was quota-exceeded.

Merged in !1083. **H3 documented as requested from the !1082 note:** USTR is set by the market (largest USD-TVL vs priced cUSTC/UST1), not a fixed `2.5 ×` USTC peg. Launch 2.5× remains ops LP seed only (`USTR_PER_USTC`). **Shipped:** hub_prices ingest + `GET /api/v1/hub-prices`; Protocol DEX card; CEX `/oracle/price/ustr|ust1|custc` stay 400; `volume_usd` uses hub for UST1/USTR; refresh does not freeze last `$1`. **Problem remaining:** portfolio/trader **Total realized P&L** USD (#551) still uses the pre-#556 client catalog (`quoteTokenUsd` pegs) instead of hub prices. Indexer Charts/Trade `price_usd` is hub; the P&L header is not. Follow-up after !1080–!1084. **Still open ops/manual:** indexer migrate `hub_prices`; Coolify; `dex.cl8y.com/protocol` USTR visible; LocalTerra reserve-ratio hand check. CI was quota-exceeded.
PlasticDigits commented 2026-08-18 03:11:30 +00:00 (Migrated from gitlab.com)

mentioned in issue #551

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

mentioned in issue #560

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

marked as related to #560

marked as related to #560
PlasticDigits commented 2026-08-18 03:12:54 +00:00 (Migrated from gitlab.com)

Post-merge follow-up: #560

Post-merge follow-up: #560
PlasticDigits commented 2026-08-18 03:14:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #553

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

Post-merge review extras:

  1. H3 wording: already tightened on merge — skill H3 now says USTR is set by the market, not a fixed 2.5× peg.
  2. Stale route table: docs/frontend.md /protocol one-liner still says “one CEX oracle card” and omits the DEX hub card. The Protocol section below it is correct.
  3. Ops: set HUB_CUSTC_ADDRESS / HUB_UST1_ADDRESS / HUB_USTR_ADDRESS on production + LocalTerra indexer env, then migrate hub_prices.
  4. Legacy quoteTokenUsd $1 / 2.5× fallback when price_usd is absent remains the mixed-version path; portfolio P&L still uses that helper (#560).
Post-merge review extras: 1. **H3 wording:** already tightened on merge — skill H3 now says USTR is set by the market, not a fixed 2.5× peg. 2. **Stale route table:** `docs/frontend.md` `/protocol` one-liner still says “one CEX oracle card” and omits the DEX hub card. The Protocol section below it is correct. 3. **Ops:** set `HUB_CUSTC_ADDRESS` / `HUB_UST1_ADDRESS` / `HUB_USTR_ADDRESS` on production + LocalTerra indexer env, then migrate `hub_prices`. 4. Legacy `quoteTokenUsd` `$1` / `2.5×` fallback when `price_usd` is absent remains the mixed-version path; portfolio P&L still uses that helper (#560).
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:40 +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 01:03:02 +00:00 (Migrated from gitlab.com)

marked as related to #570

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

mentioned in issue #570

mentioned in issue #570
PlasticDigits commented 2026-08-19 01:04:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #571

mentioned in issue #571
PlasticDigits commented 2026-08-19 01:05:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #572

mentioned in issue #572
PlasticDigits commented 2026-08-19 01:12:33 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1093

mentioned in merge request !1093
PlasticDigits commented 2026-08-19 02:28:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #573

mentioned in issue #573
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:33 +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 03:10:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #589

mentioned in issue #589
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-24 00:30:13 +00:00 (Migrated from gitlab.com)

mentioned in issue #613

mentioned in issue #613
PlasticDigits commented 2026-08-24 03:15:38 +00:00 (Migrated from gitlab.com)

mentioned in issue #618

mentioned in issue #618
PlasticDigits commented 2026-08-26 03:06:33 +00:00 (Migrated from gitlab.com)

mentioned in issue #655

mentioned in issue #655
PlasticDigits commented 2026-08-26 03:06:35 +00:00 (Migrated from gitlab.com)

marked as related to #655

marked as related to #655
PlasticDigits commented 2026-08-26 04:15:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #664

mentioned in issue #664
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-27 11:52:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #690

mentioned in issue #690
PlasticDigits commented 2026-08-27 11:52:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #691

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