From 1dd9e041ce98fe12cd6b812480b794cb8e4524c6 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Thu, 19 Dec 2024 17:45:07 +0100 Subject: [PATCH 1/5] fix: import all detected tokens automatically --- .../app/assets/asset-list/asset-list.tsx | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/ui/components/app/assets/asset-list/asset-list.tsx b/ui/components/app/assets/asset-list/asset-list.tsx index 19d9ecdd4c0d..0b863b86dcef 100644 --- a/ui/components/app/assets/asset-list/asset-list.tsx +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -1,14 +1,16 @@ -import React, { useContext, useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useContext, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Token } from '@metamask/assets-controllers'; +import { NetworkConfiguration } from '@metamask/network-controller'; import TokenList from '../token-list'; import { PRIMARY } from '../../../../helpers/constants/common'; import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency'; import { getAllDetectedTokensForSelectedAddress, getDetectedTokensInCurrentNetwork, - getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, getIsTokenNetworkFilterEqualCurrentNetwork, getSelectedAccount, + getSelectedAddress, } from '../../../../selectors'; import { getMultichainIsEvm, @@ -25,7 +27,7 @@ import { MetaMetricsEventName, } from '../../../../../shared/constants/metametrics'; import DetectedToken from '../../detected-token/detected-token'; -import { DetectedTokensBanner, ReceiveModal } from '../../../multichain'; +import { ReceiveModal } from '../../../multichain'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { FundingMethodModal } from '../../../multichain/funding-method-modal/funding-method-modal'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) @@ -35,6 +37,11 @@ import { } from '../../../multichain/ramps-card/ramps-card'; import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF +import { + getNetworkConfigurationsByChainId, + getSelectedNetworkClientId, +} from '../../../../../shared/modules/selectors/networks'; +import { addImportedTokens } from '../../../../store/actions'; import AssetListControlBar from './asset-list-control-bar'; import NativeToken from './native-token'; @@ -54,6 +61,7 @@ export type AssetListProps = { }; const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { + const dispatch = useDispatch(); const [showDetectedTokens, setShowDetectedTokens] = useState(false); const selectedAccount = useSelector(getSelectedAccount); const t = useI18nContext(); @@ -74,14 +82,17 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { }); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork) || []; - const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( - getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, - ); const isTokenNetworkFilterEqualCurrentNetwork = useSelector( getIsTokenNetworkFilterEqualCurrentNetwork, ); + const allNetworks: Record<`0x${string}`, NetworkConfiguration> = useSelector( + getNetworkConfigurationsByChainId, + ); + const networkClientId = useSelector(getSelectedNetworkClientId); + const selectedAddress = useSelector(getSelectedAddress); + const [showFundingMethodModal, setShowFundingMethodModal] = useState(false); const [showReceiveModal, setShowReceiveModal] = useState(false); @@ -104,32 +115,55 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { // for EVM assets const shouldShowTokensLinks = showTokensLinks ?? isEvm; - const detectedTokensMultichain = useSelector( - getAllDetectedTokensForSelectedAddress, + const detectedTokensMultichain: { + [key: `0x${string}`]: Token[]; + } = useSelector(getAllDetectedTokensForSelectedAddress); + + const multichainDetectedTokensLength = Object.keys( + detectedTokensMultichain || {}, + ).reduce( + (sum, key) => sum + detectedTokensMultichain[key as `0x${string}`].length, + 0, ); - const totalTokens = - process.env.PORTFOLIO_VIEW && - !isTokenNetworkFilterEqualCurrentNetwork && - detectedTokensMultichain - ? (Object.values(detectedTokensMultichain).reduce( - // @ts-expect-error TS18046: 'tokenArray' is of type 'unknown' - (count, tokenArray) => count + tokenArray.length, - 0, - ) as number) - : detectedTokens.length; + // Add detected tokens to sate + useEffect(() => { + const importAllDetectedTokens = async () => { + // TODO add event for MetaMetricsEventName.TokenAdded + + if ( + process.env.PORTFOLIO_VIEW && + !isTokenNetworkFilterEqualCurrentNetwork + ) { + const importPromises = Object.entries(detectedTokensMultichain).map( + async ([networkId, tokens]) => { + const chainConfig = allNetworks[networkId as `0x${string}`]; + const { defaultRpcEndpointIndex } = chainConfig; + const { networkClientId: networkInstanceId } = + chainConfig.rpcEndpoints[defaultRpcEndpointIndex]; + + await dispatch( + addImportedTokens(tokens as Token[], networkInstanceId), + ); + }, + ); + + await Promise.all(importPromises); + } else { + await dispatch(addImportedTokens(detectedTokens, networkClientId)); + } + }; + importAllDetectedTokens(); + }, [ + isTokenNetworkFilterEqualCurrentNetwork, + selectedAddress, + networkClientId, + detectedTokens.length, + multichainDetectedTokensLength, + ]); + return ( <> - {totalTokens && - totalTokens > 0 && - !isTokenDetectionInactiveOnNonMainnetSupportedNetwork ? ( - setShowDetectedTokens(true)} - margin={4} - marginBottom={1} - /> - ) : null} Date: Fri, 20 Dec 2024 10:41:19 +0100 Subject: [PATCH 2/5] fix: fix token detection when setting is off and send events --- .../app/assets/asset-list/asset-list.tsx | 45 +++++++++++++++++++ .../account-overview-non-evm.test.tsx | 1 + 2 files changed, 46 insertions(+) diff --git a/ui/components/app/assets/asset-list/asset-list.tsx b/ui/components/app/assets/asset-list/asset-list.tsx index 0b863b86dcef..dd6589bc070a 100644 --- a/ui/components/app/assets/asset-list/asset-list.tsx +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -11,6 +11,7 @@ import { getIsTokenNetworkFilterEqualCurrentNetwork, getSelectedAccount, getSelectedAddress, + getUseTokenDetection, } from '../../../../selectors'; import { getMultichainIsEvm, @@ -25,6 +26,7 @@ import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { MetaMetricsEventCategory, MetaMetricsEventName, + MetaMetricsTokenEventSource, } from '../../../../../shared/constants/metametrics'; import DetectedToken from '../../detected-token/detected-token'; import { ReceiveModal } from '../../../multichain'; @@ -38,10 +40,15 @@ import { import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF import { + getCurrentChainId, getNetworkConfigurationsByChainId, getSelectedNetworkClientId, } from '../../../../../shared/modules/selectors/networks'; import { addImportedTokens } from '../../../../store/actions'; +import { + AssetType, + TokenStandard, +} from '../../../../../shared/constants/transaction'; import AssetListControlBar from './asset-list-control-bar'; import NativeToken from './native-token'; @@ -92,6 +99,8 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { ); const networkClientId = useSelector(getSelectedNetworkClientId); const selectedAddress = useSelector(getSelectedAddress); + const useTokenDetection = useSelector(getUseTokenDetection); + const currentChainId = useSelector(getCurrentChainId); const [showFundingMethodModal, setShowFundingMethodModal] = useState(false); const [showReceiveModal, setShowReceiveModal] = useState(false); @@ -129,6 +138,10 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { // Add detected tokens to sate useEffect(() => { const importAllDetectedTokens = async () => { + // If autodetect tokens toggle is OFF, return + if (!useTokenDetection) { + return; + } // TODO add event for MetaMetricsEventName.TokenAdded if ( @@ -145,12 +158,44 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { await dispatch( addImportedTokens(tokens as Token[], networkInstanceId), ); + tokens.forEach((importedToken) => { + trackEvent({ + event: MetaMetricsEventName.TokenAdded, + category: MetaMetricsEventCategory.Wallet, + sensitiveProperties: { + token_symbol: importedToken.symbol, + token_contract_address: importedToken.address, + token_decimal_precision: importedToken.decimals, + source: MetaMetricsTokenEventSource.Detected, + token_standard: TokenStandard.ERC20, + asset_type: AssetType.token, + token_added_type: 'detected', + chain_id: chainConfig.chainId, + }, + }); + }); }, ); await Promise.all(importPromises); } else { await dispatch(addImportedTokens(detectedTokens, networkClientId)); + detectedTokens.forEach((importedToken: Token) => { + trackEvent({ + event: MetaMetricsEventName.TokenAdded, + category: MetaMetricsEventCategory.Wallet, + sensitiveProperties: { + token_symbol: importedToken.symbol, + token_contract_address: importedToken.address, + token_decimal_precision: importedToken.decimals, + source: MetaMetricsTokenEventSource.Detected, + token_standard: TokenStandard.ERC20, + asset_type: AssetType.token, + token_added_type: 'detected', + chain_id: currentChainId, + }, + }); + }); } }; importAllDetectedTokens(); diff --git a/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx index df7c2ef56d69..68a7978ffb53 100644 --- a/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx +++ b/ui/components/multichain/account-overview/account-overview-non-evm.test.tsx @@ -15,6 +15,7 @@ jest.mock('../../../store/actions', () => ({ setTokenNetworkFilter: jest.fn(), updateSlides: jest.fn(), removeSlide: jest.fn(), + addImportedTokens: jest.fn(), })); // Mock the dispatch function From c2bfc2474f1506224fd7140d6e7654f4e700eb12 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Fri, 20 Dec 2024 10:51:12 +0100 Subject: [PATCH 3/5] fix: fix test --- .../app/assets/asset-list/asset-list.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/components/app/assets/asset-list/asset-list.test.tsx b/ui/components/app/assets/asset-list/asset-list.test.tsx index 00a47df1c633..6b6dbbee88ad 100644 --- a/ui/components/app/assets/asset-list/asset-list.test.tsx +++ b/ui/components/app/assets/asset-list/asset-list.test.tsx @@ -72,6 +72,18 @@ jest.mock('../../../../store/actions', () => { })), tokenBalancesStartPolling: jest.fn().mockResolvedValue('pollingToken'), tokenBalancesStopPollingByPollingToken: jest.fn(), + addImportedTokens: jest.fn(), + }; +}); + +// Mock the dispatch function +const mockDispatch = jest.fn(); + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: () => mockDispatch, }; }); From 41fa3dee80af31b73fb5f9aee979a613dd116aaa Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Fri, 20 Dec 2024 11:00:42 +0100 Subject: [PATCH 4/5] fix: fix test --- .../multichain/account-overview/account-overview-eth.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/multichain/account-overview/account-overview-eth.test.tsx b/ui/components/multichain/account-overview/account-overview-eth.test.tsx index cfca7b93dddf..bc959b2e92bf 100644 --- a/ui/components/multichain/account-overview/account-overview-eth.test.tsx +++ b/ui/components/multichain/account-overview/account-overview-eth.test.tsx @@ -15,6 +15,7 @@ jest.mock('../../../store/actions', () => ({ setTokenNetworkFilter: jest.fn(), updateSlides: jest.fn(), removeSlide: jest.fn(), + addImportedTokens: jest.fn(), })); // Mock the dispatch function From eae8fbb7ae480cff2f41e76baf6d77ac5f7eab15 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Fri, 20 Dec 2024 12:27:17 +0100 Subject: [PATCH 5/5] fix: fix e2e --- .../errors-after-init-opt-in-background-state.json | 2 +- .../state-snapshots/errors-after-init-opt-in-ui-state.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index cae1a6ae8951..93ad82dd5933 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -339,7 +339,7 @@ "TokensController": { "tokens": "object", "ignoredTokens": "object", - "detectedTokens": "object", + "detectedTokens": "undefined", "allTokens": {}, "allIgnoredTokens": {}, "allDetectedTokens": {} diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index a306c63c70b6..5a22b2400a39 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -182,7 +182,7 @@ "preventPollingOnNetworkRestart": false, "tokens": "object", "ignoredTokens": "object", - "detectedTokens": "object", + "detectedTokens": "undefined", "allTokens": {}, "allIgnoredTokens": {}, "allDetectedTokens": {},