Skip to content

Commit a3c6389

Browse files
committed
feat: show all tokens in source
1 parent d0f1afe commit a3c6389

6 files changed

Lines changed: 69 additions & 61 deletions

File tree

src/components/TokenBalance.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import { useTheme } from '@mui/material/styles';
55

66
const TokenBalance = ({
77
balance,
8-
isSource,
98
isFetching,
109
price,
1110
}: {
1211
balance: sdkAmount.Amount | null;
13-
isSource?: boolean;
1412
isFetching?: boolean;
1513
price?: string | null;
1614
}) => {
@@ -26,8 +24,8 @@ const TokenBalance = ({
2624
);
2725
}
2826

29-
const shouldHideBalance =
30-
!isSource && balance && sdkAmount.display(balance) === '0';
27+
// Hide 0 / $0 for both source and destination lists
28+
const shouldHideBalance = !balance || sdkAmount.display(balance) === '0';
3129

3230
return (
3331
<Stack alignItems="flex-end">

src/hooks/useTokenList.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
sortTokensByPreference,
1010
applyTokenWhitelist,
1111
applyCustomTokenSupport,
12-
filterTokensByBalance,
1312
applyShittokenFilter,
1413
} from 'utils/tokenListUtils';
1514
import config from 'config';
@@ -62,13 +61,6 @@ export const useTokenList = ({
6261
tokens = applyShittokenFilter(tokens);
6362
}
6463

65-
// Filter by balance for source tokens when not searching
66-
// This allows users to search for zero-balance tokens by contract address if needed
67-
// Never filter destination tokens by balance
68-
if (isSourceList && !searchQuery) {
69-
tokens = filterTokensByBalance(tokens, balances, wallet.address);
70-
}
71-
7264
return tokens;
7365
// eslint-disable-next-line react-hooks/exhaustive-deps
7466
}, [

src/hooks/useTokenListWithSearch.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type { Token } from 'config/tokens';
1212
import config from 'config';
1313
import { useTokens } from 'contexts/TokensContext';
1414
import { getTokenSymbol } from 'utils';
15-
import { filterTokensByBalance } from 'utils/tokenListUtils';
1615
import type { Balances } from 'utils/wallet/types';
1716
import { unionBy } from 'es-toolkit';
1817

@@ -140,11 +139,6 @@ export const useTokenListWithSearch = ({
140139
});
141140
}
142141

143-
// Filter by balance for source tokens when not searching
144-
if (isSource && !deferredSearch) {
145-
tokens = filterTokensByBalance(tokens, balances, walletAddress);
146-
}
147-
148142
// For destination token list in same-chain swaps, filter out the source token
149143
if (!isSource && isSameChainSwap && sourceToken) {
150144
tokens = tokens.filter(
@@ -161,8 +155,6 @@ export const useTokenListWithSearch = ({
161155
isSource,
162156
isSameChainSwap,
163157
sourceToken,
164-
balances,
165-
walletAddress,
166158
]);
167159

168160
const tokenPrices = useMemo(

src/views/v2/Bridge/AssetPicker/TokenItem.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ function TokenItem(props: TokenItemProps) {
153153
<TokenBalance
154154
balance={props.balance}
155155
price={props.price}
156-
isSource={props.isSource}
157156
isFetching={props.isFetchingBalance}
158157
/>
159158
</ListItemButton>

src/views/v3/Bridge/AssetPicker/TokenItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type TokenItemProps = {
2727
isSelected?: boolean;
2828
isFetchingBalance?: boolean;
2929
isSource?: boolean;
30+
dimmed?: boolean;
3031
};
3132

3233
function TokenItem(props: TokenItemProps) {
@@ -79,6 +80,7 @@ function TokenItem(props: TokenItemProps) {
7980
sx={{
8081
...styles.tokenListItem,
8182
...(props.isSelected && styles.tokenListItemSelected),
83+
...(props.dimmed ? { opacity: 0.6 } : {}),
8284
}}
8385
dense
8486
data-testid={`token-button-${chain.toLowerCase()}-${token.address.toString()}`}
@@ -154,7 +156,6 @@ function TokenItem(props: TokenItemProps) {
154156
<TokenBalance
155157
balance={props.balance}
156158
price={props.price}
157-
isSource={props.isSource}
158159
isFetching={props.isFetchingBalance}
159160
/>
160161
</ListItemButton>

src/views/v3/Bridge/AssetPicker/TokenList.tsx

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from 'react';
1+
import React, { Fragment, useMemo } from 'react';
22
import { Box, Card, CardContent, Skeleton, useTheme } from '@mui/material';
33
import CircularProgress from '@mui/material/CircularProgress';
44
import ListItemButton from '@mui/material/ListItemButton';
@@ -14,6 +14,7 @@ import config from 'config';
1414
import { useTokens } from 'contexts/TokensContext';
1515
import type { Balances } from 'utils/wallet/types';
1616
import { useTokenListWithSearch } from 'hooks/useTokenListWithSearch';
17+
import { amount as sdkAmount } from '@wormhole-foundation/sdk';
1718

1819
type Props = {
1920
tokenList: Array<Token>;
@@ -102,11 +103,6 @@ const TokenList = (props: Props) => {
102103

103104
// Determine the current state of the token list
104105
const listState = useMemo(() => {
105-
// For source list, require wallet to show balances/tokens from wallet
106-
if (props.isSource && !props.wallet?.address && !props.isConnectingWallet) {
107-
return 'empty';
108-
}
109-
110106
// Currently fetching initial data
111107
if (props.isFetching) {
112108
return 'loading';
@@ -130,27 +126,43 @@ const TokenList = (props: Props) => {
130126
const shouldShowLoadingState = listState === 'loading';
131127
const shouldShowEmptyMessage = listState === 'empty';
132128

129+
// Build sectioned list for source picker when not searching
130+
const isGroupingEnabled = props.isSource && !props.searchQuery;
131+
132+
const { itemsForRender, ownedCount } = useMemo(() => {
133+
if (!isGroupingEnabled) {
134+
return { itemsForRender: sortedTokens, ownedCount: 0 };
135+
}
136+
137+
const hasPositiveBalance = (token: Token) => {
138+
const bal = props.balances?.[token.key]?.balance;
139+
return !!(bal && sdkAmount.units(bal) > 0n);
140+
};
141+
142+
const nativeToken = sortedTokens.find((token) => token.isNativeGasToken);
143+
const ownedTokens = sortedTokens.filter(hasPositiveBalance);
144+
const ownedSet = new Set(ownedTokens.map((t) => t.key));
145+
146+
const nativePart =
147+
nativeToken && !ownedSet.has(nativeToken.key) ? [nativeToken] : [];
148+
149+
const rest = sortedTokens.filter(
150+
(t) =>
151+
!ownedSet.has(t.key) && (!nativeToken || t.key !== nativeToken.key),
152+
);
153+
154+
const itemsForRender = [...ownedTokens, ...nativePart, ...rest];
155+
156+
return { itemsForRender, ownedCount: ownedTokens.length };
157+
}, [isGroupingEnabled, props.balances, sortedTokens]);
158+
133159
const searchList = (
134160
<SearchableList<Token>
135161
searchPlaceholder={placeholder}
136162
sx={styles.tokenList}
137163
dataTestId="token-search-list"
138164
searchQuery={props.searchQuery}
139-
listTitle={
140-
shouldShowEmptyMessage ? (
141-
emptyMessage
142-
) : (
143-
<Box display="flex" width="100%">
144-
<Typography
145-
style={{ flexGrow: '2' }}
146-
fontSize={14}
147-
color={theme.palette.text.secondary}
148-
>
149-
Tokens on {props.selectedChainConfig.displayName}
150-
</Typography>
151-
</Box>
152-
)
153-
}
165+
listTitle={shouldShowEmptyMessage ? emptyMessage : ''}
154166
loading={
155167
shouldShowLoadingState &&
156168
[1, 2, 3].map((x) => (
@@ -161,32 +173,49 @@ const TokenList = (props: Props) => {
161173
</ListItemButton>
162174
))
163175
}
164-
items={sortedTokens}
176+
items={itemsForRender}
165177
onQueryChange={(query) => {
166178
props.onSearchQueryChange(query);
167179
}}
168-
renderFn={(token: Token) => {
180+
renderFn={(token: Token, index: number) => {
169181
const balance = props.balances?.[token.key]?.balance;
170182
const tokenPrice = tokenPrices.get(token.key);
171183
const price =
172184
balance && tokenPrice !== undefined
173185
? getUSDFormat(calculateUSDPriceRaw(tokenPrice, balance, token))
174186
: null;
175187

188+
const isRestSection = isGroupingEnabled && index >= ownedCount;
189+
176190
return (
177-
<TokenItem
178-
key={token.key}
179-
token={token}
180-
chain={props.selectedChainConfig.sdkName}
181-
onClick={() => {
182-
props.onSelectToken(token);
183-
}}
184-
isSource={props.isSource}
185-
balance={balance}
186-
price={price}
187-
isSelected={token.key === props.selectedToken?.key}
188-
isFetchingBalance={props.isFetchingBalances}
189-
/>
191+
<Fragment key={token.key}>
192+
{isGroupingEnabled && index === 0 && ownedCount > 0 && (
193+
<Box sx={{ padding: '4px 16px' }}>
194+
<Typography fontSize={14} color={theme.palette.text.secondary}>
195+
Your tokens
196+
</Typography>
197+
</Box>
198+
)}
199+
{isGroupingEnabled && index === ownedCount && (
200+
<Box sx={{ padding: '4px 16px' }}>
201+
<Typography fontSize={14} color={theme.palette.text.secondary}>
202+
All tokens
203+
</Typography>
204+
</Box>
205+
)}
206+
<TokenItem
207+
key={token.key}
208+
token={token}
209+
chain={props.selectedChainConfig.sdkName}
210+
onClick={() => props.onSelectToken(token)}
211+
isSource={props.isSource}
212+
balance={balance}
213+
price={price}
214+
isSelected={token.key === props.selectedToken?.key}
215+
isFetchingBalance={props.isFetchingBalances}
216+
dimmed={isRestSection}
217+
/>
218+
</Fragment>
190219
);
191220
}}
192221
/>
@@ -196,9 +225,6 @@ const TokenList = (props: Props) => {
196225
<Card sx={styles.card} variant="elevation">
197226
<CardContent sx={styles.tokenListContainer}>
198227
<Box sx={{ display: 'flex', padding: '0 16px' }}>
199-
<Typography width="100%" sx={styles.title}>
200-
Select a token
201-
</Typography>
202228
{isFetchingToken || props.isFetchingBalances ? (
203229
<CircularProgress
204230
sx={{ alignSelf: 'flex-end', marginBottom: '12px' }}

0 commit comments

Comments
 (0)