diff --git a/.eslintrc.js b/.eslintrc.js index 58a60686452..fe907c50c25 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,7 +83,6 @@ const utilNumberImportBurndownFiles = [ 'app/components/UI/TransactionElement/utils.js', 'app/components/UI/UrlAutocomplete/Result.tsx', 'app/components/Views/AssetDetails/index.tsx', - 'app/components/Views/DetectedTokens/components/Token.tsx', 'app/components/Views/GasEducationCarousel/index.js', 'app/components/Views/NetworksManagement/NetworkDetailsView/hooks/useNetworkValidation.ts', 'app/components/Views/SocialLeaderboard/TraderPositionView/components/QuickBuyBottomSheet/useQuickBuyBottomSheet.ts', diff --git a/app/components/Nav/App/App.test.tsx b/app/components/Nav/App/App.test.tsx index d72cd66cf28..fda94b6d6c4 100644 --- a/app/components/Nav/App/App.test.tsx +++ b/app/components/Nav/App/App.test.tsx @@ -156,12 +156,6 @@ jest.mock('../../Views/LedgerSelectAccount', () => () => ( jest.mock('../../Views/ConnectHardware/SelectHardware', () => () => ( )); -jest.mock('../../Views/DetectedTokens', () => () => ( - -)); -jest.mock('../../Views/DetectedTokensConfirmation', () => () => ( - -)); jest.mock('../../Views/WalletActions', () => () => ( )); @@ -2275,13 +2269,5 @@ describe('App', () => { expect(getByTestId('mock-trade-actions')).toBeTruthy(); }); }); - - it('renders DetectedTokens flow', async () => { - const { getByTestId } = renderAppWithModal('DetectedTokens'); - - await waitFor(() => { - expect(getByTestId('mock-detected-tokens')).toBeTruthy(); - }); - }); }); }); diff --git a/app/components/Nav/App/App.tsx b/app/components/Nav/App/App.tsx index c3ceacfc9aa..8b6626b5871 100644 --- a/app/components/Nav/App/App.tsx +++ b/app/components/Nav/App/App.tsx @@ -55,8 +55,6 @@ import ConnectionDetails from '../../../components/Views/AccountPermissions/Conn import { SRPQuiz } from '../../Views/Quiz'; import { TurnOffRememberMeModal } from '../../../components/UI/TurnOffRememberMeModal'; import AssetHideConfirmation from '../../Views/AssetHideConfirmation'; -import DetectedTokens from '../../Views/DetectedTokens'; -import DetectedTokensConfirmation from '../../Views/DetectedTokensConfirmation'; import AssetOptions from '../../Views/AssetOptions'; import ImportPrivateKey from '../../Views/ImportPrivateKey'; import ImportPrivateKeySuccess from '../../Views/ImportPrivateKeySuccess'; @@ -419,20 +417,6 @@ const AddNetworkFlow = () => { ); }; -const DetectedTokensFlow = () => ( - - - - -); - interface RootModalFlowProps { route: { params: Record; @@ -596,7 +580,6 @@ const RootModalFlow = (props: RootModalFlowProps) => ( name={'AssetHideConfirmation'} component={AssetHideConfirmation} /> - diff --git a/app/components/Views/DetectedTokens/DetectedTokensView.testIds.ts b/app/components/Views/DetectedTokens/DetectedTokensView.testIds.ts deleted file mode 100644 index 61be806bed8..00000000000 --- a/app/components/Views/DetectedTokens/DetectedTokensView.testIds.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const DetectedTokensSelectorTexts = { - IMPORT_BUTTON_TEXT: 'Import (1)', -}; - -export const DetectedTokensSelectorIDs = { - IMPORT_BUTTON_ID: 'token-detection-import-button', -}; diff --git a/app/components/Views/DetectedTokens/components/Token.test.tsx b/app/components/Views/DetectedTokens/components/Token.test.tsx deleted file mode 100644 index 4c1ade3ac65..00000000000 --- a/app/components/Views/DetectedTokens/components/Token.test.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react-native'; -import Token from './Token'; -import { useDispatch, useSelector } from 'react-redux'; -import { Token as TokenType } from '@metamask/assets-controllers'; -import { selectEvmChainId } from '../../../../selectors/networkController'; -import { selectTokenMarketData } from '../../../../selectors/tokenRatesController'; -import { selectTokensBalances } from '../../../../selectors/tokenBalancesController'; -import ClipboardManager from '../../../../core/ClipboardManager'; -import { showAlert } from '../../../../actions/alert'; -import { - selectConversionRateFoAllChains, - selectCurrentCurrency, -} from '../../../../selectors/currencyRateController'; -import { selectSelectedInternalAccountAddress } from '../../../../selectors/accountsController'; - -// Mock dependencies -jest.mock('react-redux', () => ({ - useDispatch: jest.fn(), - useSelector: jest.fn(), - connect: jest.fn(), -})); - -jest.mock('../../../../core/ClipboardManager', () => ({ - setString: jest.fn(), -})); - -jest.mock('../../../UI/TokenImage', () => () => null); - -jest.mock('../../../UI/AssetOverview/Balance/Balance', () => ({ - // Mock other exports if needed, or leave empty - __esModule: true, - NetworkBadgeSource: jest.fn(() => 'mocked-network-badge-source'), -})); - -describe('Token Component', () => { - const mockDispatch = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useDispatch as jest.Mock).mockReturnValue(mockDispatch); - (useSelector as jest.Mock).mockImplementation((selector) => { - if (selector === selectSelectedInternalAccountAddress) return '0xAccount'; - if (selector === selectEvmChainId) return '1'; - if (selector === selectTokenMarketData) return {}; - if (selector === selectTokensBalances) - return { - '0xAccount': { '1': '1000000000000000000' }, // 1 token - }; - if (selector === selectConversionRateFoAllChains) return {}; - if (selector === selectCurrentCurrency) return 'USD'; - return {}; - }); - }); - - const mockToken = { - address: '0xTokenAddress', - symbol: 'ABC', - decimals: 18, - aggregators: ['Aggregator1', 'Aggregator2', 'Aggregator3'], - chainId: '1', - }; - - const renderComponent = (selected = false) => - render( - , - ); - - it('renders correctly', () => { - const { getByText } = renderComponent(); - - expect(getByText('0 ABC')).toBeTruthy(); - expect(getByText('Token address:')).toBeTruthy(); - }); - - it('renders correctly with token chainId', () => { - const { getByText } = render( - , - ); - - expect(getByText('0 ABC')).toBeTruthy(); - expect(getByText('Token address:')).toBeTruthy(); - }); - - it('expands token aggregator list on "show more" press', () => { - const { getByText } = renderComponent(); - - const showMoreButton = getByText('+ 1 more'); - fireEvent.press(showMoreButton); - - expect( - getByText('Token lists: Aggregator1, Aggregator2, Aggregator3'), - ).toBeTruthy(); - }); - - it('renders checkbox as checked when token is selected', () => { - const { getByTestId } = renderComponent(true); - - const checkbox = getByTestId('token-select-checkbox'); - expect(checkbox.props.accessibilityState.checked).toBe(true); - }); - - it('renders checkbox as unchecked when token is not selected', () => { - const { getByTestId } = renderComponent(false); - - const checkbox = getByTestId('token-select-checkbox'); - expect(checkbox.props.accessibilityState.checked).toBe(false); - }); - - it('copies address to clipboard and triggers alert', async () => { - const { getByText } = renderComponent(); - const copyButton = getByText('0xToken...dress'); - - ClipboardManager.setString = jest.fn(() => Promise.resolve()); - - await fireEvent.press(copyButton); - - expect(ClipboardManager.setString).toHaveBeenCalledWith('0xTokenAddress'); - expect(mockDispatch).toHaveBeenCalledWith( - showAlert({ - isVisible: true, - autodismiss: 1500, - content: 'clipboard-alert', - data: { msg: 'Token address copied to clipboard' }, - }), - ); - }); -}); diff --git a/app/components/Views/DetectedTokens/components/Token.tsx b/app/components/Views/DetectedTokens/components/Token.tsx deleted file mode 100644 index ee8560b4b3a..00000000000 --- a/app/components/Views/DetectedTokens/components/Token.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import React, { useState } from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; -import { - MarketDataDetails, - Token as TokenType, -} from '@metamask/assets-controllers'; -import EthereumAddress from '../../../UI/EthereumAddress'; -import Icon from 'react-native-vector-icons/Feather'; -import CheckBox from '@react-native-community/checkbox'; -import { strings } from '../../../../../locales/i18n'; -import TokenImage from '../../../UI/TokenImage'; -import { fontStyles } from '../../../../styles/common'; -import { useDispatch, useSelector } from 'react-redux'; -import { showAlert } from '../../../../actions/alert'; -import ClipboardManager from '../../../../core/ClipboardManager'; -import { selectEvmChainId } from '../../../../selectors/networkController'; -import { - balanceToFiat, - renderFromTokenMinimalUnit, -} from '../../../../util/number'; -import { useTheme } from '../../../../util/theme'; -import { - selectCurrencyRates, - selectCurrentCurrency, -} from '../../../../selectors/currencyRateController'; -import { selectTokenMarketData } from '../../../../selectors/tokenRatesController'; -import { selectTokensBalances } from '../../../../selectors/tokenBalancesController'; -import { Colors } from '../../../../util/theme/models'; -import { Hex } from '@metamask/utils'; -import BadgeWrapper, { - BadgePosition, -} from '../../../../component-library/components/Badges/BadgeWrapper'; -import Badge, { - BadgeVariant, -} from '../../../../component-library/components/Badges/Badge'; -import { NetworkBadgeSource } from '../../../UI/AssetOverview/Balance/Balance'; -import { CURRENCY_SYMBOL_BY_CHAIN_ID } from '../../../../constants/network'; -import { selectSelectedInternalAccountAddress } from '../../../../selectors/accountsController'; - -const createStyles = (colors: Colors) => - StyleSheet.create({ - logo: { - height: 40, - width: 40, - }, - tokenContainer: { flexDirection: 'row', paddingVertical: 16 }, - tokenInfoContainer: { flex: 1, marginLeft: 8, marginRight: 16 }, - tokenUnitLabel: { - ...fontStyles.normal, - fontSize: 18, - color: colors.text.default, - marginBottom: 4, - }, - tokenDollarLabel: { - ...fontStyles.normal, - fontSize: 14, - color: colors.text.alternative, - marginBottom: 4, - }, - tokenAddressContainer: { - flexDirection: 'row', - marginBottom: 4, - }, - tokenAddressLabel: { - ...fontStyles.normal, - fontSize: 14, - color: colors.text.alternative, - }, - addressLinkContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - addressLinkLabel: { - ...fontStyles.normal, - fontSize: 14, - color: colors.primary.default, - }, - copyIcon: { - marginLeft: 4, - color: colors.primary.default, - }, - tokenAggregatorContainer: { - flexDirection: 'row', - alignItems: 'center', - flexWrap: 'wrap', - }, - tokenAggregatorLabel: { - ...fontStyles.normal, - fontSize: 14, - color: colors.text.default, - }, - aggregatorLinkLabel: { - ...fontStyles.normal, - fontSize: 14, - color: colors.primary.default, - }, - checkBox: { height: 18 }, - }); - -interface Props { - token: TokenType & { chainId: Hex }; - selected: boolean; - toggleSelected: (selected: boolean) => void; -} - -const Token = ({ token, selected, toggleSelected }: Props) => { - const { address, symbol, aggregators = [], decimals } = token; - const accountAddress = useSelector(selectSelectedInternalAccountAddress); - const { colors } = useTheme(); - const styles = createStyles(colors); - const [expandTokenList, setExpandTokenList] = useState(false); - const tokenExchangeRatesAllChains = useSelector(selectTokenMarketData); - const currentChainId = useSelector(selectEvmChainId); - const tokenExchangeRates = tokenExchangeRatesAllChains[token.chainId]; - const tokenBalancesAllChains = useSelector(selectTokensBalances); - const balanceAllChainsForAccount = - tokenBalancesAllChains?.[accountAddress as Hex] ?? {}; - const chainIdToUse = token.chainId ?? currentChainId; - const tokenBalances = balanceAllChainsForAccount?.[chainIdToUse]; - const conversionRateByChainId = useSelector(selectCurrencyRates); - - const conversionRate = - conversionRateByChainId[CURRENCY_SYMBOL_BY_CHAIN_ID[token.chainId]] - ?.conversionRate; - - const currentCurrency = useSelector(selectCurrentCurrency); - - const tokenMarketData = - (tokenExchangeRates as Record)?.[address as Hex] ?? - null; - const tokenBalance = renderFromTokenMinimalUnit( - tokenBalances[address as Hex], - decimals, - ); - const tokenBalanceWithSymbol = `${ - tokenBalance === undefined ? '' : `${tokenBalance} ` - }${symbol}`; - const fiatBalance = balanceToFiat( - tokenBalance, - conversionRate, - tokenMarketData?.price || undefined, - currentCurrency, - ); - - const showMoreLink = !expandTokenList && aggregators.length > 2; - const dispatch = useDispatch(); - - const triggerShowAlert = () => - dispatch( - showAlert({ - isVisible: true, - autodismiss: 1500, - content: 'clipboard-alert', - data: { msg: strings('detected_tokens.address_copied_to_clipboard') }, - }), - ); - - const copyAddressToClipboard = async () => { - await ClipboardManager.setString(address); - triggerShowAlert(); - }; - - const triggerExpandTokenList = () => { - setExpandTokenList(true); - }; - - const triggerToggleSelected = () => { - toggleSelected(!selected); - }; - - return ( - - - } - > - - - - - {tokenBalanceWithSymbol} - {fiatBalance ? ( - {fiatBalance} - ) : null} - - - {strings('detected_tokens.token_address')} - - - - - - - - - {strings('detected_tokens.token_lists', { - listNames: aggregators - .slice(0, expandTokenList ? aggregators.length : 2) - .join(', '), - })} - - {showMoreLink ? ( - - - {strings('detected_tokens.token_more', { - remainingListCount: aggregators.slice(2, aggregators.length) - .length, - })} - - - ) : null} - - - - - ); -}; - -export default Token; diff --git a/app/components/Views/DetectedTokens/index.test.tsx b/app/components/Views/DetectedTokens/index.test.tsx deleted file mode 100644 index 965524d4e5e..00000000000 --- a/app/components/Views/DetectedTokens/index.test.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import { fireEvent, render } from '@testing-library/react-native'; -import { useDispatch, useSelector } from 'react-redux'; -import DetectedTokens from '.'; -import { - selectAllDetectedTokensFlat, - selectDetectedTokens, -} from '../../../selectors/tokensController'; -import { - selectEvmChainId, - selectEvmNetworkConfigurationsByChainId, -} from '../../../selectors/networkController'; -import { selectTokensBalances } from '../../../selectors/tokenBalancesController'; -import { selectSelectedInternalAccountAddress } from '../../../selectors/accountsController'; -import { ThemeContext } from '../../../util/theme'; -import { DetectedTokensSelectorIDs } from './DetectedTokensView.testIds'; - -// Mock dependencies -jest.mock('react-redux', () => ({ - useDispatch: jest.fn(), - useSelector: jest.fn(), -})); - -const mockNavigate = jest.fn(); -jest.mock('@react-navigation/native', () => ({ - useNavigation: jest.fn(() => ({ - navigate: mockNavigate, - })), -})); - -jest.mock('../../../util/theme', () => { - const { mockTheme } = jest.requireActual('../../../util/theme'); - return { - useTheme: jest.fn(() => mockTheme), - }; -}); - -jest.mock( - '../../../component-library/components/BottomSheets/BottomSheet', - () => ({ - __esModule: true, - default: jest.fn(({ children }) => <>{children}), - }), -); - -jest.mock('../../../components/hooks/useAnalytics/useAnalytics', () => ({ - useAnalytics: jest.fn(() => ({ - trackEvent: jest.fn(), - createEventBuilder: jest.fn(() => ({ - addProperties: jest.fn(() => ({ - build: jest.fn(), - })), - })), - })), -})); - -jest.mock('../../UI/TokenImage', () => () => null); - -jest.mock('../../UI/AssetOverview/Balance/Balance', () => ({ - // Mock other exports if needed, or leave empty - __esModule: true, - NetworkBadgeSource: jest.fn(() => 'mocked-network-badge-source'), -})); - -const mockTheme = { - colors: { - background: { default: 'white' }, - border: { default: 'red' }, - text: { default: 'black' }, - error: { default: 'red' }, - warning: { default: 'yellow' }, - primary: { default: 'blue', inverse: 'orange' }, - overlay: { inverse: 'blue' }, - }, - themeAppearance: 'light', -}; - -describe('DetectedTokens Component', () => { - const mockDispatch = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useDispatch as jest.Mock).mockReturnValue(mockDispatch); - (useSelector as jest.Mock).mockImplementation((selector) => { - if (selector === selectDetectedTokens) { - return [ - { address: '0xToken1', symbol: 'TKN1', chainId: '1' }, - { address: '0xToken2', symbol: 'TKN2', chainId: '1' }, - ]; - } - if (selector === selectSelectedInternalAccountAddress) return '0xAccount'; - if (selector === selectTokensBalances) - return { - '0xAccount': { '1': '1000000000000000000' }, // 1 token - }; - if (selector === selectAllDetectedTokensFlat) { - return [ - { address: '0xToken1', symbol: 'TKN1', chainId: '1' }, - { address: '0xToken2', symbol: 'TKN2', chainId: '1' }, - ]; - } - if (selector === selectEvmChainId) return '1'; - if (selector === selectEvmNetworkConfigurationsByChainId) return {}; - return {}; - }); - }); - - it('renders correctly with detected tokens', () => { - const { getByText } = render( - - - , - ); - - expect(getByText('2 new tokens found')).toBeOnTheScreen(); - expect(getByText('0 TKN1')).toBeOnTheScreen(); - expect(getByText('0 TKN2')).toBeOnTheScreen(); - expect(getByText('Import (2)')).toBeOnTheScreen(); - }); - - it('renders zero-token state with disabled import button', () => { - (useSelector as jest.Mock).mockImplementation((selector) => { - if (selector === selectDetectedTokens) return []; - if (selector === selectAllDetectedTokensFlat) return []; - if (selector === selectEvmChainId) return '1'; - if (selector === selectEvmNetworkConfigurationsByChainId) return {}; - return {}; - }); - - const { getByText, getByTestId } = render( - - - , - ); - - expect(getByText('0 new token found')).toBeOnTheScreen(); - expect(getByText('Import (0)')).toBeOnTheScreen(); - expect( - getByTestId(DetectedTokensSelectorIDs.IMPORT_BUTTON_ID), - ).toBeDisabled(); - }); - - it('navigates to confirmation on "Hide All" button press', () => { - const { getByText } = render( - - - , - ); - - const hideAllButton = getByText('Hide all'); - fireEvent.press(hideAllButton); - - expect(mockNavigate).toHaveBeenCalledWith( - 'DetectedTokensConfirmation', - expect.objectContaining({ isHidingAll: true }), - ); - }); -}); diff --git a/app/components/Views/DetectedTokens/index.tsx b/app/components/Views/DetectedTokens/index.tsx deleted file mode 100644 index c7cadeaebde..00000000000 --- a/app/components/Views/DetectedTokens/index.tsx +++ /dev/null @@ -1,413 +0,0 @@ -// Third party dependencies -import React, { useRef, useState, useCallback, useMemo } from 'react'; -import { StyleSheet, View, Text, InteractionManager } from 'react-native'; -import { useSelector } from 'react-redux'; -import { Token as TokenType } from '@metamask/assets-controllers'; -import { useNavigation } from '@react-navigation/native'; -import { FlatList } from 'react-native-gesture-handler'; -import { Hex } from '@metamask/utils'; - -// External Dependencies -import { MetaMetricsEvents } from '../../../core/Analytics'; -import { fontStyles } from '../../../styles/common'; -import StyledButton from '../../UI/StyledButton'; -import Token from './components/Token'; -import Engine from '../../../core/Engine'; -import NotificationManager from '../../../core/NotificationManager'; -import { strings } from '../../../../locales/i18n'; -import Logger from '../../../util/Logger'; -import { useTheme } from '../../../util/theme'; -import { getDecimalChainId } from '../../../util/networks'; -import { createNavigationDetails } from '../../../util/navigation/navUtils'; -import Routes from '../../../constants/navigation/Routes'; -import { - selectDetectedTokens, - selectAllDetectedTokensFlat, -} from '../../../selectors/tokensController'; -import { - selectEvmChainId, - selectEvmNetworkConfigurationsByChainId, - selectIsAllNetworks, - selectIsPopularNetwork, -} from '../../../selectors/networkController'; -import BottomSheet, { - BottomSheetRef, -} from '../../../component-library/components/BottomSheets/BottomSheet'; -import { useAnalytics } from '../../../components/hooks/useAnalytics/useAnalytics'; -import { DetectedTokensSelectorIDs } from './DetectedTokensView.testIds'; -import { TokenI } from '../../UI/Tokens/types'; - -// TODO: Replace "any" with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const createStyles = (colors: any) => - StyleSheet.create({ - fill: { - flex: 1, - }, - sheet: { - backgroundColor: colors.background.default, - borderTopLeftRadius: 20, - borderTopRightRadius: 20, - height: '75%', - }, - notch: { - width: 48, - height: 5, - borderRadius: 4, - backgroundColor: colors.border.default, - marginTop: 12, - alignSelf: 'center', - }, - headerLabel: { - textAlign: 'center', - ...fontStyles.normal, - fontSize: 18, - paddingVertical: 16, - color: colors.text.default, - }, - tokenList: { paddingHorizontal: 16 }, - buttonsContainer: { - padding: 16, - flexDirection: 'row', - }, - buttonDivider: { - width: 8, - }, - }); - -interface IgnoredTokensByAddress { - [address: string]: true; -} - -const DetectedTokens = () => { - const navigation = useNavigation(); - const { trackEvent, createEventBuilder } = useAnalytics(); - const sheetRef = useRef(null); - const detectedTokens = useSelector(selectDetectedTokens); - const allDetectedTokens = useSelector( - selectAllDetectedTokensFlat, - ) as TokenI[]; - const allNetworks = useSelector(selectEvmNetworkConfigurationsByChainId); - const chainId = useSelector(selectEvmChainId); - const isPopularNetworks = useSelector(selectIsPopularNetwork); - const [ignoredTokens, setIgnoredTokens] = useState( - {}, - ); - const isAllNetworks = useSelector(selectIsAllNetworks); - - const { colors } = useTheme(); - const styles = createStyles(colors); - - const currentDetectedTokens = - isAllNetworks && isPopularNetworks ? allDetectedTokens : detectedTokens; - - const detectedTokensForAnalytics = useMemo( - () => - currentDetectedTokens.map( - (token) => `${token.symbol} - ${token.address}`, - ), - [currentDetectedTokens], - ); - - const getTokenAddedAnalyticsParams = useCallback( - ({ address, symbol }: { address: string; symbol: string }) => { - try { - return { - token_address: address, - token_symbol: symbol, - chain_id: getDecimalChainId(chainId), - source: 'Add token dropdown', - }; - } catch (error) { - Logger.error( - error as Error, - 'DetectedTokens.getTokenAddedAnalyticsParams', - ); - return undefined; - } - }, - [chainId], - ); - - const dismissModalAndTriggerAction = useCallback( - (ignoreAllTokens?: boolean) => { - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { TokensController } = Engine.context as any; - let title = ''; - let description = ''; - let errorMsg = ''; - const tokensToIgnore: TokenType[] = []; - const tokensToImport = currentDetectedTokens.filter((token) => { - const isIgnored = ignoreAllTokens || ignoredTokens[token.address]; - if (isIgnored) { - tokensToIgnore.push(token); - } - return !isIgnored; - }); - - // Update toast description accordingly - if (tokensToImport.length === 0 && tokensToIgnore.length > 0) { - // Ignoring all tokens - title = strings('wallet.token_toast.tokens_hidden_title'); - description = strings('wallet.token_toast.tokens_hidden_desc'); - errorMsg = 'DetectedTokens: Failed to hide all detected tokens!'; - } else if ( - (tokensToImport.length > 0 && tokensToIgnore.length > 0) || - (tokensToImport.length > 0 && tokensToIgnore.length === 0) - ) { - // At least some tokens are imported - title = strings('wallet.token_toast.tokens_imported_title'); - description = strings('wallet.token_toast.tokens_imported_desc', { - tokenSymbols: tokensToImport - .map((token) => token.symbol.toUpperCase()) - .join(', '), - }); - errorMsg = 'DetectedTokens: Failed to import detected tokens!'; - } - - sheetRef.current?.onCloseBottomSheet(async () => { - try { - if (tokensToIgnore.length > 0) { - // Group tokens by their `chainId` using a plain object - const tokensToIgnoreByChainId: Record = {}; - - for (const token of tokensToIgnore) { - const tokenChainId: Hex = - (token as TokenI & { chainId: Hex }).chainId ?? chainId; - - if (!tokensToIgnoreByChainId[tokenChainId]) { - tokensToIgnoreByChainId[tokenChainId] = []; - } - - tokensToIgnoreByChainId[tokenChainId].push(token); - } - - // Process all grouped tokens in parallel - const ignorePromises = Object.entries(tokensToIgnoreByChainId).map( - async ([networkId, tokens]) => { - const chainConfig = allNetworks[networkId as Hex]; - const { defaultRpcEndpointIndex } = chainConfig; - const { networkClientId: networkInstanceId } = - chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; - - const tokenAddresses = tokens.map((token) => token.address); - - await TokensController.ignoreTokens( - tokenAddresses, - networkInstanceId, - ); - }, - ); - - await Promise.all(ignorePromises); - } - if (tokensToImport.length > 0) { - // Group tokens by their `chainId` using a plain object - const tokensByChainId: Record = {}; - - for (const token of tokensToImport) { - const tokenChainId: Hex = - (token as TokenI & { chainId: Hex }).chainId ?? chainId; - - if (!tokensByChainId[tokenChainId]) { - tokensByChainId[tokenChainId] = []; - } - - tokensByChainId[tokenChainId].push(token); - } - - // Process grouped tokens in parallel - const importPromises = Object.entries(tokensByChainId).map( - async ([networkId, tokens]) => { - const chainConfig = allNetworks[networkId as Hex]; - const { defaultRpcEndpointIndex } = chainConfig; - const { networkClientId: networkInstanceId } = - chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; - - await TokensController.addTokens(tokens, networkInstanceId); - }, - ); - - await Promise.all(importPromises); - InteractionManager.runAfterInteractions(() => { - tokensToImport.forEach( - ({ address, symbol }: { address: string; symbol: string }) => { - const analyticsParams = getTokenAddedAnalyticsParams({ - address, - symbol, - }); - - if (analyticsParams) { - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKEN_ADDED) - .addProperties({ - token_address: address, - token_symbol: symbol, - chain_id: getDecimalChainId(chainId), - source: 'detected', - }) - .build(), - ); - } - }, - ); - }); - } - NotificationManager.showSimpleNotification({ - status: `simple_notification`, - duration: 5000, - title, - description, - }); - } catch (err) { - Logger.log(err, errorMsg); - } - }); - }, - [ - chainId, - trackEvent, - createEventBuilder, - currentDetectedTokens, - ignoredTokens, - allNetworks, - getTokenAddedAnalyticsParams, - ], - ); - - const triggerIgnoreAllTokens = () => { - navigation.navigate('DetectedTokensConfirmation', { - onConfirm: () => dismissModalAndTriggerAction(true), - isHidingAll: true, - }); - - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKENS_HIDDEN) - .addProperties({ - location: 'token_detection', - token_standard: 'ERC20', - asset_type: 'token', - tokens: detectedTokensForAnalytics, - chain_id: getDecimalChainId(chainId), - }) - .build(), - ); - }; - - const triggerImportTokens = async () => { - if (Object.keys(ignoredTokens).length === 0) { - // Import all tokens - dismissModalAndTriggerAction(); - } else { - // Handle ignoring all or mix of imports and ignored tokens - navigation.navigate('DetectedTokensConfirmation', { - onConfirm: () => dismissModalAndTriggerAction(), - }); - } - }; - - const renderHeader = () => ( - - {strings( - `detected_tokens.title${ - currentDetectedTokens.length > 1 ? '_plural' : '' - }`, - { - tokenCount: currentDetectedTokens.length, - }, - )} - - ); - - const renderToken = ({ item }: { item: TokenType }) => { - const { address } = item; - const isChecked = !ignoredTokens[address]; - - return ( - { - const newIgnoredTokens = { ...ignoredTokens }; - if (selected) { - delete newIgnoredTokens[address]; - } else { - newIgnoredTokens[address] = true; - } - setIgnoredTokens(newIgnoredTokens); - }} - /> - ); - }; - - const getTokenId = (item: TokenType) => item.address; - - const renderDetectedTokens = () => ( - - ); - - const renderButtons = () => { - const importTokenCount = - currentDetectedTokens.length - Object.keys(ignoredTokens).length; - return ( - - - {strings('detected_tokens.hide_cta')} - - - - {strings('detected_tokens.import_cta', { - tokenCount: importTokenCount, - })} - - - ); - }; - - const trackCancelWithoutAction = (hasPendingAction?: boolean) => { - if (hasPendingAction) { - return; - } - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKEN_IMPORT_CANCELED) - .addProperties({ - source: 'detected', - tokens: detectedTokensForAnalytics, - chain_id: getDecimalChainId(chainId), - }) - .build(), - ); - }; - - return ( - - {renderHeader()} - {renderDetectedTokens()} - {renderButtons()} - - ); -}; - -export const createDetectedTokensNavDetails = createNavigationDetails( - Routes.MODAL.ROOT_MODAL_FLOW, - Routes.MODAL.DETECTED_TOKENS, -); - -export default DetectedTokens; diff --git a/app/components/Views/DetectedTokensConfirmation/index.tsx b/app/components/Views/DetectedTokensConfirmation/index.tsx deleted file mode 100644 index 91ea11e5273..00000000000 --- a/app/components/Views/DetectedTokensConfirmation/index.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useRef } from 'react'; -import { StyleSheet, View, Text } from 'react-native'; -import { RouteProp, useRoute } from '@react-navigation/native'; -import ReusableModal, { ReusableModalRef } from '../../UI/ReusableModal'; -import { fontStyles } from '../../../styles/common'; -import StyledButton from '../../UI/StyledButton'; -import { strings } from '../../../../locales/i18n'; -import { useTheme } from '../../../util/theme'; - -// TODO: Replace "any" with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const createStyles = (colors: any) => - StyleSheet.create({ - fill: { - flex: 1, - }, - screen: { justifyContent: 'center' }, - modal: { - backgroundColor: colors.background.default, - borderRadius: 10, - marginHorizontal: 16, - }, - bodyContainer: { - paddingHorizontal: 24, - paddingVertical: 32, - }, - headerLabel: { - textAlign: 'center', - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...(fontStyles.bold as any), - fontSize: 24, - marginBottom: 16, - color: colors.text.default, - }, - description: { - textAlign: 'center', - fontSize: 16, - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...(fontStyles.normal as any), - color: colors.text.default, - }, - divider: { - height: 1, - backgroundColor: colors.border.muted, - }, - buttonsContainer: { - flexDirection: 'row', - padding: 16, - }, - buttonDivider: { - width: 8, - }, - }); - -interface DetectedTokensConfirmationRouteParams { - isHidingAll?: boolean; - onConfirm: () => void; -} - -const DetectedTokensConfirmation = () => { - const route = - useRoute< - RouteProp<{ params: DetectedTokensConfirmationRouteParams }, 'params'> - >(); - const { onConfirm, isHidingAll } = route.params; - const modalRef = useRef(null); - const { colors } = useTheme(); - const styles = createStyles(colors); - - const triggerCancel = () => modalRef.current?.dismissModal(); - - const triggerConfirm = () => { - modalRef.current?.dismissModal(onConfirm); - }; - - const renderHeader = () => ( - - {strings( - `detected_tokens.confirm.${ - isHidingAll ? 'hide.title' : 'import.title' - }`, - )} - - ); - - const renderDescription = () => ( - - {strings( - `detected_tokens.confirm.${isHidingAll ? 'hide.desc' : 'import.desc'}`, - )} - - ); - - const renderButtons = () => ( - - - {strings('detected_tokens.confirm.cancel_cta')} - - - - {strings('detected_tokens.confirm.confirm_cta')} - - - ); - - return ( - - - - {renderHeader()} - {renderDescription()} - - - {renderButtons()} - - - ); -}; - -export default DetectedTokensConfirmation; diff --git a/app/components/Views/Wallet/index.test.tsx b/app/components/Views/Wallet/index.test.tsx index 28e7d5f0942..29276ea3218 100644 --- a/app/components/Views/Wallet/index.test.tsx +++ b/app/components/Views/Wallet/index.test.tsx @@ -393,9 +393,6 @@ jest.mock('../../../core/Engine', () => { PreferencesController: { setTokenNetworkFilter: jest.fn(), }, - TokensController: { - addTokens: jest.fn(), - }, NetworkEnablementController: { setEnabledNetwork: jest.fn(), setDisabledNetwork: jest.fn(), @@ -512,17 +509,6 @@ const mockInitialState = { }, }, }, - TokensController: { - ...backgroundState.TokensController, - detectedTokens: [{ address: '0x123' }], - allDetectedTokens: { - '0x1': { - '0xc4966c0d659d99699bfd7eb54d8fafee40e4a756': [ - { address: '0x123' }, - ], - }, - }, - }, RewardsController: { activeAccount: null, }, @@ -740,29 +726,6 @@ const renderWalletWithRootState = (rootState: typeof mockInitialState) => }, ); -const renderWithoutDetectedTokens = (Component: React.ComponentType) => - renderScreen( - Component, - { - name: Routes.WALLET_VIEW, - }, - { - state: { - ...mockInitialState, - engine: { - backgroundState: { - ...mockInitialState.engine.backgroundState, - TokensController: { - ...mockInitialState.engine.backgroundState.TokensController, - // @ts-expect-error we are testing the invalid case - detectedTokens: 'invalid-array', - }, - }, - }, - }, - }, - ); - describe('Wallet', () => { afterEach(() => { jest.clearAllMocks(); @@ -784,12 +747,6 @@ describe('Wallet', () => { expect(mockTabsListComponent).toHaveBeenCalled(); }); - it('should render correctly when there are no detected tokens', () => { - //@ts-expect-error we are ignoring the navigation params on purpose because we do not want to mock setOptions to test the navbar - renderWithoutDetectedTokens(Wallet); - expect(mockTabsListComponent).toHaveBeenCalled(); - }); - it('should render TabsList', () => { //@ts-expect-error we are ignoring the navigation params on purpose because we do not want to mock setOptions to test the navbar render(Wallet); @@ -798,15 +755,6 @@ describe('Wallet', () => { expect(mockTabsListComponent).toHaveBeenCalled(); }); - it('Should add tokens to state automatically when there are detected tokens', () => { - const mockedAddTokens = jest.mocked(Engine.context.TokensController); - - //@ts-expect-error we are ignoring the navigation params on purpose because we do not want to mock setOptions to test the navbar - render(Wallet); - - expect(mockedAddTokens.addTokens).toHaveBeenCalledTimes(1); - }); - it('should render correctly when Solana support is enabled', () => { jest .mocked(useSelector) diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index afcdeb118bf..5a1aa9c1ef4 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -104,33 +104,20 @@ import { ActionLocation, ActionPosition, } from '../../../util/analytics/actionButtonTracking'; -import Engine from '../../../core/Engine'; import { RootState } from '../../../reducers'; import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; import { selectAccountBalanceByChainId } from '../../../selectors/accountTrackerController'; import { selectChainId, - selectEvmNetworkConfigurationsByChainId, - selectIsAllNetworks, - selectIsPopularNetwork, - selectNetworkClientId, selectProviderConfig, } from '../../../selectors/networkController'; import { getMetamaskNotificationsUnreadCount, selectIsMetamaskNotificationsEnabled, } from '../../../selectors/notifications'; -import { - selectAllDetectedTokensFlat, - selectDetectedTokens, -} from '../../../selectors/tokensController'; import { selectSelectedAccountGroupId } from '../../../selectors/multichainAccounts/accountTreeController'; import { selectShouldShowWalletHomeOnboardingSteps } from '../../../selectors/onboarding'; -import { - getDecimalChainId, - getIsNetworkOnboarded, - isTestNet, -} from '../../../util/networks'; +import { getIsNetworkOnboarded, isTestNet } from '../../../util/networks'; import NotificationsService from '../../../util/notifications/services/NotificationService'; import { useTheme } from '../../../util/theme'; import { useAccountGroupName } from '../../hooks/multichainAccounts/useAccountGroupName'; @@ -139,8 +126,6 @@ import usePrevious from '../../hooks/usePrevious'; import { PERFORMANCE_CONFIG } from '@metamask/perps-controller'; import ErrorBoundary from '../ErrorBoundary'; -import { Token } from '@metamask/assets-controllers'; -import { Hex } from '@metamask/utils'; import { selectIsEvmNetworkSelected } from '../../../selectors/multichainNetworkController'; import { selectHomepageSectionsV1Enabled, @@ -162,13 +147,11 @@ import useCheckNftAutoDetectionModal from '../../hooks/useCheckNftAutoDetectionM import useCheckMultiRpcModal from '../../hooks/useCheckMultiRpcModal'; import { useMultichainAccountsIntroModal } from '../../hooks/useMultichainAccountsIntroModal'; import { useAccountsWithNetworkActivitySync } from '../../hooks/useAccountsWithNetworkActivitySync'; -import { selectUseTokenDetection } from '../../../selectors/preferencesController'; import Logger from '../../../util/Logger'; import { useNftDetection } from '../../hooks/useNftDetection'; import BrazeBanner from '../../UI/BrazeBanner'; import ComponentErrorBoundary from '../../UI/ComponentErrorBoundary'; import { BRAZE_BANNER_WALLET_HOME_PLACEMENT_ID } from '../../../core/Braze/constants'; -import { TokenI } from '../../UI/Tokens/types'; import NetworkConnectionBanner from '../../UI/NetworkConnectionBanner'; import { selectAssetsDefiPositionsEnabled } from '../../../selectors/featureFlagController/assetsDefiPositions'; @@ -714,9 +697,6 @@ const Wallet = ({ const dispatch = useDispatch(); const { navigateToSendPage } = useSendNavigation(); - const evmNetworkConfigurations = useSelector( - selectEvmNetworkConfigurationsByChainId, - ); const { popularEvmNetworks: evmChainIds } = useNetworkEnablement(); /** @@ -1067,19 +1047,8 @@ const Wallet = ({ getMetamaskNotificationsUnreadCount, ); - const isAllNetworks = useSelector(selectIsAllNetworks); - const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); - const isPopularNetworks = useSelector(selectIsPopularNetwork); - const detectedTokens = useSelector(selectDetectedTokens) as TokenI[]; const homeGrowthBanner = useHomeGrowthBanner(); - const allDetectedTokens = useSelector( - selectAllDetectedTokensFlat, - ) as TokenI[]; - const currentDetectedTokens = - isAllNetworks && isPopularNetworks ? allDetectedTokens : detectedTokens; - const selectedNetworkClientId = useSelector(selectNetworkClientId); - const { detectNfts } = useNftDetection(); /** @@ -1204,104 +1173,6 @@ const Wallet = ({ navigation.navigate(Routes.TRANSACTIONS_VIEW); }, [navigation, trackEvent]); - const getTokenAddedAnalyticsParams = useCallback( - ({ address, symbol }: { address: string; symbol: string }) => { - try { - return { - token_address: address, - token_symbol: symbol, - chain_id: getDecimalChainId(chainId), - source: 'Add token dropdown', - }; - } catch (error) { - Logger.error( - error as Error, - 'SearchTokenAutocomplete.getTokenAddedAnalyticsParams', - ); - return undefined; - } - }, - [chainId], - ); - - useEffect(() => { - const importAllDetectedTokens = async () => { - // If autodetect tokens toggle is OFF, return - if (!isTokenDetectionEnabled) { - return; - } - const { TokensController } = Engine.context; - if ( - Array.isArray(currentDetectedTokens) && - currentDetectedTokens.length > 0 - ) { - // Group tokens by their `chainId` using a plain object - const tokensByChainId: Record = {}; - - for (const token of currentDetectedTokens) { - // TODO: [SOLANA] Check if this logic supports non evm networks before shipping Solana - const tokenChainId: Hex = - (token as TokenI & { chainId: Hex }).chainId ?? chainId; - - if (!tokensByChainId[tokenChainId]) { - tokensByChainId[tokenChainId] = []; - } - - tokensByChainId[tokenChainId].push(token); - } - - // Process grouped tokens in parallel - const importPromises = Object.entries(tokensByChainId).map( - async ([networkId, allTokens]) => { - const chainConfig = evmNetworkConfigurations[networkId as Hex]; - const { defaultRpcEndpointIndex } = chainConfig; - const { networkClientId: networkInstanceId } = - chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; - - await TokensController.addTokens(allTokens, networkInstanceId); - }, - ); - - await Promise.all(importPromises); - - currentDetectedTokens.forEach( - ({ address, symbol }: { address: string; symbol: string }) => { - const analyticsParams = getTokenAddedAnalyticsParams({ - address, - symbol, - }); - - if (analyticsParams) { - trackEvent( - createEventBuilder(MetaMetricsEvents.TOKEN_ADDED) - .addProperties({ - token_address: address, - token_symbol: symbol, - chain_id: getDecimalChainId(chainId), - source: 'detected', - }) - .build(), - ); - } - }, - ); - } - }; - if (isEvmSelected) { - importAllDetectedTokens(); - } - }, [ - isEvmSelected, - isTokenDetectionEnabled, - evmNetworkConfigurations, - chainId, - currentDetectedTokens, - selectedNetworkClientId, - getTokenAddedAnalyticsParams, - trackEvent, - createEventBuilder, - ]); - const onChangeTab = useCallback( (obj: { i: number; ref: React.ReactNode }) => { const tabLabel = diff --git a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts index 1aa59ebd6b9..dfe9d0590f1 100644 --- a/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts +++ b/app/components/hooks/useMultichainBalances/useSelectedAccountMultichainBalances.test.ts @@ -63,7 +63,6 @@ const MOCK_STORE_STATE = { TokensController: { allTokens: {}, allIgnoredTokens: {}, - allDetectedTokens: {}, }, NetworkEnablementController: { enabledNetworks: ['0x1', '0x89'], diff --git a/app/components/hooks/useNetworkSelection/useNetworkSelection.test.ts b/app/components/hooks/useNetworkSelection/useNetworkSelection.test.ts index f3f12ae7447..49e0da076f6 100644 --- a/app/components/hooks/useNetworkSelection/useNetworkSelection.test.ts +++ b/app/components/hooks/useNetworkSelection/useNetworkSelection.test.ts @@ -140,7 +140,6 @@ jest.mock('../../../reducers/swaps', () => ({ jest.mock('../../../selectors/tokensController', () => ({ selectTokens: jest.fn(), - selectTokensControllerState: jest.fn(), selectAllTokens: jest.fn(), })); diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts index 522163ab78b..4db5ea31894 100644 --- a/app/constants/navigation/Routes.ts +++ b/app/constants/navigation/Routes.ts @@ -147,7 +147,6 @@ const Routes = { WHATS_NEW: 'WhatsNewModal', TURN_OFF_REMEMBER_ME: 'TurnOffRememberMeModal', UPDATE_NEEDED: 'UpdateNeededModal', - DETECTED_TOKENS: 'DetectedTokens', SRP_REVEAL_QUIZ: 'SRPRevealQuiz', WALLET_ACTIONS: 'WalletActions', TRADE_WALLET_ACTIONS: 'TradeWalletActions', diff --git a/app/selectors/smartTransactionsController.test.ts b/app/selectors/smartTransactionsController.test.ts index ab5e6acb701..044b90be3af 100644 --- a/app/selectors/smartTransactionsController.test.ts +++ b/app/selectors/smartTransactionsController.test.ts @@ -28,7 +28,6 @@ jest.mock('./accountsController', () => { }); jest.mock('./tokensController', () => ({ - selectTokensControllerState: jest.fn(), selectTokens: jest.fn(() => []), selectAllTokens: jest.fn(() => []), })); diff --git a/app/selectors/tokensController.test.ts b/app/selectors/tokensController.test.ts index 1c453f6bb86..35197751ea9 100644 --- a/app/selectors/tokensController.test.ts +++ b/app/selectors/tokensController.test.ts @@ -5,10 +5,7 @@ import { selectTokensByAddress, selectTokensLength, selectIgnoreTokens, - selectDetectedTokens, selectAllTokensFlat, - selectAllDetectedTokensForSelectedAddress, - selectAllDetectedTokensFlat, selectTokensByChainIdAndAddress, selectTokensByChainIdAndWalletAddress, getChainIdsToPoll, @@ -27,14 +24,6 @@ describe('TokensController Selectors', () => { '0xAddress2': [mockToken2], }, }, - allDetectedTokens: { - '0x1': { - '0xAddress1': [mockToken], - }, - '0x2': { - '0xAddress2': [mockToken2], - }, - }, allIgnoredTokens: { '0x1': { '0xAddress1': ['0xToken2'], @@ -209,38 +198,6 @@ describe('TokensController Selectors', () => { }); }); - describe('selectDetectedTokens', () => { - it('returns detected tokens', () => { - expect(selectDetectedTokens(mockRootState)).toStrictEqual([mockToken]); - }); - - it('returns undefined if no detected tokens are present', () => { - const stateWithoutDetectedTokens = { - ...mockRootState, - engine: { - backgroundState: { - TokensController: { - ...mockTokensControllerState, - allDetectedTokens: undefined, - }, - AccountsController: { - internalAccounts: { - selectedAccount: '0xAddress1', - accounts: { - '0xAddress1': { - address: '0xAddress1', - }, - }, - }, - }, - }, - }, - } as unknown as RootState; - - expect(selectDetectedTokens(stateWithoutDetectedTokens)).toBeUndefined(); - }); - }); - describe('selectAllTokensFlat', () => { it('returns all tokens as a flat array', () => { expect(selectAllTokensFlat(mockRootState)).toStrictEqual([ @@ -266,62 +223,6 @@ describe('TokensController Selectors', () => { }); }); - describe('selectAllDetectedTokensForSelectedAddress', () => { - it('returns detected tokens for the selected address', () => { - const detectedTokens = - selectAllDetectedTokensForSelectedAddress.resultFunc( - mockTokensControllerState as unknown as TokensControllerState, - '0xAddress1', - ); - expect(detectedTokens).toStrictEqual({ - '0x1': [{ ...mockToken, chainId: '0x1' }], - }); - }); - - it('returns an empty object if no selected address is provided', () => { - const detectedTokens = - selectAllDetectedTokensForSelectedAddress.resultFunc( - mockTokensControllerState as unknown as TokensControllerState, - undefined, - ); - expect(detectedTokens).toStrictEqual({}); - }); - }); - - describe('selectAllDetectedTokensFlat', () => { - it('returns all detected tokens as a flat array', () => { - const detectedTokens = selectAllDetectedTokensFlat.resultFunc({ - '0x1': [mockToken as Token], - '0x2': [mockToken2 as Token], - }); - expect(detectedTokens).toStrictEqual([ - { ...mockToken, chainId: '0x1' }, - { ...mockToken2, chainId: '0x2' }, - ]); - }); - - it('returns an empty array if no detected tokens are present', () => { - const detectedTokens = selectAllDetectedTokensFlat.resultFunc({}); - expect(detectedTokens).toStrictEqual([]); - }); - - it('preserves chain ID in detected tokens', () => { - const detectedTokens = selectAllDetectedTokensFlat.resultFunc({ - '0x1': [mockToken as Token], - '0x2': [mockToken2 as Token], - }); - expect(detectedTokens).toStrictEqual([ - { ...mockToken, chainId: '0x1' }, - { ...mockToken2, chainId: '0x2' }, - ]); - }); - - it('handles empty detected tokens gracefully', () => { - const detectedTokens = selectAllDetectedTokensFlat.resultFunc({}); - expect(detectedTokens).toStrictEqual([]); - }); - }); - describe('selectTokensByChainIdAndAddress', () => { it('returns mapped tokens for given chain ID', () => { expect( diff --git a/app/selectors/tokensController.ts b/app/selectors/tokensController.ts index 25d2b2d88fa..cd59f8b489c 100644 --- a/app/selectors/tokensController.ts +++ b/app/selectors/tokensController.ts @@ -17,13 +17,6 @@ import { getTokensControllerAllTokens, } from './assets/assets-migration'; -/** - * @deprecated - * This selector accesses deprecated AssetsController state directly. - */ -const selectTokensControllerState = (state: RootState) => - state?.engine?.backgroundState?.TokensController; - export const selectTokens = createDeepEqualSelector( getTokensControllerAllTokens, selectEvmChainId, @@ -106,24 +99,6 @@ export const selectIgnoreTokens = createSelector( ) => allIgnoredTokens?.[chainId]?.[selectedAddress as Hex], ); -/** - * @deprecated - * This selector accesses deprecated AssetsController state directly. - */ -export const selectDetectedTokens = createSelector( - selectTokensControllerState, - selectEvmChainId, - selectSelectedInternalAccountAddress, - ( - tokensControllerState: TokensControllerState, - chainId: Hex, - selectedAddress: string | undefined, - ) => - tokensControllerState?.allDetectedTokens?.[chainId]?.[ - selectedAddress as Hex - ], -); - export { getTokensControllerAllTokens as selectAllTokens }; export const getChainIdsToPoll = createDeepEqualSelector( @@ -160,60 +135,6 @@ export const selectAllTokensFlat = createSelector( }, ); -/** - * @deprecated - * This selector accesses deprecated AssetsController state directly. - */ -export const selectAllDetectedTokensForSelectedAddress = createSelector( - selectTokensControllerState, - selectSelectedInternalAccountAddress, - (tokensControllerState, selectedAddress) => { - // Updated return type to specify the structure more clearly - if (!selectedAddress) { - return {} as { [chainId: Hex]: Token[] }; // Specify return type - } - - return Object.entries( - tokensControllerState?.allDetectedTokens || {}, - ).reduce<{ - [chainId: string]: Token[]; - }>((acc, [chainId, chainTokens]) => { - const tokensForAddress = chainTokens[selectedAddress] || []; - if (tokensForAddress.length > 0) { - acc[chainId] = tokensForAddress.map((token: Token) => ({ - ...token, - chainId, - })); - } - return acc; - }, {}); - }, -); - -export const selectAllDetectedTokensFlat = createSelector( - selectAllDetectedTokensForSelectedAddress, - (detectedTokensByChain: { [chainId: string]: Token[] }) => { - if (Object.keys(detectedTokensByChain).length === 0) { - return []; - } - - const flattenedTokens: (Token & { chainId: Hex })[] = []; - - for (const [chainId, addressTokens] of Object.entries( - detectedTokensByChain, - )) { - for (const token of addressTokens) { - flattenedTokens.push({ - ...token, - chainId: chainId as Hex, - }); - } - } - - return flattenedTokens; - }, -); - // Full selector implementation with selected address filtering export const selectTransformedTokens = createSelector( getTokensControllerAllTokens, diff --git a/docs/bigint-migration-guide.md b/docs/bigint-migration-guide.md index c7e87faa1c8..231b29136ca 100644 --- a/docs/bigint-migration-guide.md +++ b/docs/bigint-migration-guide.md @@ -136,7 +136,6 @@ Includes Stake UI and Money paths owned via `**/Earn/**`, `**/earn/**`, `**/Mone - `app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDetails.test.ts` - `app/components/UI/Tokens/util/deriveBalanceFromAssetMarketDetails.ts` - `app/components/Views/AssetDetails/index.tsx` -- `app/components/Views/DetectedTokens/components/Token.tsx` - `app/selectors/assets/assets-list.ts` ### @MetaMask/mobile-core-ux diff --git a/tests/framework/fixtures/FixtureBuilder.ts b/tests/framework/fixtures/FixtureBuilder.ts index 15c4cbc284d..a270ebfc191 100644 --- a/tests/framework/fixtures/FixtureBuilder.ts +++ b/tests/framework/fixtures/FixtureBuilder.ts @@ -1488,17 +1488,6 @@ class FixtureBuilder { return this; } - withDetectedTokens(tokens: Record[]) { - merge(this.fixture.state.engine.backgroundState.TokensController, { - allDetectedTokens: { - [CHAIN_IDS.MAINNET]: { - [DEFAULT_FIXTURE_ACCOUNT]: tokens, - }, - }, - }); - return this; - } - withIncomingTransactionPreferences(incomingTransactionPreferences: boolean) { merge(this.fixture.state.engine.backgroundState.PreferencesController, { showIncomingTransactions: incomingTransactionPreferences, diff --git a/tests/page-objects/wallet/DetectedTokensView.ts b/tests/page-objects/wallet/DetectedTokensView.ts deleted file mode 100644 index a57cb91e549..00000000000 --- a/tests/page-objects/wallet/DetectedTokensView.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DetectedTokensSelectorIDs } from '../../../app/components/Views/DetectedTokens/DetectedTokensView.testIds'; -import Gestures from '../../framework/Gestures'; -import Matchers from '../../framework/Matchers'; - -class DetectedTokensView { - get importButton(): DetoxElement { - return device.getPlatform() === 'ios' - ? Matchers.getElementByID(DetectedTokensSelectorIDs.IMPORT_BUTTON_ID) - : Matchers.getElementByLabel(DetectedTokensSelectorIDs.IMPORT_BUTTON_ID); - } - - async tapImport(): Promise { - await Gestures.waitAndTap(this.importButton, { - elemDescription: 'Import Button in Detected Tokens View', - }); - } -} - -export default new DetectedTokensView(); diff --git a/tests/regression/assets/token-detection-import-all.spec.ts b/tests/regression/assets/token-detection-import-all.spec.ts deleted file mode 100644 index 72a6a06c61d..00000000000 --- a/tests/regression/assets/token-detection-import-all.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; -import { loginToApp } from '../../flows/wallet.flow'; -import { RegressionAssets } from '../../tags'; -import WalletView from '../../page-objects/wallet/WalletView'; -import Assertions from '../../framework/Assertions'; -import TestHelpers from '../../helpers'; -import FixtureBuilder from '../../framework/fixtures/FixtureBuilder'; -import { withFixtures } from '../../framework/fixtures/FixtureHelper'; - -const ETHEREUM_NAME = 'Ethereum'; -const USDC_NAME = 'USDCoin'; - -describe(RegressionAssets('Import all tokens detected'), () => { - beforeAll(async () => { - jest.setTimeout(150000); - await TestHelpers.reverseServerPort(); - }); - - it('should import all tokens detected automatically', async () => { - await withFixtures( - { - fixture: new FixtureBuilder() - .withDetectedTokens([ - { - address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - decimals: 18, - symbol: 'USDC', - chainId: '0x1', - name: 'USDCoin', - }, - ]) - .build(), - restartDevice: true, - }, - async () => { - await loginToApp(); - - await Assertions.expectElementToBeVisible(WalletView.container); - const eth = WalletView.tokenInWallet(ETHEREUM_NAME); - const usdc = WalletView.tokenInWallet(USDC_NAME); - - await Assertions.expectElementToBeVisible(eth); - await Assertions.expectElementToBeVisible(usdc); - }, - ); - }); -});