Sort token pickers by USD value via TokenList - #2830
Conversation
Apply sortBalancesByValue inside the TokenList component so that both the Send and Swap flow token pickers display tokens in descending USD value order, consistent with the Account view. This centralizes the ordering policy in the shared picker renderer, eliminating the class of bug where a new caller of TokenList forgets to sort. Add unit tests covering value-sorted rendering, empty-price no-op behavior, LP share exclusion, and hidden asset filtering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-443c6a896799db64a563 (SDF collaborators only — install instructions in the release description) |
There was a problem hiding this comment.
Pull request overview
This PR aligns the Send/Swap token picker ordering with the Account view by moving USD-value-based sorting into the shared TokenList picker component, ensuring consistent ordering wherever TokenList is used.
Changes:
- Apply
sortBalancesByValue(tokens, tokenPrices)insideTokenListbefore rendering. - Update
TokenListrendering logic to use the sorted token list for empty-state and row rendering. - Add unit tests covering value-based ordering, empty-price no-op behavior, and existing filtering rules (hidden assets + LP shares).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| extension/src/popup/components/InternalTransaction/TokenList/index.tsx | Sorts picker token rows by descending USD value (via sortBalancesByValue) before applying existing filters and rendering. |
| extension/src/popup/components/InternalTransaction/TokenList/tests/TokenList.test.tsx | Adds coverage to verify value-sort ordering, no-op behavior when prices are empty, and preservation of LP/hidden-asset filtering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
The fix work, but there is room for improvement like memoization Memoization: sortBalancesByValue runs on every render, and the Swap picker re-renders on each (debounced) keystroke. List sizes are small so this is negligible, but a useMemo(() => sortBalancesByValue(tokens, tokenPrices), [tokens, tokenPrices]) would make intent clear and avoid resorting on unrelated re-renders. |
Wrap the sortBalancesByValue call in useMemo to avoid resorting on unrelated re-renders (e.g., debounced keystroke re-renders in Swap). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proposal: Apply value sort to token pickers via TokenList
Problem
The Account view sorts token balances by descending USD value (via
sortBalancesByValueinpopup/helpers/balance.ts). The Send flow's token picker (SendDestinationAsset) and the Swap flow's token picker (SwapAsset) both render tokens insortBalancesorder — XLM first, LP shares last, everything else in API insertion order. This is not value-sorted and is inconsistent with the Account view.Solution
Centralize the value-sort inside
TokenListitself.TokenListis exclusively a picker component — it is only used bySendDestinationAsset(popup/components/send/SendDestinationAsset/index.tsx) andSwapAsset(popup/components/swap/SwapAsset/index.tsx), never by the Account view (which has its own rendering path viaAccountAssets+useStableSortedBalances). SinceTokenListalready receivestokenPricesas a prop and is the single shared picker renderer, it is the natural place to own the picker ordering policy.Why TokenList and not a shared hook? Putting the sort in a hook or helper that callers must remember to invoke is the same pattern that produced this bug — the sort existed (
sortBalancesByValue), but nobody called it in the picker paths. Placing it insideTokenListmakes correct ordering structural: every picker gets it automatically, and no future caller can accidentally skip it. The Account view is unaffected because it uses a completely separate rendering path (AccountAssetscomponent withuseStableSortedBalances).Changes
File:
extension/src/popup/components/InternalTransaction/TokenList/index.tsxsortBalancesByValuefrompopup/helpers/balance.tokensbefore rendering:tokenswithsortedTokensin the render body (the.filter().filter().map()chain).This is a single change point — both callers automatically get value-sorted rendering with zero modifications.
Testing
New tests for
TokenList:tokenPriceshas entries.tokenPricesis empty{}(no-op behavior).Note:
sortBalancesByValuealready has direct unit tests inpopup/helpers/__tests__/balance.test.jscovering its sorting logic, edge cases, and tie-breaking. No additional helper-level tests are needed.Edge Cases
SwapAsset, filtering only applies for search terms > 2 characters (useSwapFromData.tsx:107-127). When no search is active, the full balance list reachesTokenList; when search is active, a filtered subset reaches it. In both cases,sortBalancesByValueapplied insideTokenListproduces the correct descending-value order for whatever subset it receives.sortBalancesByValueis a no-op when prices is empty or null — tokens keep their existingsortBalancesorder (XLM first, LP last)."0"are treated as priced with value 0 (sorting them below assets with positive value but above unpriced assets). This matches the existingsortBalancesByValuesemantics.What this does NOT change
sortBalancesinuseGetBalancesremains (provides tie-breaking insertion order for assets without price data).Closes #2820