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 { 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] }, [56]: { ...map1[56], ...map2[56] }, [137]: { ...map1[137], ...map2[137] }, [250]: { ...map1[250], ...map2[250] }, [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().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 = useMemo(() => ({}), []); // 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)); }