From a8c6626cf191b576c94daddb46f3f0afec6dca76 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 28 Sep 2023 11:45:28 -0700 Subject: [PATCH 1/8] stuff --- src/hooks/useFavorites.ts | 54 +++++++ src/hooks/useUserLists.ts | 3 +- .../discover/components/ListsSection.js | 138 +++++++++--------- 3 files changed, 127 insertions(+), 68 deletions(-) create mode 100644 src/hooks/useFavorites.ts diff --git a/src/hooks/useFavorites.ts b/src/hooks/useFavorites.ts new file mode 100644 index 00000000000..c144ade835f --- /dev/null +++ b/src/hooks/useFavorites.ts @@ -0,0 +1,54 @@ +import { useQuery } from '@tanstack/react-query'; +import useAccountProfile from './useAccountProfile'; +import { createQueryKey, queryClient } from '@/react-query'; +import { getUniswapFavorites } from '@/handlers/localstorage/uniswap'; +import useAccountSettings from './useAccountSettings'; + +export const getFavorites = () => [ + '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', + '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + '0xc00e94cb662c3520282e6f5717214004a7f26888', + '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', + '0xbbbbca6a901c926f240b89eacb641d8aec7aeafd', + '0x408e41876cccdc0f92210600ef50372656052a38', + '0xba100000625a3754423978a60c9317c58a424e3d', + '0xdd974d5c2e2928dea5f71b9825b8b646686bd200', +]; + +export const saveFavorites = () => {}; + +export const favoritesQueryKey = ({ address }: { address: string }) => + createQueryKey('favorites', { address }, { persisterVersion: 1 }); + +export function useFavorites() { + const { accountAddress } = useAccountProfile(); + const { network } = useAccountSettings(); + + const queryKey = favoritesQueryKey({ address: accountAddress }); + + const query = useQuery( + queryKey, + async () => await getUniswapFavorites(network), + { + enabled: !!accountAddress, + staleTime: Infinity, + cacheTime: Infinity, + } + ); + + return { + favorites: query?.data ?? [], + addToFavorites: (tokenAddress: string) => { + const lowercasedAddress = tokenAddress.toLowerCase(); + if (!query?.data?.includes(lowercasedAddress)) { + queryClient.setQueryData(queryKey, (oldData: string[] | undefined) => [ + ...(oldData ?? []), + lowercasedAddress, + ]); + } + }, + removeFromFavorites: (tokenAddress: string) => {}, + }; +} diff --git a/src/hooks/useUserLists.ts b/src/hooks/useUserLists.ts index 4aa1e9f81e6..5be1d1a747d 100644 --- a/src/hooks/useUserLists.ts +++ b/src/hooks/useUserLists.ts @@ -19,7 +19,8 @@ export default function useUserLists() { const ready = useSelector(userListsReadySelector); const selectedList = useSelector(userListsSelectedListSelector); const favorites = useSelector(uniswapFavoritesSelector); - + console.log('LISTS!!'); + console.log(lists); const updateList = useCallback( // @ts-expect-error ts-migrate(2556) FIXME: Expected 2-3 arguments, but got 0 or more. (...data) => dispatch(userListsUpdateList(...data)), diff --git a/src/screens/discover/components/ListsSection.js b/src/screens/discover/components/ListsSection.js index 8b93808de2d..70f799ca544 100644 --- a/src/screens/discover/components/ListsSection.js +++ b/src/screens/discover/components/ListsSection.js @@ -26,7 +26,7 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { ethereumUtils } from '@/utils'; import { DefaultTokenLists } from '@/references'; -import { Inset } from '@/design-system'; +import { useFavorites } from '@/hooks/useFavorites'; const ListButton = styled(ButtonPressAnimation).attrs({ scaleTo: 0.96, @@ -81,6 +81,12 @@ export default function ListSection() { ({ data: { genericAssets } }) => genericAssets ); + const { favorites: favorites2 } = useFavorites(); + console.log('FAVORITES'); + console.log(selectedList); + console.log(favorites); + console.log(favorites2); + const { colors } = useTheme(); const listData = useMemo(() => DefaultTokenLists[network], [network]); @@ -242,73 +248,71 @@ export default function ListSection() { ); return ( - - - - - {lang.t('discover.lists.lists_title')} - - + } + testID="lists-section" + > + + + {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')} - - - )} - - - - + + + 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')} + + + )} + + + ); } From 997354ddf2e1469c854b5d8981940f3c4097f7ae Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 28 Sep 2023 12:59:45 -0700 Subject: [PATCH 2/8] done --- src/components/exchange/ExchangeAssetList.tsx | 14 +- src/entities/index.ts | 1 - src/entities/userLists.ts | 10 - src/handlers/localstorage/userLists.ts | 14 - src/hooks/index.ts | 1 - src/hooks/useLoadGlobalLateData.ts | 16 +- src/hooks/useUserLists.ts | 47 --- src/languages/en_US.json | 12 +- src/model/migrations.ts | 33 +- src/redux/reducers.ts | 2 - src/redux/uniswap.ts | 3 +- src/redux/userLists.ts | 253 -------------- src/references/default-token-lists.ts | 56 --- src/references/index.ts | 14 - src/screens/AddTokenSheet.js | 127 ++++--- .../discover/components/DiscoverHome.js | 72 ++-- .../discover/components/ListsSection.js | 318 ------------------ 17 files changed, 123 insertions(+), 870 deletions(-) delete mode 100644 src/entities/userLists.ts delete mode 100644 src/handlers/localstorage/userLists.ts delete mode 100644 src/hooks/useUserLists.ts delete mode 100644 src/redux/userLists.ts delete mode 100644 src/references/default-token-lists.ts delete mode 100644 src/screens/discover/components/ListsSection.js diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/exchange/ExchangeAssetList.tsx index c68654b48e0..f2b98236b51 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,8 @@ import { colors, Colors } from '@/styles'; import { EnrichedExchangeAsset } from '@/screens/CurrencySelectModal'; import ExchangeTokenRow from './ExchangeTokenRow'; import { SwappableAsset } from '@/entities'; +import { uniswapUpdateFavorites } from '@/redux/uniswap'; +import { useDispatch } from 'react-redux'; const deviceWidth = deviceUtils.dimensions.width; @@ -154,7 +152,7 @@ const ExchangeAssetList: ForwardRefRenderFunction< copyCount, onCopySwapDetailsText, } = useSwapDetailsClipboardState(); - const { updateList } = useUserLists(); + const dispatch = useDispatch(); // Scroll to top once the query is cleared if (prevQuery && prevQuery.length && !query.length) { @@ -305,7 +303,7 @@ const ExchangeAssetList: ForwardRefRenderFunction< setLocalFavorite(prev => { const address = rowData.address; const newValue = !prev?.[address]; - updateList(address, 'favorites', newValue); + dispatch(uniswapUpdateFavorites(address, newValue)); if (newValue) { ios && onNewEmoji(); haptics.notificationSuccess(); @@ -322,6 +320,7 @@ const ExchangeAssetList: ForwardRefRenderFunction< })), })), [ + dispatch, handleUnverifiedTokenPress, itemProps, items, @@ -330,7 +329,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< onCopySwapDetailsText, testID, theme, - updateList, ] ); diff --git a/src/entities/index.ts b/src/entities/index.ts index 3713a8bcebb..50e6d36d821 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -60,7 +60,6 @@ export { ZerionTransactionStatus, } from './transactions'; export type { EthereumAddress } from './wallet'; -export type { UserList } from './userLists'; export type { TokenSearchThreshold, TokenSearchTokenListId, 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/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/hooks/index.ts b/src/hooks/index.ts index 533db14f499..45674199f56 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -128,7 +128,6 @@ export { default as usePendingTransactions } from './usePendingTransactions'; export { default as useAssetsInWallet } from './useAssetsInWallet'; export { default as useUpdateAssetOnchainBalance } from './useUpdateAssetOnchainBalance'; 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/useLoadGlobalLateData.ts b/src/hooks/useLoadGlobalLateData.ts index 2d6413abb25..bb59d8b1e5f 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -14,7 +14,6 @@ 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'; const loadWalletBalanceNamesToCache = () => queryClient.prefetchQuery([WALLET_BALANCES_FROM_STORAGE], getWalletBalances); @@ -39,25 +38,22 @@ 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 = dispatch(uniswapLoadState()); // 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/useUserLists.ts b/src/hooks/useUserLists.ts deleted file mode 100644 index 5be1d1a747d..00000000000 --- a/src/hooks/useUserLists.ts +++ /dev/null @@ -1,47 +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); - console.log('LISTS!!'); - console.log(lists); - 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 c9d1420b2b8..7bb7a4c5106 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..4ec1d1bd352 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -52,11 +52,9 @@ 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'; @@ -272,22 +270,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); diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index f65bc271ec3..442ac814db6 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -19,7 +19,6 @@ 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'; @@ -43,7 +42,6 @@ export default combineReducers({ swap, transactionSignatures, uniswap, - userLists, walletconnect, wallets, }); diff --git a/src/redux/uniswap.ts b/src/redux/uniswap.ts index cc2b7b1206c..1db4065243e 100644 --- a/src/redux/uniswap.ts +++ b/src/redux/uniswap.ts @@ -240,9 +240,10 @@ export const uniswapUpdateFavorites = ( dispatch: Dispatch, getState: AppGetState ) => { + console.log('EHLLOO:?1'); const { favorites, favoritesMeta } = getState().uniswap; const normalizedFavorites = favorites.map(toLower); - + console.log('EHLLOO:?'); const updatedFavorites = add ? uniq(normalizedFavorites.concat(assetAddress)) : isArray(assetAddress) 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..e836115c949 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 { 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, @@ -144,18 +142,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], }; diff --git a/src/screens/AddTokenSheet.js b/src/screens/AddTokenSheet.js index b871100ab47..c8b56c5b485 100644 --- a/src/screens/AddTokenSheet.js +++ b/src/screens/AddTokenSheet.js @@ -1,6 +1,6 @@ import { useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Divider from '../components/Divider'; @@ -14,12 +14,13 @@ import { SlackSheet, } from '../components/sheet'; import { Emoji, Text } from '../components/text'; -import { DefaultTokenLists } from '../references/'; -import { useAccountSettings, useDimensions, useUserLists } from '@/hooks'; +import { useDimensions } from '@/hooks'; import { useNavigation } from '@/navigation'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { haptics } from '@/utils'; +import { useDispatch, useSelector } from 'react-redux'; +import { uniswapUpdateFavorites } from '../redux/uniswap'; const Container = styled(Centered).attrs({ direction: 'column', @@ -60,33 +61,37 @@ const ListEmoji = styled(Emoji).attrs({ export const sheetHeight = android ? 490 - getSoftMenuBarHeight() : 394; +const uniswapFavoritesSelector = state => state.uniswap.favorites; + export default function AddTokenSheet() { const { goBack } = useNavigation(); const { height: deviceHeight } = useDimensions(); - const { network } = useAccountSettings(); - const { favorites, lists, updateList } = useUserLists(); + const dispatch = useDispatch(); + const favorites = useSelector(uniswapFavoritesSelector); 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 isTokenInFavorites = useMemo( + () => + !!favorites?.find( + address => address.toLowerCase() === item?.address?.toLowerCase() + ), + [favorites, item.address] ); + const handleAdd = useCallback(() => { + if (isTokenInFavorites) return; + dispatch(uniswapUpdateFavorites(item.address, true)); + haptics.notificationSuccess(); + }, [dispatch, isTokenInFavorites, item.address]); + + const handleRemove = useCallback(() => { + dispatch(uniswapUpdateFavorites(item.address, false)); + haptics.notificationSuccess(); + }, [dispatch, item.address]); + const { colors } = useTheme(); return ( @@ -135,54 +140,38 @@ export default function AddTokenSheet() { - {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')} - - - )} - - ); - })} + + + + + + {lang.t('button.favorites')} + + + + {isTokenInFavorites && ( + + + 􀈔 {lang.t('button.remove')} + + + )} + 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/ListsSection.js b/src/screens/discover/components/ListsSection.js deleted file mode 100644 index 70f799ca544..00000000000 --- a/src/screens/discover/components/ListsSection.js +++ /dev/null @@ -1,318 +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 { useFavorites } from '@/hooks/useFavorites'; - -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 { favorites: favorites2 } = useFavorites(); - console.log('FAVORITES'); - console.log(selectedList); - console.log(favorites); - console.log(favorites2); - - 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')} - - - )} - - - - ); -} From 2fd5dc1fd37ea1c9e6caa57e2550ae28de3f5d9e Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 28 Sep 2023 13:19:51 -0700 Subject: [PATCH 3/8] tweaks --- src/hooks/useFavorites.ts | 54 --------------------------------------- src/redux/uniswap.ts | 2 -- 2 files changed, 56 deletions(-) delete mode 100644 src/hooks/useFavorites.ts diff --git a/src/hooks/useFavorites.ts b/src/hooks/useFavorites.ts deleted file mode 100644 index c144ade835f..00000000000 --- a/src/hooks/useFavorites.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import useAccountProfile from './useAccountProfile'; -import { createQueryKey, queryClient } from '@/react-query'; -import { getUniswapFavorites } from '@/handlers/localstorage/uniswap'; -import useAccountSettings from './useAccountSettings'; - -export const getFavorites = () => [ - '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', - '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', - '0xc00e94cb662c3520282e6f5717214004a7f26888', - '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', - '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', - '0xbbbbca6a901c926f240b89eacb641d8aec7aeafd', - '0x408e41876cccdc0f92210600ef50372656052a38', - '0xba100000625a3754423978a60c9317c58a424e3d', - '0xdd974d5c2e2928dea5f71b9825b8b646686bd200', -]; - -export const saveFavorites = () => {}; - -export const favoritesQueryKey = ({ address }: { address: string }) => - createQueryKey('favorites', { address }, { persisterVersion: 1 }); - -export function useFavorites() { - const { accountAddress } = useAccountProfile(); - const { network } = useAccountSettings(); - - const queryKey = favoritesQueryKey({ address: accountAddress }); - - const query = useQuery( - queryKey, - async () => await getUniswapFavorites(network), - { - enabled: !!accountAddress, - staleTime: Infinity, - cacheTime: Infinity, - } - ); - - return { - favorites: query?.data ?? [], - addToFavorites: (tokenAddress: string) => { - const lowercasedAddress = tokenAddress.toLowerCase(); - if (!query?.data?.includes(lowercasedAddress)) { - queryClient.setQueryData(queryKey, (oldData: string[] | undefined) => [ - ...(oldData ?? []), - lowercasedAddress, - ]); - } - }, - removeFromFavorites: (tokenAddress: string) => {}, - }; -} diff --git a/src/redux/uniswap.ts b/src/redux/uniswap.ts index 1db4065243e..a42ab89e1e3 100644 --- a/src/redux/uniswap.ts +++ b/src/redux/uniswap.ts @@ -240,10 +240,8 @@ export const uniswapUpdateFavorites = ( dispatch: Dispatch, getState: AppGetState ) => { - console.log('EHLLOO:?1'); const { favorites, favoritesMeta } = getState().uniswap; const normalizedFavorites = favorites.map(toLower); - console.log('EHLLOO:?'); const updatedFavorites = add ? uniq(normalizedFavorites.concat(assetAddress)) : isArray(assetAddress) From 4d5fc79c0cda19b80e27465623a8d000a0d6546b Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 28 Sep 2023 13:50:05 -0700 Subject: [PATCH 4/8] rm l2 support --- .../FastComponents/FastCurrencySelectionRow.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 32607eca382..e7f0ae7fd12 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -113,7 +113,6 @@ export default React.memo(function FastCurrencySelectionRow({ uniqueId, showBalance, showFavoriteButton, - showAddButton, onPress, theme, nativeCurrency, @@ -139,7 +138,7 @@ export default React.memo(function FastCurrencySelectionRow({ const rowTestID = `${testID}-exchange-coin-row-${ symbol ?? item?.symbol ?? '' }-${type || 'token'}`; - + const showAddButton = network === Network.mainnet; const isInfoButtonVisible = !item?.isNativeAsset || (!isNativeAsset(address ?? item?.address, network) && !showBalance); From 05d52fabd831cdcb41a31762aa67611b6aebaec0 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 3 Oct 2023 10:21:23 -0700 Subject: [PATCH 5/8] rm obsolete e2e --- e2e/discoverSheetFlow.spec.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/e2e/discoverSheetFlow.spec.js b/e2e/discoverSheetFlow.spec.js index 17dc9556e92..8cbd7f1f2dd 100644 --- a/e2e/discoverSheetFlow.spec.js +++ b/e2e/discoverSheetFlow.spec.js @@ -85,11 +85,7 @@ 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'); + it('Should remove Unisocks from Favorites', async () => { await Helpers.waitAndTap('remove-from-favorites'); await Helpers.checkIfNotVisible('remove-from-favorites'); From e3f2b34d96c593ab39d2f584107246337776b633 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 3 Oct 2023 12:17:19 -0700 Subject: [PATCH 6/8] move showAddButton back to props --- .../FastComponents/FastCurrencySelectionRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index e7f0ae7fd12..3e16a920104 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -113,6 +113,7 @@ export default React.memo(function FastCurrencySelectionRow({ uniqueId, showBalance, showFavoriteButton, + showAddButton, onPress, theme, nativeCurrency, @@ -138,7 +139,6 @@ export default React.memo(function FastCurrencySelectionRow({ const rowTestID = `${testID}-exchange-coin-row-${ symbol ?? item?.symbol ?? '' }-${type || 'token'}`; - const showAddButton = network === Network.mainnet; const isInfoButtonVisible = !item?.isNativeAsset || (!isNativeAsset(address ?? item?.address, network) && !showBalance); From 79a7aaa67aeae1770117e24aea2923d063ad7530 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 3 Oct 2023 12:32:53 -0700 Subject: [PATCH 7/8] rm e2e --- e2e/discoverSheetFlow.spec.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/e2e/discoverSheetFlow.spec.js b/e2e/discoverSheetFlow.spec.js index 8cbd7f1f2dd..5293f303db3 100644 --- a/e2e/discoverSheetFlow.spec.js +++ b/e2e/discoverSheetFlow.spec.js @@ -165,30 +165,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(); From befd9eef64f72a56d627bb88477ec28fd66dc1d0 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 12 Oct 2023 09:04:01 -0700 Subject: [PATCH 8/8] Lists UI Updates (#5089) * temp * stuff * rm sandbox * finished * rm extra line * rm obsolete code * rm e2e * Remove uniswap pairs redux (#5093) * move uniswap pairs from redux to react query * rm uniswap pairs redux * rm testnets util * react query + migration * more react query * rm logs * rm resources/favorites * rm comment * Remove favorites redux (#5104) * tearing everything out * fix favorites not persisting bug * docs * typo --- e2e/discoverSheetFlow.spec.js | 7 - src/App.js | 7 - .../FastCurrencySelectionRow.tsx | 27 +- src/components/coin-row/ExchangeCoinRow.js | 146 --------- src/components/coin-row/index.js | 1 - .../exchange/CurrencySelectionList.tsx | 1 - src/components/exchange/ExchangeAssetList.tsx | 29 +- src/components/exchange/ExchangeTokenRow.tsx | 2 - .../chart/ChartAddToListButton.js | 73 ----- .../chart/ChartExpandedStateHeader.js | 14 +- src/entities/index.ts | 1 - src/entities/uniswap.ts | 3 - src/handlers/localstorage/uniswap.ts | 38 --- src/handlers/swap.ts | 15 +- src/hooks/useInitializeWallet.ts | 5 - src/hooks/useLoadGlobalLateData.ts | 7 +- src/hooks/useResetAccountState.ts | 5 +- src/hooks/useSwapCurrencyList.ts | 26 +- src/model/migrations.ts | 36 ++- src/navigation/Routes.android.tsx | 6 - src/navigation/Routes.ios.tsx | 7 - src/navigation/config.tsx | 9 - src/navigation/routesNames.ts | 1 - src/redux/explorer.ts | 5 +- src/redux/reducers.ts | 2 - src/redux/uniswap.ts | 303 ------------------ src/references/index.ts | 66 +--- src/references/uniswap/index.ts | 3 - .../uniswap/uniswap-pairs-testnet.json | 1 - src/resources/favorites.ts | 160 +++++++++ src/screens/AddTokenSheet.js | 192 ----------- src/screens/CurrencySelectModal.tsx | 61 +--- .../discover/components/DiscoverSearch.js | 12 +- 33 files changed, 230 insertions(+), 1041 deletions(-) delete mode 100644 src/components/coin-row/ExchangeCoinRow.js delete mode 100644 src/components/expanded-state/chart/ChartAddToListButton.js delete mode 100644 src/entities/uniswap.ts delete mode 100644 src/handlers/localstorage/uniswap.ts delete mode 100644 src/redux/uniswap.ts delete mode 100644 src/references/uniswap/index.ts delete mode 100644 src/references/uniswap/uniswap-pairs-testnet.json create mode 100644 src/resources/favorites.ts delete mode 100644 src/screens/AddTokenSheet.js diff --git a/e2e/discoverSheetFlow.spec.js b/e2e/discoverSheetFlow.spec.js index 5293f303db3..ee085b64665 100644 --- a/e2e/discoverSheetFlow.spec.js +++ b/e2e/discoverSheetFlow.spec.js @@ -85,13 +85,6 @@ describe('Discover Screen Flow', () => { await Helpers.checkIfVisible('chart-header-Unisocks'); }); - it('Should remove Unisocks from Favorites', async () => { - 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 diff --git a/src/App.js b/src/App.js index b92fb27a3e7..792b3df7b71 100644 --- a/src/App.js +++ b/src/App.js @@ -60,7 +60,6 @@ import { } from './react-query'; import { additionalDataUpdateL2AssetBalance } from './redux/additionalAssetsData'; 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'; @@ -152,7 +151,6 @@ class OldApp extends Component { this?.handleAppStateChange ); this.setState({ eventSubscription: eventSub }); - rainbowTokenList.on('update', this.handleTokenListUpdate); appEvents.on('transactionConfirmed', this.handleTransactionConfirmed); await this.setupDeeplinking(); @@ -192,7 +190,6 @@ class OldApp extends Component { componentWillUnmount() { this.state.eventSubscription.remove(); - rainbowTokenList.off('update', this.handleTokenListUpdate); this.branchListener(); } @@ -203,10 +200,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 3e16a920104..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({ @@ -263,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 f2b98236b51..ac2eb849586 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/exchange/ExchangeAssetList.tsx @@ -38,8 +38,7 @@ import { colors, Colors } from '@/styles'; import { EnrichedExchangeAsset } from '@/screens/CurrencySelectModal'; import ExchangeTokenRow from './ExchangeTokenRow'; import { SwappableAsset } from '@/entities'; -import { uniswapUpdateFavorites } from '@/redux/uniswap'; -import { useDispatch } from 'react-redux'; +import { toggleFavorite, useFavorites } from '@/resources/favorites'; const deviceWidth = deviceUtils.dimensions.width; @@ -109,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; @@ -152,7 +149,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< copyCount, onCopySwapDetailsText, } = useSwapDetailsClipboardState(); - const dispatch = useDispatch(); // Scroll to top once the query is cleared if (prevQuery && prevQuery.length && !query.length) { @@ -250,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( () => @@ -277,9 +269,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< contextMenuProps: contextMenuProps(rowData, onCopySwapDetailsText), nativeCurrency, nativeCurrencySymbol, - onAddPress: () => { - itemProps.onActionAsset(rowData); - }, onCopySwapDetailsText, onPress: (givenItem: ReactElement) => { if (rowData.ens) { @@ -294,7 +283,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< handleUnverifiedTokenPress(rowData || asset); } }, - showAddButton: itemProps.showAddButton, showBalance: itemProps.showBalance, showFavoriteButton: itemProps.showFavoriteButton, testID, @@ -303,7 +291,7 @@ const ExchangeAssetList: ForwardRefRenderFunction< setLocalFavorite(prev => { const address = rowData.address; const newValue = !prev?.[address]; - dispatch(uniswapUpdateFavorites(address, newValue)); + toggleFavorite(address); if (newValue) { ios && onNewEmoji(); haptics.notificationSuccess(); @@ -320,7 +308,6 @@ const ExchangeAssetList: ForwardRefRenderFunction< })), })), [ - dispatch, handleUnverifiedTokenPress, itemProps, items, 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/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/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/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 bb59d8b1e5f..c09109b455e 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -13,7 +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 { favoritesQueryKey, refreshFavorites } from '@/resources/favorites'; const loadWalletBalanceNamesToCache = () => queryClient.prefetchQuery([WALLET_BALANCES_FROM_STORAGE], getWalletBalances); @@ -39,7 +39,10 @@ export default function useLoadGlobalLateData() { const p2 = loadWalletBalanceNamesToCache(); // favorites - const p3 = dispatch(uniswapLoadState()); + const p3 = queryClient.prefetchQuery({ + queryKey: favoritesQueryKey, + queryFn: refreshFavorites, + }); // contacts const p4 = dispatch(contactsLoadState()); 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/model/migrations.ts b/src/model/migrations.ts index 4ec1d1bd352..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, @@ -58,6 +61,9 @@ import { updateWebDataEnabled } from '@/redux/showcaseTokens'; 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 @@ -674,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 ccf3e1af69b..5902b115983 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'; @@ -152,11 +151,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 b5ad44a6ecf..089d222bcef 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 442ac814db6..ffcf14670f9 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -18,7 +18,6 @@ import settings from './settings'; import showcaseTokens from './showcaseTokens'; import swap from './swap'; import transactionSignatures from './transactionSignatures'; -import uniswap from './uniswap'; import walletconnect from './walletconnect'; import wallets from './wallets'; @@ -41,7 +40,6 @@ export default combineReducers({ showcaseTokens, swap, transactionSignatures, - uniswap, walletconnect, wallets, }); diff --git a/src/redux/uniswap.ts b/src/redux/uniswap.ts deleted file mode 100644 index a42ab89e1e3..00000000000 --- a/src/redux/uniswap.ts +++ /dev/null @@ -1,303 +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/references/index.ts b/src/references/index.ts index e836115c949..27bddc984cc 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -1,5 +1,5 @@ import { savingsAssets } from './compound'; -import { Asset, UniswapFavoriteTokenData } from '@/entities'; +import { Asset } from '@/entities'; import { Network } from '@/helpers/networkTypes'; export { default as balanceCheckerContractAbi } from './balances-checker-abi.json'; @@ -20,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 { @@ -142,69 +141,6 @@ export const AddCashCurrencyInfo: { }, }; -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 c8b56c5b485..00000000000 --- a/src/screens/AddTokenSheet.js +++ /dev/null @@ -1,192 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { useCallback, useMemo } 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 { useDimensions } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import styled from '@/styled-thing'; -import { position } from '@/styles'; -import { haptics } from '@/utils'; -import { useDispatch, useSelector } from 'react-redux'; -import { uniswapUpdateFavorites } from '../redux/uniswap'; - -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; - -const uniswapFavoritesSelector = state => state.uniswap.favorites; - -export default function AddTokenSheet() { - const { goBack } = useNavigation(); - const { height: deviceHeight } = useDimensions(); - const dispatch = useDispatch(); - const favorites = useSelector(uniswapFavoritesSelector); - const insets = useSafeAreaInsets(); - const { - params: { item, isL2 }, - } = useRoute(); - - const isTokenInFavorites = useMemo( - () => - !!favorites?.find( - address => address.toLowerCase() === item?.address?.toLowerCase() - ), - [favorites, item.address] - ); - - const handleAdd = useCallback(() => { - if (isTokenInFavorites) return; - dispatch(uniswapUpdateFavorites(item.address, true)); - haptics.notificationSuccess(); - }, [dispatch, isTokenInFavorites, item.address]); - - const handleRemove = useCallback(() => { - dispatch(uniswapUpdateFavorites(item.address, false)); - haptics.notificationSuccess(); - }, [dispatch, item.address]); - - const { colors } = useTheme(); - - return ( - - {ios && } - - - - - - - - - {item.name} - - - - - {lang.t('button.add_to_list')} - - - - - - - - - - - - - - {lang.t('button.favorites')} - - - - {isTokenInFavorites && ( - - - 􀈔 {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/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 78649bbd42f..71cfa92253c 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -185,21 +185,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(