feat(frontend): rank economic tokens above test tokens in the Transfer picker #136
Labels
No labels
agent:implement
agent:ready
backend
bug
cannot-reproduce
confirmed
desktop
docs
documentation
duplicate
enhancement
feature
frontend
good first issue
help wanted
high-risk
in-review
invalid
mobile
needs-triage
P0-critical
P1-high
P2-medium
P3-low
qa
QA
question
ready
report
responsive
security
security-escalate
smart-contract
solana
tablet
test-pass
ux
wallet-issue
wallet:keplr
wallet:metamask
wallet:station
wallet:walletconnect
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-bridge-monorepo#136
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
On the Transfer page token picker, economic (real-value) tokens must always appear first and noneconomic test tokens must always appear last. Bundle classification, stable sort, default selection, unit/e2e coverage, and a frontend invariant into this single issue. Display order only — do not change which tokens are bridgeable, mappings, fees, or on-chain behavior.
Current codebase
The Transfer form builds the token dropdown from Terra’s on-chain token registry, then filters to tokens that have a dest mapping on the selected source/dest chain. There is no economic vs test ranking.
buildTransferTokens()inpackages/frontend/src/services/transfer/buildTransferTokens.tsdestChainMappings; order = Terratokensquery orderObject.entries(sourceChainMappings)— insertion order of a map filled as paralleltoken_dest_mappingqueries complete (non-deterministic)evm_token_address, or a single env fallback tokenTokenSelectmapstokensin array order (packages/frontend/src/components/transfer/TokenSelect.tsx)TransferFormauto-selectstransferTokens[0]when the current id is empty or no longer valid (TransferForm.tsx~377–384)TokenOptionis{ id, symbol, tokenId, evmTokenAddress? }— noisTest/economicfield (packages/frontend/src/types/tokenOption.ts)packages/frontend/public/tokens/tokenlist.jsonlists economic assets (LUNC, USTC, CL8Y, ALPHA, USTR, USTRIX, SpaceUSD, CL8Y-cb). Test tokens are not in this list.FaucetPanel.tsxMAINNET_FAUCET_TOKENS: testa / testb / tdec (hard-coded CW20 + EVM + SPL). Local QA: TKNA / TKNB / TKNC / KDEC (+ tLUNC / synthetic SOL for faucet, not the same as mainnet economic LUNC)useTokenRegistry()paginates Terratokens { start_after, limit }— CosmWasm map iteration order, not product orderbuildTransferTokens.test.tscovers mapping-load gating only;e2e/token-selection.spec.tsonly checks that a selector/symbol appears;TokenSelecthas no dedicated sort testsKnown mainnet noneconomic test tokens (must sort to the bottom when present):
terra16ahm9hn5teayt2as384zf3uudgqvmmwahqfh0v9e3kaslhu30l8q38ftvhterra1vqfe2ake427depchntwwl6dvyfgxpu5qdlqzfjuznxvw6pqza0hqalc9g3terra1pa7jxtjcu3clmv0v8n2tfrtlfepneyv8pxa7zmhz50kj8unuv0zq37apvvPeer EVM/SPL addresses live in
FaucetPanelMAINNET_FAUCET_TOKENSand Solana register scripts. Local QA test tokens: TKNA / TKNB / TKNC / KDEC (env-driven addresses).Economic tokens (must sort to the top when present and routed): LUNC (
uluna), USTC (uusd), CL8Y (CW20terra16wtml2q66g82fdkx66tap0qjkahqwp4lwq3ngtygacg5q0kzycgqvhpax3and MegaETHCL8Y-cb0xfBAa45A537cF07dC768c469FfaC4e88208B0098D), plus other real-value registry tokens (ALPHA, USTR, USTRIX, SpaceUSD, future listed assets).The picker is the Amount field combobox (
data-testid="token-select"), not Settings → Tokens or Settings → Faucet.Why this is needed
TransferFormselectstransferTokens[0]. If testa/testb/tdec sort first (registry pagination or raceyObject.entrieson mappings), a user who opens Bridge can start a transfer on a noneconomic faucet token without noticing.Object.entriesorder can change across reloads. Users see a jumpy dropdown and an inconsistent default token.Constraints & guardrails
id/tokenId/evmTokenAddress, dest mappings, decimals, fees, hash encoding, or which tokens are offered for a route."test"(false positives). Prefer a denylist of known noneconomic token ids (Terra denom/CW20, plus EVM/SPL aliases from the faucet catalog) with case-insensitive address/denom match. Unknown registered tokens default to economic (top group) so a newly listed real asset is not buried.symbol()is spoofed toCL8Y/LUNC. Rank by canonical registryid/ addresses, not the label shown in the list.id). Reloads and mapping-query races must not reshuffle.is_economicflag, no operator API. Frontend-only.ulunaremains economic (top). Synthetic SOL used only in the faucet panel is out of scope unless it also appears in the Transfer picker.docs/FRONTEND_BRIDGE_INVARIANTS.md(INV-UX1 CTA/amount, INV-RCP1 recipient, token logos, mapping-load gating from glab #89).FaucetPanel/ docs.Relevant files
packages/frontend/src/services/transfer/buildTransferTokens.tspackages/frontend/src/services/transfer/buildTransferTokens.test.tspackages/frontend/src/types/tokenOption.tsisTest(or keep classification internal to the sorter)packages/frontend/src/utils/tokenEconomicRank.ts(name as implemented)FaucetPaneladdresses as the source of truth or extract a shared constantpackages/frontend/src/components/settings/FaucetPanel.tsxpackages/frontend/src/components/transfer/TokenSelect.tsxbuildTransferTokenspackages/frontend/src/components/transfer/AmountInput.tsxtokensthrough toTokenSelectpackages/frontend/src/components/transfer/TransferForm.tsxtransferTokens[0]; must remain valid after sortpackages/frontend/src/hooks/useSourceChainTokenMappings.tsRecordtoday; sorting in the builder must neutralize thispackages/frontend/public/tokens/tokenlist.jsonpackages/frontend/src/components/transfer/SubComponents.test.tsxpackages/frontend/e2e/token-selection.spec.tsdocs/FRONTEND_BRIDGE_INVARIANTS.mddocs/solana-mainnet-test-tokens-checklist.mdRecommended solution direction
isNoneconomicBridgeToken(id, option)) covering:MAINNET_FAUCET_TOKENSLOCAL_FAUCET_TOKENS/ env)TokenOption.id,tokenId, andevmTokenAddress(lowercased)testTokens.tsimported by FaucetPanel and the sorter) so a newly deployed test mint is classified in one place. If extraction is too large for this MR, duplicate with a comment pointing atFaucetPaneland a test that both lists stay in sync.buildTransferTokensafter each return path that yields multiple options:economic(!isNoneconomic) firstnoneconomiclastidTransferFormauto-select astransferTokens[0]— after sort that is the top economic token when any exist.idor submit path. Skip if it expands UX scope; order is the acceptance bar.docs/FRONTEND_BRIDGE_INVARIANTS.md.TokenSelectonly. Callers that usetransferTokens[0]for default selection would still pick the unsorted first item.Acceptance criteria
docs/FRONTEND_BRIDGE_INVARIANTS.mddocuments the ranking rule.make test-frontend/ package scripts).Test plan — functional paths
Unit (
buildTransferTokens+ rank helper)[testa, uluna, testb, CL8Y CW20]→[uluna, CL8Y, testa, testb](economic first; within-group stable).symbol: 'CL8Y'still ranks as test (bottom).symbol: 'testa'still ranks as economic (top).sourceChainMappingsobject) output order matches the same comparator (insertion order ignored).evm_token_addressrows) same comparator.[]while EVM mappings loading) unchanged.0xEVM addresses still match the denylist.Component (TokenSelect / AmountInput)
role="option"order matches the sortedtokensprop (no re-sort that undoes the builder).tokens.length > 1behavior) still holds.TransferForm default selection
selectedTokenId→ selects first economic id.Local QA vs mainnet catalogs
MAINNET_FAUCET_TOKENSids andLOCAL_FAUCET_TOKENSsymbols/addresses.Playwright (
packages/frontend/e2e/token-selection.spec.tsor new spec)data-tokenid; assert no test id appears before an economic id.Manual QA (mainnet staging or production with faucet)
Test plan — attack, hack & abuse vectors
symbol()toCL8Y/LUNCon a test minttestaon an economic mint to bury itsymbol: 'testa'still top{displayLabel}); nodangerouslySetInnerHTML. Sort must not inject markup<img>/<script>; expect escaped textdata-tokenidequal to the row’sid; click handler uses that id, not visible textonChangereceives test idevmTokenAddressbetween rows?token=testaforcing a buried token without disclosurearia-selectedon the real selected optiondata-tokenid/ name, not “first option”e2e/*.spec.tsfor first-token assumptions; fix in the same MRVerification criteria
Merge is verified when all of the following hold:
buildTransferTokenstests pass, including spoofed-symbol and mixed-set order.make test-frontend(or package equivalent) is green.Out of scope
economicflag or Terra registry schema changesReferences
packages/frontend/src/components/settings/FaucetPanel.tsx(MAINNET_FAUCET_TOKENS,LOCAL_FAUCET_TOKENS)docs/solana-mainnet-test-tokens-checklist.mdpackages/frontend/public/tokens/tokenlist.jsonpackages/frontend/src/components/transfer/TransferForm.tsx(transferTokens[0])mentioned in commit
2a20cd3e4fmentioned in merge request !155
mentioned in commit
d86a844dfementioned in commit
50b593d47cMerge status (!155 landed on
main; this issue auto-closed)Code AC is met (economic-then-test ranking, closed-id denylist, default first economic, keep explicit test selection). Non-blocking leftovers from review — not reopen-worthy:
e2e/token-selection.spec.tskeep-selection locator uses descendanthas: [data-tokenid]while the attribute is on the option itself; prefer[role=option][data-tokenid=…].import.meta.envthe way Vite does.No separate follow-up issue opened for these nits.