fix: show LUNC/USTC (not uluna/uusd) in token pickers #630

Closed
opened 2026-08-25 01:55:12 +00:00 by PlasticDigits · 16 comments
PlasticDigits commented 2026-08-25 01:55:12 +00:00 (Migrated from gitlab.com)

Summary

Retail token pickers (Swap You Pay / You Receive, and every other TokenSearchSelect / TokenSelect / TokenDisplay surface) show bank denoms uluna and uusd instead of product tickers LUNC and USTC. Wrapped rows already show cLUNC / cUSTC. Users cannot tell native bank LUNC/USTC from CosmWasm wrap CW20s by ticker, and the denom strings look like internal ids.

Reported from production Swap token dropdown (native rows sit next to cLUNC / cUSTC / USTR). LCD can be down (Could not connect to the network) while the indexer catalog is still up — that is enough to leak the wrong labels.

Related: #507 (cLUNC / cUSTC product symbols), #481 (Swap combobox), #541 (copy payload stays denom), #489 (retail copy).


Current codebase

What already maps correctly

Layer Behavior
frontend-dapp/src/utils/tokenRegistry.ts DENOM_MAP: uluna → LUNC, uusd → USTC. Wrap CW20s → cLUNC / cUSTC.
getTokenDisplaySymbol Uses lookupByTokenId; unit tests already expect uluna → LUNC.
tokenSearchQuery.ts Search haystack includes denom and registry symbol, so typing LUNC / uluna both match. Filter ids stay uluna / uusd.
getAllTokens When wrap env is set, appends native uluna / uusd plus wrap CW20s so Swap can wrap without a factory pair (#507).
Wallet chip (#140) Bank uluna already labeled LUNC.

Token ids used for balances, quotes, and execute must stay uluna / uusd. Only the visible label is wrong.

Why the dropdown still shows uluna / uusd

useTokenDisplayInfo is the label source for TokenSearchSelect (Swap, Pool one-sided, Pay invoice) and TokenSelect (Mint):

const wrapProductSymbol =
  registryCw20?.symbol === 'cLUNC' || registryCw20?.symbol === 'cUSTC' ? registryCw20.symbol : undefined
const symbol = wrapProductSymbol || indexerMeta?.symbol?.trim() || chainSymbol
  • Wrap CW20s have a #507 override so indexer/on-chain LUNC-C cannot win.
  • Natives have no equivalent override. If GET /api/v1/tokens returns symbol: "uluna" / "uusd", that beats the registry (LUNC / USTC).
  • chainSymbol (registry via getCachedTokenSymbol) is only the last fallback.

Why the indexer catalog stores denom-as-symbol

indexer/src/indexer/asset_resolver.rs on first native sight:

assets::upsert_asset(pool, None, Some(denom), false, denom, denom, 6, None)

name and symbol are both the raw denom. Integration seeds use the retail pair (uluna / Luna Classic / LUNC), but live ingest does not. GET /api/v1/tokens then serves symbol=uluna / uusd.

Surfaces that inherit the leak

Same hook or raw indexer asset_*.symbol:

Surface File
Swap You Pay / You Receive TokenSearchSelect ← useTokenDisplayInfo
Pool one-sided add/withdraw same combobox
Pay-with-any-token PayWithAnyToken.tsx
Mint faucet list TokenSelect.tsx
Compact logo+symbol TokenDisplay.tsx, TokenIdentity.tsx
Pool manage labels PoolAdvancedManage.tsx
Trade / Charts / Pool pair chrome activePair.asset_0.symbol / asset_1.symbol (factory pairs are usually CW20-only; still fix catalog so native legs cannot leak)

data-testid stays token-option-uluna / token-option-uusd (id, not label). E2E helpers must keep selecting by id.


Why this is needed

Retail users know LUNC and USTC. Showing uluna / uusd next to cLUNC / cUSTC looks like four different assets (or broken metadata). That is a wrap/unwrap selection hazard: picking the wrong row wraps when the user wanted wrapped CW20, or the reverse.

getTokenDisplaySymbol and the registry already encode the product names. The shared display hook throws that away whenever the indexer is reachable. QA wrap checklist already requires the selector to list LUNC, cLUNC, USTC, cUSTC (docs/qa-templates/wrap-unwrap-test-pass.md).


Constraints / guardrails

  1. Ids unchanged. Execute, balances, route/solve, wrap-mapper, and React Query keys stay uluna / uusd (or wrap CW20 addresses). Do not rename on-chain denoms.
  2. Do not merge native and wrap. LUNC ≠ cLUNC; USTC ≠ cUSTC. Four rows stay four rows when wrap env is set. Logos stay distinct (LUNC.png / CLUNC.png, USTC.png / CUSTC.png).
  3. Registry wins for known natives, same idea as #507 wrapProductSymbol. A compromised or stale indexer must not relabel uluna as UST1 / USTR / a gem ticker.
  4. Unknown natives stay raw. Only allowlist uluna → LUNC and uusd → USTC (casefold). Do not invent tickers for IBC or other bank denoms.
  5. Copy / explorer (#541). Copy payload is the denom, never the display symbol. Natives stay copy-only (no Finder URL).
  6. Search still matches both. Haystack keeps uluna / uusd and LUNC / USTC so power users and retail queries work. Do not drop denom from search.
  7. Factory gate (#481). Picker options stay getAllTokens(pairs) + wrap enrichment. Do not add an external token list to “fix” labels.
  8. Create Pair (#542). Natives must not become selectable create-pair tokens. This issue is labels, not catalog membership.
  9. Gems / economic rank (#534 / #562). uluna / uusd stay economic hubs. Do not hide them in production. Do not treat a spoofed indexer symbol as gem/hub identity.
  10. No HTML. Labels render as text only. Logos still go through resolveTrustedTokenLogoUrl.
  11. Indexer repair is optional-follow. Frontend must be correct even if production assets.symbol stays uluna until a repair migration. If indexer upsert is fixed, unknown denoms still use denom-as-symbol (fail closed).
  12. #489. Do not add always-on “uluna means LUNC” essays on the Swap card.

Relevant files

Frontend (primary)

  • frontend-dapp/src/hooks/useTokenDisplayInfo.ts — label precedence
  • frontend-dapp/src/utils/tokenRegistry.ts — lookupByDenom / TOKENS
  • frontend-dapp/src/utils/tokenDisplay.ts — getTokenDisplaySymbol (already correct)
  • frontend-dapp/src/components/trade/TokenSearchSelect.tsx
  • frontend-dapp/src/components/ui/TokenSelect.tsx
  • frontend-dapp/src/components/ui/TokenDisplay.tsx
  • frontend-dapp/src/components/ui/TokenIdentity.tsx
  • frontend-dapp/src/pages/SwapPage.tsx
  • frontend-dapp/src/components/pool/OneSidedAddCard.tsx, OneSidedWithdrawCard.tsx
  • frontend-dapp/src/components/payments/PayWithAnyToken.tsx
  • frontend-dapp/src/utils/tokenSearchQuery.ts
  • docs/frontend.md § Token search combobox
  • docs/qa-templates/wrap-unwrap-test-pass.md
  • NATIVE_TOKEN_WRAPPING.md (E11: selector shows LUNC and cLUNC, USTC and cUSTC)

Indexer (catalog consistency)

  • indexer/src/indexer/asset_resolver.rs — native upsert name=denom, symbol=denom
  • indexer/src/db/queries/assets.rs — upsert_asset
  • indexer/src/api/tokens.rs — GET /api/v1/tokens
  • indexer/tests/common/mod.rs / indexer/scripts/seed-charts-integration.sql — already seed LUNC

Tests to extend

  • frontend-dapp/src/utils/__tests__/tokenDisplay.test.ts
  • frontend-dapp/src/utils/__tests__/tokenRegistry.test.ts
  • frontend-dapp/src/components/trade/__tests__/TokenSearchSelect.test.tsx
  • New: frontend-dapp/src/hooks/__tests__/useTokenDisplayInfo.test.ts (or equivalent)

1. dApp (must ship; unblocks the screenshot)

In useTokenDisplayInfo, resolve a product symbol from the static registry for known natives and wrap CW20s before indexerMeta.symbol:

  • lookupByDenom(uluna|uusd) → LUNC / USTC
  • existing wrapProductSymbol → cLUNC / cUSTC
  • then indexer symbol (unknown CW20s / unknown natives)
  • then getCachedTokenSymbol / shortened address / raw id

Prefer a small helper (e.g. registryProductSymbol(tokenId)) used by both the hook and getTokenDisplaySymbol so precedence cannot drift.

Do not special-case only TokenSearchSelect. Fixing the hook covers Mint, Pool, Pay invoice, TokenDisplay, TokenIdentity.

2. Indexer (same issue; keeps API / pair chrome honest)

When inserting uluna / uusd the first time, write retail name / symbol (Luna Classic / LUNC, TerraClassicUSD / USTC), matching tokenRegistry and existing test seeds. Leave other denoms as denom/denom.

Optional: one-time SQL repair for existing assets rows where denom in ('uluna','uusd') and symbol is the denom. Do not rewrite CW20 wrap rows (those stay cLUNC / cUSTC via #507).

3. Docs / verify target

Add a short invariant to docs/frontend.md § Token search (and wrap QA template if not already explicit): visible option text is LUNC / USTC, never uluna / uusd. make verify-issue-<this> runs the new Vitest files.


Acceptance criteria

  • Swap You Pay / You Receive listboxes show LUNC and USTC for native rows; cLUNC and cUSTC stay distinct.
  • Combobox trigger (closed and open-before-edit, #498) shows LUNC / USTC, not the denom.
  • Same labels on Pool one-sided pickers, Pay-with-any-token, Mint TokenSelect, TokenDisplay, and TokenIdentity.
  • Indexer GET /api/v1/tokens (after ingest or repair) reports symbol=LUNC / USTC for denom=uluna / uusd.
  • Token id in onChange, data-testid, balances, and execute remains uluna / uusd.
  • Search for LUNC, lunc, uluna, USTC, uusd still finds the correct row.
  • A mocked indexer payload with symbol: "uluna" / "UST1" / HTML / empty string cannot override registry LUNC/USTC.
  • Unknown native denom (e.g. ufoo) still displays as ufoo.
  • #541: copy still copies uluna / uusd; no Finder link for natives.
  • #542: Create Pair catalog still excludes natives.
  • #498: phone-width open/close CLS unchanged (logo + queryDraft).
  • No always-on educational blurb on the Swap card (#489).

Test plan (all paths)

Unit — display precedence

  • Registry lookupByDenom('uluna'|'uusd'|'ULUNA') → LUNC / USTC (existing + casefold).
  • getTokenDisplaySymbol('uluna'|'uusd') → LUNC / USTC.
  • useTokenDisplayInfo({ native_token: { denom: 'uluna' } }) with:
    • indexer down / empty list → LUNC
    • indexer { denom: 'uluna', symbol: 'uluna' } → LUNC (registry wins)
    • indexer { denom: 'uluna', symbol: 'UST1' } → LUNC (spoof rejected)
    • indexer { denom: 'uusd', symbol: 'uusd' } → USTC
  • Wrap CW20 still cLUNC / cUSTC when indexer says LUNC-C / USTC-C (#507).
  • Unknown native { denom: 'ibc/ABC' } → raw denom (or indexer symbol if present and not a known-native override).
  • CW20 not in registry still uses indexer / token_info / shortened address.
  • TokenSearchSelect options: visible text LUNC / USTC; data-testid still token-option-uluna / token-option-uusd.
  • Closed trigger value is LUNC when value="uluna".
  • filterTokensByLocalSearch(..., 'LUNC') and 'uluna' both return ['uluna'] (and USTC / uusd).
  • excludeToken still omits the other leg.
  • Economic-first sort (#534) unchanged (uluna still sorts as economic LUNC).

Indexer

  • resolve_asset for NativeToken { denom: "uluna" } / "uusd" upserts symbol LUNC / USTC (not the denom).
  • Other denom (e.g. usdr) still stores denom as symbol.
  • Existing CW20 upsert path unchanged.
  • If a repair migration ships: uluna/uusd rows with symbol=denom become LUNC/USTC; wrap CW20 rows untouched.

Integration / E2E (LocalTerra or production-shaped env)

  • Swap: open You Pay → rows cLUNC, LUNC, cUSTC, USTC (order per #534). Select LUNC → trigger shows LUNC; quote/wrap path still uses uluna.
  • Swap: select USTC; wrap/unwrap pairing still cUSTC ↔ uusd.
  • Pool one-sided: native picker labels LUNC/USTC; auto-wrap still bank denom.
  • Pay invoice: native pay-token label LUNC/USTC.
  • Mint: if natives are in the faucet list, labels match.
  • Phone-width CLS: e2e/swap-token-select-cls.spec.ts still passes (#498).
  • Create Pair: natives still absent (#542).
  • Trade / Charts pair chrome: no uluna / uusd as the visible pair-leg ticker if a native leg exists; factory CW20 pairs unchanged (cLUNC/cUSTC).

Regression (must not break)

  • make test-frontend scoped to token registry / display / TokenSearchSelect / TokenSelect keyboard / token identity.
  • cd frontend-dapp && npm test -- src/utils/__tests__/tokenSearchQuery.test.ts src/components/trade/__tests__/TokenSearchSelect.test.tsx src/components/ui/__tests__/TokenSelect.keyboard.test.tsx
  • Indexer lib / asset resolver tests if upsert changes.
  • Wrap fee / pause / rate-limit CTAs (#507 / #389) unchanged.
  • Hybrid quote / route display still uses getTokenDisplaySymbol (already LUNC).

Test plan (attack / abuse)

Vector Expectation
Indexer returns symbol=UST1 (or USTR / CL8Y / gem ticker) for denom=uluna UI still LUNC. Do not retarget oracle, invert, or gem-hide off the spoofed ticker. Identity is denom.
Indexer returns symbol=<script>… / HTML / very long string for uluna Registry LUNC wins. If a future unknown-token path shows indexer text, it is text-only (no innerHTML).
Indexer logo_url points at javascript: or a non-allowlisted host resolveTrustedTokenLogoUrl drops it; LUNC/USTC fall back to tokenlist GitLab art.
Indexer lists a second row with contract_address colliding on string uluna indexerTokenForId must not bind a CW20 to the native id. Native match is denom === tokenId only.
User pastes 10k chars into search Truncate at TOKEN_SEARCH_MAX_QUERY_LENGTH (128). No freeze.
User types uluna hoping to pick a look-alike CW20 named ULUNA Factory-gated ids only. A CW20 whose token_info.symbol is uluna / LUNC is a different row (address id). Do not collapse it into native LUNC.
User copies the visible LUNC label and pastes it as a contract Copy control (#541) copies uluna, not LUNC. Search for LUNC is fine; execute never uses the display string as an address.
Confuse LUNC vs cLUNC to drain wrap Labels must stay four-way distinct. Selecting LUNC still wraps via treasury; selecting cLUNC is CW20. No auto-collapse.
Create Pair paste of uluna Rejected as not a CW20 address (#542 / terra address validation).
Production gem hide (#562) Native LUNC/USTC never classified as gems. Spoofed gem symbol on uluna must not hide bank LUNC.
localStorage cl8y-dex-token-info poisoned with uluna: { symbol: 'HACK' } lookupByTokenId / registry runs before that cache for known natives.

Verification criteria

# Display + picker
cd frontend-dapp && npm test -- \
  src/utils/__tests__/tokenDisplay.test.ts \
  src/utils/__tests__/tokenRegistry.test.ts \
  src/utils/__tests__/tokenSearchQuery.test.ts \
  src/components/trade/__tests__/TokenSearchSelect.test.tsx \
  src/components/ui/__tests__/TokenSelect.keyboard.test.tsx \
  src/hooks/__tests__/useTokenDisplayInfo.test.ts

# Identity copy-payload regression
cd frontend-dapp && npm test -- src/utils/__tests__/tokenIdentity.test.ts

# If indexer upsert / repair ships
cd indexer && cargo test --lib asset_resolver

# Issue-scoped (add Makefile target)
make verify-issue-<this>

Manual (mainnet or LocalTerra with wrap env):

  1. Open Swap (phone and desktop). Open You Pay. Native rows read LUNC and USTC; wrap rows cLUNC and cUSTC. No uluna / uusd in option text.
  2. Select LUNC → closed combobox shows LUNC. Quote / Wrap still uses bank uluna (network fee row still LUNC).
  3. Select USTC; same for uusd.
  4. Type uluna and LUNC — both highlight the native LUNC row. Type uusd / USTC — native USTC.
  5. Disconnect / LCD error banner: labels still LUNC/USTC (registry), not blank and not denom.
  6. Pool one-sided + Pay invoice pickers match.
  7. Token identity copy on Pool/Trade/Charts still copies denom for a native leg.
  8. Create Pair still has no LUNC/USTC/uluna/uusd options.

Done when a reviewer cannot find uluna or uusd as visible ticker text on Swap/Pool/Pay/Mint pickers, and execute still uses the bank denoms.

## Summary Retail token pickers (Swap **You Pay** / **You Receive**, and every other `TokenSearchSelect` / `TokenSelect` / `TokenDisplay` surface) show bank denoms **`uluna`** and **`uusd`** instead of product tickers **LUNC** and **USTC**. Wrapped rows already show **cLUNC** / **cUSTC**. Users cannot tell native bank LUNC/USTC from CosmWasm wrap CW20s by ticker, and the denom strings look like internal ids. Reported from production Swap token dropdown (native rows sit next to cLUNC / cUSTC / USTR). LCD can be down (`Could not connect to the network`) while the indexer catalog is still up — that is enough to leak the wrong labels. Related: [#507](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/507) (cLUNC / cUSTC product symbols), [#481](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/481) (Swap combobox), [#541](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/541) (copy payload stays denom), [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) (retail copy). --- ## Current codebase ### What already maps correctly | Layer | Behavior | |-------|----------| | [`frontend-dapp/src/utils/tokenRegistry.ts`](frontend-dapp/src/utils/tokenRegistry.ts) | `DENOM_MAP`: `uluna` → **LUNC**, `uusd` → **USTC**. Wrap CW20s → **cLUNC** / **cUSTC**. | | [`getTokenDisplaySymbol`](frontend-dapp/src/utils/tokenDisplay.ts) | Uses `lookupByTokenId`; unit tests already expect `uluna` → `LUNC`. | | [`tokenSearchQuery.ts`](frontend-dapp/src/utils/tokenSearchQuery.ts) | Search haystack includes denom **and** registry symbol, so typing `LUNC` / `uluna` both match. Filter ids stay `uluna` / `uusd`. | | [`getAllTokens`](frontend-dapp/src/services/terraclassic/router.ts) | When wrap env is set, appends native `uluna` / `uusd` plus wrap CW20s so Swap can wrap without a factory pair ([#507](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/507)). | | Wallet chip ([#140](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/140)) | Bank `uluna` already labeled **LUNC**. | Token **ids** used for balances, quotes, and execute must stay `uluna` / `uusd`. Only the **visible label** is wrong. ### Why the dropdown still shows `uluna` / `uusd` [`useTokenDisplayInfo`](frontend-dapp/src/hooks/useTokenDisplayInfo.ts) is the label source for `TokenSearchSelect` (Swap, Pool one-sided, Pay invoice) and `TokenSelect` (Mint): ```ts const wrapProductSymbol = registryCw20?.symbol === 'cLUNC' || registryCw20?.symbol === 'cUSTC' ? registryCw20.symbol : undefined const symbol = wrapProductSymbol || indexerMeta?.symbol?.trim() || chainSymbol ``` - Wrap CW20s have a **#507 override** so indexer/on-chain `LUNC-C` cannot win. - Natives have **no** equivalent override. If `GET /api/v1/tokens` returns `symbol: "uluna"` / `"uusd"`, that **beats** the registry (`LUNC` / `USTC`). - `chainSymbol` (registry via `getCachedTokenSymbol`) is only the last fallback. ### Why the indexer catalog stores denom-as-symbol [`indexer/src/indexer/asset_resolver.rs`](indexer/src/indexer/asset_resolver.rs) on first native sight: ```rust assets::upsert_asset(pool, None, Some(denom), false, denom, denom, 6, None) ``` `name` and `symbol` are both the raw denom. Integration seeds use the retail pair (`uluna` / `Luna Classic` / `LUNC`), but live ingest does not. `GET /api/v1/tokens` then serves `symbol=uluna` / `uusd`. ### Surfaces that inherit the leak Same hook or raw indexer `asset_*.symbol`: | Surface | File | |---------|------| | Swap You Pay / You Receive | `TokenSearchSelect` ← `useTokenDisplayInfo` | | Pool one-sided add/withdraw | same combobox | | Pay-with-any-token | `PayWithAnyToken.tsx` | | Mint faucet list | `TokenSelect.tsx` | | Compact logo+symbol | `TokenDisplay.tsx`, `TokenIdentity.tsx` | | Pool manage labels | `PoolAdvancedManage.tsx` | | Trade / Charts / Pool pair chrome | `activePair.asset_0.symbol` / `asset_1.symbol` (factory pairs are usually CW20-only; still fix catalog so native legs cannot leak) | `data-testid` stays `token-option-uluna` / `token-option-uusd` (id, not label). E2E helpers must keep selecting by id. --- ## Why this is needed Retail users know **LUNC** and **USTC**. Showing `uluna` / `uusd` next to **cLUNC** / **cUSTC** looks like four different assets (or broken metadata). That is a wrap/unwrap selection hazard: picking the wrong row wraps when the user wanted wrapped CW20, or the reverse. `getTokenDisplaySymbol` and the registry already encode the product names. The shared display hook **throws that away** whenever the indexer is reachable. QA wrap checklist already requires the selector to list **LUNC**, **cLUNC**, **USTC**, **cUSTC** ([`docs/qa-templates/wrap-unwrap-test-pass.md`](docs/qa-templates/wrap-unwrap-test-pass.md)). --- ## Constraints / guardrails 1. **Ids unchanged.** Execute, balances, route/solve, wrap-mapper, and React Query keys stay `uluna` / `uusd` (or wrap CW20 addresses). Do not rename on-chain denoms. 2. **Do not merge native and wrap.** LUNC ≠ cLUNC; USTC ≠ cUSTC. Four rows stay four rows when wrap env is set. Logos stay distinct (LUNC.png / CLUNC.png, USTC.png / CUSTC.png). 3. **Registry wins for known natives**, same idea as #507 `wrapProductSymbol`. A compromised or stale indexer must not relabel `uluna` as UST1 / USTR / a gem ticker. 4. **Unknown natives stay raw.** Only allowlist `uluna` → LUNC and `uusd` → USTC (casefold). Do not invent tickers for IBC or other bank denoms. 5. **Copy / explorer (#541).** Copy payload is the **denom**, never the display symbol. Natives stay copy-only (no Finder URL). 6. **Search still matches both.** Haystack keeps `uluna` / `uusd` **and** `LUNC` / `USTC` so power users and retail queries work. Do not drop denom from search. 7. **Factory gate (#481).** Picker options stay `getAllTokens(pairs)` + wrap enrichment. Do not add an external token list to “fix” labels. 8. **Create Pair (#542).** Natives must **not** become selectable create-pair tokens. This issue is labels, not catalog membership. 9. **Gems / economic rank (#534 / #562).** `uluna` / `uusd` stay economic hubs. Do not hide them in production. Do not treat a spoofed indexer symbol as gem/hub identity. 10. **No HTML.** Labels render as text only. Logos still go through `resolveTrustedTokenLogoUrl`. 11. **Indexer repair is optional-follow.** Frontend must be correct even if production `assets.symbol` stays `uluna` until a repair migration. If indexer upsert is fixed, unknown denoms still use denom-as-symbol (fail closed). 12. **#489.** Do not add always-on “uluna means LUNC” essays on the Swap card. --- ## Relevant files **Frontend (primary)** - `frontend-dapp/src/hooks/useTokenDisplayInfo.ts` — label precedence - `frontend-dapp/src/utils/tokenRegistry.ts` — `lookupByDenom` / `TOKENS` - `frontend-dapp/src/utils/tokenDisplay.ts` — `getTokenDisplaySymbol` (already correct) - `frontend-dapp/src/components/trade/TokenSearchSelect.tsx` - `frontend-dapp/src/components/ui/TokenSelect.tsx` - `frontend-dapp/src/components/ui/TokenDisplay.tsx` - `frontend-dapp/src/components/ui/TokenIdentity.tsx` - `frontend-dapp/src/pages/SwapPage.tsx` - `frontend-dapp/src/components/pool/OneSidedAddCard.tsx`, `OneSidedWithdrawCard.tsx` - `frontend-dapp/src/components/payments/PayWithAnyToken.tsx` - `frontend-dapp/src/utils/tokenSearchQuery.ts` - `docs/frontend.md` § Token search combobox - `docs/qa-templates/wrap-unwrap-test-pass.md` - `NATIVE_TOKEN_WRAPPING.md` (E11: selector shows LUNC and cLUNC, USTC and cUSTC) **Indexer (catalog consistency)** - `indexer/src/indexer/asset_resolver.rs` — native upsert `name=denom, symbol=denom` - `indexer/src/db/queries/assets.rs` — `upsert_asset` - `indexer/src/api/tokens.rs` — `GET /api/v1/tokens` - `indexer/tests/common/mod.rs` / `indexer/scripts/seed-charts-integration.sql` — already seed `LUNC` **Tests to extend** - `frontend-dapp/src/utils/__tests__/tokenDisplay.test.ts` - `frontend-dapp/src/utils/__tests__/tokenRegistry.test.ts` - `frontend-dapp/src/components/trade/__tests__/TokenSearchSelect.test.tsx` - New: `frontend-dapp/src/hooks/__tests__/useTokenDisplayInfo.test.ts` (or equivalent) --- ## Recommended direction **1. dApp (must ship; unblocks the screenshot)** In `useTokenDisplayInfo`, resolve a **product symbol** from the static registry for known natives **and** wrap CW20s **before** `indexerMeta.symbol`: - `lookupByDenom(uluna|uusd)` → LUNC / USTC - existing `wrapProductSymbol` → cLUNC / cUSTC - then indexer symbol (unknown CW20s / unknown natives) - then `getCachedTokenSymbol` / shortened address / raw id Prefer a small helper (e.g. `registryProductSymbol(tokenId)`) used by both the hook and `getTokenDisplaySymbol` so precedence cannot drift. Do **not** special-case only `TokenSearchSelect`. Fixing the hook covers Mint, Pool, Pay invoice, TokenDisplay, TokenIdentity. **2. Indexer (same issue; keeps API / pair chrome honest)** When inserting `uluna` / `uusd` the first time, write retail `name` / `symbol` (`Luna Classic` / `LUNC`, `TerraClassicUSD` / `USTC`), matching `tokenRegistry` and existing test seeds. Leave other denoms as denom/denom. Optional: one-time SQL repair for existing `assets` rows where `denom in ('uluna','uusd')` and `symbol` is the denom. Do not rewrite CW20 wrap rows (those stay cLUNC / cUSTC via #507). **3. Docs / verify target** Add a short invariant to `docs/frontend.md` § Token search (and wrap QA template if not already explicit): visible option text is **LUNC** / **USTC**, never `uluna` / `uusd`. `make verify-issue-<this>` runs the new Vitest files. --- ## Acceptance criteria - [ ] Swap You Pay / You Receive listboxes show **LUNC** and **USTC** for native rows; **cLUNC** and **cUSTC** stay distinct. - [ ] Combobox trigger (closed and open-before-edit, #498) shows **LUNC** / **USTC**, not the denom. - [ ] Same labels on Pool one-sided pickers, Pay-with-any-token, Mint `TokenSelect`, `TokenDisplay`, and `TokenIdentity`. - [ ] Indexer `GET /api/v1/tokens` (after ingest or repair) reports `symbol=LUNC` / `USTC` for `denom=uluna` / `uusd`. - [ ] Token **id** in `onChange`, `data-testid`, balances, and execute remains `uluna` / `uusd`. - [ ] Search for `LUNC`, `lunc`, `uluna`, `USTC`, `uusd` still finds the correct row. - [ ] A mocked indexer payload with `symbol: "uluna"` / `"UST1"` / HTML / empty string cannot override registry LUNC/USTC. - [ ] Unknown native denom (e.g. `ufoo`) still displays as `ufoo`. - [ ] #541: copy still copies `uluna` / `uusd`; no Finder link for natives. - [ ] #542: Create Pair catalog still excludes natives. - [ ] #498: phone-width open/close CLS unchanged (logo + `queryDraft`). - [ ] No always-on educational blurb on the Swap card (#489). --- ## Test plan (all paths) ### Unit — display precedence - Registry `lookupByDenom('uluna'|'uusd'|'ULUNA')` → LUNC / USTC (existing + casefold). - `getTokenDisplaySymbol('uluna'|'uusd')` → LUNC / USTC. - `useTokenDisplayInfo({ native_token: { denom: 'uluna' } })` with: - indexer down / empty list → **LUNC** - indexer `{ denom: 'uluna', symbol: 'uluna' }` → **LUNC** (registry wins) - indexer `{ denom: 'uluna', symbol: 'UST1' }` → **LUNC** (spoof rejected) - indexer `{ denom: 'uusd', symbol: 'uusd' }` → **USTC** - Wrap CW20 still **cLUNC** / **cUSTC** when indexer says `LUNC-C` / `USTC-C` (#507). - Unknown native `{ denom: 'ibc/ABC' }` → raw denom (or indexer symbol if present and not a known-native override). - CW20 not in registry still uses indexer / `token_info` / shortened address. ### Unit — picker / search - `TokenSearchSelect` options: visible text LUNC / USTC; `data-testid` still `token-option-uluna` / `token-option-uusd`. - Closed trigger value is LUNC when `value="uluna"`. - `filterTokensByLocalSearch(..., 'LUNC')` and `'uluna'` both return `['uluna']` (and USTC / `uusd`). - `excludeToken` still omits the other leg. - Economic-first sort (#534) unchanged (`uluna` still sorts as economic LUNC). ### Indexer - `resolve_asset` for `NativeToken { denom: "uluna" }` / `"uusd"` upserts symbol LUNC / USTC (not the denom). - Other denom (e.g. `usdr`) still stores denom as symbol. - Existing CW20 upsert path unchanged. - If a repair migration ships: `uluna`/`uusd` rows with symbol=denom become LUNC/USTC; wrap CW20 rows untouched. ### Integration / E2E (LocalTerra or production-shaped env) - Swap: open You Pay → rows **cLUNC**, **LUNC**, **cUSTC**, **USTC** (order per #534). Select LUNC → trigger shows LUNC; quote/wrap path still uses `uluna`. - Swap: select USTC; wrap/unwrap pairing still cUSTC ↔ `uusd`. - Pool one-sided: native picker labels LUNC/USTC; auto-wrap still bank denom. - Pay invoice: native pay-token label LUNC/USTC. - Mint: if natives are in the faucet list, labels match. - Phone-width CLS: `e2e/swap-token-select-cls.spec.ts` still passes (#498). - Create Pair: natives still absent (#542). - Trade / Charts pair chrome: no `uluna` / `uusd` as the **visible** pair-leg ticker if a native leg exists; factory CW20 pairs unchanged (cLUNC/cUSTC). ### Regression (must not break) - `make test-frontend` scoped to token registry / display / TokenSearchSelect / TokenSelect keyboard / token identity. - `cd frontend-dapp && npm test -- src/utils/__tests__/tokenSearchQuery.test.ts src/components/trade/__tests__/TokenSearchSelect.test.tsx src/components/ui/__tests__/TokenSelect.keyboard.test.tsx` - Indexer lib / asset resolver tests if upsert changes. - Wrap fee / pause / rate-limit CTAs (#507 / #389) unchanged. - Hybrid quote / route display still uses `getTokenDisplaySymbol` (already LUNC). --- ## Test plan (attack / abuse) | Vector | Expectation | |--------|-------------| | Indexer returns `symbol=UST1` (or USTR / CL8Y / gem ticker) for `denom=uluna` | UI still **LUNC**. Do not retarget oracle, invert, or gem-hide off the spoofed ticker. Identity is denom. | | Indexer returns `symbol=<script>…` / HTML / very long string for `uluna` | Registry **LUNC** wins. If a future unknown-token path shows indexer text, it is text-only (no `innerHTML`). | | Indexer `logo_url` points at `javascript:` or a non-allowlisted host | `resolveTrustedTokenLogoUrl` drops it; LUNC/USTC fall back to tokenlist GitLab art. | | Indexer lists a second row with `contract_address` colliding on string `uluna` | `indexerTokenForId` must not bind a CW20 to the native id. Native match is `denom === tokenId` only. | | User pastes 10k chars into search | Truncate at `TOKEN_SEARCH_MAX_QUERY_LENGTH` (128). No freeze. | | User types `uluna` hoping to pick a look-alike CW20 named ULUNA | Factory-gated ids only. A CW20 whose `token_info.symbol` is `uluna` / `LUNC` is a **different** row (address id). Do not collapse it into native LUNC. | | User copies the visible **LUNC** label and pastes it as a contract | Copy control (#541) copies `uluna`, not `LUNC`. Search for `LUNC` is fine; execute never uses the display string as an address. | | Confuse LUNC vs cLUNC to drain wrap | Labels must stay four-way distinct. Selecting LUNC still wraps via treasury; selecting cLUNC is CW20. No auto-collapse. | | Create Pair paste of `uluna` | Rejected as not a CW20 address (#542 / terra address validation). | | Production gem hide (#562) | Native LUNC/USTC never classified as gems. Spoofed gem symbol on `uluna` must not hide bank LUNC. | | localStorage `cl8y-dex-token-info` poisoned with `uluna: { symbol: 'HACK' }` | `lookupByTokenId` / registry runs **before** that cache for known natives. | --- ## Verification criteria ```bash # Display + picker cd frontend-dapp && npm test -- \ src/utils/__tests__/tokenDisplay.test.ts \ src/utils/__tests__/tokenRegistry.test.ts \ src/utils/__tests__/tokenSearchQuery.test.ts \ src/components/trade/__tests__/TokenSearchSelect.test.tsx \ src/components/ui/__tests__/TokenSelect.keyboard.test.tsx \ src/hooks/__tests__/useTokenDisplayInfo.test.ts # Identity copy-payload regression cd frontend-dapp && npm test -- src/utils/__tests__/tokenIdentity.test.ts # If indexer upsert / repair ships cd indexer && cargo test --lib asset_resolver # Issue-scoped (add Makefile target) make verify-issue-<this> ``` **Manual (mainnet or LocalTerra with wrap env):** 1. Open Swap (phone and desktop). Open You Pay. Native rows read **LUNC** and **USTC**; wrap rows **cLUNC** and **cUSTC**. No `uluna` / `uusd` in option **text**. 2. Select LUNC → closed combobox shows **LUNC**. Quote / Wrap still uses bank `uluna` (network fee row still **LUNC**). 3. Select USTC; same for `uusd`. 4. Type `uluna` and `LUNC` — both highlight the native LUNC row. Type `uusd` / `USTC` — native USTC. 5. Disconnect / LCD error banner: labels still LUNC/USTC (registry), not blank and not denom. 6. Pool one-sided + Pay invoice pickers match. 7. Token identity copy on Pool/Trade/Charts still copies denom for a native leg. 8. Create Pair still has no LUNC/USTC/`uluna`/`uusd` options. Done when a reviewer cannot find `uluna` or `uusd` as **visible ticker text** on Swap/Pool/Pay/Mint pickers, and execute still uses the bank denoms.
PlasticDigits commented 2026-08-25 01:57:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #632

mentioned in issue #632
PlasticDigits commented 2026-08-25 06:01:21 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1141

mentioned in merge request !1141
PlasticDigits commented 2026-08-25 06:04:06 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1142

mentioned in merge request !1142
PlasticDigits commented 2026-08-25 06:19:13 +00:00 (Migrated from gitlab.com)

mentioned in commit d7050d853c

mentioned in commit d7050d853cb14fdce1eec9ca3891ea883694b5da
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-25 06:19:14 +00:00
PlasticDigits commented 2026-08-25 06:25:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 796b647e7e

mentioned in commit 796b647e7e9d2a9f60f7d7ffaebf52fa79220ba8
PlasticDigits commented 2026-08-25 06:29:20 +00:00 (Migrated from gitlab.com)

!1142 merged to main (d7050d85) without waiting for CI. Conflicts were Makefile help/PHONY only. make verify-issue-630 11/11 (N630-1–N630-8 + indexer lib + #541 copy stays denom).

Leftover (not proven on this merge):

  • Coolify indexer deploy + migrate 20260825140000_repair_native_bank_tickers.sql so live GET /api/v1/tokens stores symbol=LUNC / USTC. The dApp no longer needs that catalog repair (registry wins), but the catalog leak remains until migrate.
  • Manual reviewer: Swap / Pool / Pay invoice / Mint visible tickers; Create Pair still excludes natives.

Do not reopen unless a merged invariant is wrong. Operator leftovers go on the post-merge leftover issue.

!1142 merged to `main` (`d7050d85`) without waiting for CI. Conflicts were Makefile help/PHONY only. `make verify-issue-630` **11/11** (N630-1–N630-8 + indexer lib + #541 copy stays denom). **Leftover (not proven on this merge):** - Coolify indexer deploy + migrate `20260825140000_repair_native_bank_tickers.sql` so live `GET /api/v1/tokens` stores `symbol=LUNC` / `USTC`. The dApp no longer needs that catalog repair (registry wins), but the catalog leak remains until migrate. - Manual reviewer: Swap / Pool / Pay invoice / Mint visible tickers; Create Pair still excludes natives. Do **not** reopen unless a merged invariant is wrong. Operator leftovers go on the post-merge leftover issue.
PlasticDigits commented 2026-08-25 06:29:43 +00:00 (Migrated from gitlab.com)

mentioned in issue #638

mentioned in issue #638
PlasticDigits commented 2026-08-25 06:29:44 +00:00 (Migrated from gitlab.com)

marked as related to #638

marked as related to #638
PlasticDigits commented 2026-08-25 06:29:55 +00:00 (Migrated from gitlab.com)

Post-merge leftovers for the !1140/!1142/!1143/!1144/!1145 pass: #638.

Post-merge leftovers for the !1140/!1142/!1143/!1144/!1145 pass: #638.
PlasticDigits commented 2026-08-26 04:12:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #661

mentioned in issue #661
PlasticDigits commented 2026-08-27 00:20:54 +00:00 (Migrated from gitlab.com)

mentioned in issue #680

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

mentioned in issue #691

mentioned in issue #691
PlasticDigits commented 2026-08-31 04:56:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #711

mentioned in issue #711
PlasticDigits commented 2026-08-31 05:36:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #713

mentioned in issue #713
PlasticDigits commented 2026-08-31 16:08:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #715

mentioned in issue #715
PlasticDigits commented 2026-08-31 16:08:54 +00:00 (Migrated from gitlab.com)

marked as related to #715

marked as related to #715
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#630
No description provided.