import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list' import { TokenList } from '@uniswap/token-lists' import { useMemo } from 'react' import { useSelector } from 'react-redux' import sortByListPriority from '../../utils/listSort' import UNSUPPORTED_TOKEN_LIST from '../../constants/tokenLists/uniswap-v2-unsupported.tokenlist.json' import { AppState } from '../index' import { UNSUPPORTED_LIST_URLS } from './../../constants/lists' import { WrappedTokenInfo } from './wrappedTokenInfo' export type TokenAddressMap = Readonly<{ [chainId: number]: Readonly<{ [tokenAddress: string]: { token: WrappedTokenInfo; list: TokenList } }> }> const listCache: WeakMap | null = typeof WeakMap !== 'undefined' ? new WeakMap() : null export function listToTokenMap(list: TokenList): TokenAddressMap { const result = listCache?.get(list) if (result) return result const map = list.tokens.reduce((tokenMap, tokenInfo) => { const token = new WrappedTokenInfo(tokenInfo, list) if (tokenMap[token.chainId]?.[token.address] !== undefined) { console.error(new Error(`Duplicate token! ${token.address}`)) return tokenMap } return { ...tokenMap, [token.chainId]: { ...tokenMap[token.chainId], [token.address]: { token, list, }, }, } }, {}) listCache?.set(list, map) return map } const TRANSFORMED_DEFAULT_TOKEN_LIST = listToTokenMap(DEFAULT_TOKEN_LIST) export function useAllLists(): AppState['glists']['byUrl'] { return useSelector((state) => state.glists.byUrl) } function combineMaps(map1: TokenAddressMap, map2: TokenAddressMap): TokenAddressMap { return { [1]: { ...map1[1], ...map2[1] }, [4]: { ...map1[4], ...map2[4] }, [3]: { ...map1[3], ...map2[3] }, [42]: { ...map1[42], ...map2[42] }, [5]: { ...map1[5], ...map2[5] }, [25]: { ...map1[25], ...map2[25] }, [56]: { ...map1[56], ...map2[56] }, [137]: { ...map1[137], ...map2[137] }, [43114]: { ...map1[43114], ...map2[43114] }, } } // merge tokens contained within lists from urls function useCombinedTokenMapFromUrls(urls: string[] | undefined): TokenAddressMap { const lists = useAllLists() return useMemo(() => { if (!urls) return {} return ( urls .slice() // sort by priority so top priority goes last .sort(sortByListPriority) .reduce((allTokens, currentUrl) => { const current = lists[currentUrl]?.current if (!current) return allTokens try { return combineMaps(allTokens, listToTokenMap(current)) } catch (error) { console.error('Could not show token list due to error', error) return allTokens } }, {}) ) }, [lists, urls]) } // filter out unsupported lists export function useActiveListUrls(): string[] | undefined { return useSelector((state) => state.glists.activeListUrls)?.filter( (url) => !UNSUPPORTED_LIST_URLS.includes(url) ) } export function useInactiveListUrls(): string[] { const lists = useAllLists() const allActiveListUrls = useActiveListUrls() return Object.keys(lists).filter((url) => !allActiveListUrls?.includes(url) && !UNSUPPORTED_LIST_URLS.includes(url)) } // get all the tokens from active lists, combine with local default tokens export function useCombinedActiveList(): TokenAddressMap { const activeListUrls = useActiveListUrls() const activeTokens = useCombinedTokenMapFromUrls(activeListUrls) return combineMaps(activeTokens, TRANSFORMED_DEFAULT_TOKEN_LIST) } // list of tokens not supported on interface, used to show warnings and prevent swaps and adds export function useUnsupportedTokenList(): TokenAddressMap { // get hard coded unsupported tokens const localUnsupportedListMap = listToTokenMap(UNSUPPORTED_TOKEN_LIST) // get any loaded unsupported tokens const loadedUnsupportedListMap = useCombinedTokenMapFromUrls(UNSUPPORTED_LIST_URLS) // format into one token address map return useMemo( () => combineMaps(localUnsupportedListMap, loadedUnsupportedListMap), [localUnsupportedListMap, loadedUnsupportedListMap] ) } export function useIsListActive(url: string): boolean { const activeListUrls = useActiveListUrls() return Boolean(activeListUrls?.includes(url)) }