diff --git a/.changeset/silly-feet-buy.md b/.changeset/silly-feet-buy.md new file mode 100644 index 000000000..e176fc331 --- /dev/null +++ b/.changeset/silly-feet-buy.md @@ -0,0 +1,5 @@ +--- +'@reservoir0x/relay-kit-ui': patch +--- + +Optimize token list merging and sorting logic in TokenSelector diff --git a/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx b/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx index 1201ae2e1..060cc8323 100644 --- a/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx +++ b/packages/ui/src/components/common/TokenSelector/TokenSelector.tsx @@ -34,6 +34,7 @@ import { bitcoin } from '../../../utils/bitcoin.js' import { evmDeadAddress } from '@reservoir0x/relay-sdk' import { solDeadAddress } from '@reservoir0x/relay-sdk' import { bitcoinDeadAddress } from '@reservoir0x/relay-sdk' +import { mergeTokenLists } from '../../../utils/tokens.js' export type TokenSelectorProps = { openState?: [boolean, React.Dispatch>] @@ -263,7 +264,7 @@ const TokenSelector: FC = ({ suggestedTokenQuery ? { tokens: suggestedTokenQuery, - limit: 20, + limit: 30, depositAddressOnly } : undefined, @@ -299,47 +300,44 @@ const TokenSelector: FC = ({ return mergedList }, [tokenList, externalTokenList]) - // Filter out unconfigured chains and append Relay Chain to each currency + // Enhance token list with Relay chain data, balances and sort by usd value/balances const enhancedCurrencyList = useMemo(() => { - const _tokenList = - combinedTokenList && (combinedTokenList as any).length - ? (combinedTokenList as CurrencyList[]) - : undefined - + // Filter suggested tokens by chain if needed const filteredSuggestedTokens = chainFilter.id - ? suggestedTokens - ?.map((tokenList) => - tokenList.filter((token) => token.chainId === chainFilter.id) - ) - .filter((tokenList) => tokenList.length > 0) + ? suggestedTokens?.map((tokenList) => + tokenList.filter((token) => token.chainId === chainFilter.id) + ) : suggestedTokens - let list = - context === 'from' && - useDefaultTokenList && - chainFilter.id === undefined && - filteredSuggestedTokens && - filteredSuggestedTokens.length > 0 - ? filteredSuggestedTokens - : combinedTokenList - - const ethTokens = _tokenList?.find( - (list) => list[0] && list[0].groupID === 'ETH' + // Only merge suggested tokens when using default list + const list = useDefaultTokenList + ? mergeTokenLists([filteredSuggestedTokens, combinedTokenList]) + : combinedTokenList || [] + + // Prioritize ETH and USDC tokens + const ethTokens = list.find( + (tokenList) => tokenList[0] && tokenList[0].groupID === 'ETH' ) - const usdcTokens = _tokenList?.find( - (list) => list[0] && list[0].groupID === 'USDC' + const usdcTokens = list.find( + (tokenList) => tokenList[0] && tokenList[0].groupID === 'USDC' ) - if (list && suggestedTokens) { - list = list?.filter( - (tokenList) => - tokenList[0] && - tokenList[0].groupID !== 'ETH' && - tokenList[0].groupID !== 'USDC' - ) - list = [ethTokens ?? [], usdcTokens ?? []].concat(list) - } - const mappedList = list?.map((currencyList) => { + // Remove ETH/USDC from main list and add them to the front + const filteredList = list.filter( + (tokenList) => + tokenList[0] && + tokenList[0].groupID !== 'ETH' && + tokenList[0].groupID !== 'USDC' + ) + + const sortedList = [ + ...(ethTokens ? [ethTokens] : []), + ...(usdcTokens ? [usdcTokens] : []), + ...filteredList + ] + + // Map and enhance the currency list + const mappedList = sortedList?.map((currencyList) => { const filteredList = currencyList .map((currency) => { const relayChain = configuredChains.find( @@ -394,8 +392,21 @@ const TokenSelector: FC = ({ totalValueUsd } }) - .filter((list) => list !== undefined) - .sort((a, b) => (b?.totalValueUsd ?? 0) - (a?.totalValueUsd ?? 0)) + .filter((list): list is NonNullable => list !== undefined) + .sort((a, b) => { + // First sort by USD value if available + if (a.totalValueUsd !== b.totalValueUsd) { + return b.totalValueUsd - a.totalValueUsd + } + // Then sort by balance if USD value is equal or undefined + if (a.totalBalance !== b.totalBalance) { + return Number(b.totalBalance - a.totalBalance) + } + // Finally prioritize verified tokens + const aVerified = a.chains[0]?.metadata?.verified ?? false + const bVerified = b.chains[0]?.metadata?.verified ?? false + return bVerified === aVerified ? 0 : bVerified ? 1 : -1 + }) }, [ context, combinedTokenList, diff --git a/packages/ui/src/components/common/TokenSelector/steps/SetCurrencyStep.tsx b/packages/ui/src/components/common/TokenSelector/steps/SetCurrencyStep.tsx index 932de20de..fa8cc4e1e 100644 --- a/packages/ui/src/components/common/TokenSelector/steps/SetCurrencyStep.tsx +++ b/packages/ui/src/components/common/TokenSelector/steps/SetCurrencyStep.tsx @@ -409,7 +409,7 @@ export const SetCurrencyStep: FC = ({ }} /> - Popular Tokens + Suggested Tokens ) : null} diff --git a/packages/ui/src/utils/tokens.ts b/packages/ui/src/utils/tokens.ts index cf5e14161..065ff1d77 100644 --- a/packages/ui/src/utils/tokens.ts +++ b/packages/ui/src/utils/tokens.ts @@ -1,3 +1,4 @@ +import type { CurrencyList } from '@reservoir0x/relay-kit-hooks' import type { Token } from '../types/index.js' import { ASSETS_RELAY_API } from '@reservoir0x/relay-sdk' import type { paths, RelayChain } from '@reservoir0x/relay-sdk' @@ -42,3 +43,26 @@ export const findBridgableToken = (chain?: RelayChain, token?: Token) => { } return null } + +export const mergeTokenLists = (lists: (CurrencyList[] | undefined)[]) => { + const mergedList: CurrencyList[] = [] + const seenTokens = new Set() + + lists.forEach((list) => { + if (!list) return + + list.forEach((currencyList) => { + const currency = currencyList[0] + if (!currency) return + + const tokenKey = `${currency.chainId}:${currency.address?.toLowerCase()}` + + if (!seenTokens.has(tokenKey)) { + seenTokens.add(tokenKey) + mergedList.push(currencyList) + } + }) + }) + + return mergedList +}