diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b59b21ae8111..6f3f33a5a2ce 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -3255,8 +3255,8 @@ export default class MetamaskController extends EventEmitter { currencyRateController, tokenBalancesController, tokenDetectionController, - ensController, tokenListController, + ensController, gasFeeController, metaMetricsController, networkController, @@ -4081,14 +4081,6 @@ export default class MetamaskController extends EventEmitter { tokenListStopPollingByPollingToken: tokenListController.stopPollingByPollingToken.bind(tokenListController), - tokenBalancesStartPolling: tokenBalancesController.startPolling.bind( - tokenBalancesController, - ), - tokenBalancesStopPollingByPollingToken: - tokenBalancesController.stopPollingByPollingToken.bind( - tokenBalancesController, - ), - // GasFeeController gasFeeStartPollingByNetworkClientId: gasFeeController.startPollingByNetworkClientId.bind(gasFeeController), diff --git a/ui/components/app/assets/asset-list/asset-list.tsx b/ui/components/app/assets/asset-list/asset-list.tsx index b4b47444a43b..394a43eb0840 100644 --- a/ui/components/app/assets/asset-list/asset-list.tsx +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -7,6 +7,7 @@ import { getAllDetectedTokensForSelectedAddress, getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, + getPreferences, getSelectedAccount, } from '../../../../selectors'; import { @@ -80,6 +81,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); + const { tokenNetworkFilter } = useSelector(getPreferences); const [showFundingMethodModal, setShowFundingMethodModal] = useState(false); const [showReceiveModal, setShowReceiveModal] = useState(false); @@ -108,13 +110,14 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { getAllDetectedTokensForSelectedAddress, ); - const totalTokens = process.env.PORTFOLIO_VIEW - ? (Object.values(detectedTokensMultichain).reduce( - // @ts-expect-error TS18046: 'tokenArray' is of type 'unknown' - (count, tokenArray) => count + tokenArray.length, - 0, - ) as number) - : detectedTokens.length; + const totalTokens = + process.env.PORTFOLIO_VIEW && Object.keys(tokenNetworkFilter).length > 1 + ? (Object.values(detectedTokensMultichain).reduce( + // @ts-expect-error TS18046: 'tokenArray' is of type 'unknown' + (count, tokenArray) => count + tokenArray.length, + 0, + ) as number) + : detectedTokens.length; return ( <> diff --git a/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js b/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js index e5c6adc0c5c7..dc6c89fa833f 100644 --- a/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js +++ b/ui/components/app/detected-token/detected-token-selection-popover/detected-token-selection-popover.js @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; @@ -12,7 +12,9 @@ import { import { getAllDetectedTokensForSelectedAddress, getCurrentChainId, + getCurrentNetwork, getDetectedTokensInCurrentNetwork, + getPreferences, } from '../../../../selectors'; import Popover from '../../../ui/popover'; @@ -35,17 +37,23 @@ const DetectedTokenSelectionPopover = ({ const chainId = useSelector(getCurrentChainId); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); + const { tokenNetworkFilter } = useSelector(getPreferences); + + const currentNetwork = useSelector(getCurrentNetwork); const detectedTokensMultichain = useSelector( getAllDetectedTokensForSelectedAddress, ); - const totalTokens = process.env.PORTFOLIO_VIEW - ? Object.values(detectedTokensMultichain).reduce( - (count, tokenArray) => count + tokenArray.length, - 0, - ) - : detectedTokens.length; + const totalTokens = useMemo(() => { + return process.env.PORTFOLIO_VIEW && + Object.keys(tokenNetworkFilter).length > 1 + ? Object.values(detectedTokensMultichain).reduce( + (count, tokenArray) => count + tokenArray.length, + 0, + ) + : detectedTokens.length; + }, [detectedTokensMultichain, detectedTokens, tokenNetworkFilter]); const { selected: selectedTokens = [] } = sortingBasedOnTokenSelection(tokensListDetected); @@ -104,7 +112,8 @@ const DetectedTokenSelectionPopover = ({ onClose={onClose} footer={footer} > - {process.env.PORTFOLIO_VIEW ? ( + {process.env.PORTFOLIO_VIEW && + Object.keys(tokenNetworkFilter).length > 1 ? ( {Object.entries(detectedTokensMultichain).map( ([networkId, tokens]) => { @@ -129,6 +138,7 @@ const DetectedTokenSelectionPopover = ({ token={token} handleTokenSelection={handleTokenSelection} tokensListDetected={tokensListDetected} + chainId={currentNetwork.chainId} /> ); })} diff --git a/ui/components/app/detected-token/detected-token-values/detected-token-values.js b/ui/components/app/detected-token/detected-token-values/detected-token-values.js index 667c356b0e1f..07c70edcf196 100644 --- a/ui/components/app/detected-token/detected-token-values/detected-token-values.js +++ b/ui/components/app/detected-token/detected-token-values/detected-token-values.js @@ -7,10 +7,14 @@ import { TextColor, TextVariant, } from '../../../../helpers/constants/design-system'; -import { useTokenTracker } from '../../../../hooks/useTokenTracker'; import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount'; -import { getUseCurrencyRateCheck } from '../../../../selectors'; +import { + getCurrentChainId, + getSelectedAddress, + getUseCurrencyRateCheck, +} from '../../../../selectors'; import { Box, Checkbox, Text } from '../../../component-library'; +import { useTokenTracker } from '../../../../hooks/useTokenBalances'; const DetectedTokenValues = ({ token, @@ -21,12 +25,25 @@ const DetectedTokenValues = ({ return tokensListDetected[token.address]?.selected; }); - const { tokensWithBalances } = useTokenTracker({ tokens: [token] }); + const selectedAddress = useSelector(getSelectedAddress); + const currentChainId = useSelector(getCurrentChainId); + const chainId = token.chainId ?? currentChainId; + + const { tokensWithBalances } = useTokenTracker({ + chainId, + tokens: [token], + address: selectedAddress, + hideZeroBalanceTokens: false, + }); + const balanceString = tokensWithBalances[0]?.string; const formattedFiatBalance = useTokenFiatAmount( token.address, balanceString, token.symbol, + {}, + false, + chainId, ); const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck); @@ -73,6 +90,7 @@ DetectedTokenValues.propTypes = { symbol: PropTypes.string, iconUrl: PropTypes.string, aggregators: PropTypes.array, + chainId: PropTypes.string, }), handleTokenSelection: PropTypes.func.isRequired, tokensListDetected: PropTypes.object, diff --git a/ui/components/app/detected-token/detected-token.js b/ui/components/app/detected-token/detected-token.js index f7af9b7bfb80..a993f8ff42c6 100644 --- a/ui/components/app/detected-token/detected-token.js +++ b/ui/components/app/detected-token/detected-token.js @@ -1,4 +1,4 @@ -import React, { useState, useContext } from 'react'; +import React, { useState, useContext, useEffect, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import { chain } from 'lodash'; @@ -12,6 +12,7 @@ import { getAllDetectedTokensForSelectedAddress, getDetectedTokensInCurrentNetwork, getNetworkConfigurationsByChainId, + getPreferences, getSelectedNetworkClientId, } from '../../../selectors'; import { MetaMetricsContext } from '../../../contexts/metametrics'; @@ -57,35 +58,48 @@ const DetectedToken = ({ setShowDetectedTokens }) => { const detectedTokensMultichain = useSelector( getAllDetectedTokensForSelectedAddress, ); + const { tokenNetworkFilter } = useSelector(getPreferences); const configuration = useSelector(getNetworkConfigurationsByChainId); - const totalDetectedTokens = process.env.PORTFOLIO_VIEW - ? Object.values(detectedTokensMultichain).flat().length - : detectedTokens.length; - - const [tokensListDetected, setTokensListDetected] = useState(() => { - if (process.env.PORTFOLIO_VIEW) { - return Object.entries(detectedTokensMultichain).reduce( - (acc, [chainId, tokens]) => { - if (Array.isArray(tokens)) { - tokens.forEach((token) => { - acc[token.address] = { - token: { ...token, chainId }, - selected: true, - }; - }); - } - return acc; - }, - {}, - ); - } + const totalDetectedTokens = useMemo(() => { + return process.env.PORTFOLIO_VIEW && + Object.keys(tokenNetworkFilter).length > 1 + ? Object.values(detectedTokensMultichain).flat().length + : detectedTokens.length; + }, [tokenNetworkFilter, detectedTokens, detectedTokensMultichain]); + + const [tokensListDetected, setTokensListDetected] = useState({}); + + useEffect(() => { + const newTokensList = () => { + if ( + process.env.PORTFOLIO_VIEW && + Object.keys(tokenNetworkFilter).length > 1 + ) { + return Object.entries(detectedTokensMultichain).reduce( + (acc, [chainId, tokens]) => { + if (Array.isArray(tokens)) { + tokens.forEach((token) => { + acc[token.address] = { + token: { ...token, chainId }, + selected: true, + }; + }); + } + return acc; + }, + {}, + ); + } + + return detectedTokens.reduce((tokenObj, token) => { + tokenObj[token.address] = { token, selected: true }; + return tokenObj; + }, {}); + }; - return detectedTokens.reduce((tokenObj, token) => { - tokenObj[token.address] = { token, selected: true }; - return tokenObj; - }, {}); - }); + setTokensListDetected(newTokensList()); + }, [tokenNetworkFilter, detectedTokensMultichain, detectedTokens]); const [showDetectedTokenIgnoredPopover, setShowDetectedTokenIgnoredPopover] = useState(false); @@ -110,7 +124,10 @@ const DetectedToken = ({ setShowDetectedTokens }) => { }); }); - if (process.env.PORTFOLIO_VIEW) { + if ( + process.env.PORTFOLIO_VIEW && + Object.keys(tokenNetworkFilter).length > 1 + ) { const tokensByChainId = selectedTokens.reduce((acc, token) => { const { chainId } = token; diff --git a/ui/components/multichain/detected-token-banner/detected-token-banner.js b/ui/components/multichain/detected-token-banner/detected-token-banner.js index c5f795ab2d38..6f57bb4e1e63 100644 --- a/ui/components/multichain/detected-token-banner/detected-token-banner.js +++ b/ui/components/multichain/detected-token-banner/detected-token-banner.js @@ -8,6 +8,7 @@ import { getCurrentChainId, getDetectedTokensInCurrentNetwork, getAllDetectedTokensForSelectedAddress, + getPreferences, } from '../../../selectors'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { @@ -24,27 +25,29 @@ export const DetectedTokensBanner = ({ }) => { const t = useI18nContext(); const trackEvent = useContext(MetaMetricsContext); + const { tokenNetworkFilter } = useSelector(getPreferences); const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); const detectedTokensMultichain = useSelector( getAllDetectedTokensForSelectedAddress, ); - - const detectedTokensDetails = process.env.PORTFOLIO_VIEW - ? Object.values(detectedTokensMultichain) - .flat() - .map(({ address, symbol }) => `${symbol} - ${address}`) - : detectedTokens.map(({ address, symbol }) => `${symbol} - ${address}`); - const chainId = useSelector(getCurrentChainId); - const totalTokens = process.env.PORTFOLIO_VIEW - ? Object.values(detectedTokensMultichain).reduce( - (count, tokenArray) => count + tokenArray.length, - 0, - ) - : detectedTokens.length; + const detectedTokensDetails = + process.env.PORTFOLIO_VIEW && Object.keys(tokenNetworkFilter).length > 1 + ? Object.values(detectedTokensMultichain) + .flat() + .map(({ address, symbol }) => `${symbol} - ${address}`) + : detectedTokens.map(({ address, symbol }) => `${symbol} - ${address}`); + + const totalTokens = + process.env.PORTFOLIO_VIEW && Object.keys(tokenNetworkFilter).length > 1 + ? Object.values(detectedTokensMultichain).reduce( + (count, tokenArray) => count + tokenArray.length, + 0, + ) + : detectedTokens.length; const handleOnClick = () => { actionButtonOnClick(); diff --git a/ui/hooks/useTokenFiatAmount.js b/ui/hooks/useTokenFiatAmount.js index dfa4144b90e3..2a5dd1dc5d62 100644 --- a/ui/hooks/useTokenFiatAmount.js +++ b/ui/hooks/useTokenFiatAmount.js @@ -5,10 +5,13 @@ import { getCurrentCurrency, getShouldShowFiat, getConfirmationExchangeRates, + getMarketData, + getCurrencyRates, } from '../selectors'; import { getTokenFiatAmount } from '../helpers/utils/token-util'; import { getConversionRate } from '../ducks/metamask/metamask'; import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; +import { CHAIN_ID_TO_CURRENCY_SYMBOL_MAP } from '../../shared/constants/network'; /** * Get the token balance converted to fiat and formatted for display @@ -22,6 +25,7 @@ import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; * @param {boolean} [overrides.showFiat] - If truthy, ensures the fiat value is shown even if the showFiat value from state is falsey * @param {boolean} hideCurrencySymbol - Indicates whether the returned formatted amount should include the trailing currency symbol * @returns {string} The formatted token amount in the user's chosen fiat currency + * @param {string} [chainId] - The chain id */ export function useTokenFiatAmount( tokenAddress, @@ -29,17 +33,40 @@ export function useTokenFiatAmount( tokenSymbol, overrides = {}, hideCurrencySymbol, + chainId = null, ) { + const allMarketData = useSelector(getMarketData); + const contractExchangeRates = useSelector( getTokenExchangeRates, shallowEqual, ); + + const contractMarketData = chainId + ? Object.entries(allMarketData[chainId]).reduce( + (acc, [address, marketData]) => { + acc[address] = marketData?.price ?? null; + return acc; + }, + {}, + ) + : null; + + const tokenMarketData = chainId ? contractMarketData : contractExchangeRates; + const confirmationExchangeRates = useSelector(getConfirmationExchangeRates); const mergedRates = { - ...contractExchangeRates, + ...tokenMarketData, ...confirmationExchangeRates, }; + + const currencyRates = useSelector(getCurrencyRates); const conversionRate = useSelector(getConversionRate); + + const tokenConversionRate = chainId + ? currencyRates[CHAIN_ID_TO_CURRENCY_SYMBOL_MAP[chainId]]?.usdConversionRate + : conversionRate; + const currentCurrency = useSelector(getCurrentCurrency); const userPrefersShownFiat = useSelector(getShouldShowFiat); const showFiat = overrides.showFiat ?? userPrefersShownFiat; @@ -53,7 +80,7 @@ export function useTokenFiatAmount( () => getTokenFiatAmount( tokenExchangeRate, - conversionRate, + tokenConversionRate, currentCurrency, tokenAmount, tokenSymbol, @@ -61,8 +88,8 @@ export function useTokenFiatAmount( hideCurrencySymbol, ), [ + tokenConversionRate, tokenExchangeRate, - conversionRate, currentCurrency, tokenAmount, tokenSymbol, diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index d44700440299..039ea876140d 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -85,6 +85,7 @@ import { getLedgerTransportType, isAddressLedger, getIsUnlocked, + getCompletedOnboarding, } from '../ducks/metamask/metamask'; import { getLedgerWebHidConnectedStatus,