diff --git a/e2e/discoverSheetFlow.spec.js b/e2e/discoverSheetFlow.spec.js index 17dc9556e92..ee085b64665 100644 --- a/e2e/discoverSheetFlow.spec.js +++ b/e2e/discoverSheetFlow.spec.js @@ -85,17 +85,6 @@ describe('Discover Screen Flow', () => { await Helpers.checkIfVisible('chart-header-Unisocks'); }); - it('Should add Unisocks to Watchlist & remove from Favorites', async () => { - await Helpers.waitAndTap('add-to-list-button'); - await Helpers.checkIfVisible('add-token-sheet'); - await Helpers.waitAndTap('add-to-watchlist'); - await Helpers.checkIfVisible('remove-from-watchlist'); - await Helpers.waitAndTap('remove-from-favorites'); - await Helpers.checkIfNotVisible('remove-from-favorites'); - - await Helpers.waitAndTap('close-action-button'); - }); - it('Should close expanded state and return to search', async () => { if (ios) { // RNBW-4035 @@ -169,30 +158,6 @@ describe('Discover Screen Flow', () => { await Helpers.checkIfVisible('discover-header'); }); - // TODO: seems the test doesn't do sideswipe on the horizonal list - // skipping the test till someone fixes it, apparently it's low - // priority right now - it.skip('Should cycle through token lists', async () => { - android && (await Helpers.swipe('discover-sheet', 'up', 'slow')); - await Helpers.swipeUntilVisible( - 'lists-section', - 'discover-sheet', - 'up', - 100 - ); - await Helpers.checkIfVisible('lists-section-favorites'); - await Helpers.checkIfNotVisible('list-coin-row-Unisocks'); - await Helpers.waitAndTap('list-watchlist'); - await Helpers.checkIfVisible('lists-section-watchlist'); - await Helpers.checkIfVisible('list-coin-row-Unisocks'); - await Helpers.waitAndTap('list-trending'); - await Helpers.checkIfVisible('lists-section-trending'); - await Helpers.waitAndTap('list-defi'); - await Helpers.checkIfVisible('lists-section-defi'); - await Helpers.waitAndTap('list-stablecoins'); - await Helpers.checkIfVisible('lists-section-stablecoins'); - }); - afterAll(async () => { // Reset the app state await device.clearKeychain(); diff --git a/src/App.js b/src/App.js index 6e0dd2f380a..cee9647c9d8 100644 --- a/src/App.js +++ b/src/App.js @@ -59,7 +59,6 @@ import { queryClient, } from './react-query'; import store from './redux/store'; -import { uniswapPairsInit } from './redux/uniswap'; import { walletConnectLoadState } from './redux/walletconnect'; import { rainbowTokenList } from './references'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; @@ -151,7 +150,6 @@ class OldApp extends Component { this?.handleAppStateChange ); this.setState({ eventSubscription: eventSub }); - rainbowTokenList.on('update', this.handleTokenListUpdate); appEvents.on('transactionConfirmed', this.handleTransactionConfirmed); await this.setupDeeplinking(); @@ -191,7 +189,6 @@ class OldApp extends Component { componentWillUnmount() { this.state.eventSubscription.remove(); - rainbowTokenList.off('update', this.handleTokenListUpdate); this.branchListener(); } @@ -202,10 +199,6 @@ class OldApp extends Component { PerformanceContextMap.set('initialRoute', initialRoute); }; - async handleTokenListUpdate() { - store.dispatch(uniswapPairsInit()); - } - handleAppStateChange = async nextAppState => { // Restore WC connectors when going from BG => FG if (this.state.appState === 'background' && nextAppState === 'active') { diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 32607eca382..0f04e6c0efe 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -70,13 +70,11 @@ export function FavStar({ toggleFavorite, favorite, theme }: FavStarProps) { interface InfoProps { contextMenuProps: any; showFavoriteButton: boolean; - showAddButton: boolean; theme: any; } export function Info({ contextMenuProps, - showAddButton, showFavoriteButton, theme, }: InfoProps) { @@ -85,7 +83,7 @@ export function Info({ @@ -264,26 +258,6 @@ export default React.memo(function FastCurrencySelectionRow({ toggleFavorite={toggleFavorite} /> ))} - {showAddButton && ( - - - - + - - - - )} )} diff --git a/src/components/coin-row/ExchangeCoinRow.js b/src/components/coin-row/ExchangeCoinRow.js deleted file mode 100644 index 61a3fa8a8b3..00000000000 --- a/src/components/coin-row/ExchangeCoinRow.js +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { ButtonPressAnimation } from '../animations'; -import { CoinIconSize } from '../coin-icon'; -import { FloatingEmojis } from '../floating-emojis'; -import { ColumnWithMargins, Row } from '../layout'; -import BalanceText from './BalanceText'; -import BottomRowText from './BottomRowText'; -import CoinName from './CoinName'; -import CoinRow from './CoinRow'; -import CoinRowAddButton from './CoinRowAddButton'; -import CoinRowFavoriteButton from './CoinRowFavoriteButton'; -import CoinRowInfoButton from './CoinRowInfoButton'; -import { useDimensions } from '@/hooks'; -import styled from '@/styled-thing'; -import { haptics, neverRerender } from '@/utils'; - -const CoinRowPaddingTop = 9.5; -const CoinRowPaddingBottom = 9.5; -const containerStyles = { - height: CoinIconSize + CoinRowPaddingTop + CoinRowPaddingBottom, -}; - -const FloatingFavoriteEmojis = styled(FloatingEmojis).attrs({ - centerVertically: true, - disableHorizontalMovement: true, - disableVerticalMovement: true, - distance: 70, - duration: 400, - emojis: ['glowing_star'], - fadeOut: false, - marginTop: 10.25, - range: [0, 0], - scaleTo: 0, - size: 32, - wiggleFactor: 0, -})({ - left: ({ deviceWidth }) => deviceWidth - 52.25, - position: 'absolute', - right: 0, - top: 0, - zIndex: 100, -}); - -const ExchangeCoinName = styled(CoinName)({ - width: ({ showBalance }) => (showBalance ? '100%' : '90%'), -}); - -const BottomRow = ({ showBalance, symbol }) => - showBalance ? null : {symbol}; - -const TopRow = ({ name, showBalance }) => ( - - {name} - -); - -const ExchangeCoinRow = ({ - item, - isVerified, - onActionAsset, - onCopySwapDetailsText, - onPress, - onUnverifiedTokenPress, - showBalance, - showFavoriteButton, - showAddButton, - testID, -}) => { - const { width: deviceWidth } = useDimensions(); - const [localFavorite, setLocalFavorite] = useState(!!item.favorite); - const handlePress = useCallback(() => { - if (isVerified || showBalance) { - onPress(item); - } else { - onUnverifiedTokenPress(item); - } - }, [isVerified, item, onPress, onUnverifiedTokenPress, showBalance]); - - const toggleFavorite = onNewEmoji => { - setLocalFavorite(prevLocalFavorite => { - const newLocalFavorite = !prevLocalFavorite; - if (newLocalFavorite) { - haptics.notificationSuccess(); - ios && onNewEmoji(); - } else { - haptics.selection(); - } - onActionAsset(item, newLocalFavorite); - return newLocalFavorite; - }); - }; - - return ( - <> - - - {showBalance && ( - - {item?.native?.balance?.display || '–'} - {item?.balance?.display || ''} - - )} - - - {!item.isNativeAsset && !showBalance && ( - - )} - {showFavoriteButton && ios && ( - - {({ onNewEmoji }) => ( - toggleFavorite(onNewEmoji)} - /> - )} - - )} - {showFavoriteButton && android && ( - - )} - {showAddButton && ( - { - onActionAsset(item); - }} - /> - )} - - ); -}; - -export default neverRerender(ExchangeCoinRow); diff --git a/src/components/coin-row/index.js b/src/components/coin-row/index.js index 30e90633dd3..2ee887fca10 100644 --- a/src/components/coin-row/index.js +++ b/src/components/coin-row/index.js @@ -6,7 +6,6 @@ export { default as CoinRowDetailsIcon } from './CoinRowDetailsIcon'; export { default as CoinRowFavoriteButton } from './CoinRowFavoriteButton'; export { default as CollectiblesSendRow } from './CollectiblesSendRow'; export { default as ContractInteractionCoinRow } from './ContractInteractionCoinRow'; -export { default as ExchangeCoinRow } from './ExchangeCoinRow'; export { default as ListCoinRow } from './ListCoinRow'; export { default as RequestCoinRow } from './RequestCoinRow'; export { default as SendCoinRow } from './SendCoinRow'; diff --git a/src/components/exchange/CurrencySelectionList.tsx b/src/components/exchange/CurrencySelectionList.tsx index 71390dbccff..8388ed687f6 100644 --- a/src/components/exchange/CurrencySelectionList.tsx +++ b/src/components/exchange/CurrencySelectionList.tsx @@ -16,7 +16,6 @@ interface CurrencySelectionListProps { footerSpacer: boolean; fromDiscover?: boolean; itemProps: { - onActionAsset: (asset: any, isFavorited?: any) => void; onPress: (item: any) => void; showBalance: boolean; showFavoriteButton: boolean; diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/exchange/ExchangeAssetList.tsx index c68654b48e0..ac2eb849586 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/exchange/ExchangeAssetList.tsx @@ -26,11 +26,7 @@ import { GradientText } from '../text'; import { CopyToast, ToastPositionContainer } from '../toasts'; import contextMenuProps from './exchangeAssetRowContextMenuProps'; import { TokenSectionTypes } from '@/helpers'; -import { - useAndroidScrollViewGestureHandler, - usePrevious, - useUserLists, -} from '@/hooks'; +import { useAndroidScrollViewGestureHandler, usePrevious } from '@/hooks'; import { useNavigation } from '@/navigation'; import store from '@/redux/store'; import Routes from '@/navigation/routesNames'; @@ -42,6 +38,7 @@ import { colors, Colors } from '@/styles'; import { EnrichedExchangeAsset } from '@/screens/CurrencySelectModal'; import ExchangeTokenRow from './ExchangeTokenRow'; import { SwappableAsset } from '@/entities'; +import { toggleFavorite, useFavorites } from '@/resources/favorites'; const deviceWidth = deviceUtils.dimensions.width; @@ -111,11 +108,9 @@ interface ExchangeAssetListProps { footerSpacer: boolean; keyboardDismissMode?: 'none' | 'interactive' | 'on-drag'; itemProps: { - onActionAsset: (asset: any, isFavorited?: any) => void; onPress: (item: any) => void; showBalance: boolean; showFavoriteButton: boolean; - showAddButton?: boolean; }; items: { data: EnrichedExchangeAsset[]; title: string }[]; onLayout?: () => void; @@ -154,7 +149,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< copyCount, onCopySwapDetailsText, } = useSwapDetailsClipboardState(); - const { updateList } = useUserLists(); // Scroll to top once the query is cleared if (prevQuery && prevQuery.length && !query.length) { @@ -252,23 +246,19 @@ const ExchangeAssetList: ForwardRefRenderFunction< const isFocused = useIsFocused(); const theme = useTheme(); - + const { favoritesMetadata } = useFavorites(); const { nativeCurrency, nativeCurrencySymbol } = useAccountSettings(); const [localFavorite, setLocalFavorite] = useState< Record | undefined - >(() => { - const meta = store.getState().uniswap.favoritesMeta; - if (!meta) { - return; - } - return Object.keys(meta).reduce( + >(() => + Object.keys(favoritesMetadata).reduce( (acc: Record, curr: string) => { - acc[curr] = meta[curr].favorite; + acc[curr] = favoritesMetadata[curr].favorite; return acc; }, {} - ); - }); + ) + ); const enrichedItems = useMemo( () => @@ -279,9 +269,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< contextMenuProps: contextMenuProps(rowData, onCopySwapDetailsText), nativeCurrency, nativeCurrencySymbol, - onAddPress: () => { - itemProps.onActionAsset(rowData); - }, onCopySwapDetailsText, onPress: (givenItem: ReactElement) => { if (rowData.ens) { @@ -296,7 +283,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< handleUnverifiedTokenPress(rowData || asset); } }, - showAddButton: itemProps.showAddButton, showBalance: itemProps.showBalance, showFavoriteButton: itemProps.showFavoriteButton, testID, @@ -305,7 +291,7 @@ const ExchangeAssetList: ForwardRefRenderFunction< setLocalFavorite(prev => { const address = rowData.address; const newValue = !prev?.[address]; - updateList(address, 'favorites', newValue); + toggleFavorite(address); if (newValue) { ios && onNewEmoji(); haptics.notificationSuccess(); @@ -330,7 +316,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< onCopySwapDetailsText, testID, theme, - updateList, ] ); diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index de2ccead548..b5bb1346fd2 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -23,7 +23,6 @@ export default React.memo(function ExchangeTokenRow({ uniqueId, showBalance, showFavoriteButton, - showAddButton, onPress, theme, nativeCurrency, @@ -126,7 +125,6 @@ export default React.memo(function ExchangeTokenRow({ {isInfoButtonVisible && ( diff --git a/src/components/expanded-state/chart/ChartAddToListButton.js b/src/components/expanded-state/chart/ChartAddToListButton.js deleted file mode 100644 index e9e1c3c6be2..00000000000 --- a/src/components/expanded-state/chart/ChartAddToListButton.js +++ /dev/null @@ -1,73 +0,0 @@ -import React, { Fragment, useCallback } from 'react'; -import { View } from 'react-native'; -import { IS_TESTING } from 'react-native-dotenv'; -import RadialGradient from 'react-native-radial-gradient'; -import { ButtonPressAnimation } from '../../animations'; -import { Centered } from '../../layout'; -import { Text } from '../../text'; -import { isL2Asset } from '@/handlers/assets'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; -import { magicMemo } from '@/utils'; - -const AddToListButtonPadding = 19; - -const AddToListButton = styled(Centered)({ - ...padding.object(0, AddToListButtonPadding), - height: 40, - marginRight: 10, - width: 40, -}); - -const Circle = styled(IS_TESTING === 'true' ? View : RadialGradient).attrs( - ({ theme: { colors } }) => ({ - center: [0, 20], - colors: colors.gradients.lightestGrey, - }) -)({ - borderRadius: 20, - height: 40, - overflow: 'hidden', - width: 40, -}); - -const PlusIcon = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.4), - letterSpacing: 'zero', - size: 'large', - weight: 'bold', -}))({ - height: '100%', - lineHeight: 39, - width: '100%', -}); - -const ChartAddToListButton = ({ asset }) => { - const { navigate } = useNavigation(); - - const handlePress = useCallback(() => { - navigate(Routes.ADD_TOKEN_SHEET, { - isL2: isL2Asset(asset?.network), - item: asset, - }); - }, [asset, navigate]); - - return ( - - - - 􀅼 - - - - ); -}; - -export default magicMemo(ChartAddToListButton, ['asset']); diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 3a8c607a88a..33cbb9d4fe9 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -3,7 +3,6 @@ import React, { useMemo } from 'react'; import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; import { CoinIcon, CoinIconGroup } from '../../coin-icon'; import { Column, ColumnWithMargins, Row, RowWithMargins } from '../../layout'; -import ChartAddToListButton from './ChartAddToListButton'; import ChartContextButton from './ChartContextButton'; import { ChartDateLabel, @@ -12,12 +11,12 @@ import { ChartPriceLabel, } from './chart-data-labels'; import { useChartData } from '@/react-native-animated-charts/src'; -import { Network } from '@/helpers'; import ChartTypes from '@/helpers/chartTypes'; import { convertAmountToNativeDisplay } from '@/helpers/utilities'; -import { useAccountSettings, useBooleanState } from '@/hooks'; +import { useAccountSettings, useBooleanState, useDimensions } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; +import { useDispatch, useSelector } from 'react-redux'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -61,7 +60,7 @@ export default function ChartExpandedStateHeader({ const tokens = useMemo(() => { return isPool ? asset.tokens : [asset]; }, [asset, isPool]); - const { nativeCurrency, network: currentNetwork } = useAccountSettings(); + const { nativeCurrency } = useAccountSettings(); const tabularNums = useTabularNumsWhileScrubbing(); const isNoPriceData = latestPrice === noPriceData; @@ -133,12 +132,7 @@ export default function ChartExpandedStateHeader({ )} - - {currentNetwork === Network.mainnet && !isPool && ( - - )} - - + ; diff --git a/src/entities/userLists.ts b/src/entities/userLists.ts deleted file mode 100644 index 15f68f564ad..00000000000 --- a/src/entities/userLists.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * A single user list, with fields based on the data found in - * `src/references/default-token-lists.json`. - */ -export interface UserList { - emoji: string; - id: string; - name: string; - tokens: string[]; -} diff --git a/src/handlers/localstorage/uniswap.ts b/src/handlers/localstorage/uniswap.ts deleted file mode 100644 index c34b22bf411..00000000000 --- a/src/handlers/localstorage/uniswap.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - EthereumAddress, - RainbowToken, - UniswapFavoriteTokenData, -} from '../../entities'; -import { - DefaultUniswapFavorites, - DefaultUniswapFavoritesMeta, -} from '../../references'; -import { getGlobal, saveGlobal } from './common'; -import { Network } from '@/helpers/networkTypes'; - -const UNISWAP_FAVORITES = 'uniswapFavorites'; -const UNISWAP_FAVORITES_METADATA = 'uniswapFavoritesMetadata'; -const uniswapFavoritesMetadataVersion = '0.1.0'; - -export const getUniswapFavorites = ( - network: Network -): Promise => - // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - getGlobal(UNISWAP_FAVORITES, DefaultUniswapFavorites[network]); - -export const saveUniswapFavorites = (favorites: any) => - saveGlobal(UNISWAP_FAVORITES, favorites); - -export const getUniswapFavoritesMetadata = ( - network: Network = Network.mainnet -): Promise => - getGlobal( - UNISWAP_FAVORITES_METADATA, - DefaultUniswapFavoritesMeta[network], - uniswapFavoritesMetadataVersion - ); - -export const saveUniswapFavoritesMetadata = ( - data: Record -) => - saveGlobal(UNISWAP_FAVORITES_METADATA, data, uniswapFavoritesMetadataVersion); diff --git a/src/handlers/localstorage/userLists.ts b/src/handlers/localstorage/userLists.ts deleted file mode 100644 index c8130a48149..00000000000 --- a/src/handlers/localstorage/userLists.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getGlobal, saveGlobal } from './common'; - -const USER_LISTS = 'userLists'; -const USER_LISTS_SELECTED_LIST = 'userListsSelectedList'; - -export const getUserLists = () => getGlobal(USER_LISTS, null); - -export const saveUserLists = (lists: any) => saveGlobal(USER_LISTS, lists); - -export const getSelectedUserList = () => - getGlobal(USER_LISTS_SELECTED_LIST, null); - -export const saveSelectedUserList = (listId: any) => - saveGlobal(USER_LISTS_SELECTED_LIST, listId); diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index 1fa6adeea19..f605c459f01 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -33,7 +33,7 @@ import { subtract, } from '@/helpers/utilities'; import { Network } from '@/helpers/networkTypes'; -import { erc20ABI, ethUnits, UNISWAP_TESTNET_TOKEN_LIST } from '@/references'; +import { erc20ABI, ethUnits } from '@/references'; import { ethereumUtils, logger } from '@/utils'; export enum Field { @@ -240,19 +240,6 @@ export const getSwapGasLimitWithFakeApproval = async ( return getDefaultGasLimitForTrade(tradeDetails, chainId); }; -export const getTestnetUniswapPairs = ( - network: Network -): { [key: string]: Asset } => { - const pairs: { [address: string]: Asset } = - (UNISWAP_TESTNET_TOKEN_LIST as any)?.[network] ?? {}; - - const loweredPairs = mapKeys(pairs, (_, key) => key.toLowerCase()); - return mapValues(loweredPairs, value => ({ - ...value, - address: value.address.toLowerCase(), - })); -}; - export const estimateSwapGasLimit = async ({ chainId, requiresApprove, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 580f2457a21..6a338456af8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -127,7 +127,6 @@ export { default as useTransactionConfirmation } from './useTransactionConfirmat export { default as usePendingTransactions } from './usePendingTransactions'; export { default as useAssetsInWallet } from './useAssetsInWallet'; export { default as useUserAccounts } from './useUserAccounts'; -export { default as useUserLists } from './useUserLists'; export { default as useWalletBalances } from './useWalletBalances'; export { default as useWalletCloudBackup } from './useWalletCloudBackup'; export { default as useWalletConnectConnections } from './useWalletConnectConnections'; diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts index 3befc1b5645..5c0e383b9da 100644 --- a/src/hooks/useInitializeWallet.ts +++ b/src/hooks/useInitializeWallet.ts @@ -12,7 +12,6 @@ import { settingsLoadNetwork, settingsUpdateAccountAddress, } from '../redux/settings'; -import { uniswapPairsInit } from '../redux/uniswap'; import { walletsLoadState } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; import useHideSplashScreen from './useHideSplashScreen'; @@ -148,10 +147,6 @@ export default function useInitializeWallet() { dispatch(appStateUpdate({ walletReady: true })); - if (!switching) { - dispatch(uniswapPairsInit()); - } - logger.sentry('💰 Wallet initialized'); PerformanceTracking.finishMeasuring( PerformanceMetrics.useInitializeWallet, diff --git a/src/hooks/useLoadGlobalLateData.ts b/src/hooks/useLoadGlobalLateData.ts index 2d6413abb25..c09109b455e 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -13,8 +13,7 @@ import { imageMetadataCacheLoadState } from '@/redux/imageMetadata'; import { keyboardHeightsLoadState } from '@/redux/keyboardHeight'; import { transactionSignaturesLoadState } from '@/redux/transactionSignatures'; import { contactsLoadState } from '@/redux/contacts'; -import { uniswapLoadState } from '@/redux/uniswap'; -import { userListsLoadState } from '@/redux/userLists'; +import { favoritesQueryKey, refreshFavorites } from '@/resources/favorites'; const loadWalletBalanceNamesToCache = () => queryClient.prefetchQuery([WALLET_BALANCES_FROM_STORAGE], getWalletBalances); @@ -39,25 +38,25 @@ export default function useLoadGlobalLateData() { // mainnet eth balances for all wallets const p2 = loadWalletBalanceNamesToCache(); - // user lists - const p3 = dispatch(userListsLoadState()); - // favorites - const p4 = dispatch(uniswapLoadState()); + const p3 = queryClient.prefetchQuery({ + queryKey: favoritesQueryKey, + queryFn: refreshFavorites, + }); // contacts - const p5 = dispatch(contactsLoadState()); + const p4 = dispatch(contactsLoadState()); // image metadata - const p6 = dispatch(imageMetadataCacheLoadState()); + const p5 = dispatch(imageMetadataCacheLoadState()); // keyboard heights - const p7 = dispatch(keyboardHeightsLoadState()); + const p6 = dispatch(keyboardHeightsLoadState()); // tx method names - const p8 = dispatch(transactionSignaturesLoadState()); + const p7 = dispatch(transactionSignaturesLoadState()); - promises.push(p1, p2, p3, p4, p5, p6, p7, p8); + promises.push(p1, p2, p3, p4, p5, p6, p7); // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '(Promise | ((dispatch: Dis... Remove this comment to see the full error message return promiseUtils.PromiseAllWithFails(promises); diff --git a/src/hooks/useResetAccountState.ts b/src/hooks/useResetAccountState.ts index d04a6f11627..fc424429df7 100644 --- a/src/hooks/useResetAccountState.ts +++ b/src/hooks/useResetAccountState.ts @@ -3,7 +3,6 @@ import { useDispatch } from 'react-redux'; import { dataResetState } from '../redux/data'; import { explorerClearState } from '../redux/explorer'; import { requestsResetState } from '../redux/requests'; -import { uniswapResetState } from '../redux/uniswap'; import { promiseUtils } from '../utils'; export default function useResetAccountState() { @@ -13,9 +12,9 @@ export default function useResetAccountState() { const p0 = dispatch(explorerClearState()); const p1 = dispatch(dataResetState()); const p2 = dispatch(requestsResetState()); - const p3 = dispatch(uniswapResetState()); + // @ts-expect-error ts-migrate(2739) FIXME: Type '(dispatch: any) => void' is missing the foll... Remove this comment to see the full error message - await promiseUtils.PromiseAllWithFails([p0, p1, p2, p3]); + await promiseUtils.PromiseAllWithFails([p0, p1, p2]); }, [dispatch]); return resetAccountState; diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index 285a7778a97..0b24a05a1cb 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -4,9 +4,6 @@ import { ChainId, EthereumAddress } from '@rainbow-me/swaps'; import { Contract } from '@ethersproject/contracts'; import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../redux/store'; -import { uniswapUpdateFavorites } from '../redux/uniswap'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; import { @@ -32,6 +29,7 @@ import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; +import { useFavorites } from '@/resources/favorites'; const MAINNET_CHAINID = 1; type swapCurrencyListType = @@ -41,11 +39,6 @@ type swapCurrencyListType = | 'favoriteAssets' | 'curatedAssets' | 'importedAssets'; -const uniswapCuratedTokensSelector = (state: AppState) => state.uniswap.pairs; -const uniswapFavoriteMetadataSelector = (state: AppState) => - state.uniswap.favoritesMeta; -const uniswapFavoritesSelector = (state: AppState): string[] => - state.uniswap.favorites; type CrosschainVerifiedAssets = { [Network.mainnet]: RT[]; @@ -106,12 +99,14 @@ const useSwapCurrencyList = ( () => searchQuery !== '' || MAINNET_CHAINID !== searchChainId, [searchChainId, searchQuery] ); - const dispatch = useDispatch(); - const curatedMap = useSelector(uniswapCuratedTokensSelector); - const favoriteMap = useSelector(uniswapFavoriteMetadataSelector); + const { + favorites: favoriteAddresses, + favoritesMetadata: favoriteMap, + } = useFavorites(); + + const curatedMap = rainbowTokenList.CURATED_TOKENS; const unfilteredFavorites = Object.values(favoriteMap); - const favoriteAddresses = useSelector(uniswapFavoritesSelector); const [loading, setLoading] = useState(true); const [favoriteAssets, setFavoriteAssets] = useState([]); @@ -660,17 +655,10 @@ const useSwapCurrencyList = ( searchQuery, ]); - const updateFavorites = useCallback( - (...data: [string | string[], boolean]) => - dispatch(uniswapUpdateFavorites(...data)), - [dispatch] - ); - return { crosschainExactMatches, swapCurrencyList: currencyList, swapCurrencyListLoading: loading, - updateFavorites, }; }; diff --git a/src/hooks/useUserLists.ts b/src/hooks/useUserLists.ts deleted file mode 100644 index 4aa1e9f81e6..00000000000 --- a/src/hooks/useUserLists.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { - userListsClearList, - userListsSetSelectedList, - userListsUpdateList, -} from '../redux/userLists'; -import { AppState } from '@/redux/store'; - -const userListsSelector = (state: AppState) => state.userLists.lists; -const userListsReadySelector = (state: AppState) => state.userLists.ready; -const userListsSelectedListSelector = (state: AppState) => - state.userLists.selectedList; -const uniswapFavoritesSelector = (state: AppState) => state.uniswap.favorites; - -export default function useUserLists() { - const dispatch = useDispatch(); - const lists = useSelector(userListsSelector); - const ready = useSelector(userListsReadySelector); - const selectedList = useSelector(userListsSelectedListSelector); - const favorites = useSelector(uniswapFavoritesSelector); - - const updateList = useCallback( - // @ts-expect-error ts-migrate(2556) FIXME: Expected 2-3 arguments, but got 0 or more. - (...data) => dispatch(userListsUpdateList(...data)), - [dispatch] - ); - const clearList = useCallback( - (listId: string) => dispatch(userListsClearList(listId)), - [dispatch] - ); - const setSelectedList = useCallback( - (listId: string) => dispatch(userListsSetSelectedList(listId)), - [dispatch] - ); - - return { - clearList, - favorites, - lists, - ready, - selectedList, - setSelectedList, - updateList, - }; -} diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 1bfa05aa621..607eaf7aca5 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -240,6 +240,7 @@ "exchange_again": "Exchange Again", "exchange_search_placeholder": "Search tokens", "exchange_search_placeholder_network": "Search tokens on %{network}", + "favorites": "Favorites", "go_back": "Go Back", "go_back_lowercase": "Go back", "got_it": "Got it", @@ -432,17 +433,6 @@ "sync_codepush": "Sync codepush" }, "discover": { - "lists": { - "lists_title": "Lists", - "this_list_is_empty": "This list is empty!", - "types": { - "trending": "Trending", - "watchlist": "Watchlist", - "favorites": "Favorites", - "defi": "DeFi", - "stablecoins": "Stablecoins" - } - }, "pulse": { "pulse_description": "All the top DeFi tokens in one", "today_suffix": "today", diff --git a/src/model/migrations.ts b/src/model/migrations.ts index e43b5543f39..cc9d801c162 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -5,7 +5,10 @@ import { findKey, isNumber, keys } from 'lodash'; import uniq from 'lodash/uniq'; import RNFS from 'react-native-fs'; import { MMKV } from 'react-native-mmkv'; -import { deprecatedRemoveLocal } from '../handlers/localstorage/common'; +import { + deprecatedRemoveLocal, + getGlobal, +} from '../handlers/localstorage/common'; import { IMAGE_METADATA } from '../handlers/localstorage/globalSettings'; import { getMigrationVersion, @@ -52,14 +55,15 @@ import { savePinnedCoins, } from '@/handlers/localstorage/accountLocal'; import { getContacts, saveContacts } from '@/handlers/localstorage/contacts'; -import { getUserLists, saveUserLists } from '@/handlers/localstorage/userLists'; import { resolveNameOrAddress } from '@/handlers/web3'; import { returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; -import { DefaultTokenLists } from '@/references'; import { ethereumUtils, profileUtils } from '@/utils'; import { REVIEW_ASKED_KEY } from '@/utils/reviewAlert'; import logger from '@/utils/logger'; +import { queryClient } from '@/react-query'; +import { favoritesQueryKey } from '@/resources/favorites'; +import { EthereumAddress, RainbowToken } from '@/entities'; export default async function runMigrations() { // get current version @@ -272,22 +276,25 @@ export default async function runMigrations() { migrations.push(v5); + /** + * NOTICE: this migration is no longer in use. userLists has been removed. + */ /* Fix dollars => stablecoins */ const v6 = async () => { - try { - const userLists = await getUserLists(); - const newLists = userLists.map((list: { id: string }) => { - if (list?.id !== 'dollars') { - return list; - } - return DefaultTokenLists['mainnet'].find( - ({ id }) => id === 'stablecoins' - ); - }); - await saveUserLists(newLists); - } catch (e) { - logger.log('ignoring lists migrations'); - } + // try { + // const userLists = await getUserLists(); + // const newLists = userLists.map((list: { id: string }) => { + // if (list?.id !== 'dollars') { + // return list; + // } + // return DefaultTokenLists['mainnet'].find( + // ({ id }) => id === 'stablecoins' + // ); + // }); + // await saveUserLists(newLists); + // } catch (e) { + // logger.log('ignoring lists migrations'); + // } }; migrations.push(v6); @@ -673,10 +680,36 @@ export default async function runMigrations() { migrations.push(v17); + /** + *************** Migration v18 ****************** + Move favorites from local storage to react query persistent cache (AsyncStorage) + */ + const v18 = async () => { + const favoritesMetadata = await getGlobal( + 'uniswapFavoritesMetadata', + undefined, + '0.1.0' + ); + + if (favoritesMetadata) { + const lowercasedFavoritesMetadata: Record< + EthereumAddress, + RainbowToken + > = {}; + Object.keys(favoritesMetadata).forEach((address: string) => { + lowercasedFavoritesMetadata[address.toLowerCase()] = + favoritesMetadata[address]; + }); + queryClient.setQueryData(favoritesQueryKey, lowercasedFavoritesMetadata); + } + }; + + migrations.push(v18); + logger.sentry( `Migrations: ready to run migrations starting on number ${currentVersion}` ); - + // await setMigrationVersion(17); if (migrations.length === currentVersion) { logger.sentry(`Migrations: Nothing to run`); return; diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index c7e79849409..556efb5b89b 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -3,7 +3,6 @@ import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { StatusBar } from 'react-native'; import { AddCashSheet } from '../screens/AddCash'; -import AddTokenSheet from '../screens/AddTokenSheet'; import AvatarBuilder from '../screens/AvatarBuilder'; import BackupSheet from '../screens/BackupSheet'; import ChangeWalletSheet from '../screens/ChangeWalletSheet'; @@ -153,11 +152,6 @@ function MainNavigator() { name={Routes.ADD_CASH_SHEET} options={addCashSheet} /> - - ({ - ...buildCoolModalConfig({ - ...params, - longFormHeight: 394, - }), - }), -}; - export const positionSheetConfig = { options: ({ route: { params = {} } }: { route: { params: any } }) => { const height = getPositionSheetHeight(params); diff --git a/src/navigation/routesNames.ts b/src/navigation/routesNames.ts index e5235427d43..2172e629e32 100644 --- a/src/navigation/routesNames.ts +++ b/src/navigation/routesNames.ts @@ -3,7 +3,6 @@ import { IS_IOS } from '@/env'; const Routes = { ADD_CASH_SCREEN_NAVIGATOR: 'AddCashSheetNavigator', ADD_CASH_SHEET: 'AddCashSheet', - ADD_TOKEN_SHEET: 'AddTokenSheet', ADD_WALLET_NAVIGATOR: 'AddWalletNavigator', ADD_WALLET_SHEET: 'AddWalletSheet', AVATAR_BUILDER: 'AvatarBuilder', diff --git a/src/redux/explorer.ts b/src/redux/explorer.ts index 7207325d614..9b626a87f7f 100644 --- a/src/redux/explorer.ts +++ b/src/redux/explorer.ts @@ -31,6 +31,7 @@ import { ETH_ADDRESS, MATIC_MAINNET_ADDRESS, OP_ADDRESS, + rainbowTokenList, } from '@/references'; import { TokensListenedCache } from '@/utils'; import logger from '@/utils/logger'; @@ -354,7 +355,7 @@ const explorerUnsubscribe = () => (_: Dispatch, getState: AppGetState) => { assetsSocket, } = getState().explorer; const { nativeCurrency } = getState().settings; - const { pairs } = getState().uniswap; + const pairs = rainbowTokenList.CURATED_TOKENS; if (!isNil(addressSocket)) { addressSocket.emit( ...addressSubscription(addressSubscribed!, nativeCurrency, 'unsubscribe') @@ -387,7 +388,7 @@ export const explorerInit = () => async ( getState: AppGetState ) => { const { network, accountAddress, nativeCurrency } = getState().settings; - const { pairs } = getState().uniswap; + const pairs = rainbowTokenList.CURATED_TOKENS; const { addressSocket, assetsSocket } = getState().explorer; // if there is another socket unsubscribe first diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index c6ddbd53e87..b4424dc7dc7 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -17,8 +17,6 @@ import settings from './settings'; import showcaseTokens from './showcaseTokens'; import swap from './swap'; import transactionSignatures from './transactionSignatures'; -import uniswap from './uniswap'; -import userLists from './userLists'; import walletconnect from './walletconnect'; import wallets from './wallets'; @@ -40,8 +38,6 @@ export default combineReducers({ showcaseTokens, swap, transactionSignatures, - uniswap, - userLists, walletconnect, wallets, }); diff --git a/src/redux/uniswap.ts b/src/redux/uniswap.ts deleted file mode 100644 index cc2b7b1206c..00000000000 --- a/src/redux/uniswap.ts +++ /dev/null @@ -1,304 +0,0 @@ -import produce from 'immer'; -import isArray from 'lodash/isArray'; -import toLower from 'lodash/toLower'; -import uniq from 'lodash/uniq'; -import without from 'lodash/without'; -import { Dispatch } from 'redux'; -import { AppGetState } from './store'; -import { - EthereumAddress, - RainbowToken, - UniswapFavoriteTokenData, -} from '@/entities'; -import { getUniswapV2Tokens } from '@/handlers/dispersion'; -import { - getUniswapFavorites, - getUniswapFavoritesMetadata as getUniswapFavoritesMetadataLS, - saveUniswapFavorites, - saveUniswapFavoritesMetadata, -} from '@/handlers/localstorage/uniswap'; -import { getTestnetUniswapPairs } from '@/handlers/swap'; -import { Network } from '@/helpers/networkTypes'; -import { - DefaultUniswapFavorites, - DefaultUniswapFavoritesMeta, - ETH_ADDRESS, - rainbowTokenList, - WETH_ADDRESS, -} from '@/references'; -import logger from '@/utils/logger'; - -// -- Constants ------------------------------------------------------------- // - -const UNISWAP_LOAD_REQUEST = 'uniswap/UNISWAP_LOAD_REQUEST'; -const UNISWAP_LOAD_SUCCESS = 'uniswap/UNISWAP_LOAD_SUCCESS'; -const UNISWAP_LOAD_FAILURE = 'uniswap/UNISWAP_LOAD_FAILURE'; - -const UNISWAP_UPDATE_PAIRS = 'uniswap/UNISWAP_UPDATE_PAIRS'; - -const UNISWAP_UPDATE_FAVORITES = 'uniswap/UNISWAP_UPDATE_FAVORITES'; -const UNISWAP_CLEAR_STATE = 'uniswap/UNISWAP_CLEAR_STATE'; - -// -- Actions --------------------------------------------------------------- // - -/** - * Represents the current state of the `uniswap` reducer. - */ -interface UniswapState { - /** - * An array of addresses for the user's favorite Uniswap pairs. - */ - favorites: string[]; - - /** - * Data associated with user's favorite Uniswap pairs. - */ - favoritesMeta: UniswapFavoriteTokenData; - - /** - * Whether or not data from Uniswap is currently being loaded. - */ - loadingUniswap: boolean; - - /** - * Data for Uniswap pairs as an object mapping addresses to tokens. - */ - pairs: Record; -} - -/** - * An action for the `uniswap` reducer. - */ -type UniswapAction = - | UniswapLoadRequestAction - | UniswapLoadSuccessAction - | UniswapLoadFailureAction - | UniswapUpdatePairsAction - | UniswapUpdateFavoritesAction - | UniswapClearStateAction; - -/** - * The action for starting to load data from Uniswap. - */ -interface UniswapLoadRequestAction { - type: typeof UNISWAP_LOAD_REQUEST; -} - -/** - * The action used when data from Uniswap is loaded successfully. - */ -interface UniswapLoadSuccessAction { - type: typeof UNISWAP_LOAD_SUCCESS; - payload: { - favorites: UniswapState['favorites']; - favoritesMeta: UniswapState['favoritesMeta']; - }; -} - -/** - * The action used when loading data from Uniswap fails. - */ -interface UniswapLoadFailureAction { - type: typeof UNISWAP_LOAD_FAILURE; -} - -/** - * The action for updating Uniswap pair data. - */ -interface UniswapUpdatePairsAction { - type: typeof UNISWAP_UPDATE_PAIRS; - payload: UniswapState['pairs']; -} - -/** - * The action for updating Uniswap favorites. - */ -interface UniswapUpdateFavoritesAction { - type: typeof UNISWAP_UPDATE_FAVORITES; - payload: { - favorites: UniswapState['favorites']; - favoritesMeta: UniswapState['favoritesMeta']; - }; -} - -/** - * The action for resetting the state. - */ -interface UniswapClearStateAction { - type: typeof UNISWAP_CLEAR_STATE; -} - -/** - * Loads Uniswap favorites from global storage and updates state. - */ -export const uniswapLoadState = () => async ( - dispatch: Dispatch< - | UniswapLoadRequestAction - | UniswapLoadSuccessAction - | UniswapLoadFailureAction - >, - getState: AppGetState -) => { - const { network } = getState().settings; - dispatch({ type: UNISWAP_LOAD_REQUEST }); - try { - const favorites: string[] = await getUniswapFavorites(network); - const favoritesMeta = await getUniswapFavoritesMetadataLS(network); - dispatch({ - payload: { - favorites, - favoritesMeta, - }, - type: UNISWAP_LOAD_SUCCESS, - }); - } catch (error) { - dispatch({ type: UNISWAP_LOAD_FAILURE }); - } -}; - -/** - * Updates state to use initial data for Uniswap pairs. - */ -export const uniswapPairsInit = () => ( - dispatch: Dispatch, - getState: AppGetState -) => { - const { network } = getState().settings; - const pairs = - network === Network.mainnet - ? rainbowTokenList.CURATED_TOKENS - : getTestnetUniswapPairs(network); - dispatch({ - // @ts-expect-error - payload: pairs, - type: UNISWAP_UPDATE_PAIRS, - }); -}; - -/** - * Resets the state. - */ -export const uniswapResetState = () => ( - dispatch: Dispatch -) => dispatch({ type: UNISWAP_CLEAR_STATE }); - -/** - * Loads uniswap favorites metadata from local storage or fetches new data - * on update / when persisting default favorites for the first time - */ -const getUniswapFavoritesMetadata = async ( - addresses: EthereumAddress[] -): Promise => { - const favoritesMetadata: UniswapFavoriteTokenData = {}; - try { - const newFavoritesMeta = await getUniswapV2Tokens( - addresses.map(address => { - return address === ETH_ADDRESS ? WETH_ADDRESS : address.toLowerCase(); - }) - ); - const ethIsFavorited = addresses.includes(ETH_ADDRESS); - const wethIsFavorited = addresses.includes(WETH_ADDRESS); - if (newFavoritesMeta) { - if (newFavoritesMeta[WETH_ADDRESS] && ethIsFavorited) { - const favorite = newFavoritesMeta[WETH_ADDRESS]; - newFavoritesMeta[ETH_ADDRESS] = { - ...favorite, - address: ETH_ADDRESS, - name: 'Ethereum', - symbol: 'ETH', - uniqueId: ETH_ADDRESS, - }; - } - Object.entries(newFavoritesMeta).forEach(([address, favorite]) => { - if (address !== WETH_ADDRESS || wethIsFavorited) { - favoritesMetadata[address] = { ...favorite, favorite: true }; - } - }); - } - } catch (e) { - logger.sentry( - `An error occurred while fetching uniswap favorite metadata: ${e}` - ); - } - if (favoritesMetadata) { - saveUniswapFavoritesMetadata(favoritesMetadata); - } - return favoritesMetadata; -}; - -/** - * Updates a user's Uniswap favorites in state and updates global storage. - * - * @param assetAddress The addresses to use when updating favorites. - * @param add Whether these assets should be added or removed. `true` indicates - * assets should be added and `false` indicates they should be removed. - */ -export const uniswapUpdateFavorites = ( - assetAddress: string | string[], - add = true -) => async ( - dispatch: Dispatch, - getState: AppGetState -) => { - const { favorites, favoritesMeta } = getState().uniswap; - const normalizedFavorites = favorites.map(toLower); - - const updatedFavorites = add - ? uniq(normalizedFavorites.concat(assetAddress)) - : isArray(assetAddress) - ? without(normalizedFavorites, ...assetAddress) - : without(normalizedFavorites, assetAddress); - const updatedFavoritesMeta = - (await getUniswapFavoritesMetadata(updatedFavorites)) || favoritesMeta; - dispatch({ - payload: { - favorites: updatedFavorites, - favoritesMeta: updatedFavoritesMeta, - }, - type: UNISWAP_UPDATE_FAVORITES, - }); - saveUniswapFavorites(updatedFavorites); - saveUniswapFavoritesMetadata(updatedFavoritesMeta); -}; - -// -- Reducer --------------------------------------------------------------- // - -export const INITIAL_UNISWAP_STATE: UniswapState = { - favorites: DefaultUniswapFavorites[Network.mainnet], - favoritesMeta: DefaultUniswapFavoritesMeta[Network.mainnet], - loadingUniswap: false, - get pairs() { - return rainbowTokenList.CURATED_TOKENS; - }, -}; - -export default ( - state: UniswapState = INITIAL_UNISWAP_STATE, - action: UniswapAction -): UniswapState => - produce(state, draft => { - switch (action.type) { - case UNISWAP_LOAD_REQUEST: - draft.loadingUniswap = true; - break; - case UNISWAP_UPDATE_PAIRS: - draft.pairs = action.payload; - break; - case UNISWAP_LOAD_SUCCESS: - draft.favorites = action.payload.favorites; - draft.favoritesMeta = action.payload.favoritesMeta; - draft.loadingUniswap = false; - break; - case UNISWAP_UPDATE_FAVORITES: - draft.favorites = action.payload.favorites; - draft.favoritesMeta = action.payload.favoritesMeta; - break; - case UNISWAP_LOAD_FAILURE: - draft.loadingUniswap = false; - break; - case UNISWAP_CLEAR_STATE: - return INITIAL_UNISWAP_STATE; - default: - break; - } - }); diff --git a/src/redux/userLists.ts b/src/redux/userLists.ts deleted file mode 100644 index ff964f301e2..00000000000 --- a/src/redux/userLists.ts +++ /dev/null @@ -1,253 +0,0 @@ -import produce from 'immer'; -import { isArray, without } from 'lodash'; -import uniq from 'lodash/uniq'; -import { InteractionManager } from 'react-native'; -import { Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { UserList } from '@/entities'; -import { - getSelectedUserList, - getUserLists, - saveSelectedUserList, - saveUserLists, -} from '@/handlers/localstorage/userLists'; -import { emitAssetRequest } from '@/redux/explorer'; -import { AppGetState, AppState } from '@/redux/store'; -import { uniswapUpdateFavorites } from '@/redux/uniswap'; -import { DefaultTokenLists, TokenListsExtendedRecord } from '@/references'; - -// -- Constants ------------------------------------------------------------- // -const USER_LISTS_READY = 'userLists/USER_LISTS_READY'; -const USER_LISTS_LOAD_REQUEST = 'userLists/USER_LISTS_LOAD_REQUEST'; -const USER_LISTS_LOAD_SUCCESS = 'userLists/USER_LISTS_LOAD_SUCCESS'; -const USER_LISTS_LOAD_FAILURE = 'userLists/USER_LISTS_LOAD_FAILURE'; - -const USER_LISTS_UPDATE_LISTS = 'userLists/USER_LISTS_UPDATE_LISTS'; -const USER_LISTS_CLEAR_STATE = 'userLists/USER_LISTS_CLEAR_STATE'; -const USER_LISTS_SET_SELECTED_LIST = 'userLists/USER_LISTS_SET_SELECTED_LIST'; -const FAVORITES_LIST_ID = 'favorites'; - -// -- Actions --------------------------------------------------------------- // - -/** - * The current `userLists` state. - */ -interface UserListsState { - lists: UserList[]; - loadingUserLists: boolean; - ready: boolean; - selectedList: string | null; -} - -/** - * A `userLists` Redux action. - */ -type UserListsAction = - | UserListsLoadRequestAction - | UserListsLoadSuccessAction - | UserListsSetSelectedListAction - | UserListsUpdateListsAction - | UserListsFailureAction - | UserListsReadyAction - | UserListsClearStateAction; - -interface UserListsLoadRequestAction { - type: typeof USER_LISTS_LOAD_REQUEST; -} - -interface UserListsLoadSuccessAction { - type: typeof USER_LISTS_LOAD_SUCCESS; - payload: UserList[]; -} - -interface UserListsSetSelectedListAction { - type: typeof USER_LISTS_SET_SELECTED_LIST; - payload: string; -} - -interface UserListsUpdateListsAction { - type: typeof USER_LISTS_UPDATE_LISTS; - payload: UserList[]; -} - -interface UserListsFailureAction { - type: typeof USER_LISTS_LOAD_FAILURE; -} - -interface UserListsReadyAction { - type: typeof USER_LISTS_READY; -} - -interface UserListsClearStateAction { - type: typeof USER_LISTS_CLEAR_STATE; -} - -export const userListsLoadState = () => async ( - dispatch: ThunkDispatch< - AppState, - unknown, - | UserListsLoadSuccessAction - | UserListsLoadRequestAction - | UserListsReadyAction - | UserListsFailureAction - >, - getState: AppGetState -) => { - const { network } = getState().settings; - - dispatch({ type: USER_LISTS_LOAD_REQUEST }); - try { - const defaultLists = - (DefaultTokenLists as TokenListsExtendedRecord)[network] || []; - const userLists: UserList[] = await getUserLists(); - const lists = userLists?.length ? userLists : defaultLists; - let allAddresses: string[] = []; - lists.forEach((list: { tokens: any }) => { - allAddresses = [...allAddresses, ...list.tokens]; - }); - dispatch({ - payload: lists, - type: USER_LISTS_LOAD_SUCCESS, - }); - const selectedUserList = (await getSelectedUserList()) || FAVORITES_LIST_ID; - dispatch(userListsSetSelectedList(selectedUserList, false)); - dispatch(emitAssetRequest(allAddresses)); - dispatch({ - type: USER_LISTS_READY, - }); - } catch (error) { - dispatch({ type: USER_LISTS_LOAD_FAILURE }); - } -}; - -export const userListsSetSelectedList = ( - listId: string, - save: boolean = true -) => (dispatch: Dispatch) => { - dispatch({ - payload: listId, - type: USER_LISTS_SET_SELECTED_LIST, - }); - if (save) { - InteractionManager.runAfterInteractions(() => { - saveSelectedUserList(listId); - }); - } -}; - -export const userListsClearList = (listId: string) => ( - dispatch: Dispatch, - getState: AppGetState -) => { - const { lists } = getState().userLists; - const allNewLists = [...lists]; - - // Find the list index - let listIndex = null; - allNewLists.find((list, index) => { - if (list?.id === listId) { - listIndex = index; - return true; - } - return false; - }); - - // update the list - if (listIndex !== null) { - const newList = { ...allNewLists[listIndex] }; - newList.tokens = []; - allNewLists[listIndex] = newList; - - dispatch({ - payload: allNewLists, - type: USER_LISTS_UPDATE_LISTS, - }); - saveUserLists(allNewLists); - } -}; - -export const userListsUpdateList = ( - assetAddress: string, - listId: string, - add = true -) => ( - dispatch: ThunkDispatch, - getState: AppGetState -) => { - if (listId === FAVORITES_LIST_ID) { - dispatch(uniswapUpdateFavorites(assetAddress, add)); - } else { - const { lists } = getState().userLists; - const allNewLists = [...lists]; - - // Find the list index - let listIndex = null; - allNewLists.find((list, index) => { - if (list?.id === listId) { - listIndex = index; - return true; - } - return false; - }); - - // add or remove - if (listIndex !== null) { - const updatedListTokens = add - ? uniq(allNewLists[listIndex].tokens.concat(assetAddress)) - : isArray(assetAddress) - ? without(allNewLists[listIndex].tokens, ...assetAddress) - : without(allNewLists[listIndex].tokens, assetAddress); - if (add) { - dispatch(emitAssetRequest(assetAddress)); - } - - // update the list - const newList = { ...allNewLists[listIndex] }; - newList.tokens = updatedListTokens; - allNewLists[listIndex] = newList; - - dispatch({ - payload: allNewLists, - type: USER_LISTS_UPDATE_LISTS, - }); - saveUserLists(allNewLists); - } - } -}; - -// -- Reducer --------------------------------------------------------------- // -export const INITIAL_USER_LISTS_STATE: UserListsState = { - lists: [], - loadingUserLists: false, - ready: false, - selectedList: null, -}; - -export default (state = INITIAL_USER_LISTS_STATE, action: UserListsAction) => - produce(state, draft => { - switch (action.type) { - case USER_LISTS_LOAD_REQUEST: - draft.loadingUserLists = true; - break; - case USER_LISTS_LOAD_SUCCESS: - draft.lists = action.payload; - draft.loadingUserLists = false; - break; - case USER_LISTS_SET_SELECTED_LIST: - draft.selectedList = action.payload; - break; - case USER_LISTS_UPDATE_LISTS: - draft.lists = action.payload; - break; - case USER_LISTS_LOAD_FAILURE: - draft.loadingUserLists = false; - break; - case USER_LISTS_READY: - draft.ready = true; - break; - case USER_LISTS_CLEAR_STATE: - return INITIAL_USER_LISTS_STATE; - default: - break; - } - }); diff --git a/src/references/default-token-lists.ts b/src/references/default-token-lists.ts deleted file mode 100644 index 96633bc2aed..00000000000 --- a/src/references/default-token-lists.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as i18n from '@/languages'; - -export default { - mainnet: [ - { - emoji: 'fire', - id: 'trending', - name: i18n.t(i18n.l.discover.lists.types.trending), - tokens: [], - }, - { - emoji: 'television', - id: 'watchlist', - name: i18n.t(i18n.l.discover.lists.types.watchlist), - tokens: [], - }, - { - emoji: 'star', - id: 'favorites', - name: i18n.t(i18n.l.discover.lists.types.favorites), - tokens: [], - }, - { - emoji: 'roller_coaster', - id: 'defi', - name: i18n.t(i18n.l.discover.lists.types.defi), - tokens: [ - '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', // UNI - '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', // AAVE - '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', // SNX - '0xc00e94cb662c3520282e6f5717214004a7f26888', // COMP - '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', // MKR - '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', // YFI - '0xbbbbca6a901c926f240b89eacb641d8aec7aeafd', // LRCv2 - '0x408e41876cccdc0f92210600ef50372656052a38', // REN - '0xba100000625a3754423978a60c9317c58a424e3d', // BAL - '0xdd974d5c2e2928dea5f71b9825b8b646686bd200', // KNC - ], - }, - { - emoji: 'dollar_banknote', - id: 'stablecoins', - name: i18n.t(i18n.l.discover.lists.types.stablecoins), - tokens: [ - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD - '0x4fabb145d64652a948d72533023f6e7a623c7c53', // BUSD - '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // SUSD - '0x056fd409e1d7a124bd7017459dfea2f387b6d5cd', // GUSD - '0xdb25f211ab05b1c97d595516f45794528a807ad8', // EURS - ], - }, - ], -}; diff --git a/src/references/index.ts b/src/references/index.ts index 36a9c29e717..27bddc984cc 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -1,11 +1,9 @@ import { savingsAssets } from './compound'; -import { default as DefaultTokenListsSource } from './default-token-lists'; -import { Asset, UniswapFavoriteTokenData } from '@/entities'; +import { Asset } from '@/entities'; import { Network } from '@/helpers/networkTypes'; export { default as balanceCheckerContractAbi } from './balances-checker-abi.json'; export { default as chainAssets } from './chain-assets.json'; -export { DefaultTokenListsSource as DefaultTokenLists }; export { signatureRegistryABI, SIGNATURE_REGISTRY_ADDRESS, @@ -22,7 +20,6 @@ export { DPI_ADDRESS } from './indexes'; export { default as supportedNativeCurrencies } from './native-currencies.json'; export { default as shitcoins } from './shitcoins'; export { default as smartContractMethods } from './smartcontract-methods.json'; -export { UNISWAP_TESTNET_TOKEN_LIST } from './uniswap'; export { rainbowTokenList } from './rainbow-token-list'; export { @@ -144,81 +141,6 @@ export const AddCashCurrencyInfo: { }, }; -/** - * A `Record` representation of the default token lists. This is useful - * for instances where a `Network` must be used as a key for the token lists, - * but the particular network does not actually exist in the data. In that - * case, we can cast the token lists to `TokenListsExtendedRecord` to get - * undefined as the value, instead of a TypeScript compilation error. - */ -export type TokenListsExtendedRecord = Record< - Network, - typeof DefaultTokenListsSource[keyof typeof DefaultTokenListsSource] ->; - -export const DefaultUniswapFavorites = { - mainnet: [ETH_ADDRESS, DAI_ADDRESS, WBTC_ADDRESS, SOCKS_ADDRESS], -}; - -export const DefaultUniswapFavoritesMeta: Record< - string, - UniswapFavoriteTokenData -> = { - mainnet: { - [DAI_ADDRESS]: { - address: DAI_ADDRESS, - color: '#F0B340', - decimals: 18, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Dai', - symbol: 'DAI', - type: 'token', - uniqueId: DAI_ADDRESS, - }, - [ETH_ADDRESS]: { - address: ETH_ADDRESS, - color: '#25292E', - decimals: 18, - favorite: true, - highLiquidity: true, - isVerified: true, - name: 'Ethereum', - symbol: 'ETH', - type: 'token', - uniqueId: ETH_ADDRESS, - }, - [SOCKS_ADDRESS]: { - address: SOCKS_ADDRESS, - color: '#E15EE5', - decimals: 18, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Unisocks', - symbol: 'SOCKS', - type: 'token', - uniqueId: SOCKS_ADDRESS, - }, - [WBTC_ADDRESS]: { - address: WBTC_ADDRESS, - color: '#FF9900', - decimals: 8, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Wrapped Bitcoin', - symbol: 'WBTC', - type: 'token', - uniqueId: WBTC_ADDRESS, - }, - }, -}; - export const savingsAssetsList: Record< string, Record diff --git a/src/references/uniswap/index.ts b/src/references/uniswap/index.ts deleted file mode 100644 index c9dccf666c3..00000000000 --- a/src/references/uniswap/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { default as UNISWAP_TESTNET_TOKEN_LIST } from './uniswap-pairs-testnet.json'; - -export { UNISWAP_TESTNET_TOKEN_LIST }; diff --git a/src/references/uniswap/uniswap-pairs-testnet.json b/src/references/uniswap/uniswap-pairs-testnet.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/src/references/uniswap/uniswap-pairs-testnet.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts new file mode 100644 index 00000000000..4238fc64b67 --- /dev/null +++ b/src/resources/favorites.ts @@ -0,0 +1,160 @@ +import { EthereumAddress, RainbowToken } from '@/entities'; +import { getUniswapV2Tokens } from '@/handlers/dispersion'; +import { createQueryKey, queryClient } from '@/react-query'; +import { + DAI_ADDRESS, + ETH_ADDRESS, + SOCKS_ADDRESS, + WBTC_ADDRESS, + WETH_ADDRESS, +} from '@/references'; +import { useQuery } from '@tanstack/react-query'; +import { without } from 'lodash'; + +export const favoritesQueryKey = createQueryKey( + 'favorites', + {}, + { persisterVersion: 1 } +); + +const DEFAULT: Record = { + [DAI_ADDRESS]: { + address: DAI_ADDRESS, + color: '#F0B340', + decimals: 18, + favorite: true, + highLiquidity: true, + isRainbowCurated: true, + isVerified: true, + name: 'Dai', + symbol: 'DAI', + type: 'token', + uniqueId: DAI_ADDRESS, + }, + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + color: '#25292E', + decimals: 18, + favorite: true, + highLiquidity: true, + isVerified: true, + name: 'Ethereum', + symbol: 'ETH', + type: 'token', + uniqueId: ETH_ADDRESS, + }, + [SOCKS_ADDRESS]: { + address: SOCKS_ADDRESS, + color: '#E15EE5', + decimals: 18, + favorite: true, + highLiquidity: true, + isRainbowCurated: true, + isVerified: true, + name: 'Unisocks', + symbol: 'SOCKS', + type: 'token', + uniqueId: SOCKS_ADDRESS, + }, + [WBTC_ADDRESS]: { + address: WBTC_ADDRESS, + color: '#FF9900', + decimals: 8, + favorite: true, + highLiquidity: true, + isRainbowCurated: true, + isVerified: true, + name: 'Wrapped Bitcoin', + symbol: 'WBTC', + type: 'token', + uniqueId: WBTC_ADDRESS, + }, +}; + +/** + * Returns a map of the given `addresses` to their corresponding `RainbowToken` metadata. + */ +async function fetchMetadata(addresses: string[]) { + const favoritesMetadata: Record = {}; + const newFavoritesMeta = await getUniswapV2Tokens( + addresses.map(address => { + return address === ETH_ADDRESS ? WETH_ADDRESS : address.toLowerCase(); + }) + ); + const ethIsFavorited = addresses.includes(ETH_ADDRESS); + const wethIsFavorited = addresses.includes(WETH_ADDRESS); + if (newFavoritesMeta) { + if (newFavoritesMeta[WETH_ADDRESS] && ethIsFavorited) { + const favorite = newFavoritesMeta[WETH_ADDRESS]; + newFavoritesMeta[ETH_ADDRESS] = { + ...favorite, + address: ETH_ADDRESS, + name: 'Ethereum', + symbol: 'ETH', + uniqueId: ETH_ADDRESS, + }; + } + Object.entries(newFavoritesMeta).forEach(([address, favorite]) => { + if (address !== WETH_ADDRESS || wethIsFavorited) { + favoritesMetadata[address] = { ...favorite, favorite: true }; + } + }); + } + return favoritesMetadata; +} + +/** + * Refreshes the metadata associated with all favorites. + */ +export async function refreshFavorites() { + const favorites = Object.keys( + queryClient.getQueryData(favoritesQueryKey) ?? DEFAULT + ); + const updatedMetadata = await fetchMetadata(favorites); + return updatedMetadata; +} + +/** + * Toggles the favorited status of the given `address`. + */ +export async function toggleFavorite(address: string) { + const favorites = Object.keys( + queryClient.getQueryData(favoritesQueryKey) ?? [] + ); + const lowercasedAddress = address.toLowerCase(); + let updatedFavorites; + if (favorites.includes(lowercasedAddress)) { + updatedFavorites = without(favorites, lowercasedAddress); + } else { + updatedFavorites = [...favorites, lowercasedAddress]; + } + const metadata = await fetchMetadata(updatedFavorites); + queryClient.setQueryData(favoritesQueryKey, metadata); +} + +/** + * Returns `favorites`, an array of favorited addresses, as well as `favoritesMetadata`, a map of these + * addresses to their corresponding `RainbowToken`. These values are cached in AsyncStorage and is only + * modified/updated when `toggleFavorite` or `refreshFavorites` is called. + */ +export function useFavorites(): { + favorites: string[]; + favoritesMetadata: Record; +} { + const query = useQuery>( + favoritesQueryKey, + refreshFavorites, + { + staleTime: Infinity, + cacheTime: Infinity, + } + ); + + const favoritesMetadata = query.data ?? {}; + const favorites = Object.keys(favoritesMetadata); + + return { + favorites, + favoritesMetadata, + }; +} diff --git a/src/screens/AddTokenSheet.js b/src/screens/AddTokenSheet.js deleted file mode 100644 index b871100ab47..00000000000 --- a/src/screens/AddTokenSheet.js +++ /dev/null @@ -1,203 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { useCallback } from 'react'; -import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import Divider from '../components/Divider'; -import TouchableBackdrop from '../components/TouchableBackdrop'; -import { ButtonPressAnimation } from '../components/animations'; -import { CoinIcon } from '../components/coin-icon'; -import { Centered, Column, Row } from '../components/layout'; -import { - SheetActionButton, - SheetActionButtonRow, - SlackSheet, -} from '../components/sheet'; -import { Emoji, Text } from '../components/text'; -import { DefaultTokenLists } from '../references/'; -import { useAccountSettings, useDimensions, useUserLists } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import styled from '@/styled-thing'; -import { position } from '@/styles'; -import { haptics } from '@/utils'; - -const Container = styled(Centered).attrs({ - direction: 'column', -})(({ deviceHeight, height }) => ({ - ...position.coverAsObject, - ...(height ? { height: height + deviceHeight } : {}), -})); - -const RemoveButton = styled(ButtonPressAnimation)({ - backgroundColor: ({ theme: { colors } }) => colors.alpha(colors.red, 0.06), - borderRadius: 15, - height: 30, - marginLeft: 8, - paddingLeft: 6, - paddingRight: 10, - paddingTop: 5, - top: android ? 0 : 2, -}); - -const RemoveButtonContent = styled(Text).attrs(({ theme: { colors } }) => ({ - color: colors.red, - letterSpacing: 'roundedTight', - size: 'lmedium', - weight: 'bold', -}))(android ? { marginTop: -5 } : {}); - -const ListButton = styled(ButtonPressAnimation)({ - paddingBottom: 15, - paddingTop: 15, -}); - -const ListEmoji = styled(Emoji).attrs({ - size: 'large', -})({ - marginRight: 6, - marginTop: android ? 4 : 1, -}); - -export const sheetHeight = android ? 490 - getSoftMenuBarHeight() : 394; - -export default function AddTokenSheet() { - const { goBack } = useNavigation(); - const { height: deviceHeight } = useDimensions(); - const { network } = useAccountSettings(); - const { favorites, lists, updateList } = useUserLists(); - const insets = useSafeAreaInsets(); - const { - params: { item, isL2 }, - } = useRoute(); - const writeableLists = isL2 ? ['watchlist'] : ['watchlist', 'favorites']; - - const isTokenInList = useCallback( - listId => { - if (listId === 'favorites') { - return !!favorites?.find( - address => address.toLowerCase() === item?.address?.toLowerCase() - ); - } else { - const list = lists?.find(list => list?.id === listId); - return !!list?.tokens?.find( - token => token.toLowerCase() === item?.address?.toLowerCase() - ); - } - }, - [favorites, item.address, lists] - ); - - const { colors } = useTheme(); - - return ( - - {ios && } - - - - - - - - - {item.name} - - - - - {lang.t('button.add_to_list')} - - - - - - - - - {DefaultTokenLists[network] - .filter(list => writeableLists.includes(list?.id)) - .map(list => { - const alreadyAdded = isTokenInList(list?.id); - const handleAdd = () => { - if (alreadyAdded) return; - updateList(item.address, list?.id, !alreadyAdded); - haptics.notificationSuccess(); - }; - const handleRemove = () => { - updateList(item.address, list?.id, false); - haptics.notificationSuccess(); - }; - return ( - - - - - - {list.name} - - - - {alreadyAdded && ( - - - 􀈔 {lang.t('button.remove')} - - - )} - - ); - })} - - - - - - - - - ); -} diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx index b18d8806bf3..df025c00f6c 100644 --- a/src/screens/CurrencySelectModal.tsx +++ b/src/screens/CurrencySelectModal.tsx @@ -21,7 +21,7 @@ import { TextInput, } from 'react-native'; import { MMKV } from 'react-native-mmkv'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import { useDispatch } from 'react-redux'; import { useDebounce } from 'use-debounce'; import GestureBlocker from '../components/GestureBlocker'; @@ -144,14 +144,8 @@ export default function CurrencySelectModal() { const searchInputRef = useRef(null); const { handleFocus } = useMagicAutofocus(searchInputRef, undefined, true); - const [assetsToFavoriteQueue, setAssetsToFavoriteQueue] = useState< - Record - >({}); const [searchQuery, setSearchQuery] = useState(''); const [searchQueryForSearch] = useDebounce(searchQuery, 350); - const searchQueryExists = useMemo(() => searchQuery.length > 0, [ - searchQuery, - ]); const assetsInWallet = useAssetsInWallet() as SwappableAsset[]; const { hiddenCoinsObj } = useCoinListEditOptions(); @@ -206,7 +200,6 @@ export default function CurrencySelectModal() { crosschainExactMatches, swapCurrencyList, swapCurrencyListLoading, - updateFavorites, } = useSwapCurrencyList(searchQueryForSearch, currentChainId, false); const { @@ -371,23 +364,6 @@ export default function CurrencySelectModal() { return list.filter(section => section.data.length > 0); }, [activeSwapCurrencyList, getWalletCurrencyList, type]); - const handleFavoriteAsset = useCallback( - (asset: any, isFavorited: any) => { - setAssetsToFavoriteQueue(prevFavoriteQueue => ({ - ...prevFavoriteQueue, - [asset.address]: isFavorited, - })); - analytics.track('Toggled an asset as Favorited', { - isFavorited, - name: asset.name, - symbol: asset.symbol, - tokenAddress: asset.address, - type, - }); - }, - [type] - ); - const handleNavigate = useCallback( (item: any) => { delayNext(); @@ -527,38 +503,17 @@ export default function CurrencySelectModal() { const itemProps = useMemo(() => { const isMainnet = currentChainId === ChainId.mainnet; return { - onActionAsset: handleFavoriteAsset, onPress: handleSelectAsset, showBalance: type === CurrencySelectionTypes.input, showFavoriteButton: type === CurrencySelectionTypes.output && isMainnet, }; - }, [handleFavoriteAsset, handleSelectAsset, type, currentChainId]); + }, [handleSelectAsset, type, currentChainId]); const searchingOnL2Network = useMemo( () => isL2Network(ethereumUtils.getNetworkFromChainId(currentChainId)), [currentChainId] ); - const handleApplyFavoritesQueue = useCallback(() => { - const addresses = Object.keys(assetsToFavoriteQueue); - const [assetsToAdd, assetsToRemove] = addresses.reduce( - ([add, remove]: string[][], current) => { - if (assetsToFavoriteQueue[current]) { - add.push(current); - } else { - remove.push(current); - } - return [add, remove]; - }, - [[], []] - ); - - /* @ts-expect-error Can't ascertain correct return type */ - updateFavorites(assetsToAdd, true).then(() => - updateFavorites(assetsToRemove, false) - ); - }, [assetsToFavoriteQueue, updateFavorites]); - const [startInteraction] = useInteraction(); useEffect(() => { if (!fromDiscover) { @@ -566,7 +521,6 @@ export default function CurrencySelectModal() { toggleGestureEnabled(!isFocused); } if (!isFocused && prevIsFocused) { - handleApplyFavoritesQueue(); restoreFocusOnSwapModal?.(); setTimeout(() => { setIsTransitioning(false); // hide list now that we have arrived on main exchange modal @@ -574,7 +528,6 @@ export default function CurrencySelectModal() { } } }, [ - handleApplyFavoritesQueue, isFocused, startInteraction, prevIsFocused, @@ -594,16 +547,6 @@ export default function CurrencySelectModal() { setIsTransitioning(true); // continue to display list while transitiong back }, [inputCurrency?.type]); - const shouldUpdateFavoritesRef = useRef(false); - useEffect(() => { - if (!searchQueryExists && shouldUpdateFavoritesRef.current) { - shouldUpdateFavoritesRef.current = false; - handleApplyFavoritesQueue(); - } else if (searchQueryExists) { - shouldUpdateFavoritesRef.current = true; - } - }, [assetsToFavoriteQueue, handleApplyFavoritesQueue, searchQueryExists]); - useEffect(() => { // check if list has items before attempting to scroll if (!currencyList[0]?.data) return; diff --git a/src/screens/discover/components/DiscoverHome.js b/src/screens/discover/components/DiscoverHome.js index 7886d2566f7..358c2b14599 100644 --- a/src/screens/discover/components/DiscoverHome.js +++ b/src/screens/discover/components/DiscoverHome.js @@ -6,7 +6,6 @@ import useExperimentalFlag, { MINTS, NFT_OFFERS, } from '@rainbow-me/config/experimentalHooks'; -import Lists from './ListsSection'; import { isTestnetNetwork } from '@/handlers/web3'; import { Inline, Inset, Stack } from '@/design-system'; import { useAccountSettings, useWallets } from '@/hooks'; @@ -56,47 +55,42 @@ export default function DiscoverHome() { ).length > 0; return ( - - - - {!testNetwork ? ( + + {!testNetwork ? ( + + + + {isProfilesEnabled && } + + {mintsEnabled && (mints?.length || isFetching) && ( - - - {isProfilesEnabled && } - - {mintsEnabled && (mints?.length || isFetching) && ( - - {!!featuredMint && } - - - - - )} - {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */} - {!IS_TEST && nftOffersEnabled && } - {/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/} - {(opRewardsRemoteFlag || opRewardsLocalFlag) && } - {hardwareWalletsEnabled && !hasHardwareWallets && } - {isProfilesEnabled && } - - - - - - - ) : ( - - - - - - + {!!featuredMint && } + + + )} - - - + {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */} + {!IS_TEST && nftOffersEnabled && } + {/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/} + {(opRewardsRemoteFlag || opRewardsLocalFlag) && } + {hardwareWalletsEnabled && !hasHardwareWallets && } + {isProfilesEnabled && } + + + + + + + ) : ( + + + + + + + + )} ); } diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 6c8bd913edb..9a9e1db97e3 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -208,21 +208,13 @@ export default function DiscoverSearch() { ] ); - const handleActionAsset = useCallback( - item => { - navigate(Routes.ADD_TOKEN_SHEET, { item }); - }, - [navigate] - ); - const itemProps = useMemo( () => ({ - onActionAsset: handleActionAsset, onPress: handlePress, - showAddButton: true, + showFavoriteButton: true, showBalance: false, }), - [handleActionAsset, handlePress] + [handlePress] ); const addEnsResults = useCallback( diff --git a/src/screens/discover/components/ListsSection.js b/src/screens/discover/components/ListsSection.js deleted file mode 100644 index 8b93808de2d..00000000000 --- a/src/screens/discover/components/ListsSection.js +++ /dev/null @@ -1,314 +0,0 @@ -import lang from 'i18n-js'; -import React, { - Fragment, - useCallback, - useEffect, - useMemo, - useRef, -} from 'react'; -import { FlatList, LayoutAnimation } from 'react-native'; -import { IS_TESTING } from 'react-native-dotenv'; -import { useDispatch, useSelector } from 'react-redux'; -import { emitAssetRequest, emitChartsRequest } from '@/redux/explorer'; -import { ButtonPressAnimation } from '@/components/animations'; -import { AssetListItemSkeleton } from '@/components/asset-list'; -import { ListCoinRow } from '@/components/coin-row'; -import { Centered, Column, Flex, Row } from '@/components/layout'; -import { Emoji, Text } from '@/components/text'; -import EdgeFade from '@/components/EdgeFade'; -import { analytics } from '@/analytics'; -import { getTrendingAddresses } from '@/handlers/dispersion'; -import networkTypes from '@/helpers/networkTypes'; -import { times } from '@/helpers/utilities'; -import { useAccountSettings, useUserLists } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { ethereumUtils } from '@/utils'; -import { DefaultTokenLists } from '@/references'; -import { Inset } from '@/design-system'; - -const ListButton = styled(ButtonPressAnimation).attrs({ - scaleTo: 0.96, -})(({ selected, theme: { colors } }) => ({ - marginRight: 16, - paddingTop: ios ? 6.5 : 4.5, - - ...(selected && { - backgroundColor: colors.alpha(colors.blueGreyDark, 0.06), - borderRadius: 12, - height: 30, - paddingHorizontal: 8, - }), -})); - -const ListName = styled(Text)({ - marginLeft: 3, - marginTop: ios ? -4.5 : 0, -}); - -// Update trending lists every 5 minutes -const TRENDING_LIST_UPDATE_INTERVAL = 5 * 60 * 1000; - -const layouts = [ - { length: 110, offset: 0 }, - { length: 89, offset: 110 }, - { length: 120, offset: 200 }, - { length: 81, offset: 320 }, - { length: 92, offset: 400 }, -]; -const getItemLayout = (_, index) => ({ - index, - ...layouts[index], -}); - -export default function ListSection() { - const dispatch = useDispatch(); - const { network, nativeCurrency } = useAccountSettings(); - const { navigate } = useNavigation(); - const { - favorites, - lists, - updateList, - ready, - selectedList, - setSelectedList, - clearList, - } = useUserLists(); - const listRef = useRef(null); - const initialized = useRef(false); - const genericAssets = useSelector( - ({ data: { genericAssets } }) => genericAssets - ); - - const { colors } = useTheme(); - const listData = useMemo(() => DefaultTokenLists[network], [network]); - - const assetsSocket = useSelector( - ({ explorer: { assetsSocket } }) => assetsSocket - ); - useEffect(() => { - if (assetsSocket !== null) { - Object.values(listData).forEach(({ tokens }) => { - dispatch(emitAssetRequest(tokens)); - dispatch(emitChartsRequest(tokens)); - }); - } - }, [assetsSocket, dispatch, listData]); - - const trendingListHandler = useRef(null); - - const updateTrendingList = useCallback(async () => { - const tokens = await getTrendingAddresses(); - clearList('trending'); - - if (tokens) { - dispatch(emitAssetRequest(tokens)); - dispatch(emitChartsRequest(tokens)); - updateList(tokens, 'trending', true); - } - - trendingListHandler.current = setTimeout( - () => updateTrendingList(), - TRENDING_LIST_UPDATE_INTERVAL - ); - }, [clearList, dispatch, updateList]); - - const handleSwitchList = useCallback( - (id, index) => { - if (IS_TESTING !== 'true') { - LayoutAnimation.configureNext( - LayoutAnimation.create(200, 'easeInEaseOut', 'opacity') - ); - } - setSelectedList(id); - listRef.current?.scrollToIndex({ - animated: true, - index, - viewPosition: 0.5, - }); - }, - [setSelectedList] - ); - - useEffect(() => { - if (ready && !initialized.current) { - ready && updateTrendingList(); - const currentListIndex = lists.findIndex( - list => list?.id === selectedList - ); - if (listData?.length > 0) { - setTimeout(() => { - if (lists[currentListIndex]) { - handleSwitchList(lists[currentListIndex]?.id, currentListIndex); - } - }, 300); - } - initialized.current = true; - } - return () => { - clearTimeout(trendingListHandler.current); - }; - }, [ - ready, - clearList, - dispatch, - updateList, - updateTrendingList, - lists, - selectedList, - handleSwitchList, - listData?.length, - ]); - - const listItems = useMemo(() => { - if (network !== networkTypes.mainnet) { - return []; - } - let items = []; - if (selectedList === 'favorites') { - items = favorites - .map( - address => - ethereumUtils.getAccountAsset(address) || - ethereumUtils.formatGenericAsset( - genericAssets[address.toLowerCase()], - nativeCurrency - ) - ) - .sort((a, b) => (a.name > b.name ? 1 : -1)); - } else { - if (!lists?.length) return []; - const currentList = lists.find(list => list?.id === selectedList); - if (!currentList) { - return []; - } - - items = currentList.tokens.map( - address => - ethereumUtils.getAccountAsset(address) || - ethereumUtils.formatGenericAsset( - genericAssets[address.toLowerCase()], - nativeCurrency - ) - ); - } - - return items.filter(item => item.symbol && Number(item.price?.value) > 0); - }, [favorites, genericAssets, lists, nativeCurrency, network, selectedList]); - - const handlePress = useCallback( - item => { - analytics.track('Pressed List Item', { - category: 'discover', - selectedList, - symbol: item.symbol, - }); - navigate(Routes.EXPANDED_ASSET_SHEET, { - asset: item, - fromDiscover: true, - type: 'token', - }); - }, - [navigate, selectedList] - ); - - const renderItem = useCallback( - ({ item: list, index }) => ( - handleSwitchList(list?.id, index)} - selected={selectedList === list?.id} - testID={`list-${list?.id}`} - > - - - - {list.name} - - - - ), - [colors, handleSwitchList, selectedList] - ); - - return ( - - - - - {lang.t('discover.lists.lists_title')} - - - - - - item?.id} - ref={listRef} - renderItem={renderItem} - scrollsToTop={false} - showsHorizontalScrollIndicator={false} - testID={`lists-section-${selectedList}`} - /> - {IS_TESTING !== 'true' && } - - - {!ready && IS_TESTING !== 'true' ? ( - times(2, index => ( - - )) - ) : listItems?.length ? ( - listItems.map(item => ( - handlePress(item)} - showBalance={false} - /> - )) - ) : ( - - - {lang.t('discover.lists.this_list_is_empty')} - - - )} - - - - - ); -}