diff --git a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx index 5ab9d0aba4d..5f405e38754 100644 --- a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx @@ -8,10 +8,6 @@ import Text, { import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors'; import { strings } from '../../../../../../locales/i18n'; import { useSelector } from 'react-redux'; -import { - selectDetectedTokens, - selectAllDetectedTokensFlat, -} from '../../../../../selectors/tokensController'; import { isZero } from '../../../../../util/lodash'; import useRampNetwork from '../../../Ramp/hooks/useRampNetwork'; import { createBuyNavigationDetails } from '../../../Ramp/routes/utils'; @@ -25,45 +21,19 @@ import { MetaMetricsEvents, useMetrics, } from '../../../../../components/hooks/useMetrics'; -import { - getDecimalChainId, - isPortfolioViewEnabled, -} from '../../../../../util/networks'; -import { - selectChainId, - selectIsAllNetworks, - selectIsPopularNetwork, -} from '../../../../../selectors/networkController'; +import { getDecimalChainId } from '../../../../../util/networks'; +import { selectChainId } from '../../../../../selectors/networkController'; import { TokenI } from '../../types'; -import { selectUseTokenDetection } from '../../../../../selectors/preferencesController'; interface TokenListFooterProps { tokens: TokenI[]; goToAddToken: () => void; - showDetectedTokens: () => void; isAddTokenEnabled: boolean; } -const getDetectedTokensCount = ( - isPortfolioEnabled: boolean, - isAllNetworksSelected: boolean, - allTokens: TokenI[], - filteredTokens: TokenI[] | undefined, - isPopularNetworks: boolean, -): number => { - if (!isPortfolioEnabled) { - return filteredTokens?.length ?? 0; - } - - return isAllNetworksSelected && isPopularNetworks - ? allTokens.length - : filteredTokens?.length ?? 0; -}; - export const TokenListFooter = ({ tokens, goToAddToken, - showDetectedTokens, isAddTokenEnabled, }: TokenListFooterProps) => { const navigation = useNavigation(); @@ -71,16 +41,7 @@ export const TokenListFooter = ({ const { trackEvent, createEventBuilder } = useMetrics(); const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork(); - const detectedTokens = useSelector(selectDetectedTokens) as TokenI[]; - const allDetectedTokens = useSelector( - selectAllDetectedTokensFlat, - ) as TokenI[]; - - const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); const chainId = useSelector(selectChainId); - const isAllNetworks = useSelector(selectIsAllNetworks); - const isPopularNetworks = useSelector(selectIsPopularNetwork); - const styles = createStyles(colors); const mainToken = tokens.find(({ isETH }) => isETH); @@ -103,35 +64,8 @@ export const TokenListFooter = ({ ); }; - const tokenCount = getDetectedTokensCount( - isPortfolioViewEnabled(), - isAllNetworks, - allDetectedTokens, - detectedTokens, - isPopularNetworks, - ); - - const areTokensDetected = tokenCount > 0; - return ( <> - {/* renderTokensDetectedSection */} - {areTokensDetected && isTokenDetectionEnabled && ( - - - {strings('wallet.tokens_detected_in_account', { - tokenCount, - tokensLabel: tokenCount > 1 ? 'tokens' : 'token', - })} - - - )} {/* render buy button */} {isBuyableToken && ( diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 0aec38bdeae..c8af59a20b9 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -43,6 +43,9 @@ import { } from '../../../util/networks'; import { selectChainId, + selectIsAllNetworks, + selectIsPopularNetwork, + selectNetworkClientId, selectNetworkConfigurations, selectProviderConfig, selectTicker, @@ -52,6 +55,8 @@ import { selectNetworkImageSource, } from '../../../selectors/networkInfos'; import { + selectAllDetectedTokensFlat, + selectDetectedTokens, selectTokens, selectTokensByChainIdAndAddress, } from '../../../selectors/tokensController'; @@ -72,7 +77,10 @@ import Text, { import { useMetrics } from '../../../components/hooks/useMetrics'; import { RootState } from '../../../reducers'; import usePrevious from '../../hooks/usePrevious'; -import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; +import { + selectSelectedInternalAccount, + selectSelectedInternalAccountFormattedAddress, +} from '../../../selectors/accountsController'; import { selectAccountBalanceByChainId } from '../../../selectors/accountTrackerController'; import { hideNftFetchingLoadingIndicator as hideNftFetchingLoadingIndicatorAction, @@ -92,7 +100,13 @@ import { PortfolioBalance } from '../../UI/Tokens/TokenList/PortfolioBalance'; import useCheckNftAutoDetectionModal from '../../hooks/useCheckNftAutoDetectionModal'; import useCheckMultiRpcModal from '../../hooks/useCheckMultiRpcModal'; import { selectContractBalances } from '../../../selectors/tokenBalancesController'; -import { selectTokenNetworkFilter } from '../../../selectors/preferencesController'; +import { + selectTokenNetworkFilter, + selectUseTokenDetection, +} from '../../../selectors/preferencesController'; +import { TokenI } from '../../UI/Tokens/types'; +import { Hex } from '@metamask/utils'; +import { Token } from '@metamask/assets-controllers'; const createStyles = ({ colors, typography }: Theme) => StyleSheet.create({ @@ -311,6 +325,24 @@ const Wallet = ({ const networkImageSource = useSelector(selectNetworkImageSource); const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); + const isAllNetworks = useSelector(selectIsAllNetworks); + const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); + const isPopularNetworks = useSelector(selectIsPopularNetwork); + const detectedTokens = useSelector(selectDetectedTokens) as TokenI[]; + + const allDetectedTokens = useSelector( + selectAllDetectedTokensFlat, + ) as TokenI[]; + const currentDetectedTokens = + isPortfolioViewEnabled() && isAllNetworks && isPopularNetworks + ? allDetectedTokens + : detectedTokens; + const allNetworks = useSelector(selectNetworkConfigurations); + const selectedNetworkClientId = useSelector(selectNetworkClientId); + const selectedAddress = useSelector( + selectSelectedInternalAccountFormattedAddress, + ); + /** * Shows Nft auto detect modal if the user is on mainnet, never saw the modal and have nft detection off */ @@ -444,6 +476,74 @@ const Wallet = ({ readNotificationCount, ]); + useEffect(() => { + const importAllDetectedTokens = async () => { + // If autodetect tokens toggle is OFF, return + if (!isTokenDetectionEnabled) { + return; + } + const { TokensController } = Engine.context; + if (currentDetectedTokens.length > 0) { + if (isPortfolioViewEnabled()) { + // Group tokens by their `chainId` using a plain object + const tokensByChainId: Record = {}; + + for (const token of currentDetectedTokens) { + 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 = allNetworks[networkId as Hex]; + const { defaultRpcEndpointIndex } = chainConfig; + const { networkClientId: networkInstanceId } = + chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; + + await TokensController.addTokens(allTokens, networkInstanceId); + }, + ); + + await Promise.all(importPromises); + } else { + await TokensController.addTokens( + currentDetectedTokens, + selectedNetworkClientId, + ); + } + + currentDetectedTokens.forEach(({ address, symbol }) => + trackEvent( + createEventBuilder(MetaMetricsEvents.TOKEN_ADDED) + .addProperties({ + token_address: address, + token_symbol: symbol, + chain_id: getDecimalChainId(chainId), + source: 'detected', + }) + .build(), + ), + ); + } + }; + importAllDetectedTokens(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + selectedAddress, + isTokenDetectionEnabled, + allNetworks, + chainId, + currentDetectedTokens, + selectedNetworkClientId, + ]); + const renderTabBar = useCallback( (props) => (