feat: compact token identity links on Pool, Trade, and Charts #541
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#541
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
Pool (
/pool), Trade (/trade), and Charts (/charts) show pair symbols (and logos on Pool) but give retailers no clear way to inspect the actual tokens they are looking at. A look-alike ticker is indistinguishable from the real CW20. This issue ships one shared, compact token-identity control on those three pages — explorer + copy for CW20s, copy-only for native denoms, plus a compact pair-contract chip — without dumping always-on address essays.Related (do not re-implement): #188
AddressRow(LP withdraw + wallet + trader done; pair chips on Pool / Charts still listed as leftover), #184 / #478 explorer URL helpers, #430 SEC-E10 href validation, #183CopyButton, #489 anti-cognitive-overload, #378 trust boundaries, #524 display invert.Current codebase
Tokens on these pages are display-only. Identity (contract / denom / explorer) is implemented elsewhere and never mounted on the pair chrome.
/poolpair card headerTokenDisplaylogo + symbol forasset_infos[0]/[1], plus a monopairInfoMenuLabellineTokenDisplayis a<span>only./poolreserve cardsTokenDisplayover each reserve amount/poolwithdrawAddressRow(pool-lp-token-address-row)/poolpair contract/tradePairSearchSelect+ invert pill (#524). Ticket / book / chart use symbols from indexerasset_0/asset_1orgetTokenDisplaySymbol/chartsMenuSelect, 24h stats titled withindexerPairMenuLabel,PriceChartinvert pillAddressRow./protocolAddressRow(audit surface)SwapPreSubmitSummary(#449)Primitives that already exist (reuse, do not fork):
getExplorerAddressUrl— network-aware Finder / local LCD;nullunless the segment is a valid Terra bech32 (#184, #478, #430).AddressRow— shortened label +CopyButton+ explorer icon (target="_blank"rel="noopener noreferrer").useTokenDisplayInfo/tokenDisplay.ts— symbol resolution (registry wrap product symbols cLUNC/cUSTC, indexer, CW20 query, shorten fallback).isValidTerraBech32Address— checksummedterra1…only.Gap: native denoms (
uluna,uusd) have no explorer helper.getExplorerAddressUrl('uluna')is correctlynull. Pair/token pickers must stay comboboxes — identity chrome must not live inside the listbox options.Why this is needed
asset_0.symbol/ logo can be wrong or hostile (#378). Retailers on Pool / Trade / Charts cannot verify the contract without leaving the dApp and guessing.AddressRow; the two legs and the pair contract on Pool / Trade / Charts do not.Constraints / guardrails
PoolPage/TradePage/ChartsPage. ExtendTokenDisplayor add a thinTokenIdentitythat composesTokenDisplay+CopyButton+ the same explorer icon/getExplorerAddressUrlpath asAddressRow. Pair contract staysAddressRow(or the same dense chip).title/aria-label/ optional<details>, not an always-on mono dump. Labels ≤ ~5 words. No “View token on explorer” paragraphs, no “use Wrap / UST1 / Swap” banners (#489 invariant 9).<a>. Do not wrapTokenDisplay/ pair-select labels in a navigation link. That fightsPairSearchSelect, invert pills (#524), and keyboard listbox behavior. Explorer is a sibling control.hrefgoes throughgetExplorerAddressUrl. Invalid /javascript:/ HTML / empty / non-bech32 →null→ omit the anchor. Do not interpolate indexercontract_addrinto a URL string. Keep #430 adversarial tests intact.uluna/uusd(and anynative_tokendenom): copy denom only. Do not invent a Finder “token” URL, do not pass the denom togetExplorerAddressUrl, do not render a dead explorer icon.AssetInfo(or indexercontract_addr/denomafter the same bech32/denom checks). Never usesymbolas the copy payload or URL segment. Display invert changes labels, not which contract the icon points at./token/:id. Do not link CoinGecko, CoinMarketCap, Twitter, or indexer-supplied “website” /logo_urlhosts. Logo allowlist (tokenLogoAllowlist.ts) is unchanged.PairSearchSelect/TokenSearchSelectoption rows (noise + nested interactive).pair.contract_addr/pair_address). Hide explorer when the helper returnsnull. Factory-unknown / invalid route segments stay on existing #175 / #176 notices — do not render identity chrome for garbage.target="_blank"rel="noopener noreferrer". ExplicitcopyAriaLabel/explorerAriaLabelper token (“Copy cLUNC address”, “View cLUNC on explorer”).:focus-visiblerings (#144)./protocolonly, #378).AddressRowbehavior except to share CSS if needed./token pickers.docs/frontend.mdsection +skills/AGENTS_FRONTEND_TOKEN_IDENTITY.md+AGENTS.mdplaybook row. Invariants T541-1–T541-8.make dev.Relevant files
frontend-dapp/src/components/ui/TokenDisplay.tsxfrontend-dapp/src/hooks/useTokenDisplayInfo.tsfrontend-dapp/src/components/ui/AddressRow.tsxfrontend-dapp/src/components/ui/CopyButton.tsxfrontend-dapp/src/utils/terraExplorer.tsgetExplorerAddressUrlonlyfrontend-dapp/src/utils/terraAddressValidation.tsfrontend-dapp/src/pages/PoolPage.tsxAddressRowfrontend-dapp/src/pages/TradePage.tsxtrade-pair-select-panel)frontend-dapp/src/pages/ChartsPage.tsxfrontend-dapp/src/components/charts/PriceChart.tsxfrontend-dapp/src/components/trade/PairSearchSelect.tsxfrontend-dapp/src/hooks/usePairDisplayOrientation.tsfrontend-dapp/src/components/ui/__tests__/AddressRow.explorerSafety.test.tsxfrontend-dapp/src/utils/__tests__/terraExplorer.test.tsdocs/frontend.mdskills/AGENTS_FRONTEND_ADDRESS_ROW.mdskills/AGENTS_FRONTEND_TERRA_EXPLORER.mdskills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.mdRecommended direction
tokenIdentityTarget(info)→{ kind: 'cw20', address, explorerUrl } | { kind: 'native', denom } | null. CW20 explorer URL isgetExplorerAddressUrl(address)or omit. Reject native denoms and invalid bech32.TokenIdentity(preferred over making everyTokenDisplayclickable):CopyButtonof address or denom.explorerUrlis non-null.data-testids:token-identity-{base,quote,pair}(and pool reserve variants).PairTokenLinks— one compact row: base identity, quote identity, pairAddressRow(short 8/6). Place:TokenDisplayheader (or replace those two displays withTokenIdentity); pair chip beside / under the factory badge. Reserve cards may keep plainTokenDisplayor the same dense identity — pick one and test both breakpoints.trade-pair-select-panel, outside the combobox.MenuSelect, outside the combobox.usePairDisplayOrientation; copy/explorer payloads stayasset_0/asset_1(factory order). Tests must catch a swapped href after invert.terra1placeholders, no links on#176/#175notice states.make verify-issue-541, playbookskills/AGENTS_FRONTEND_TOKEN_IDENTITY.md.Invariants:
hrefis onlygetExplorerAddressUrl; native is copy-only.<a>; explorer is a sibling control./tokenroute, no picker-option icons, no Protocol factory/router clone.#489/#378: no always-on address essay or cross-nav banner.Acceptance criteria
/poolselected pair: both legs have copy; CW20 legs have an explorer control whosehrefmatchesgetExplorerAddressUrl. Native legs have copy and no explorer anchor./poolshows a compact pair-contractAddressRow(or equivalent) for the card’scontract_addr. LP withdrawpool-lp-token-address-rowstill works./tradewith a known factory pair: same leg + pair identity row under the pair select, not inside the listbox. Invalid/unknown pair notices (#176 / #175): row absent./chartswith a selected pair: same row under the pair select. Empty “No pairs yet”: no dead links.#524invert on Trade and Charts: labels/pill may flip; copied addresses and explorerhrefs stay on the same factory assets.target="_blank"rel="noopener noreferrer"; omitted when helper isnull(local without LCD shape, bad address, native)./token/:id; no factory/router rows on these pages; Swap confirm unchanged.:focus-visibleon copy/explorer.docs/frontend.md+ skill +AGENTS.md+make verify-issue-541.Test plan (all paths)
Unit
tokenIdentityTargetCW20 valid → address + explorer URL perVITE_NETWORK(mainnet/columbus-5/address/…, testnet Hexxagon, local LCD accounts).uluna/uusd→kind: 'native', no URL.javascript:alert(1)/ HTML / wrong checksum →null, no URL.symbol: 'UST1'and attackercontract_addr→ copy/href use the address, notUST1.UI / integration
AddressRowstill present on withdraw.trade-pair-select-panel; listbox options have no extra explorer buttons.#176invalid and#175unknown: no identity links.hrefand clipboard text unchanged for each asset.:focus-visiblering; Enter on explorer does not change the selected pair.Playwright smoke (5 workers; no tx required)
/poolselected factory pair:token-identity-*+ pair explorer (orAddressRow) visible./trade/<known-pair>: identity row visible; pair select still changes pair./charts: select pair → identity row; explorerhrefis Finder/LCD, not the dApp origin./trade/lilwayne%20babyyy(or current #176 fixture): no explorer anchors in the identity slot.Test plan (attack, hack, and abuse)
contract_addrhref. Regression alongsideAddressRow.explorerSafety/terraExploreradversarial cases.`${explorerUrl}/${addr}`breaks out of FindergetExplorerAddressUrlis used; test that components never concatenate hosts.noopenerrelincludesnoopener noreferrer.logo_urlorwebsiteused as explorerhrefhref="…/address/uluna"<a>steals clicks / breaks listboxterra1+ 38×xdangerouslySetInnerHTML.finder.*via raw<a href>hardcoded in a pagechainlist.json. Do not widen production CSPconnect-srcfor this (navigation is notconnect-src).position: fixedover the ticket.pairAddr; after switch, copy matches the new pair.AddressRowcloned onto Trade “for convenience”Verification criteria
Close when all of the following are true:
make verify-issue-541is green (unit + scoped frontend + Playwright smoke).#188skill leftover line (“pair chips on Pool / Charts”) is updated to point here./tokenroute, no new outbound product hosts, Swap confirm and LP withdraw unchanged except shared styles.marked as related to #188
changed the description
mentioned in merge request !1070
mentioned in commit
64c6dd9623mentioned in commit
c8ab55bcbementioned in commit
d7aa1c07e1Merged via !1070 onto
main(c8ab55bc) after rebasing through !1069. Conflict resolution kept bothverify-issue-541andverify-issue-543Makefile/playbook rows.Sanity check on
main:make verify-issue-541— 11/11 pass (T1–T5 / A1 / A4 / A6 / A7 unit, TokenIdentity + PairTokenLinks explorer safety, Pool/Trade/Charts #541 describes, docs T541-1–T541-8). Playwright: P4 (#176 no identity anchors) passed; P1–P3 skipped (PLAYWRIGHT_SKIP_CHAIN=1/ no factory pair row in that smoke).Remaining (not a merge blocker): AC8 light/dark + mobile/desktop wrap and keyboard
:focus-visiblestill need a visual pass. Re-run P1–P3 against a live pair list. Tracked in the post-merge follow-up.mentioned in issue #542
mentioned in issue #545
marked as related to #545
mentioned in issue #547
marked as related to #547
mentioned in issue #556
mentioned in issue #570
marked as related to #570
mentioned in issue #593
mentioned in issue #630
mentioned in merge request !1141
mentioned in merge request !1142
mentioned in issue #656
mentioned in issue #664