From 9c5d5b57b74534a022a0323f460ccef9ba73724e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 22 Oct 2024 20:15:38 +0500 Subject: [PATCH] refactor: fix lp tokens segregation --- .../updaters/orders/OrdersFromApiUpdater.ts | 12 +- .../containers/SelectTokenWidget/index.tsx | 4 +- .../updaters/BalancesAndAllowancesUpdater.tsx | 4 +- ...{useAllTokens.ts => useAllActiveTokens.ts} | 3 +- .../tokens/src/hooks/tokens/useAllLpTokens.ts | 23 +-- libs/tokens/src/index.ts | 2 +- libs/tokens/src/state/tokens/allTokensAtom.ts | 133 +++++------------- .../src/utils/tokenMapToListWithLogo.ts | 14 +- libs/types/src/common.ts | 2 + 9 files changed, 70 insertions(+), 127 deletions(-) rename libs/tokens/src/hooks/tokens/{useAllTokens.ts => useAllActiveTokens.ts} (78%) diff --git a/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts b/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts index 3d0a497c21..94bdd650f5 100644 --- a/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts +++ b/apps/cowswap-frontend/src/common/updaters/orders/OrdersFromApiUpdater.ts @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { NATIVE_CURRENCIES } from '@cowprotocol/common-const' import { EnrichedOrder, EthflowData, OrderClass, SupportedChainId as ChainId } from '@cowprotocol/cow-sdk' -import { TokensByAddress, useAllTokens } from '@cowprotocol/tokens' +import { TokensByAddress, useAllActiveTokens } from '@cowprotocol/tokens' import { useIsSafeWallet, useWalletInfo } from '@cowprotocol/wallet' import { Order, OrderStatus } from 'legacy/state/orders/actions' @@ -32,7 +32,7 @@ const statusMapping: Record = { function _transformOrderBookOrderToStoreOrder( order: EnrichedOrder, chainId: ChainId, - allTokens: TokensByAddress + allTokens: TokensByAddress, ): Order | undefined { const { uid: id, @@ -63,7 +63,7 @@ function _transformOrderBookOrderToStoreOrder( console.warn( `OrdersFromApiUpdater::Tokens not found for order ${id}: sellToken ${ !inputToken ? sellToken : 'found' - } - buyToken ${!outputToken ? buyToken : 'found'}` + } - buyToken ${!outputToken ? buyToken : 'found'}`, ) return } @@ -110,7 +110,7 @@ function _getInputToken( isEthFlow: boolean, chainId: ChainId, sellToken: string, - allTokens: TokensByAddress + allTokens: TokensByAddress, ): ReturnType { return isEthFlow ? NATIVE_CURRENCIES[chainId] : getTokenFromMapping(sellToken, chainId, allTokens) } @@ -141,7 +141,7 @@ export function OrdersFromApiUpdater(): null { const clearOrderStorage = useClearOrdersStorage() const { account, chainId } = useWalletInfo() - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const tokensAreLoaded = useMemo(() => Object.keys(allTokens).length > 0, [allTokens]) const addOrUpdateOrders = useAddOrUpdateOrders() const updateApiOrders = useSetAtom(apiOrdersAtom) @@ -176,7 +176,7 @@ export function OrdersFromApiUpdater(): null { console.error(`OrdersFromApiUpdater::Failed to fetch orders`, e) } }, - [addOrUpdateOrders, ordersFromOrderBook, getTokensForOrdersList, isSafeWallet] + [addOrUpdateOrders, ordersFromOrderBook, getTokensForOrdersList, isSafeWallet], ) useEffect(() => { diff --git a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx index 6c221910ab..5d4edd86c9 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/containers/SelectTokenWidget/index.tsx @@ -9,7 +9,7 @@ import { useAddList, useAddUserToken, useAllListsList, - useAllTokens, + useAllActiveTokens, useFavoriteTokens, useUnsupportedTokens, useUserAddedTokens, @@ -75,7 +75,7 @@ export function SelectTokenWidget({ displayLpTokenLists }: SelectTokenWidgetProp const addCustomTokenLists = useAddList((source) => addListAnalytics('Success', source)) const importTokenCallback = useAddUserToken() - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const favoriteTokens = useFavoriteTokens() const userAddedTokens = useUserAddedTokens() const allTokenLists = useAllListsList() diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 422502970d..35d39d8006 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo } from 'react' import { LpToken, NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { useAllTokens } from '@cowprotocol/tokens' +import { useAllActiveTokens } from '@cowprotocol/tokens' import ms from 'ms.macro' @@ -24,7 +24,7 @@ export interface BalancesAndAllowancesUpdaterProps { export function BalancesAndAllowancesUpdater({ account, chainId }: BalancesAndAllowancesUpdaterProps) { const setBalances = useSetAtom(balancesAtom) - const allTokens = useAllTokens() + const allTokens = useAllActiveTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account) const tokenAddresses = useMemo( diff --git a/libs/tokens/src/hooks/tokens/useAllTokens.ts b/libs/tokens/src/hooks/tokens/useAllActiveTokens.ts similarity index 78% rename from libs/tokens/src/hooks/tokens/useAllTokens.ts rename to libs/tokens/src/hooks/tokens/useAllActiveTokens.ts index 958701c96a..1e75125d32 100644 --- a/libs/tokens/src/hooks/tokens/useAllTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllActiveTokens.ts @@ -4,7 +4,6 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { activeTokensAtom } from '../../state/tokens/allTokensAtom' - -export function useAllTokens(): TokenWithLogo[] { +export function useAllActiveTokens(): TokenWithLogo[] { return useAtomValue(activeTokensAtom) } diff --git a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts index 938ca1f6c3..badabbd9f8 100644 --- a/libs/tokens/src/hooks/tokens/useAllLpTokens.ts +++ b/libs/tokens/src/hooks/tokens/useAllLpTokens.ts @@ -1,27 +1,28 @@ -import { useAtomValue } from 'jotai' +import { useAtomValue } from 'jotai/index' import { LpToken, SWR_NO_REFRESH_OPTIONS } from '@cowprotocol/common-const' import useSWR from 'swr' -import { lpTokensByCategoryAtom } from '../../state/tokens/allTokensAtom' +import { activeTokensAtom, inactiveTokensAtom } from '../../state/tokens/allTokensAtom' import { TokenListCategory } from '../../types' const fallbackData: LpToken[] = [] export function useAllLpTokens(categories: TokenListCategory[] | null): LpToken[] { - const lpTokensByCategory = useAtomValue(lpTokensByCategoryAtom) + const activeTokens = useAtomValue(activeTokensAtom) + const inactiveTokens = useAtomValue(inactiveTokensAtom) return useSWR( - categories ? [lpTokensByCategory, categories] : null, - ([lpTokensByCategory, categories]) => { - return categories.reduce((acc, category) => { - if (category === TokenListCategory.LP || category === TokenListCategory.COW_AMM_LP) { - acc.push(...lpTokensByCategory[category]) - } + categories ? [activeTokens, inactiveTokens, categories] : null, + ([activeTokens, inactiveTokens, categories]) => { + const allTokens = [...activeTokens, ...inactiveTokens] + const selectOnlyCoWAmm = categories?.length === 1 && categories.includes(TokenListCategory.COW_AMM_LP) - return acc - }, []) + return allTokens.filter((token) => { + const isLp = token instanceof LpToken + return isLp ? (selectOnlyCoWAmm ? token.isCowAmm : true) : false + }) as LpToken[] }, { ...SWR_NO_REFRESH_OPTIONS, fallbackData }, ).data diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index 6100e61f21..825141feff 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -16,7 +16,7 @@ export type { TokenSearchResponse } from './hooks/tokens/useSearchToken' // Hooks export { useAllListsList } from './hooks/lists/useAllListsList' export { useAddList } from './hooks/lists/useAddList' -export { useAllTokens } from './hooks/tokens/useAllTokens' +export { useAllActiveTokens } from './hooks/tokens/useAllActiveTokens' export { useVirtualLists } from './hooks/lists/useVirtualLists' export { useFavoriteTokens } from './hooks/tokens/favorite/useFavoriteTokens' export { useUserAddedTokens } from './hooks/tokens/userAdded/useUserAddedTokens' diff --git a/libs/tokens/src/state/tokens/allTokensAtom.ts b/libs/tokens/src/state/tokens/allTokensAtom.ts index ead0e86e21..d122bc6f30 100644 --- a/libs/tokens/src/state/tokens/allTokensAtom.ts +++ b/libs/tokens/src/state/tokens/allTokensAtom.ts @@ -1,12 +1,12 @@ import { atom } from 'jotai' -import { LpToken, NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' +import { NATIVE_CURRENCIES, TokenWithLogo } from '@cowprotocol/common-const' import { TokenInfo } from '@cowprotocol/types' import { favoriteTokensAtom } from './favoriteTokensAtom' import { userAddedTokensAtom } from './userAddedTokensAtom' -import { LP_TOKEN_LIST_CATEGORIES, TokenListCategory, TokensMap } from '../../types' +import { TokenListCategory, TokensMap } from '../../types' import { lowerCaseTokensMap } from '../../utils/lowerCaseTokensMap' import { parseTokenInfo } from '../../utils/parseTokenInfo' import { tokenMapToListWithLogo } from '../../utils/tokenMapToListWithLogo' @@ -21,22 +21,9 @@ export interface TokensBySymbol { [address: string]: TokenWithLogo[] } -type TokenCategoryByMap = Record - -type LpTokensByCategory = { - [TokenListCategory.LP]: LpToken[] - [TokenListCategory.COW_AMM_LP]: LpToken[] -} - interface TokensState { - activeTokens: TokenCategoryByMap - inactiveTokens: TokenCategoryByMap -} - -const DEFAULT_TOKEN_CATEGORY_BY_MAP = { - [TokenListCategory.ERC20]: {}, - [TokenListCategory.LP]: {}, - [TokenListCategory.COW_AMM_LP]: {}, + activeTokens: TokensMap + inactiveTokens: TokensMap } const tokensStateAtom = atom((get) => { @@ -49,69 +36,38 @@ const tokensStateAtom = atom((get) => { const isListEnabled = listsEnabledState[list.source] list.list.tokens.forEach((token) => { + const category = list.category || TokenListCategory.ERC20 const tokenInfo = parseTokenInfo(chainId, token) const tokenAddressKey = tokenInfo?.address.toLowerCase() if (!tokenInfo || !tokenAddressKey) return - const category = list.category || TokenListCategory.ERC20 + if (category === TokenListCategory.LP) { + tokenInfo.isLpToken = true + } + + if (category === TokenListCategory.COW_AMM_LP) { + tokenInfo.isLpToken = true + tokenInfo.isCoWAmmToken = true + } if (isListEnabled) { - if (!acc.activeTokens[category][tokenAddressKey]) { - acc.activeTokens[category][tokenAddressKey] = tokenInfo + if (!acc.activeTokens[tokenAddressKey]) { + acc.activeTokens[tokenAddressKey] = tokenInfo } } else { - if (!acc.inactiveTokens[category][tokenAddressKey]) { - acc.inactiveTokens[category][tokenAddressKey] = tokenInfo + if (!acc.inactiveTokens[tokenAddressKey]) { + acc.inactiveTokens[tokenAddressKey] = tokenInfo } } }) return acc }, - { activeTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP }, inactiveTokens: { ...DEFAULT_TOKEN_CATEGORY_BY_MAP } }, + { activeTokens: {}, inactiveTokens: {} }, ) }) -const lpTokensMapAtom = atom((get) => { - const { chainId } = get(environmentAtom) - const listsStatesList = get(listsStatesListAtom) - - return listsStatesList.reduce( - (acc, list) => { - const category = !list.category || !LP_TOKEN_LIST_CATEGORIES.includes(list.category) ? undefined : list.category - - if (!category) { - return acc - } - - list.list.tokens.forEach((token) => { - const tokenInfo = parseTokenInfo(chainId, token) - const tokenAddressKey = tokenInfo?.address.toLowerCase() - - if (!tokenInfo || !tokenAddressKey) return - - acc[category][tokenAddressKey] = tokenInfo - }) - - return acc - }, - { [TokenListCategory.COW_AMM_LP]: {}, [TokenListCategory.LP]: {} } as TokenCategoryByMap, - ) -}) - -export const lpTokensByCategoryAtom = atom((get) => { - const { chainId } = get(environmentAtom) - const lpTokensMap = get(lpTokensMapAtom) - const getTokensByCategory = (category: TokenListCategory) => - tokenMapToListWithLogo(lpTokensMap[category], category, chainId) as LpToken[] - - return { - [TokenListCategory.LP]: getTokensByCategory(TokenListCategory.LP), - [TokenListCategory.COW_AMM_LP]: getTokensByCategory(TokenListCategory.COW_AMM_LP), - } -}) - /** * Returns a list of tokens that are active and sorted alphabetically * The list includes: native token, user added tokens, favorite tokens and tokens from active lists @@ -123,46 +79,35 @@ export const activeTokensAtom = atom((get) => { const favoriteTokensState = get(favoriteTokensAtom) const tokensMap = get(tokensStateAtom) - const lpTokensByCategory = get(lpTokensByCategoryAtom) const nativeToken = NATIVE_CURRENCIES[chainId] - return [ - // Native, user added and favorite tokens - ...tokenMapToListWithLogo( - { - [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, - ...lowerCaseTokensMap(userAddedTokens[chainId]), - ...lowerCaseTokensMap(favoriteTokensState[chainId]), - }, - TokenListCategory.ERC20, - chainId, - ), - // Tokens from active lists - ...Object.keys(tokensMap.activeTokens).reduce((acc, _category) => { - const category = _category as TokenListCategory - const categoryMap = tokensMap.activeTokens[category] - - acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) - return acc - }, []), - // LP tokens - ...(enableLpTokensByDefault - ? lpTokensByCategory[TokenListCategory.LP].concat(lpTokensByCategory[TokenListCategory.COW_AMM_LP]) - : []), - ] + return tokenMapToListWithLogo( + { + [nativeToken.address.toLowerCase()]: nativeToken as TokenInfo, + ...tokensMap.activeTokens, + ...lowerCaseTokensMap(userAddedTokens[chainId]), + ...lowerCaseTokensMap(favoriteTokensState[chainId]), + ...(enableLpTokensByDefault + ? Object.keys(tokensMap.inactiveTokens).reduce((acc, key) => { + const token = tokensMap.inactiveTokens[key] + + if (token.isLpToken) { + acc[key] = token + } + + return acc + }, {}) + : null), + }, + chainId, + ) }) export const inactiveTokensAtom = atom((get) => { const { chainId } = get(environmentAtom) const tokensMap = get(tokensStateAtom) - return Object.keys(tokensMap.inactiveTokens).reduce((acc, _category) => { - const category = _category as TokenListCategory - const categoryMap = tokensMap.inactiveTokens[category] - - acc.push(...tokenMapToListWithLogo(categoryMap, category, chainId)) - return acc - }, []) + return tokenMapToListWithLogo(tokensMap.inactiveTokens, chainId) }) export const tokensByAddressAtom = atom((get) => { diff --git a/libs/tokens/src/utils/tokenMapToListWithLogo.ts b/libs/tokens/src/utils/tokenMapToListWithLogo.ts index 7675d0c080..b3c7e77081 100644 --- a/libs/tokens/src/utils/tokenMapToListWithLogo.ts +++ b/libs/tokens/src/utils/tokenMapToListWithLogo.ts @@ -1,21 +1,17 @@ import { LpToken, TokenWithLogo } from '@cowprotocol/common-const' -import { TokenListCategory, TokensMap } from '../types' +import { TokensMap } from '../types' /** * Convert a tokens map to a list of tokens and sort them alphabetically */ -export function tokenMapToListWithLogo( - tokenMap: TokensMap, - category: TokenListCategory, - chainId: number, -): TokenWithLogo[] { - const isCoWAMM = category === TokenListCategory.COW_AMM_LP - +export function tokenMapToListWithLogo(tokenMap: TokensMap, chainId: number): TokenWithLogo[] { return Object.values(tokenMap) .filter((token) => token.chainId === chainId) .sort((a, b) => a.symbol.localeCompare(b.symbol)) .map((token) => - token.tokens ? LpToken.fromTokenToLp(token, isCoWAMM) : TokenWithLogo.fromToken(token, token.logoURI), + token.isLpToken + ? LpToken.fromTokenToLp(token, !!token.isCoWAmmToken) + : TokenWithLogo.fromToken(token, token.logoURI), ) } diff --git a/libs/types/src/common.ts b/libs/types/src/common.ts index 0228b65166..1b3e2ae88b 100644 --- a/libs/types/src/common.ts +++ b/libs/types/src/common.ts @@ -24,4 +24,6 @@ export type TokenInfo = { symbol: string logoURI?: string tokens?: string[] + isLpToken?: boolean + isCoWAmmToken?: boolean }