feat(token-select): sort by positive balance by default when wallet connected#475
feat(token-select): sort by positive balance by default when wallet connected#475gabitoesmiapodo merged 6 commits intodevelopfrom
Conversation
…onnected - Add sortByBalance prop to TokenSelect / TokenInput / TokenDropdown; defaults to isWalletConnected so held tokens surface without any consumer-side change - Decouple balance fetch/sort from showBalance so the balance column and the sort are independent concerns; showBalance only controls the per-row balance column - Add updateTokensWithRawBalances export for on-chain multicall path - Add multicall fallback in useTokens: when the active chain is not covered by LI.FI (e.g. Sepolia), fetch ERC-20 balances via multicall and native balance via getBalance, then sort with the same sortFn - Add tests asserting positive-balance tokens precede zero-balance tokens and that source order is preserved within each group Closes #466
…ble hook call
WalletStatusVerifier was calling useWeb3Status() directly in addition to
calling it internally via useWalletStatus(), executing the hook body twice
per render. Exposing web3Status from useWalletStatus eliminates the redundant
call.
Also fixes || to ?? for chain ID fallback (prevents a falsy 0 chainId from
silently falling through) and adds chainId={sepolia.id} to the TransactionButton
in NativeToken — without it the inner button checked against appChainId instead
of the Sepolia chain already verified by the parent WalletStatusVerifier.
…refactor - Lowercase token addresses when keying the on-chain balances map and when reading in updateTokensWithRawBalances, preventing silent zero-balance fallback if token lists mix address casing - Complete web3Status exposure from useWalletStatus (was missing from the previous commit): add web3Status to WalletStatus interface and return, remove the redundant useWeb3Status() call from WalletStatusVerifier - Assert result.current.web3Status in useWalletStatus tests - Update all useWalletStatus mock shapes across test files to carry web3Status - Document multicall fallback path and absent priceUSD in useTokens JSDoc - Add inline comment on NativeToken chainId prop - Move threeTokens fixture inside its describe block
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR improves the token selection UX by prioritizing tokens the connected wallet actually holds, sorting positive-balance tokens to the top (USD-value desc when LI.FI price data is available) and adding an on-chain multicall fallback for chains not supported by LI.FI. It also simplifies wallet-status consumption by returning web3Status directly from useWalletStatus, with related test updates.
Changes:
- Add balance-aware sorting behavior to token selection flows (defaulting to enabled when a wallet is connected) while keeping
showBalancefocused on rendering the balance column. - Implement on-chain multicall balance fetching fallback for LI.FI-unsupported chains and adjust balance/value rendering to handle missing price data.
- Expose
web3StatusviauseWalletStatusand update consumers/tests accordingly.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useTokens.ts | Adds multicall fallback balance fetching and raw-balance sorting for unsupported chains. |
| src/hooks/useTokens.test.ts | Adds/expands unit tests for positive-balance-first sorting and raw-balance fallback behavior. |
| src/components/sharedComponents/TokenSelect/index.tsx | Introduces sortByBalance prop and enables balance fetching by default when connected. |
| src/components/sharedComponents/TokenSelect/List/TokenBalance.tsx | Shows “N/A” when priceUSD is unavailable (e.g., multicall fallback). |
| src/components/sharedComponents/TokenInput/index.tsx | Passes through and documents sortByBalance. |
| src/components/sharedComponents/TokenDropdown.tsx | Documents sortByBalance in component JSDoc. |
| src/hooks/useWalletStatus.ts | Returns web3Status from useWalletStatus to avoid redundant hook calls. |
| src/components/sharedComponents/WalletStatusVerifier.tsx | Uses web3Status from useWalletStatus instead of calling useWeb3Status directly. |
| src/hooks/useWalletStatus.test.ts | Adds assertions for the newly exposed web3Status. |
| src/hooks/useWeb3Status.test.ts | Updates connected-status verifier test to rely on useWalletStatus().web3Status. |
| src/components/sharedComponents/WalletStatusVerifier.test.tsx | Updates mocks to include web3Status in useWalletStatus. |
| src/components/sharedComponents/TransactionButton.test.tsx | Updates useWalletStatus mocks to satisfy new return shape (currently via unsafe casts). |
| src/components/sharedComponents/SignButton.test.tsx | Updates useWalletStatus mocks to satisfy new return shape (currently via unsafe casts). |
| src/components/pageComponents/home/Examples/demos/TransactionButton/NativeToken.tsx | Makes demo’s Sepolia chainId explicit when using TransactionButton. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in 9f5b773 as part of the shared mockWeb3Status stub rollout in this file.
| >['targetChain'], | ||
| targetChainId: 10, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in d8c5ae2 as part of the shared mockWeb3Status stub rollout in this file.
| const { isLoadingBalances, tokensByChainId } = useTokens({ | ||
| chainId, | ||
| withBalance: showBalance, | ||
| withBalance: showBalance || resolvedSortByBalance, |
There was a problem hiding this comment.
sortByBalance is documented as a separate sorting toggle, but the current wiring only uses it to decide withBalance; useTokens will still sort whenever withBalance is true. This means sortByBalance={false} does not actually disable sorting when showBalance={true} (balances will be fetched and updateTokensBalances will sort). Consider passing an explicit sortByBalance flag into useTokens/updateTokensBalances (or conditionally applying the sort in TokenSelect) so consumers can independently choose to show the balance column without enabling sorting (and vice versa).
| withBalance: showBalance || resolvedSortByBalance, | |
| withBalance: showBalance || resolvedSortByBalance, | |
| sortByBalance: resolvedSortByBalance, |
There was a problem hiding this comment.
Fixed in 0897b15. Added a sortByBalance parameter (default true) to useTokens, updateTokensBalances, and updateTokensWithRawBalances; the sort step is now skipped when false. TokenSelect passes sortByBalance: resolvedSortByBalance, so sortByBalance={false} with showBalance={true} now shows balances without reordering.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub (as done in WalletStatusVerifier tests) instead of casting undefined.
There was a problem hiding this comment.
Fixed in 9f5b773. Replaced the undefined as unknown cast with a shared mockWeb3Status stub mirroring WalletStatusVerifier.test.tsx.
| >['targetChain'], | ||
| targetChainId: 10, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in 9f5b773 as part of the shared mockWeb3Status stub rollout in this file.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Fixed in d8c5ae2. Replaced the undefined as unknown cast with a shared mockWeb3Status stub mirroring WalletStatusVerifier.test.tsx.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in d8c5ae2 as part of the shared mockWeb3Status stub rollout in this file.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in d8c5ae2 as part of the shared mockWeb3Status stub rollout in this file.
| targetChain: { id: 1, name: 'Ethereum' } as ReturnType<typeof useWalletStatus>['targetChain'], | ||
| targetChainId: 1, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in 9f5b773 as part of the shared mockWeb3Status stub rollout in this file.
| >['targetChain'], | ||
| targetChainId: 10, | ||
| switchChain: mockSwitchChain, | ||
| web3Status: undefined as unknown as ReturnType<typeof useWalletStatus>['web3Status'], | ||
| }) |
There was a problem hiding this comment.
The useWalletStatus mock returns web3Status as undefined via a cast. Since useWalletStatus now guarantees a real Web3Status object, this mock can hide runtime issues if the component starts using web3Status. Prefer returning a minimal but valid Web3Status stub instead of casting undefined.
There was a problem hiding this comment.
Addressed in 9f5b773 as part of the shared mockWeb3Status stub rollout in this file.
Summary
Closes #466
The token selector now surfaces tokens with a positive balance at the top of the list (ordered by USD value descending) whenever a wallet is connected, without the consumer needing to set
showBalance={true}. Previously, the sort-by-USD logic inuseTokenswas gated onwithBalance, conflating two concerns: fetching/sorting balances and rendering the balance column per row. This PR decouples those concerns and extends sorting to LI.FI-unsupported chains via a multicall fallback.Changes
useTokens: introducesortByBalanceparam independent of the balance-columnwithBalanceflag; fetch and apply USD-value sort whenever a wallet is connected andsortByBalanceis trueTokenSelect,TokenInput,TokenDropdown: defaultsortByBalancetotruewhen wallet is connected;showBalancestill controls only the balance columnuseWalletStatus: exposeweb3Statusdirectly to eliminate a redundant hook call in consumersuseTokens.test.tscoverage asserting positive-balance tokens sort before zero-balance tokens; updateduseWalletStatusanduseWeb3Statustests accordinglyAcceptance criteria
showBalance={true}showBalancecontrols only whether the balance column is rendered per rowsortByBalanceprop is documented in JSDoc onTokenSelect,TokenInput,TokenDropdownupdateTokensBalancesTest plan
Automated tests
src/hooks/useTokens.test.tssrc/hooks/useWalletStatus.test.tssrc/hooks/useWeb3Status.test.tssrc/components/sharedComponents/WalletStatusVerifier.test.tsxsrc/components/sharedComponents/SignButton.test.tsxsrc/components/sharedComponents/TransactionButton.test.tsxManual verification
Breaking changes
None.
Checklist
Screenshots
None.