diff --git a/android/.project b/android/.project index 3cf8618bf4c..e6990dbd1ce 100644 --- a/android/.project +++ b/android/.project @@ -14,4 +14,15 @@ org.eclipse.buildship.core.gradleprojectnature + + + 1731607498998 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index e8895216fd3..e479558406c 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=false +show.console.view=false +show.executions.view=false diff --git a/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.test.tsx b/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.test.tsx index 82014a8debc..c650e8640f5 100644 --- a/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.test.tsx +++ b/app/components/UI/AccountFromToInfoCard/AccountFromToInfoCard.test.tsx @@ -38,8 +38,12 @@ const mockInitialState: DeepPartial = { }, }, TokenBalancesController: { - contractBalances: { - '0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': '0x5', + tokenBalances: { + '0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': { + '0x5': { + '0x326836cc6cd09B5aa59B81A7F72F25FcC0136b95': '0x2b46', + }, + }, }, }, AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE, diff --git a/app/components/UI/AssetElement/index.tsx b/app/components/UI/AssetElement/index.tsx index ff39a4eac1a..e2337d0092d 100644 --- a/app/components/UI/AssetElement/index.tsx +++ b/app/components/UI/AssetElement/index.tsx @@ -94,8 +94,7 @@ const AssetElement: React.FC = ({ {balance && ( ) => void; @@ -69,8 +75,9 @@ const AssetOverview: React.FC = ({ displaySwapsButton, }: AssetOverviewProps) => { const [timePeriod, setTimePeriod] = React.useState('1d'); - const currentCurrency = useSelector(selectCurrentCurrency); const conversionRate = useSelector(selectConversionRate); + const conversionRateByTicker = useSelector(selectCurrencyRates); + const currentCurrency = useSelector(selectCurrentCurrency); const accountsByChainId = useSelector(selectAccountsByChainId); const primaryCurrency = useSelector( (state: RootState) => state.settings.primaryCurrency, @@ -81,12 +88,20 @@ const AssetOverview: React.FC = ({ ); const { trackEvent } = useMetrics(); const tokenExchangeRates = useSelector(selectContractExchangeRates); + const tokenExchangeRateByChainId = useSelector(selectTokenMarketData); const tokenBalances = useSelector(selectContractBalances); - const chainId = useSelector((state: RootState) => selectChainId(state)); - const ticker = useSelector((state: RootState) => selectTicker(state)); + const selectedChainId = useSelector((state: RootState) => + selectChainId(state), + ); + const selectedTicker = useSelector((state: RootState) => selectTicker(state)); + + const chainId = isPortfolioViewEnabled + ? (asset.chainId as Hex) + : selectedChainId; + const ticker = isPortfolioViewEnabled ? asset.symbol : selectedTicker; const { data: prices = [], isLoading } = useTokenHistoricalPrices({ - address: asset.isETH ? zeroAddress() : asset.address, + address: asset.address, chainId, timePeriod, vsCurrency: currentCurrency, @@ -201,55 +216,84 @@ const AssetOverview: React.FC = ({ ); const itemAddress = safeToChecksumAddress(asset.address); - const exchangeRate = itemAddress - ? tokenExchangeRates?.[itemAddress]?.price - : undefined; + + let exchangeRate; + if (!isPortfolioViewEnabled) { + exchangeRate = itemAddress + ? tokenExchangeRates?.[itemAddress]?.price + : undefined; + } else { + exchangeRate = + tokenExchangeRateByChainId?.[chainId]?.[itemAddress as Hex]?.price; + } let balance, balanceFiat; - if (asset.isETH) { - balance = renderFromWei( - //@ts-expect-error - This should be fixed at the accountsController selector level, ongoing discussion - accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, - ); - balanceFiat = weiToFiat( - hexToBN( + if (!isPortfolioViewEnabled) { + if (asset.isETH) { + balance = renderFromWei( //@ts-expect-error - This should be fixed at the accountsController selector level, ongoing discussion accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, - ), - conversionRate, - currentCurrency, - ); + ); + balanceFiat = weiToFiat( + hexToBN( + //@ts-expect-error - This should be fixed at the accountsController selector level, ongoing discussion + accountsByChainId[toHexadecimal(chainId)][selectedAddress]?.balance, + ), + conversionRate, + currentCurrency, + ); + } else { + balance = + itemAddress && tokenBalances?.[itemAddress] + ? renderFromTokenMinimalUnit( + tokenBalances[itemAddress], + asset.decimals, + ) + : 0; + balanceFiat = balanceToFiat( + balance, + conversionRateByTicker[asset.symbol].conversionRate, + exchangeRate, + currentCurrency, + ); + } } else { - balance = - itemAddress && tokenBalances?.[itemAddress] - ? renderFromTokenMinimalUnit(tokenBalances[itemAddress], asset.decimals) - : 0; - balanceFiat = balanceToFiat( - balance, - conversionRate, - exchangeRate, - currentCurrency, - ); + balance = asset.balance; + balanceFiat = asset.balanceFiat; } let mainBalance, secondaryBalance; - if (primaryCurrency === 'ETH') { - mainBalance = `${balance} ${asset.symbol}`; - secondaryBalance = balanceFiat; + if (!isPortfolioViewEnabled) { + if (primaryCurrency === 'ETH') { + mainBalance = `${balance} ${asset.symbol}`; + secondaryBalance = balanceFiat; + } else { + mainBalance = !balanceFiat ? `${balance} ${asset.symbol}` : balanceFiat; + secondaryBalance = !balanceFiat + ? balanceFiat + : `${balance} ${asset.symbol}`; + } } else { - mainBalance = !balanceFiat ? `${balance} ${asset.symbol}` : balanceFiat; - secondaryBalance = !balanceFiat - ? balanceFiat - : `${balance} ${asset.symbol}`; + mainBalance = `${balance} ${asset.symbol}`; + secondaryBalance = asset.balanceFiat; } let currentPrice = 0; let priceDiff = 0; - if (asset.isETH) { - currentPrice = conversionRate || 0; - } else if (exchangeRate && conversionRate) { - currentPrice = exchangeRate * conversionRate; + if (!isPortfolioViewEnabled) { + if (asset.isETH) { + currentPrice = conversionRate || 0; + } else if (exchangeRate && conversionRate) { + currentPrice = exchangeRate * conversionRate; + } + } else { + const tickerConversionRate = + conversionRateByTicker[asset.symbol].conversionRate; + currentPrice = + exchangeRate && tickerConversionRate + ? exchangeRate * tickerConversionRate + : 0; } const comparePrice = prices[0]?.[1] || 0; diff --git a/app/components/UI/AssetOverview/Balance/Balance.tsx b/app/components/UI/AssetOverview/Balance/Balance.tsx index fed53bd539a..fe81320c418 100644 --- a/app/components/UI/AssetOverview/Balance/Balance.tsx +++ b/app/components/UI/AssetOverview/Balance/Balance.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { View } from 'react-native'; +import { Hex } from '@metamask/utils'; import { strings } from '../../../../../locales/i18n'; import { useStyles } from '../../../../component-library/hooks'; import styleSheet from './Balance.styles'; @@ -9,6 +10,7 @@ import { selectNetworkName } from '../../../../selectors/networkInfos'; import { selectChainId } from '../../../../selectors/networkController'; import { getTestNetImageByChainId, + getDefaultNetworkByChainId, isLineaMainnetByChainId, isMainnetByChainId, isTestNet, @@ -20,6 +22,7 @@ import Badge from '../../../../component-library/components/Badges/Badge/Badge'; import NetworkMainAssetLogo from '../../NetworkMainAssetLogo'; import AvatarToken from '../../../../component-library/components/Avatars/Avatar/variants/AvatarToken'; import { AvatarSize } from '../../../../component-library/components/Avatars/Avatar'; +import NetworkAssetLogo from '../../NetworkAssetLogo'; import Text, { TextVariant, } from '../../../../component-library/components/Texts/Text'; @@ -27,6 +30,11 @@ import { TokenI } from '../../Tokens/types'; import { useNavigation } from '@react-navigation/native'; import { isPooledStakingFeatureEnabled } from '../../Stake/constants'; import StakingBalance from '../../Stake/components/StakingBalance/StakingBalance'; +import { + PopularList, + UnpopularNetworkList, + CustomNetworkImgMapping, +} from '../../../../util/networks/customNetworks'; interface BalanceProps { asset: TokenI; @@ -34,6 +42,8 @@ interface BalanceProps { secondaryBalance?: string; } +const isPortfolioViewEnabled = process.env.PORTFOLIO_VIEW === 'true'; + export const NetworkBadgeSource = (chainId: string, ticker: string) => { const isMainnet = isMainnetByChainId(chainId); const isLineaMainnet = isLineaMainnetByChainId(chainId); @@ -51,7 +61,93 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => { const { styles } = useStyles(styleSheet, {}); const navigation = useNavigation(); const networkName = useSelector(selectNetworkName); - const chainId = useSelector(selectChainId); + const selectedChainId = useSelector(selectChainId); + + const chainId = isPortfolioViewEnabled + ? (asset.chainId as Hex) + : selectedChainId; + + const isMainnet = isMainnetByChainId(chainId); + const isLineaMainnet = isLineaMainnetByChainId(chainId); + const ticker = asset.symbol; + + const renderNetworkAvatar = useCallback(() => { + if (!isPortfolioViewEnabled && asset.isETH) { + return ; + } + + if (isPortfolioViewEnabled && asset.isNative) { + return ( + + ); + } + + return ( + + ); + }, [ + asset.isETH, + asset.image, + asset.symbol, + asset.isNative, + asset.chainId, + styles.ethLogo, + ]); + + const networkBadgeSource = useCallback( + (currentChainId: Hex) => { + if (!isPortfolioViewEnabled) { + if (isTestNet(chainId)) return getTestNetImageByChainId(chainId); + if (isMainnet) return images.ETHEREUM; + + if (isLineaMainnet) return images['LINEA-MAINNET']; + + if (CustomNetworkImgMapping[chainId]) { + return CustomNetworkImgMapping[chainId]; + } + + return ticker ? images[ticker as keyof typeof images] : undefined; + } + if (isTestNet(currentChainId)) + return getTestNetImageByChainId(currentChainId); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const defaultNetwork = getDefaultNetworkByChainId(currentChainId) as any; + + if (defaultNetwork) { + return defaultNetwork.imageSource; + } + + const unpopularNetwork = UnpopularNetworkList.find( + (networkConfig) => networkConfig.chainId === currentChainId, + ); + + const customNetworkImg = CustomNetworkImgMapping[currentChainId]; + + const popularNetwork = PopularList.find( + (networkConfig) => networkConfig.chainId === currentChainId, + ); + + const network = unpopularNetwork || popularNetwork; + if (network) { + return network.rpcPrefs.imageSource; + } + if (customNetworkImg) { + return customNetworkImg; + } + }, + [chainId, isLineaMainnet, isMainnet, ticker], + ); return ( @@ -69,20 +165,12 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => { badgeElement={ } > - {asset.isETH ? ( - - ) : ( - - )} + {renderNetworkAvatar()} {asset.name || asset.symbol} diff --git a/app/components/UI/AssetOverview/Price/Price.tsx b/app/components/UI/AssetOverview/Price/Price.tsx index 9d95b9e17b5..a6145a7dcd8 100644 --- a/app/components/UI/AssetOverview/Price/Price.tsx +++ b/app/components/UI/AssetOverview/Price/Price.tsx @@ -90,7 +90,10 @@ const Price = ({ {asset.symbol} )} {!isNaN(price) && ( - + {isLoading ? ( diff --git a/app/components/UI/NetworkAssetLogo/index.tsx b/app/components/UI/NetworkAssetLogo/index.tsx new file mode 100644 index 00000000000..90629c5f871 --- /dev/null +++ b/app/components/UI/NetworkAssetLogo/index.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { ChainId } from '@metamask/controller-utils'; +import TokenIcon from '../Swaps/components/TokenIcon'; + +interface NetworkAssetLogoProps { + chainId: string; + ticker: string; + style: object; + big: boolean; + biggest: boolean; + testID: string; +} + +function NetworkAssetLogo({ + chainId, + ticker, + style, + big, + biggest, + testID, +}: NetworkAssetLogoProps) { + if (chainId === ChainId.mainnet) { + return ( + + ); + } + return ( + + ); +} + +export default NetworkAssetLogo; diff --git a/app/components/UI/NetworkModal/index.tsx b/app/components/UI/NetworkModal/index.tsx index 3c58495c9f5..8b7e7f588d5 100644 --- a/app/components/UI/NetworkModal/index.tsx +++ b/app/components/UI/NetworkModal/index.tsx @@ -109,6 +109,23 @@ const NetworkModals = (props: NetworkProps) => { return true; }; + const customNetworkInformation = { + chainId, + blockExplorerUrl, + chainName: nickname, + rpcUrl, + icon: imageUrl, + ticker, + alerts, + }; + + const onUpdateNetworkFilter = useCallback(() => { + const { PreferencesController } = Engine.context; + PreferencesController.setTokenNetworkFilter({ + [customNetworkInformation.chainId]: true, + }); + }, [customNetworkInformation.chainId]); + const addNetwork = async () => { const isValidUrl = validateRpcUrl(rpcUrl); if (showPopularNetworkModal) { @@ -172,16 +189,6 @@ const NetworkModals = (props: NetworkProps) => { selectNetworkConfigurations, ); - const customNetworkInformation = { - chainId, - blockExplorerUrl, - chainName: nickname, - rpcUrl, - icon: imageUrl, - ticker, - alerts, - }; - const checkNetwork = useCallback(async () => { if (useSafeChainsListValidation) { const alertsNetwork = await checkSafeNetwork( @@ -245,6 +252,7 @@ const NetworkModals = (props: NetworkProps) => { } if (networkClientId) { + onUpdateNetworkFilter(); await NetworkController.setActiveNetwork(networkClientId); } @@ -270,7 +278,7 @@ const NetworkModals = (props: NetworkProps) => { const { networkClientId } = updatedNetwork?.rpcEndpoints?.[updatedNetwork.defaultRpcEndpointIndex] ?? {}; - + onUpdateNetworkFilter(); await NetworkController.setActiveNetwork(networkClientId); }; @@ -339,6 +347,7 @@ const NetworkModals = (props: NetworkProps) => { addedNetwork?.rpcEndpoints?.[addedNetwork.defaultRpcEndpointIndex] ?? {}; + onUpdateNetworkFilter(); NetworkController.setActiveNetwork(networkClientId); } onClose(); diff --git a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx index 92093347577..4e2a3edb7f6 100644 --- a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx @@ -8,7 +8,10 @@ import Text, { import { WalletViewSelectorsIDs } from '../../../../../../e2e/selectors/wallet/WalletView.selectors'; import { strings } from '../../../../../../locales/i18n'; import { useSelector } from 'react-redux'; -import { selectDetectedTokens } from '../../../../../selectors/tokensController'; +import { + selectDetectedTokens, + selectAllDetectedTokensFlat, +} from '../../../../../selectors/tokensController'; import { isZero } from '../../../../../util/lodash'; import useRampNetwork from '../../../Ramp/hooks/useRampNetwork'; import { createBuyNavigationDetails } from '../../../Ramp/routes/utils'; @@ -23,9 +26,15 @@ import { useMetrics, } from '../../../../../components/hooks/useMetrics'; import { getDecimalChainId } from '../../../../../util/networks'; -import { selectChainId } from '../../../../../selectors/networkController'; +import { + selectChainId, + selectNetworkConfigurations, +} from '../../../../../selectors/networkController'; import { TokenI } from '../../types'; -import { selectUseTokenDetection } from '../../../../../selectors/preferencesController'; +import { + selectUseTokenDetection, + selectTokenNetworkFilter, +} from '../../../../../selectors/preferencesController'; interface TokenListFooterProps { tokens: TokenI[]; @@ -34,6 +43,21 @@ interface TokenListFooterProps { isAddTokenEnabled: boolean; } +const isPortfolioViewEnabled = process.env.PORTFOLIO_VIEW === 'true'; + +const getDetectedTokensCount = ( + isPortfolioEnabled: boolean, + isAllNetworksSelected: boolean, + allTokens: TokenI[], + filteredTokens: TokenI[] | undefined, +): number => { + if (!isPortfolioEnabled) { + return filteredTokens?.length ?? 0; + } + + return isAllNetworksSelected ? allTokens.length : filteredTokens?.length ?? 0; +}; + export const TokenListFooter = ({ tokens, goToAddToken, @@ -45,9 +69,19 @@ export const TokenListFooter = ({ const { trackEvent } = useMetrics(); const [isNetworkRampSupported, isNativeTokenRampSupported] = useRampNetwork(); - const detectedTokens = useSelector(selectDetectedTokens); + const detectedTokens = useSelector(selectDetectedTokens) as TokenI[]; + const allDetectedTokens = useSelector( + selectAllDetectedTokensFlat, + ) as TokenI[]; + const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); const chainId = useSelector(selectChainId); + // TODO: Can probably create "isAllNetworks" selector for these + // since they are re-used in multiple places + const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); // X + const allNetworks = useSelector(selectNetworkConfigurations); // X + const isAllNetworks = + Object.keys(tokenNetworkFilter).length === Object.keys(allNetworks).length; // X const styles = createStyles(colors); @@ -67,10 +101,19 @@ export const TokenListFooter = ({ }); }; + const tokenCount = getDetectedTokensCount( + isPortfolioViewEnabled, + isAllNetworks, + allDetectedTokens, + detectedTokens, + ); + + const areTokensDetected = tokenCount > 0; + return ( <> {/* renderTokensDetectedSection */} - {detectedTokens?.length !== 0 && isTokenDetectionEnabled && ( + {areTokensDetected && isTokenDetectionEnabled && ( {strings('wallet.tokens_detected_in_account', { - tokenCount: detectedTokens.length, - tokensLabel: detectedTokens.length > 1 ? 'tokens' : 'token', + tokenCount, + tokensLabel: tokenCount > 1 ? 'tokens' : 'token', })} diff --git a/app/components/UI/Tokens/TokenList/TokenListItem/index.tsx b/app/components/UI/Tokens/TokenList/TokenListItem/index.tsx index 68c53c0576c..bbdfc332877 100644 --- a/app/components/UI/Tokens/TokenList/TokenListItem/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListItem/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { View } from 'react-native'; import { Hex } from '@metamask/utils'; import { zeroAddress } from 'ethereumjs-util'; @@ -14,7 +14,10 @@ import { selectProviderConfig, selectTicker, } from '../../../../../selectors/networkController'; -import { selectContractExchangeRates } from '../../../../../selectors/tokenRatesController'; +import { + selectContractExchangeRates, + selectTokenMarketData, +} from '../../../../../selectors/tokenRatesController'; import { selectConversionRate, selectCurrentCurrency, @@ -27,6 +30,7 @@ import { isLineaMainnetByChainId, isMainnetByChainId, isTestNet, + getDefaultNetworkByChainId, } from '../../../../../util/networks'; import createStyles from '../../styles'; import BadgeWrapper from '../../../../../component-library/components/Badges/BadgeWrapper'; @@ -41,14 +45,21 @@ import Text, { import PercentageChange from '../../../../../component-library/components-temp/Price/PercentageChange'; import AssetElement from '../../../AssetElement'; import NetworkMainAssetLogo from '../../../NetworkMainAssetLogo'; +import NetworkAssetLogo from '../../../NetworkAssetLogo'; import images from 'images/image-icons'; import { TokenI } from '../../types'; import { strings } from '../../../../../../locales/i18n'; import { ScamWarningIcon } from '../ScamWarningIcon'; import { ScamWarningModal } from '../ScamWarningModal'; import { StakeButton } from '../../../Stake/components/StakeButton'; -import { CustomNetworkImgMapping } from '../../../../../util/networks/customNetworks'; import useStakingChain from '../../../Stake/hooks/useStakingChain'; +import { + PopularList, + UnpopularNetworkList, + CustomNetworkImgMapping, +} from '../../../../../util/networks/customNetworks'; + +const isPortfolioViewEnabled = process.env.PORTFOLIO_VIEW === 'true'; interface TokenListItemProps { asset: TokenI; @@ -84,6 +95,7 @@ export const TokenListItem = ({ const primaryCurrency = useSelector( (state: RootState) => state.settings.primaryCurrency, ); + const multiChainMarketData = useSelector(selectTokenMarketData); const styles = createStyles(colors); @@ -98,92 +110,175 @@ export const TokenListItem = ({ currentCurrency, ); - const pricePercentChange1d = itemAddress - ? tokenExchangeRates?.[itemAddress as `0x${string}`]?.pricePercentChange1d - : tokenExchangeRates?.[zeroAddress() as Hex]?.pricePercentChange1d; + let pricePercentChange1d: number; + + if (isPortfolioViewEnabled) { + const tokenPercentageChange = asset.address + ? multiChainMarketData?.[asset.chainId as Hex]?.[asset.address as Hex] + ?.pricePercentChange1d + : 0; + + pricePercentChange1d = asset.isNative + ? multiChainMarketData?.[asset.chainId as Hex]?.[zeroAddress() as Hex] + ?.pricePercentChange1d + : tokenPercentageChange; + } else { + pricePercentChange1d = itemAddress + ? tokenExchangeRates?.[itemAddress as Hex]?.pricePercentChange1d + : tokenExchangeRates?.[zeroAddress() as Hex]?.pricePercentChange1d; + } // render balances according to primary currency let mainBalance; let secondaryBalance; - // Set main and secondary balances based on the primary currency and asset type. - if (primaryCurrency === 'ETH') { - // Default to displaying the formatted balance value and its fiat equivalent. - mainBalance = balanceValueFormatted; - secondaryBalance = balanceFiat; - - // For ETH as a native currency, adjust display based on network safety. - if (asset.isETH) { - // Main balance always shows the formatted balance value for ETH. + if (!isPortfolioViewEnabled) { + // Set main and secondary balances based on the primary currency and asset type. + if (primaryCurrency === 'ETH') { + // Default to displaying the formatted balance value and its fiat equivalent. mainBalance = balanceValueFormatted; - // Display fiat value as secondary balance only for original native tokens on safe networks. - secondaryBalance = isOriginalNativeTokenSymbol ? balanceFiat : null; - } - } else { - // For non-ETH currencies, determine balances based on the presence of fiat value. - mainBalance = !balanceFiat ? balanceValueFormatted : balanceFiat; - secondaryBalance = !balanceFiat ? balanceFiat : balanceValueFormatted; - - // Adjust balances for native currencies in non-ETH scenarios. - if (asset.isETH) { - // Main balance logic: Show crypto value if fiat is absent or fiat value on safe networks. - if (!balanceFiat) { - mainBalance = balanceValueFormatted; // Show crypto value if fiat is not preferred - } else if (isOriginalNativeTokenSymbol) { - mainBalance = balanceFiat; // Show fiat value if it's a safe network - } else { - mainBalance = ''; // Otherwise, set to an empty string + secondaryBalance = balanceFiat; + + // For ETH as a native currency, adjust display based on network safety. + if (asset.isETH) { + // Main balance always shows the formatted balance value for ETH. + mainBalance = balanceValueFormatted; + // Display fiat value as secondary balance only for original native tokens on safe networks. + secondaryBalance = isOriginalNativeTokenSymbol ? balanceFiat : null; } - // Secondary balance mirrors the main balance logic for consistency. + } else { + // For non-ETH currencies, determine balances based on the presence of fiat value. + mainBalance = !balanceFiat ? balanceValueFormatted : balanceFiat; secondaryBalance = !balanceFiat ? balanceFiat : balanceValueFormatted; + + // Adjust balances for native currencies in non-ETH scenarios. + if (asset.isETH) { + // Main balance logic: Show crypto value if fiat is absent or fiat value on safe networks. + if (!balanceFiat) { + mainBalance = balanceValueFormatted; // Show crypto value if fiat is not preferred + } else if (isOriginalNativeTokenSymbol) { + mainBalance = balanceFiat; // Show fiat value if it's a safe network + } else { + mainBalance = ''; // Otherwise, set to an empty string + } + // Secondary balance mirrors the main balance logic for consistency. + secondaryBalance = !balanceFiat ? balanceFiat : balanceValueFormatted; + } } - } - if (asset?.hasBalanceError) { - mainBalance = asset.symbol; - secondaryBalance = strings('wallet.unable_to_load'); - } + if (asset?.hasBalanceError) { + mainBalance = asset.symbol; + secondaryBalance = strings('wallet.unable_to_load'); + } - if (balanceFiat === TOKEN_RATE_UNDEFINED) { - mainBalance = balanceValueFormatted; - secondaryBalance = strings('wallet.unable_to_find_conversion_rate'); - } + if (balanceFiat === TOKEN_RATE_UNDEFINED) { + mainBalance = balanceValueFormatted; + secondaryBalance = strings('wallet.unable_to_find_conversion_rate'); + } - asset = { ...asset, balanceFiat }; + asset = { ...asset, balanceFiat }; + } else { + mainBalance = asset.balance; + secondaryBalance = asset.balanceFiat; + } const isMainnet = isMainnetByChainId(chainId); const isLineaMainnet = isLineaMainnetByChainId(chainId); const { isStakingSupportedChain } = useStakingChain(); - const NetworkBadgeSource = () => { - if (isTestNet(chainId)) return getTestNetImageByChainId(chainId); + const networkBadgeSource = useCallback( + (currentChainId: Hex) => { + if (!isPortfolioViewEnabled) { + if (isTestNet(chainId)) return getTestNetImageByChainId(chainId); + if (isMainnet) return images.ETHEREUM; - if (isMainnet) return images.ETHEREUM; + if (isLineaMainnet) return images['LINEA-MAINNET']; - if (isLineaMainnet) return images['LINEA-MAINNET']; + if (CustomNetworkImgMapping[chainId]) { + return CustomNetworkImgMapping[chainId]; + } - if (CustomNetworkImgMapping[chainId]) { - return CustomNetworkImgMapping[chainId]; - } + return ticker ? images[ticker] : undefined; + } + if (isTestNet(currentChainId)) + return getTestNetImageByChainId(currentChainId); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const defaultNetwork = getDefaultNetworkByChainId(currentChainId) as any; - return ticker ? images[ticker] : undefined; - }; + if (defaultNetwork) { + return defaultNetwork.imageSource; + } + + const unpopularNetwork = UnpopularNetworkList.find( + (networkConfig) => networkConfig.chainId === currentChainId, + ); + + const customNetworkImg = CustomNetworkImgMapping[currentChainId]; + + const popularNetwork = PopularList.find( + (networkConfig) => networkConfig.chainId === currentChainId, + ); + + const network = unpopularNetwork || popularNetwork; + if (network) { + return network.rpcPrefs.imageSource; + } + if (customNetworkImg) { + return customNetworkImg; + } + }, + [chainId, isLineaMainnet, isMainnet, ticker], + ); const onItemPress = (token: TokenI) => { // if the asset is staked, navigate to the native asset details if (asset.isStaked) { - return navigation.navigate('Asset', {...token.nativeAsset}); + return navigation.navigate('Asset', { ...token.nativeAsset }); } navigation.navigate('Asset', { ...token, }); }; + const renderNetworkAvatar = useCallback(() => { + if (!isPortfolioViewEnabled && asset.isETH) { + return ; + } + + if (isPortfolioViewEnabled && asset.isNative) { + return ( + + ); + } + + return ( + + ); + }, [ + asset.isETH, + asset.image, + asset.symbol, + asset.isNative, + asset.chainId, + styles.ethLogo, + ]); + return ( } > - {asset.isETH ? ( - - ) : ( - - )} + {renderNetworkAvatar()} @@ -222,7 +311,9 @@ export const TokenListItem = ({ {asset.name || asset.symbol} {/** Add button link to Portfolio Stake if token is supported ETH chain and not a staked asset */} - {asset.isETH && isStakingSupportedChain && !asset.isStaked && } + {asset.isETH && isStakingSupportedChain && !asset.isStaked && ( + + )} {!isTestNet(chainId) ? ( diff --git a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx index 4720c09660b..82eeffc8ddd 100644 --- a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx +++ b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx @@ -1,6 +1,9 @@ -import React, { useRef } from 'react'; +import React, { useRef, useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { selectChainId } from '../../../../selectors/networkController'; +import { + selectChainId, + selectNetworkConfigurations, +} from '../../../../selectors/networkController'; import { selectTokenNetworkFilter } from '../../../../selectors/preferencesController'; import BottomSheet, { BottomSheetRef, @@ -15,25 +18,31 @@ import Text, { import ListItemSelect from '../../../../component-library/components/List/ListItemSelect'; import { VerticalAlignment } from '../../../../component-library/components/List/ListItem'; import { strings } from '../../../../../locales/i18n'; +import { enableAllNetworksFilter } from '../util/enableAllNetworksFilter'; enum FilterOption { - AllNetworks = 0, - CurrentNetwork = 1, + AllNetworks, + CurrentNetwork, } const TokenFilterBottomSheet = () => { const sheetRef = useRef(null); + const allNetworks = useSelector(selectNetworkConfigurations); const { colors } = useTheme(); const styles = createStyles(colors); const chainId = useSelector(selectChainId); const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); + const allNetworksEnabled = useMemo( + () => enableAllNetworksFilter(allNetworks), + [allNetworks], + ); const onFilterControlsBottomSheetPress = (option: FilterOption) => { const { PreferencesController } = Engine.context; switch (option) { case FilterOption.AllNetworks: - PreferencesController.setTokenNetworkFilter({}); + PreferencesController.setTokenNetworkFilter(allNetworksEnabled); sheetRef.current?.onCloseBottomSheet(); break; case FilterOption.CurrentNetwork: @@ -47,7 +56,11 @@ const TokenFilterBottomSheet = () => { } }; - const isSelectedNetwork = Boolean(tokenNetworkFilter?.[chainId]); + const isCurrentNetwork = Boolean( + tokenNetworkFilter[chainId] && Object.keys(tokenNetworkFilter).length === 1, + ); + const isAllNetworks = + Object.keys(tokenNetworkFilter).length === Object.keys(allNetworks).length; return ( @@ -59,7 +72,7 @@ const TokenFilterBottomSheet = () => { onPress={() => onFilterControlsBottomSheetPress(FilterOption.AllNetworks) } - isSelected={!isSelectedNetwork} + isSelected={isAllNetworks} gap={8} verticalAlignment={VerticalAlignment.Center} > @@ -71,7 +84,7 @@ const TokenFilterBottomSheet = () => { onPress={() => onFilterControlsBottomSheetPress(FilterOption.CurrentNetwork) } - isSelected={isSelectedNetwork} + isSelected={isCurrentNetwork} gap={8} verticalAlignment={VerticalAlignment.Center} > diff --git a/app/components/UI/Tokens/index.test.tsx b/app/components/UI/Tokens/index.test.tsx index 401c3c8ed9d..12cef311341 100644 --- a/app/components/UI/Tokens/index.test.tsx +++ b/app/components/UI/Tokens/index.test.tsx @@ -40,7 +40,7 @@ jest.mock('../../../core/Engine', () => ({ updateExchangeRate: jest.fn(() => Promise.resolve()), }, TokenRatesController: { - updateExchangeRates: jest.fn(() => Promise.resolve()), + updateExchangeRatesByChainId: jest.fn(() => Promise.resolve()), }, NetworkController: { getNetworkClientById: () => ({ @@ -359,7 +359,7 @@ describe('Tokens', () => { Engine.context.CurrencyRateController.updateExchangeRate, ).toHaveBeenCalled(); expect( - Engine.context.TokenRatesController.updateExchangeRates, + Engine.context.TokenRatesController.updateExchangeRatesByChainId, ).toHaveBeenCalled(); }); }); diff --git a/app/components/UI/Tokens/index.tsx b/app/components/UI/Tokens/index.tsx index a19a8cbaa92..22a43c58c8b 100644 --- a/app/components/UI/Tokens/index.tsx +++ b/app/components/UI/Tokens/index.tsx @@ -1,4 +1,5 @@ import React, { useRef, useState, LegacyRef, useMemo } from 'react'; +import { Hex } from '@metamask/utils'; import { View, Text } from 'react-native'; import ActionSheet from '@metamask/react-native-actionsheet'; import { useSelector } from 'react-redux'; @@ -41,6 +42,9 @@ import { import ButtonBase from '../../../component-library/components/Buttons/Button/foundation/ButtonBase'; import { selectNetworkName } from '../../../selectors/networkInfos'; import ButtonIcon from '../../../component-library/components/Buttons/ButtonIcon'; +import { enableAllNetworksFilter } from './util/enableAllNetworksFilter'; +import { selectAccountTokensAcrossChains } from '../../../selectors/multichain'; +import { filterAssets } from './util/filterAssets'; // this will be imported from TokenRatesController when it is exported from there // PR: https://github.com/MetaMask/core/pull/4622 @@ -73,6 +77,8 @@ interface TokenListNavigationParamList { [key: string]: undefined | object; } +const isPortfolioViewEnabled = process.env.PORTFOLIO_VIEW === 'true'; + const Tokens: React.FC = ({ tokens }) => { const navigation = useNavigation< @@ -102,23 +108,69 @@ const Tokens: React.FC = ({ tokens }) => { ), ), ]; + const allNetworks = useSelector(selectNetworkConfigurations); + const selectedAccountTokensChains = useSelector( + selectAccountTokensAcrossChains, + ); const actionSheet = useRef(); const [tokenToRemove, setTokenToRemove] = useState(); const [refreshing, setRefreshing] = useState(false); const [isAddTokenEnabled, setIsAddTokenEnabled] = useState(true); + const allNetworksEnabled = useMemo( + () => enableAllNetworksFilter(allNetworks), + [allNetworks], + ); const styles = createStyles(colors); const tokensList = useMemo(() => { - // Filter tokens based on hideZeroBalanceTokens flag + if (isPortfolioViewEnabled) { + // MultiChain implementation + const allTokens = Object.values(selectedAccountTokensChains).flat(); + + // First filter zero balance tokens if setting is enabled + const tokensWithBalance = hideZeroBalanceTokens + ? allTokens.filter((token) => !isZero(token.balance) || token.isNative) + : allTokens; + + // Then apply network filters + const filteredAssets = filterAssets(tokensWithBalance, [ + { + key: 'chainId', + opts: tokenNetworkFilter, + filterCallback: 'inclusive', + }, + ]); + + const { nativeTokens, nonNativeTokens } = filteredAssets.reduce<{ + nativeTokens: TokenI[]; + nonNativeTokens: TokenI[]; + }>( + ( + acc: { nativeTokens: TokenI[]; nonNativeTokens: TokenI[] }, + currToken: unknown, + ) => { + if ((currToken as TokenI).isNative) { + acc.nativeTokens.push(currToken as TokenI); + } else { + acc.nonNativeTokens.push(currToken as TokenI); + } + return acc; + }, + { nativeTokens: [], nonNativeTokens: [] }, + ); + const assets = [...nativeTokens, ...nonNativeTokens]; + return sortAssets(assets, tokenSortConfig); + } + + // Previous implementation const tokensToDisplay = hideZeroBalanceTokens ? tokens.filter( ({ address, isETH }) => !isZero(tokenBalances[address]) || isETH, ) : tokens; - // Calculate fiat balances for tokens const tokenFiatBalances = conversionRate ? tokensToDisplay.map((asset) => asset.isETH @@ -133,16 +185,11 @@ const Tokens: React.FC = ({ tokens }) => { ) : []; - // Combine tokens with their fiat balances - // tokenFiatAmount is the key in PreferencesController to sort by when sorting by declining fiat balance - // this key in the controller is also used by extension, so this is for consistency in syntax and config - // actual balance rendering for each token list item happens in TokenListItem component const tokensWithBalances = tokensToDisplay.map((token, i) => ({ ...token, tokenFiatAmount: tokenFiatBalances[i], })); - // Sort the tokens based on tokenSortConfig return sortAssets(tokensWithBalances, tokenSortConfig); }, [ conversionRate, @@ -152,6 +199,9 @@ const Tokens: React.FC = ({ tokens }) => { tokenExchangeRates, tokenSortConfig, tokens, + // Dependencies for multichain implementation + selectedAccountTokensChains, + tokenNetworkFilter, ]); const showRemoveMenu = (token: TokenI) => { @@ -178,9 +228,21 @@ const Tokens: React.FC = ({ tokens }) => { AccountTrackerController, CurrencyRateController, TokenRatesController, + NetworkController, + AccountsController, } = Engine.context; + + const networkConfigurations = + NetworkController.state.networkConfigurationsByChainId; + const chainIds = Object.keys(networkConfigurations); + const selectedAddress = + AccountsController.state.internalAccounts.selectedAccount; + const actions = [ - TokenDetectionController.detectTokens(), + TokenDetectionController.detectTokens({ + chainIds: chainIds as Hex[], + selectedAddress, + }), AccountTrackerController.refresh(), CurrencyRateController.updateExchangeRate(nativeCurrencies), TokenRatesController.updateExchangeRates(), @@ -239,7 +301,9 @@ const Tokens: React.FC = ({ tokens }) => { const onActionSheetPress = (index: number) => index === 0 ? removeToken() : null; - const isTokenFilterEnabled = process.env.PORTFOLIO_VIEW === 'true'; + const allNetworksFilterShown = + Object.keys(tokenNetworkFilter).length !== + Object.keys(allNetworksEnabled).length; return ( = ({ tokens }) => { testID={WalletViewSelectorsIDs.TOKENS_CONTAINER} > - {isTokenFilterEnabled ? ( + {isPortfolioViewEnabled ? ( - {tokenNetworkFilter[chainId] + {allNetworksFilterShown ? networkName ?? strings('wallet.current_network') : strings('wallet.all_networks')} diff --git a/app/components/UI/Tokens/types.ts b/app/components/UI/Tokens/types.ts index 76bdef78718..90fa5ad8b49 100644 --- a/app/components/UI/Tokens/types.ts +++ b/app/components/UI/Tokens/types.ts @@ -12,7 +12,6 @@ export interface TokensI { export interface TokenI { address: string; aggregators: string[]; - hasBalanceError?: boolean; decimals: number; image: string; name: string; @@ -21,6 +20,9 @@ export interface TokenI { balanceFiat: string; logo: string | undefined; isETH: boolean | undefined; + hasBalanceError?: boolean; isStaked?: boolean | undefined; nativeAsset?: TokenI | undefined; + chainId?: string; + isNative?: boolean; } diff --git a/app/components/UI/Tokens/util/enableAllNetworksFilter.test.ts b/app/components/UI/Tokens/util/enableAllNetworksFilter.test.ts new file mode 100644 index 00000000000..0a41d9c6db7 --- /dev/null +++ b/app/components/UI/Tokens/util/enableAllNetworksFilter.test.ts @@ -0,0 +1,164 @@ +import { RpcEndpointType } from '@metamask/network-controller'; +import { NETWORK_CHAIN_ID } from '../../../../util/networks/customNetworks'; +import { + enableAllNetworksFilter, + KnownNetworkConfigurations, +} from './enableAllNetworksFilter'; + +type TestNetworkConfigurations = Pick< + KnownNetworkConfigurations, + '0x1' | '0x89' +>; + +type FlareTestNetworkConfigurations = Pick< + KnownNetworkConfigurations, + '0xe' | '0x13' +>; + +type MultiNetworkConfigurations = Pick< + KnownNetworkConfigurations, + '0x1' | '0x89' | typeof NETWORK_CHAIN_ID.BASE +>; + +describe('enableAllNetworksFilter', () => { + it('should create a record with all network chain IDs mapped to true', () => { + const mockNetworks: TestNetworkConfigurations = { + [NETWORK_CHAIN_ID.MAINNET]: { + chainId: NETWORK_CHAIN_ID.MAINNET, + name: 'Ethereum Mainnet', + blockExplorerUrls: ['https://etherscan.io'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'ETH', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.MAINNET, + url: 'https://mainnet.infura.io/v3/{infuraProjectId}', + }, + ], + }, + [NETWORK_CHAIN_ID.POLYGON]: { + chainId: NETWORK_CHAIN_ID.POLYGON, + name: 'Polygon', + blockExplorerUrls: ['https://polygonscan.com'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'MATIC', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.POLYGON, + url: 'https://polygon-rpc.com', + }, + ], + }, + }; + + const result = enableAllNetworksFilter(mockNetworks); + + expect(result).toEqual({ + [NETWORK_CHAIN_ID.MAINNET]: true, + [NETWORK_CHAIN_ID.POLYGON]: true, + }); + }); + + it('should handle empty networks object', () => { + const result = enableAllNetworksFilter({}); + expect(result).toEqual({}); + }); + + it('should work with NETWORK_CHAIN_ID constants', () => { + const mockNetworks: FlareTestNetworkConfigurations = { + [NETWORK_CHAIN_ID.FLARE_MAINNET]: { + chainId: NETWORK_CHAIN_ID.FLARE_MAINNET, + name: 'Flare Mainnet', + blockExplorerUrls: ['https://flare.network'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'FLR', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.FLARE_MAINNET, + url: 'https://flare-rpc.com', + }, + ], + }, + [NETWORK_CHAIN_ID.SONGBIRD_TESTNET]: { + chainId: NETWORK_CHAIN_ID.SONGBIRD_TESTNET, + name: 'Songbird Testnet', + blockExplorerUrls: ['https://songbird.flare.network'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'SGB', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.SONGBIRD_TESTNET, + url: 'https://songbird-rpc.flare.network', + }, + ], + }, + }; + + const result = enableAllNetworksFilter(mockNetworks); + + expect(result).toEqual({ + [NETWORK_CHAIN_ID.FLARE_MAINNET]: true, + [NETWORK_CHAIN_ID.SONGBIRD_TESTNET]: true, + }); + }); + + it('should handle networks with different property values', () => { + const mockNetworks: MultiNetworkConfigurations = { + [NETWORK_CHAIN_ID.MAINNET]: { + chainId: NETWORK_CHAIN_ID.MAINNET, + name: 'Network 1', + blockExplorerUrls: ['https://etherscan.io'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'ETH', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.MAINNET, + url: 'https://mainnet.infura.io/v3/your-api-key', + }, + ], + }, + [NETWORK_CHAIN_ID.POLYGON]: { + chainId: NETWORK_CHAIN_ID.POLYGON, + name: 'Network 2', + blockExplorerUrls: ['https://polygonscan.com'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'MATIC', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.POLYGON, + url: 'https://polygon-rpc.com', + }, + ], + }, + [NETWORK_CHAIN_ID.BASE]: { + chainId: NETWORK_CHAIN_ID.BASE, + name: 'Network 3', + blockExplorerUrls: ['https://base.network'], + defaultRpcEndpointIndex: 0, + nativeCurrency: 'BASE', + rpcEndpoints: [ + { + type: RpcEndpointType.Custom, + networkClientId: NETWORK_CHAIN_ID.BASE, + url: 'https://base-rpc.com', + }, + ], + }, + }; + + const result = enableAllNetworksFilter(mockNetworks); + + expect(Object.values(result).every((value) => value === true)).toBe(true); + expect(Object.keys(result)).toEqual([ + NETWORK_CHAIN_ID.MAINNET, + NETWORK_CHAIN_ID.POLYGON, + NETWORK_CHAIN_ID.BASE, + ]); + }); +}); diff --git a/app/components/UI/Tokens/util/enableAllNetworksFilter.ts b/app/components/UI/Tokens/util/enableAllNetworksFilter.ts new file mode 100644 index 00000000000..2d77801aa7c --- /dev/null +++ b/app/components/UI/Tokens/util/enableAllNetworksFilter.ts @@ -0,0 +1,18 @@ +import { NetworkConfiguration } from '@metamask/network-controller'; +import { Hex } from '@metamask/utils'; +import { NETWORK_CHAIN_ID } from '../../../../util/networks/customNetworks'; + +export type KnownNetworkConfigurations = { + [K in (typeof NETWORK_CHAIN_ID)[keyof typeof NETWORK_CHAIN_ID]]: NetworkConfiguration; +}; + +export function enableAllNetworksFilter( + networks: Partial, +) { + const allOpts: Record = {}; + Object.keys(networks).forEach((chainId) => { + const hexChainId = chainId as Hex; + allOpts[hexChainId] = true; + }); + return allOpts; +} diff --git a/app/components/UI/Tokens/util/filterAssets.test.ts b/app/components/UI/Tokens/util/filterAssets.test.ts new file mode 100644 index 00000000000..b6b04c3404a --- /dev/null +++ b/app/components/UI/Tokens/util/filterAssets.test.ts @@ -0,0 +1,98 @@ +import { filterAssets, FilterCriteria } from './filterAssets'; + +describe('filterAssets function - balance and chainId filtering', () => { + interface MockToken { + name: string; + symbol: string; + chainId: string; // Updated to string (e.g., '0x01', '0x89') + balance: number; + } + + const mockTokens: MockToken[] = [ + { name: 'Token1', symbol: 'T1', chainId: '0x01', balance: 100 }, + { name: 'Token2', symbol: 'T2', chainId: '0x02', balance: 50 }, + { name: 'Token3', symbol: 'T3', chainId: '0x01', balance: 200 }, + { name: 'Token4', symbol: 'T4', chainId: '0x89', balance: 150 }, + ]; + + test('filters by inclusive chainId', () => { + const criteria: FilterCriteria[] = [ + { + key: 'chainId', + opts: { '0x01': true, '0x89': true }, // ChainId must be '0x01' or '0x89' + filterCallback: 'inclusive', + }, + ]; + + const filtered = filterAssets(mockTokens, criteria); + + expect(filtered.length).toBe(3); // Should include 3 tokens with chainId '0x01' and '0x89' + expect(filtered.map((token) => token.chainId)).toEqual([ + '0x01', + '0x01', + '0x89', + ]); + }); + + test('filters tokens with balance between 100 and 150 inclusive', () => { + const criteria: FilterCriteria[] = [ + { + key: 'balance', + opts: { min: 100, max: 150 }, // Balance between 100 and 150 + filterCallback: 'range', + }, + ]; + + const filtered = filterAssets(mockTokens, criteria); + + expect(filtered.length).toBe(2); // Token1 and Token4 + expect(filtered.map((token) => token.balance)).toEqual([100, 150]); + }); + + test('filters by inclusive chainId and balance range', () => { + const criteria: FilterCriteria[] = [ + { + key: 'chainId', + opts: { '0x01': true, '0x89': true }, // ChainId must be '0x01' or '0x89' + filterCallback: 'inclusive', + }, + { + key: 'balance', + opts: { min: 100, max: 150 }, // Balance between 100 and 150 + filterCallback: 'range', + }, + ]; + + const filtered = filterAssets(mockTokens, criteria); + + expect(filtered.length).toBe(2); // Token1 and Token4 meet both criteria + }); + + test('returns no tokens if no chainId matches', () => { + const criteria: FilterCriteria[] = [ + { + key: 'chainId', + opts: { '0x04': true }, // No token with chainId '0x04' + filterCallback: 'inclusive', + }, + ]; + + const filtered = filterAssets(mockTokens, criteria); + + expect(filtered.length).toBe(0); // No matching tokens + }); + + test('returns no tokens if balance is not within range', () => { + const criteria: FilterCriteria[] = [ + { + key: 'balance', + opts: { min: 300, max: 400 }, // No token with balance between 300 and 400 + filterCallback: 'range', + }, + ]; + + const filtered = filterAssets(mockTokens, criteria); + + expect(filtered.length).toBe(0); // No matching tokens + }); +}); diff --git a/app/components/UI/Tokens/util/filterAssets.ts b/app/components/UI/Tokens/util/filterAssets.ts new file mode 100644 index 00000000000..d7b7eaab20e --- /dev/null +++ b/app/components/UI/Tokens/util/filterAssets.ts @@ -0,0 +1,62 @@ +import { get } from 'lodash'; + +export interface FilterCriteria { + key: string; + opts: Record; // Use opts for range, inclusion, etc. + filterCallback: FilterCallbackKeys; // Specify the type of filter: 'range', 'inclusive', etc. +} + +export type FilterType = string | number | boolean | Date; +type FilterCallbackKeys = keyof FilterCallbacksT; + +export interface FilterCallbacksT { + inclusive: (value: string, opts: Record) => boolean; + range: (value: number, opts: Record) => boolean; +} + +const filterCallbacks: FilterCallbacksT = { + inclusive: (value: string, opts: Record) => { + if (Object.entries(opts).length === 0) { + return false; + } + return opts[value]; + }, + range: (value: number, opts: Record) => + value >= opts.min && value <= opts.max, +}; + +function getNestedValue(obj: T, keyPath: string): FilterType { + return get(obj, keyPath); +} + +export function filterAssets(assets: T[], criteria: FilterCriteria[]): T[] { + if (criteria.length === 0) { + return assets; + } + + return assets.filter((asset) => + criteria.every(({ key, opts, filterCallback }) => { + const nestedValue = getNestedValue(asset, key); + + // If there's no callback or options, exit early and don't filter based on this criterion. + if (!filterCallback || !opts) { + return true; + } + + switch (filterCallback) { + case 'inclusive': + return filterCallbacks.inclusive( + nestedValue as string, + opts as Record, + ); + case 'range': + return filterCallbacks.range( + nestedValue as number, + opts as { min: number; max: number }, + ); + default: + return true; + } + }), + ); +} diff --git a/app/components/UI/Tokens/util/organizeTokensByChainId.test.ts b/app/components/UI/Tokens/util/organizeTokensByChainId.test.ts new file mode 100644 index 00000000000..12da972cb97 --- /dev/null +++ b/app/components/UI/Tokens/util/organizeTokensByChainId.test.ts @@ -0,0 +1,104 @@ +import { TokenI } from '../types'; +import { organizeTokensByChainId } from './organizeTokensByChainId'; + +describe('organizeTokensByChainId', () => { + const mockTokens: TokenI[] = [ + { + address: '0x1', + chainId: '0x1', + name: 'Token1', + symbol: 'TK1', + decimals: 18, + balance: '100', + balanceFiat: '1000', + image: 'image1.png', + aggregators: ['agg1'], + logo: 'logo1.png', + isETH: false, + }, + { + address: '0x2', + chainId: '0x1', + name: 'Token2', + symbol: 'TK2', + decimals: 18, + balance: '200', + balanceFiat: '2000', + image: 'image2.png', + aggregators: ['agg2'], + logo: 'logo2.png', + isETH: false, + }, + { + address: '0x3', + chainId: '0x89', + name: 'Token3', + symbol: 'TK3', + decimals: 18, + balance: '300', + balanceFiat: '3000', + image: 'image3.png', + aggregators: ['agg3'], + logo: 'logo3.png', + isETH: false, + }, + ]; + + it('should organize tokens by chainId', () => { + const result = organizeTokensByChainId(mockTokens); + + expect(Object.keys(result)).toHaveLength(2); + expect(result['0x1']).toHaveLength(2); + expect(result['0x89']).toHaveLength(1); + + expect(result['0x1'][0].name).toBe('Token1'); + expect(result['0x1'][1].name).toBe('Token2'); + expect(result['0x89'][0].name).toBe('Token3'); + }); + + it('should handle empty array', () => { + const result = organizeTokensByChainId([]); + + expect(result).toEqual({}); + }); + + it('should skip tokens without chainId', () => { + const tokensWithMissingChainId: TokenI[] = [ + { + address: '0x1', + name: 'Token1', + symbol: 'TK1', + decimals: 18, + balance: '100', + balanceFiat: '1000', + image: 'image1.png', + aggregators: ['agg1'], + logo: 'logo1.png', + isETH: false, + }, + ...mockTokens, + ]; + + const result = organizeTokensByChainId(tokensWithMissingChainId); + + expect(Object.keys(result)).toHaveLength(2); + expect(result['0x1']).toHaveLength(2); + expect(result['0x89']).toHaveLength(1); + }); + + it('should maintain token properties', () => { + const result = organizeTokensByChainId(mockTokens); + const firstToken = result['0x1'][0]; + + expect(firstToken).toEqual(mockTokens[0]); + }); + + it('should handle array with single token', () => { + const singleToken = [mockTokens[0]]; + const result = organizeTokensByChainId(singleToken); + + expect(Object.keys(result)).toHaveLength(1); + expect(result['0x1']).toHaveLength(1); + expect(result['0x1'][0]).toEqual(singleToken[0]); + }); +}); diff --git a/app/components/UI/Tokens/util/organizeTokensByChainId.ts b/app/components/UI/Tokens/util/organizeTokensByChainId.ts new file mode 100644 index 00000000000..e5d041d8bcd --- /dev/null +++ b/app/components/UI/Tokens/util/organizeTokensByChainId.ts @@ -0,0 +1,23 @@ +import { TokenI } from '../types'; + +/** + * Organizes an array of tokens into groups by chainId + * @param tokens Array of tokens to organize + * @returns Object with chainId keys and arrays of tokens as values + */ +export const organizeTokensByChainId = ( + tokens: TokenI[], +): { [chainId: string]: TokenI[] } => + tokens.reduce<{ [chainId: string]: TokenI[] }>((acc, token) => { + if (!token.chainId) { + return acc; + } + + if (!acc[token.chainId]) { + acc[token.chainId] = []; + } + + acc[token.chainId].push(token); + + return acc; + }, {}); diff --git a/app/components/Views/DetectedTokens/components/Token.tsx b/app/components/Views/DetectedTokens/components/Token.tsx index 7f14122fc5b..839c7f7a09b 100644 --- a/app/components/Views/DetectedTokens/components/Token.tsx +++ b/app/components/Views/DetectedTokens/components/Token.tsx @@ -10,19 +10,27 @@ import { fontStyles } from '../../../../styles/common'; import { useDispatch, useSelector } from 'react-redux'; import { showAlert } from '../../../../actions/alert'; import ClipboardManager from '../../../../core/ClipboardManager'; +import { selectChainId } from '../../../../selectors/networkController'; import { balanceToFiat, renderFromTokenMinimalUnit, } from '../../../../util/number'; import { useTheme } from '../../../../util/theme'; import { - selectConversionRate, + selectConversionRateFoAllChains, selectCurrentCurrency, } from '../../../../selectors/currencyRateController'; -import { selectContractExchangeRates } from '../../../../selectors/tokenRatesController'; -import { selectContractBalances } from '../../../../selectors/tokenBalancesController'; +import { selectTokenMarketData } from '../../../../selectors/tokenRatesController'; +import { selectTokensBalances } from '../../../../selectors/tokenBalancesController'; import { Colors } from '../../../../util/theme/models'; import { Hex } from '@metamask/utils'; +import BadgeWrapper 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'; // Replace this interface by importing from TokenRatesController when it exports it interface MarketDataDetails { @@ -109,25 +117,37 @@ const createStyles = (colors: Colors) => }); interface Props { - token: TokenType; + 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 tokenExchangeRates = useSelector(selectContractExchangeRates); - const tokenBalances = useSelector(selectContractBalances); - const conversionRate = useSelector(selectConversionRate); + const tokenExchangeRatesAllChains = useSelector(selectTokenMarketData); + const currentChainId = useSelector(selectChainId); + const tokenExchangeRates = tokenExchangeRatesAllChains[token.chainId]; + const tokenBalancesAllChains = useSelector(selectTokensBalances); + const balanceAllChainsForAccount = + tokenBalancesAllChains[accountAddress as Hex]; + const tokenBalances = + balanceAllChainsForAccount[(token.chainId as Hex) ?? currentChainId]; + const conversionRateByChainId = useSelector(selectConversionRateFoAllChains); + + 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], + tokenBalances[address as Hex], decimals, ); const tokenBalanceWithSymbol = `${ @@ -168,11 +188,32 @@ const Token = ({ token, selected, toggleSelected }: Props) => { return ( - + {process.env.PORTFOLIO_VIEW === 'true' ? ( + + } + > + + + ) : ( + + )} + {tokenBalanceWithSymbol} {fiatBalance ? ( diff --git a/app/components/Views/DetectedTokens/index.tsx b/app/components/Views/DetectedTokens/index.tsx index 4df619a584f..d7c76f170e3 100644 --- a/app/components/Views/DetectedTokens/index.tsx +++ b/app/components/Views/DetectedTokens/index.tsx @@ -1,11 +1,17 @@ // Third party dependencies import React, { useRef, useState, useCallback, useMemo } from 'react'; -import { StyleSheet, View, Text, InteractionManager } from 'react-native'; +import { + StyleSheet, + View, + Text, + InteractionManager, + ViewStyle, +} 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'; @@ -19,16 +25,23 @@ import { useTheme } from '../../../util/theme'; import { getDecimalChainId } from '../../../util/networks'; import { createNavigationDetails } from '../../../util/navigation/navUtils'; import Routes from '../../../constants/navigation/Routes'; -import { selectDetectedTokens } from '../../../selectors/tokensController'; +import { + selectDetectedTokens, + selectAllDetectedTokensFlat, +} from '../../../selectors/tokensController'; import { selectChainId, selectNetworkClientId, + selectNetworkConfigurations, } from '../../../selectors/networkController'; import BottomSheet, { BottomSheetRef, } from '../../../component-library/components/BottomSheets/BottomSheet'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { DetectedTokensSelectorIDs } from '../../../../e2e/selectors/wallet/DetectedTokensView.selectors'; +import { TokenI } from '../../UI/Tokens/types'; +import { selectTokenNetworkFilter } from '../../../selectors/preferencesController'; +import { organizeTokensByChainId } from '../../UI/Tokens/util/organizeTokensByChainId'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -53,9 +66,7 @@ const createStyles = (colors: any) => }, headerLabel: { textAlign: 'center', - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...(fontStyles.normal as any), + ...(fontStyles.normal as ViewStyle), fontSize: 18, paddingVertical: 16, color: colors.text.default, @@ -74,22 +85,42 @@ interface IgnoredTokensByAddress { [address: string]: true; } +const isPortfolioViewEnabled = process.env.PORTFOLIO_VIEW === 'true'; + const DetectedTokens = () => { const navigation = useNavigation(); const { trackEvent } = useMetrics(); const sheetRef = useRef(null); const detectedTokens = useSelector(selectDetectedTokens); + const allDetectedTokens = useSelector( + selectAllDetectedTokensFlat, + ) as (TokenI & { chainId: Hex })[]; const chainId = useSelector(selectChainId); const networkClientId = useSelector(selectNetworkClientId); const [ignoredTokens, setIgnoredTokens] = useState( {}, ); + // TODO: Can probably create "isAllNetworks" selector for these + // since they are re-used in multiple places + const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); // X + const allNetworks = useSelector(selectNetworkConfigurations); // X + const isAllNetworks = + Object.keys(tokenNetworkFilter).length === Object.keys(allNetworks).length; // X + const { colors } = useTheme(); const styles = createStyles(colors); + const currentDetectedTokens = + isPortfolioViewEnabled && isAllNetworks + ? allDetectedTokens + : detectedTokens; + const detectedTokensForAnalytics = useMemo( - () => detectedTokens.map((token) => `${token.symbol} - ${token.address}`), - [detectedTokens], + () => + currentDetectedTokens.map( + (token) => `${token.symbol} - ${token.address}`, + ), + [currentDetectedTokens], ); const dismissModalAndTriggerAction = useCallback( @@ -101,7 +132,7 @@ const DetectedTokens = () => { let description = ''; let errorMsg = ''; const tokensToIgnore: string[] = []; - const tokensToImport = detectedTokens.filter((token) => { + const tokensToImport = currentDetectedTokens.filter((token) => { const isIgnored = ignoreAllTokens || ignoredTokens[token.address]; if (isIgnored) { tokensToIgnore.push(token.address); @@ -134,7 +165,44 @@ const DetectedTokens = () => { tokensToIgnore.length > 0 && (await TokensController.ignoreTokens(tokensToIgnore)); if (tokensToImport.length > 0) { - await TokensController.addTokens(tokensToImport, networkClientId); + if (isPortfolioViewEnabled) { + const tokensByChainId = tokensToImport.reduce>( + (acc, token) => { + const tokenChainId: Hex = + (token as TokenI & { chainId: Hex }).chainId ?? chainId; + + if (!acc.has(tokenChainId)) { + acc.set(tokenChainId, []); + } + + acc.get(tokenChainId)?.push(token); + return acc; + }, + new Map(), + ); + + const importPromises = Array.from(tokensByChainId.entries()).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); + } else { + const tokensByChainId = organizeTokensByChainId( + tokensToImport as TokenI[], + ); + Object.keys(tokensByChainId).forEach(async (chainIdKey) => { + await TokensController.addTokens( + tokensByChainId[chainIdKey as Hex], + chainIdKey, + ); + }); + } InteractionManager.runAfterInteractions(() => tokensToImport.forEach(({ address, symbol }) => trackEvent(MetaMetricsEvents.TOKEN_ADDED, { @@ -157,7 +225,7 @@ const DetectedTokens = () => { } }); }, - [chainId, detectedTokens, ignoredTokens, trackEvent, networkClientId], + [chainId, currentDetectedTokens, ignoredTokens, trackEvent, allNetworks], ); const triggerIgnoreAllTokens = () => { @@ -190,9 +258,11 @@ const DetectedTokens = () => { const renderHeader = () => ( {strings( - `detected_tokens.title${detectedTokens.length > 1 ? '_plural' : ''}`, + `detected_tokens.title${ + currentDetectedTokens.length > 1 ? '_plural' : '' + }`, { - tokenCount: detectedTokens.length, + tokenCount: currentDetectedTokens.length, }, )} @@ -224,7 +294,7 @@ const DetectedTokens = () => { const renderDetectedTokens = () => ( { const renderButtons = () => { const importTokenCount = - detectedTokens.length - Object.keys(ignoredTokens).length; + currentDetectedTokens.length - Object.keys(ignoredTokens).length; return ( { isReadOnly: false, }); + const setTokenNetworkFilter = useCallback((chainId: string) => { + const { PreferencesController } = Engine.context; + PreferencesController.setTokenNetworkFilter({ + [chainId]: true, + }); + }, []); + const onRpcSelect = useCallback( async (clientId: string, chainId: `0x${string}`) => { const { NetworkController } = Engine.context; @@ -237,10 +241,7 @@ const NetworkSelector = () => { const deleteModalSheetRef = useRef(null); const onSetRpcTarget = async (networkConfiguration: NetworkConfiguration) => { - const { - NetworkController, - SelectedNetworkController, - } = Engine.context; + const { NetworkController, SelectedNetworkController } = Engine.context; trace({ name: TraceName.SwitchCustomNetwork, parentContext: parentSpan, @@ -267,7 +268,7 @@ const NetworkSelector = () => { await NetworkController.setActiveNetwork(networkClientId); } - + setTokenNetworkFilter(chainId); sheetRef.current?.onCloseBottomSheet(); endTrace({ name: TraceName.SwitchCustomNetwork }); endTrace({ name: TraceName.NetworkSwitch }); @@ -370,7 +371,6 @@ const NetworkSelector = () => { if (domainIsConnectedDapp && process.env.MULTICHAIN_V1) { SelectedNetworkController.setNetworkClientIdForDomain(origin, type); } else { - const networkConfiguration = networkConfigurations[BUILT_IN_NETWORKS[type].chainId]; @@ -380,6 +380,7 @@ const NetworkSelector = () => { ].networkClientId ?? type; NetworkController.setActiveNetwork(clientId); + setTokenNetworkFilter(networkConfiguration.chainId); closeRpcModal(); AccountTrackerController.refresh(); diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index d06231fc9b9..eedbd170c64 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -54,6 +54,7 @@ import { selectNetworkImageSource, } from '../../../selectors/networkInfos'; import { selectTokens } from '../../../selectors/tokensController'; +import { selectTokenNetworkFilter } from '../../../selectors/preferencesController'; import { NavigationProp, ParamListBase, @@ -303,6 +304,7 @@ const Wallet = ({ const networkName = networkConfigurations?.[chainId]?.name ?? name; const networkImageSource = useSelector(selectNetworkImageSource); + const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); /** * Shows Nft auto detect modal if the user is on mainnet, never saw the modal and have nft detection off */ @@ -325,6 +327,24 @@ const Wallet = ({ }); }, [navigate, providerConfig.chainId, trackEvent]); + /** + * Handle network filter called when app is mounted and tokenNetworkFilter is empty + */ + const handleNetworkFilter = useCallback(() => { + // TODO: Come back possibly just add the chain id of the eth + // network as the default state instead of doing this + const { PreferencesController } = Engine.context; + if (Object.keys(tokenNetworkFilter).length === 0) { + PreferencesController.setTokenNetworkFilter({ + [chainId]: true, + }); + } + }, [chainId, tokenNetworkFilter]); + + useEffect(() => { + handleNetworkFilter(); + }, [chainId, handleNetworkFilter]); + /** * Check to see if notifications are enabled */ @@ -395,7 +415,14 @@ const Wallet = ({ () => { requestAnimationFrame(async () => { const { AccountTrackerController } = Engine.context; - AccountTrackerController.refresh(); + + Object.values(networkConfigurations).forEach( + ({ defaultRpcEndpointIndex, rpcEndpoints }) => { + AccountTrackerController.refresh( + rpcEndpoints[defaultRpcEndpointIndex].networkClientId, + ); + }, + ); }); }, /* eslint-disable-next-line */ @@ -483,7 +510,6 @@ const Wallet = ({ screen: Routes.SHEET.BASIC_FUNCTIONALITY, }); }, [navigation]); - const renderContent = useCallback(() => { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -515,6 +541,7 @@ const Wallet = ({ assets.push(nativeAsset); let stakedAsset; + // TODO: Need to handle staked asset in multi chain if (accountBalanceByChainId.stakedBalance) { stakedBalance = renderFromWei(accountBalanceByChainId.stakedBalance); stakedAsset = { diff --git a/app/components/hooks/AssetPolling/AssetPollingProvider.tsx b/app/components/hooks/AssetPolling/AssetPollingProvider.tsx index 7c7c3eda5fa..4315d97d2da 100644 --- a/app/components/hooks/AssetPolling/AssetPollingProvider.tsx +++ b/app/components/hooks/AssetPolling/AssetPollingProvider.tsx @@ -1,11 +1,19 @@ import React, { ReactNode } from 'react'; import useCurrencyRatePolling from './useCurrencyRatePolling'; +import useTokenRatesPolling from './useTokenRatesPolling'; +import useTokenBalancesPolling from './useTokenBalancesPolling'; +import useTokenListPolling from './useTokenListPolling'; +import useTokenDetectionPolling from './useTokenAutoDetectionPolling'; // This provider is a step towards making controller polling fully UI based. // Eventually, individual UI components will call the use*Polling hooks to // poll and return particular data. This polls globally in the meantime. export const AssetPollingProvider = ({ children }: { children: ReactNode }) => { useCurrencyRatePolling(); + useTokenRatesPolling(); + useTokenListPolling(); + useTokenBalancesPolling(); + useTokenDetectionPolling(); return <>{children}; }; diff --git a/app/components/hooks/AssetPolling/useTokenAutoDetectionPolling.ts b/app/components/hooks/AssetPolling/useTokenAutoDetectionPolling.ts new file mode 100644 index 00000000000..94082565811 --- /dev/null +++ b/app/components/hooks/AssetPolling/useTokenAutoDetectionPolling.ts @@ -0,0 +1,49 @@ +import { useSelector } from 'react-redux'; +import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import { selectUseTokenDetection } from '../../../selectors/preferencesController'; +import { selectAllDetectedTokensForSelectedAddress } from '../../../selectors/tokensController'; +import usePolling from '../usePolling'; +import Engine from '../../../core/Engine'; +import { selectSelectedInternalAccountAddress } from '../../../selectors/accountsController'; +import { Hex } from '@metamask/utils'; + +type TokenDetectionPollingInput = { + chainIds: Hex[]; + address: string; +}; + +const useTokenDetectionPolling = () => { + const networkConfigurations = useSelector(selectNetworkConfigurations); + const selectedAddress = useSelector(selectSelectedInternalAccountAddress); + + const useTokenDetection = useSelector(selectUseTokenDetection); + const chainIds = Object.keys(networkConfigurations); + + const detectedTokens = useSelector(selectAllDetectedTokensForSelectedAddress); + + const { TokenDetectionController } = Engine.context; + + const pollingInput: TokenDetectionPollingInput[] = [ + { + chainIds: chainIds as Hex[], + address: selectedAddress ?? '', + }, + ]; + + usePolling({ + startPolling: TokenDetectionController.startPolling.bind( + TokenDetectionController, + ), + stopPollingByPollingToken: + TokenDetectionController.stopPollingByPollingToken.bind( + TokenDetectionController, + ), + input: useTokenDetection ? pollingInput : [{ chainIds: [], address: '' }], + }); + + return { + detectedTokens, + }; +}; + +export default useTokenDetectionPolling; diff --git a/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts b/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts new file mode 100644 index 00000000000..306e9ecff4b --- /dev/null +++ b/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts @@ -0,0 +1,35 @@ +import { useSelector } from 'react-redux'; +import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import Engine from '../../../core/Engine'; +import usePolling from '../usePolling'; +import { selectTokensBalances } from '../../../selectors/tokenBalancesController'; + +const useTokenBalancesPolling = () => { + // Selectors to determine polling input + const networkConfigurations = useSelector(selectNetworkConfigurations); + + const tokensBalances = useSelector(selectTokensBalances); + + const input = Object.values(networkConfigurations).map(({ chainId }) => ({ + chainId, + })); + + const { TokenBalancesController } = Engine.context; + + usePolling({ + startPolling: TokenBalancesController.startPolling.bind( + TokenBalancesController, + ), + stopPollingByPollingToken: + TokenBalancesController.stopPollingByPollingToken.bind( + TokenBalancesController, + ), + input, + }); + + return { + tokensBalances, + }; +}; + +export default useTokenBalancesPolling; diff --git a/app/components/hooks/AssetPolling/useTokenListPolling.ts b/app/components/hooks/AssetPolling/useTokenListPolling.ts new file mode 100644 index 00000000000..0b8df82ebce --- /dev/null +++ b/app/components/hooks/AssetPolling/useTokenListPolling.ts @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; +import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import Engine from '../../../core/Engine'; +import usePolling from '../usePolling'; +import { selectTokenList } from '../../../selectors/tokenListController'; + +const useTokenListPolling = () => { + // Selectors to determine polling input + const networkConfigurations = useSelector(selectNetworkConfigurations); + + const tokenList = useSelector(selectTokenList); + + const input = Object.values(networkConfigurations).map(({ chainId }) => ({ + chainId, + })); + + const { TokenListController } = Engine.context; + + usePolling({ + startPolling: TokenListController.startPolling.bind(TokenListController), + stopPollingByPollingToken: + TokenListController.stopPollingByPollingToken.bind(TokenListController), + input, + }); + + return { + tokenList, + }; +}; + +export default useTokenListPolling; diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts new file mode 100644 index 00000000000..d4182f16518 --- /dev/null +++ b/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts @@ -0,0 +1,63 @@ +import useTokenRatesPolling from './useTokenRatesPolling'; +import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; +import Engine from '../../../core/Engine'; + +jest.mock('../../../core/Engine', () => ({ + context: { + TokenRatesController: { + startPolling: jest.fn(), + stopPollingByPollingToken: jest.fn(), + }, + }, +})); + +describe('useTokenRatesPolling', () => { + + beforeEach(() => { + jest.resetAllMocks(); + }); + + const state = { + engine: { + backgroundState: { + TokenRatesController: { + marketData: {}, + }, + NetworkController: { + networkConfigurationsByChainId: { + '0x1': {}, + '0x89': {}, + }, + }, + }, + }, + }; + + it('Should poll by provided chain ids', async () => { + + renderHookWithProvider(() => useTokenRatesPolling({chainIds: ['0x1']}), {state}); + + const mockedTokenRatesController = jest.mocked(Engine.context.TokenRatesController); + + expect(mockedTokenRatesController.startPolling).toHaveBeenCalledTimes(1); + expect( + mockedTokenRatesController.startPolling + ).toHaveBeenCalledWith({chainId: '0x1'}); + + }); + + it('Should poll by all chain ids in network state, if no chain ids are explicitly provided', async () => { + + renderHookWithProvider(() => useTokenRatesPolling(), {state}); + + const mockedTokenRatesController = jest.mocked(Engine.context.TokenRatesController); + + expect(mockedTokenRatesController.startPolling).toHaveBeenCalledTimes(2); + expect( + mockedTokenRatesController.startPolling + ).toHaveBeenCalledWith({chainId: '0x1'}); + expect( + mockedTokenRatesController.startPolling + ).toHaveBeenCalledWith({chainId: '0x89'}); + }); +}); diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.ts new file mode 100644 index 00000000000..8f89aa0958a --- /dev/null +++ b/app/components/hooks/AssetPolling/useTokenRatesPolling.ts @@ -0,0 +1,36 @@ +import { useSelector } from 'react-redux'; +import usePolling from '../usePolling'; +import Engine from '../../../core/Engine'; +import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import { Hex } from '@metamask/utils'; +import { + selectContractExchangeRates, + selectTokenMarketData, +} from '../../../selectors/tokenRatesController'; + +const useTokenRatesPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { + // Selectors to determine polling input + const networkConfigurations = useSelector(selectNetworkConfigurations); + + // Selectors returning state updated by the polling + const contractExchangeRates = useSelector(selectContractExchangeRates); + const tokenMarketData = useSelector(selectTokenMarketData); + + const { TokenRatesController } = Engine.context; + + usePolling({ + startPolling: TokenRatesController.startPolling.bind(TokenRatesController), + stopPollingByPollingToken: + TokenRatesController.stopPollingByPollingToken.bind(TokenRatesController), + input: (chainIds ?? Object.keys(networkConfigurations)).map((chainId) => ({ + chainId: chainId as Hex, + })), + }); + + return { + contractExchangeRates, + tokenMarketData, + }; +}; + +export default useTokenRatesPolling; diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 55d6fdbf2e3..2920cacaaf3 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1542,6 +1542,8 @@ export class Engine { assetsContractController.getBalancesInSingleCall.bind( assetsContractController, ), + platform: 'mobile', + useAccountsAPI: true, }), new NftDetectionController({ @@ -1572,15 +1574,22 @@ export class Engine { name: 'TokenBalancesController', allowedActions: [ 'AccountsController:getSelectedAccount', - 'AssetsContractController:getERC20BalanceOf', + 'NetworkController:getState', + 'NetworkController:getNetworkClientById', + 'PreferencesController:getState', + 'TokensController:getState', + ], + allowedEvents: [ + 'TokensController:stateChange', + 'NetworkController:stateChange', + 'PreferencesController:stateChange', ], - allowedEvents: ['TokensController:stateChange'], }), interval: 180000, - tokens: [ - ...tokensController.state.tokens, - ...tokensController.state.detectedTokens, - ], + // tokens: [ + // ...tokensController.state.tokens, + // ...tokensController.state.detectedTokens, + // ], state: initialState.TokenBalancesController, }), new TokenRatesController({ @@ -1793,7 +1802,7 @@ export class Engine { this.configureControllersOnNetworkChange(); this.startPolling(); - this.handleVaultBackup(); + // this.handleVaultBackup(); this._addTransactionControllerListeners(); Engine.instance = this; @@ -1908,14 +1917,12 @@ export class Engine { TokenDetectionController, TokenListController, TransactionController, - TokenRatesController, } = this.context; TokenListController.start(); TokenDetectionController.start(); // leaving the reference of TransactionController here, rather than importing it from utils to avoid circular dependency TransactionController.startIncomingTransactionPolling(); - TokenRatesController.start(); } configureControllersOnNetworkChange() { @@ -1991,13 +1998,19 @@ export class Engine { selectSelectedInternalAccountChecksummedAddress ] ) { - const balanceBN = hexToBN(accountsByChainId[toHexadecimal(chainId)][ - selectSelectedInternalAccountChecksummedAddress - ].balance); - const stakedBalanceBN = hexToBN(accountsByChainId[toHexadecimal(chainId)][ - selectSelectedInternalAccountChecksummedAddress - ].stakedBalance || '0x00'); - const totalAccountBalance = balanceBN.add(stakedBalanceBN).toString('hex'); + const balanceBN = hexToBN( + accountsByChainId[toHexadecimal(chainId)][ + selectSelectedInternalAccountChecksummedAddress + ].balance, + ); + const stakedBalanceBN = hexToBN( + accountsByChainId[toHexadecimal(chainId)][ + selectSelectedInternalAccountChecksummedAddress + ].stakedBalance || '0x00', + ); + const totalAccountBalance = balanceBN + .add(stakedBalanceBN) + .toString('hex'); ethFiat = weiToFiatNumber( totalAccountBalance, conversionRate, @@ -2014,8 +2027,23 @@ export class Engine { : ethFiat; if (tokens.length > 0) { - const { contractBalances: tokenBalances } = + const { tokenBalances: contractBalances } = TokenBalancesController.state; + + const selectedNetworkClientId = + NetworkController.state.selectedNetworkClientId; + const chainId = NetworkController.getNetworkClientById( + selectedNetworkClientId, + ).configuration.chainId; + const selectedInternalAccount = AccountsController.getAccount( + AccountsController.state.internalAccounts.selectedAccount, + ); + + const tokenBalances = + contractBalances?.[selectedInternalAccount?.address as Hex]?.[ + chainId + ] ?? {}; + tokens.forEach( (item: { address: string; balance?: string; decimals: number }) => { const exchangeRate = @@ -2025,7 +2053,7 @@ export class Engine { item.balance || (item.address in tokenBalances ? renderFromTokenMinimalUnit( - tokenBalances[item.address], + tokenBalances[item.address as Hex], item.decimals, ) : undefined); @@ -2103,18 +2131,32 @@ export class Engine { const { engine: { backgroundState }, } = store.getState(); + const { NetworkController, AccountsController } = this.context; + // TODO: Check `allNfts[currentChainId]` property instead // @ts-expect-error This property does not exist const nfts = backgroundState.NftController.nfts; const tokens = backgroundState.TokensController.tokens; + + const selectedNetworkClientId = + backgroundState.NetworkController.selectedNetworkClientId; + const chainId = NetworkController.getNetworkClientById( + selectedNetworkClientId, + ).configuration.chainId; + const selectedInternalAccount = AccountsController.getAccount( + AccountsController.state.internalAccounts.selectedAccount, + ); + const tokenBalances = - backgroundState.TokenBalancesController.contractBalances; + backgroundState.TokenBalancesController.tokenBalances?.[ + selectedInternalAccount?.address as Hex + ]?.[chainId]; let tokenFound = false; tokens.forEach((token: { address: string | number }) => { if ( - tokenBalances[token.address] && - !isZero(tokenBalances[token.address]) + tokenBalances[token.address as Hex] && + !isZero(tokenBalances[token.address as Hex]) ) { tokenFound = true; } @@ -2158,11 +2200,11 @@ export class Engine { // SelectedNetworkController.unsetAllDomains() //Clear assets info - TokensController.reset(); - NftController.reset(); + TokensController.resetState(); + NftController.resetState(); - TokenBalancesController.reset(); - TokenRatesController.reset(); + TokenBalancesController.resetState(); + TokenRatesController.resetState(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (TransactionController as any).update(() => ({ diff --git a/app/selectors/currencyRateController.ts b/app/selectors/currencyRateController.ts index 9617d02710d..92fd7ba4e08 100644 --- a/app/selectors/currencyRateController.ts +++ b/app/selectors/currencyRateController.ts @@ -27,6 +27,12 @@ export const selectConversionRate = createSelector( }, ); +export const selectConversionRateFoAllChains = createSelector( + selectCurrencyRateControllerState, + (currencyRateControllerState: CurrencyRateState) => + currencyRateControllerState?.currencyRates, +); + export const selectCurrentCurrency = createSelector( selectCurrencyRateControllerState, selectTicker, @@ -37,7 +43,16 @@ export const selectCurrentCurrency = createSelector( export const selectCurrencyRates = createSelector( selectCurrencyRateControllerState, - ( - currencyRateControllerState: CurrencyRateState, - ) => currencyRateControllerState?.currencyRates, + (currencyRateControllerState: CurrencyRateState) => + currencyRateControllerState?.currencyRates, +); + +export const selectConversionRateByTicker = createSelector( + selectCurrencyRateControllerState, + (_: RootState, ticker: string) => ticker, + (currencyRateControllerState: CurrencyRateState, ticker: string) => + ticker + ? currencyRateControllerState?.currencyRates?.[ticker]?.conversionRate || + 0 + : 0, ); diff --git a/app/selectors/multichain.test.ts b/app/selectors/multichain.test.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/selectors/multichain.ts b/app/selectors/multichain.ts new file mode 100644 index 00000000000..3a15bbb063a --- /dev/null +++ b/app/selectors/multichain.ts @@ -0,0 +1,234 @@ +import { createSelector } from 'reselect'; +import { Hex } from '@metamask/utils'; +import { zeroAddress } from 'ethereumjs-util'; +import { RootState } from '../reducers'; +import I18n from '../../locales/i18n'; +import { + selectSelectedInternalAccountChecksummedAddress, + selectSelectedInternalAccount, +} from './accountsController'; +import { selectAllTokens } from './tokensController'; +import { selectTokensBalances } from './tokenBalancesController'; +import { selectAccountsByChainId } from './accountTrackerController'; +import { selectNetworkConfigurations } from './networkController'; +import { selectTokenMarketData as selectMarketData } from './tokenRatesController'; +import { + selectCurrentCurrency, + selectConversionRateByTicker, +} from './currencyRateController'; + +import { AssetType } from '../components/UI/SimulationDetails/types'; +import { TokenI } from '../components/UI/Tokens/types'; + +import { getTicker } from '../util/transactions'; +import { + renderFromWei, + renderFromTokenMinimalUnit, + weiToFiat, + hexToBN, +} from '../util/number'; +import { toHex } from '@metamask/controller-utils'; + +interface AccountInfo { + balance: string; +} + +interface ChainAccounts { + [address: string]: AccountInfo; +} + +interface AccountsByChainId { + [chainId: string]: ChainAccounts; +} + +export function getNativeTokenInfo(state: RootState, chainId: Hex) { + const networkConfigurationsByChainId = selectNetworkConfigurations(state); + const networkConfig = networkConfigurationsByChainId?.[chainId]; + + if (networkConfig) { + const symbol = networkConfig.nativeCurrency || AssetType.Native; + const decimals = 18; + const name = networkConfig.name || 'Native Token'; + + return { + symbol, + decimals, + name, + }; + } +} + +export function getSelectedAccountNativeTokenCachedBalanceByChainId( + state: RootState, +) { + const selectedAddress = + selectSelectedInternalAccountChecksummedAddress(state); + const accountsByChainId = selectAccountsByChainId(state); + + const balancesByChainId: { [chainId: string]: string } = {}; + + if (!selectedAddress) { + return balancesByChainId; + } + + for (const [chainId, accounts] of Object.entries( + accountsByChainId || ({} as AccountsByChainId), + )) { + if (accounts[selectedAddress]) { + balancesByChainId[chainId] = accounts[selectedAddress].balance; + } + } + + return balancesByChainId; +} + +export const selectAccountTokensAcrossChains = createSelector( + [ + selectSelectedInternalAccount, + selectAllTokens, + selectTokensBalances, + selectNetworkConfigurations, + getSelectedAccountNativeTokenCachedBalanceByChainId, + selectMarketData, + selectCurrentCurrency, + (state: RootState) => state, + ], + ( + selectedAccount, + allTokens, + tokenBalances, + networkConfigurations, + nativeTokenBalancesByChainId, + tokenExchangeRates, + currentCurrency, + state, + ) => { + const selectedAddress = selectedAccount?.address; + const tokensByChain: { [chainId: string]: TokenI[] } = {}; + + if (!selectedAddress) { + return tokensByChain; + } + + // Create a list of available chainIds + const chainIds = Object.keys(networkConfigurations); + + Array.from(chainIds).forEach((chainId) => { + const currentChainId = chainId as Hex; + tokensByChain[currentChainId] = []; + + // Add non-native tokens + const userTokens = (allTokens[currentChainId]?.[selectedAddress] || + []) as TokenI[]; + const ticker = networkConfigurations?.[chainId as Hex].nativeCurrency; + const conversionRateByTicker = selectConversionRateByTicker( + state, + ticker, + ); + const chainBalances = + tokenBalances[selectedAddress as Hex]?.[currentChainId] || {}; + const tokenExchangeRateByChainId = tokenExchangeRates[chainId as Hex]; + + // Add non-native tokens if they exist for this chain + tokensByChain[currentChainId] = userTokens.map((token) => { + const tokenAddress = token.address as Hex; + const tokenExchangeRatePriceByTokenAddress = + // TODO: Some exchange rates for some tokens don't exist? Is this expected? + tokenExchangeRateByChainId[tokenAddress]?.price || 0; + + // Calculate token balance + const tokenBalance = renderFromTokenMinimalUnit( + chainBalances[token.address as Hex] || '0x0', + token.decimals || 18, + ); + + // Remove any non-numeric characters except decimal point e.g. < 0.00001 + const cleanTokenBalance = tokenBalance.replace(/[^0-9.]/g, ''); + const floatTokenBalance = parseFloat(cleanTokenBalance); + + const adjustedTokenBalance = tokenBalance.startsWith('<') + ? 0.00001 + : floatTokenBalance; + + // Format token balance in fiat + const tokenFiatAmount = + tokenExchangeRatePriceByTokenAddress * + conversionRateByTicker * + adjustedTokenBalance; + const balanceFiat = new Intl.NumberFormat(I18n.locale, { + currency: currentCurrency.toUpperCase(), + style: 'currency', + }).format(tokenFiatAmount); + + return { + ...token, + balance: tokenBalance, + chainId, + balanceFiat, + logo: token.image, + isETH: false, + isNative: false, + symbol: getTicker(ticker), + }; + }); + + // Add native token if it exists for this chain + const nativeBalance = nativeTokenBalancesByChainId[chainId]; + if (nativeBalance && nativeBalance !== toHex(0)) { + const nativeTokenInfo = getNativeTokenInfo(state, chainId as Hex); + + // Calculate native token balance + const nativeBalanceFormatted = renderFromWei(nativeBalance); + const isETH = ['ETH', 'GOETH', 'SepoliaETH', 'LineaETH'].includes( + nativeTokenInfo?.symbol || '', + ); + + // Remove any non-numeric characters except decimal point e.g. < 0.00001 + const cleanNativeBalance = nativeBalanceFormatted.replace( + /[^0-9.]/g, + '', + ); + const floatNativeBalance = parseFloat(cleanNativeBalance); + + let nativeBalanceFiat = ''; + + // calculate balance in fiat depending on the token + if (isETH) { + nativeBalanceFiat = weiToFiat( + hexToBN(nativeBalance), + conversionRateByTicker, + currentCurrency, + ); + } else { + const tokenFiatAmount = floatNativeBalance * conversionRateByTicker; + nativeBalanceFiat = new Intl.NumberFormat(I18n.locale, { + currency: currentCurrency.toUpperCase(), + style: 'currency', + }).format(tokenFiatAmount); + } + tokensByChain[chainId].push({ + ...nativeTokenInfo, + // TODO: Check if this is correct for native tokens + // or do we need the wrapped native token address? + // for example the AVAX token has + // 0x0000000000000000000000000000000000000000 on its network + // but the wrapped version is 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7 + address: zeroAddress() as Hex, + balance: nativeBalanceFormatted, + balanceFiat: nativeBalanceFiat, + chainId, + isNative: true, + aggregators: [], + image: '', + logo: isETH ? '../images/eth-logo-new.png' : '', + isETH, + decimals: nativeTokenInfo?.decimals || 18, + name: isETH ? `Ethereum` : ticker, + symbol: getTicker(nativeTokenInfo?.symbol), + }); + } + }); + + return tokensByChain; + }, +); diff --git a/app/selectors/networkController.ts b/app/selectors/networkController.ts index de8b46c43a9..d46e2a64e28 100644 --- a/app/selectors/networkController.ts +++ b/app/selectors/networkController.ts @@ -148,7 +148,7 @@ export const selectNetworkStatus = createSelector( export const selectNetworkConfigurations = createSelector( selectNetworkControllerState, (networkControllerState: NetworkState) => - networkControllerState.networkConfigurationsByChainId, + networkControllerState?.networkConfigurationsByChainId, ); export const selectNetworkClientId = createSelector( diff --git a/app/selectors/tokenBalancesController.ts b/app/selectors/tokenBalancesController.ts index 4d8005022be..cec4be433da 100644 --- a/app/selectors/tokenBalancesController.ts +++ b/app/selectors/tokenBalancesController.ts @@ -2,12 +2,28 @@ import { createSelector } from 'reselect'; import { RootState } from '../reducers'; import { TokenBalancesControllerState } from '@metamask/assets-controllers'; +import { selectChainId } from './networkController'; +import { selectSelectedInternalAccountAddress } from './accountsController'; const selectTokenBalancesControllerState = (state: RootState) => state.engine.backgroundState.TokenBalancesController; -export const selectContractBalances = createSelector( +export const selectTokensBalances = createSelector( selectTokenBalancesControllerState, (tokenBalancesControllerState: TokenBalancesControllerState) => - tokenBalancesControllerState.contractBalances, + tokenBalancesControllerState.tokenBalances, +); + +export const selectContractBalances = createSelector( + selectTokenBalancesControllerState, + selectChainId, + selectSelectedInternalAccountAddress, + ( + tokenBalancesControllerState: TokenBalancesControllerState, + chainId: string, + address, + ) => + tokenBalancesControllerState.tokenBalances?.[address as `0x${string}`]?.[ + chainId as `0x${string}` + ] ?? {}, ); diff --git a/app/selectors/tokenRatesController.ts b/app/selectors/tokenRatesController.ts index e41d78a0dd3..995e5988fd4 100644 --- a/app/selectors/tokenRatesController.ts +++ b/app/selectors/tokenRatesController.ts @@ -14,3 +14,9 @@ export const selectContractExchangeRates = createSelector( (chainId: Hex, tokenRatesControllerState: TokenRatesControllerState) => tokenRatesControllerState.marketData[chainId], ); + +export const selectTokenMarketData = createSelector( + selectTokenRatesControllerState, + (tokenRatesControllerState: TokenRatesControllerState) => + tokenRatesControllerState.marketData, +); diff --git a/app/selectors/tokensController.ts b/app/selectors/tokensController.ts index ccebe64ebfb..5bf543f8b27 100644 --- a/app/selectors/tokensController.ts +++ b/app/selectors/tokensController.ts @@ -2,6 +2,7 @@ import { createSelector } from 'reselect'; import { TokensControllerState, Token } from '@metamask/assets-controllers'; import { RootState } from '../reducers'; import { createDeepEqualSelector } from './util'; +import { selectSelectedInternalAccountAddress } from './accountsController'; const selectTokensControllerState = (state: RootState) => state?.engine?.backgroundState?.TokensController; @@ -38,23 +39,75 @@ export const selectDetectedTokens = createSelector( tokensControllerState?.detectedTokens, ); -const selectAllTokens = createSelector( +export const selectAllTokens = createSelector( selectTokensControllerState, (tokensControllerState: TokensControllerState) => tokensControllerState?.allTokens, ); +export const selectAllDetectedTokensForSelectedAddress = createSelector( + selectTokensControllerState, + selectSelectedInternalAccountAddress, + ( + tokensControllerState: TokensControllerState, + selectedAddress: string | null, + ) => { + // Updated return type to specify the structure more clearly + if (!selectedAddress) { + return {} as { [chainId: string]: 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; + }, {}); + }, +); + +// TODO: This isn't working fully, once a network has been selected then it +// can detect all tokens in that network. But by default it only shows +// detected tokens if the user has chosen it in the past +export const selectAllDetectedTokensFlat = createSelector( + selectAllDetectedTokensForSelectedAddress, + (detectedTokensByChain: { [chainId: string]: Token[] }) => { + // Updated type here + if (Object.keys(detectedTokensByChain).length === 0) { + return []; + } + + return Object.entries(detectedTokensByChain).reduce( + (acc, [_, addressTokens]) => { + const tokensForChain = Object.values(addressTokens).flat(); + return acc.concat(tokensForChain); + }, + [], + ); + }, +); + export const selectAllTokensFlat = createSelector( selectAllTokens, - (tokensByAccountByChain) => { + (tokensByAccountByChain: { + [account: string]: { [chainId: string]: Token[] }; + }): Token[] => { if (Object.values(tokensByAccountByChain).length === 0) { return []; } const tokensByAccountArray = Object.values(tokensByAccountByChain); - return tokensByAccountArray.reduce((acc, tokensByAccount) => { - const tokensArray = Object.values(tokensByAccount); + return tokensByAccountArray.reduce((acc, tokensByAccount) => { + const tokensArray = Object.values(tokensByAccount).flat(); return acc.concat(...tokensArray); - }, [] as Token[]); + }, []); }, ); diff --git a/app/util/networks/customNetworks.tsx b/app/util/networks/customNetworks.tsx index fe301ceae68..e0c7dc94a5c 100644 --- a/app/util/networks/customNetworks.tsx +++ b/app/util/networks/customNetworks.tsx @@ -1,4 +1,6 @@ +import { Hex } from '@metamask/utils'; import { toHex } from '@metamask/controller-utils'; +import { CHAIN_IDS } from '@metamask/transaction-controller'; /* eslint-disable @typescript-eslint/no-require-imports, import/no-commonjs */ const InfuraKey = process.env.MM_INFURA_PROJECT_ID; @@ -130,10 +132,29 @@ export const UnpopularNetworkList = [ }, ]; -export const CustomNetworkImgMapping: Record<`0x${string}`, string> = { - '0xe': require('../../images/flare-mainnet.png'), // Flare Mainnet - '0x13': require('../../images/songbird.png'), // Songbird Testnet - '0x8157': require('../../images/ape-network.png'), // ApeChain testnet - '0x8173': require('../../images/ape-network.png'), // ApeChain mainnet - '0x659': require('../../images/gravity.png'), // Gravity Alpha Mainnet +export const NETWORK_CHAIN_ID: { + readonly FLARE_MAINNET: '0xe'; + readonly SONGBIRD_TESTNET: '0x13'; + readonly APE_CHAIN_TESTNET: '0x8157'; + readonly APE_CHAIN_MAINNET: '0x8173'; + readonly GRAVITY_ALPHA_MAINNET: '0x659'; +} & typeof CHAIN_IDS = { + FLARE_MAINNET: '0xe', + SONGBIRD_TESTNET: '0x13', + APE_CHAIN_TESTNET: '0x8157', + APE_CHAIN_MAINNET: '0x8173', + GRAVITY_ALPHA_MAINNET: '0x659', + ...CHAIN_IDS, }; + +/* eslint-disable @typescript-eslint/no-require-imports, import/no-commonjs */ +export const CustomNetworkImgMapping: Record = { + [NETWORK_CHAIN_ID.FLARE_MAINNET]: require('../../images/flare-mainnet.png'), + [NETWORK_CHAIN_ID.SONGBIRD_TESTNET]: require('../../images/songbird.png'), + [NETWORK_CHAIN_ID.APE_CHAIN_TESTNET]: require('../../images/ape-network.png'), + [NETWORK_CHAIN_ID.APE_CHAIN_MAINNET]: require('../../images/ape-network.png'), + [NETWORK_CHAIN_ID.GRAVITY_ALPHA_MAINNET]: require('../../images/gravity.png'), + [NETWORK_CHAIN_ID.LINEA_MAINNET]: require('../../images/linea-mainnet-logo.png'), +}; + + diff --git a/package.json b/package.json index a92eeba5f85..f81484675f4 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "@metamask/accounts-controller": "^18.2.1", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.1.0", - "@metamask/assets-controllers": "^41.0.0", + "@metamask/assets-controllers": "^43.1.1", "@metamask/base-controller": "^7.0.1", "@metamask/composable-controller": "^3.0.0", "@metamask/contract-metadata": "^2.1.0", diff --git a/patches/@metamask+assets-controllers+41.0.0.patch b/patches/@metamask+assets-controllers+41.0.0.patch deleted file mode 100644 index 30bedc1ce68..00000000000 --- a/patches/@metamask+assets-controllers+41.0.0.patch +++ /dev/null @@ -1,1081 +0,0 @@ -diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs -index aac84e5..983c3f1 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.cjs -@@ -1,19 +1,19 @@ - "use strict"; --var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { -- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); -- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); -- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); --}; - var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { - if (kind === "m") throw new TypeError("Private method is not writable"); - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); - return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; - }; -+var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { -+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); -+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); -+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); -+}; - var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; - }; --var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; -+var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.AccountTrackerController = void 0; - const controller_utils_1 = require("@metamask/controller-utils"); -@@ -47,8 +47,10 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo - * @param options.interval - Polling interval used to fetch new account balances. - * @param options.state - Initial state to set on this controller. - * @param options.messenger - The controller messaging system. -+ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. -+ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. - */ -- constructor({ interval = 10000, state, messenger, }) { -+ constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) { - const { selectedNetworkClientId } = messenger.call('NetworkController:getState'); - const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - super({ -@@ -65,7 +67,11 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo - }); - _AccountTrackerController_instances.add(this); - _AccountTrackerController_refreshMutex.set(this, new async_mutex_1.Mutex()); -+ _AccountTrackerController_includeStakedAssets.set(this, void 0); -+ _AccountTrackerController_getStakedBalanceForChain.set(this, void 0); - _AccountTrackerController_handle.set(this, void 0); -+ __classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f"); -+ __classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f"); - this.setIntervalLength(interval); - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises -@@ -171,6 +177,15 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo - balance, - }; - } -+ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { -+ const stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); -+ if (stakedBalance) { -+ accountsForChain[address] = { -+ ...accountsForChain[address], -+ stakedBalance, -+ }; -+ } -+ } - } - this.update((state) => { - if (chainId === __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getCurrentChainId).call(this)) { -@@ -196,18 +211,23 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo - return (0, controller_utils_1.safelyExecuteWithTimeout)(async () => { - (0, utils_1.assert)(ethQuery, 'Provider not set.'); - const balance = await (0, controller_utils_1.query)(ethQuery, 'getBalance', [address]); -- return [address, balance]; -+ let stakedBalance; -+ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { -+ stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); -+ } -+ return [address, balance, stakedBalance]; - }); - })).then((value) => { - return value.reduce((obj, item) => { - if (!item) { - return obj; - } -- const [address, balance] = item; -+ const [address, balance, stakedBalance] = item; - return { - ...obj, - [address]: { - balance, -+ stakedBalance, - }, - }; - }, {}); -@@ -215,7 +235,7 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo - } - } - exports.AccountTrackerController = AccountTrackerController; --_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { -+_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { - const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState'); - const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - return chainId; -diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts -index 144e018..6b7d8cd 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.cts -@@ -2,6 +2,7 @@ import type { AccountsControllerSelectedEvmAccountChangeEvent, AccountsControlle - import type { ControllerStateChangeEvent, ControllerGetStateAction, RestrictedControllerMessenger } from "@metamask/base-controller"; - import type { NetworkClientId, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller"; - import type { PreferencesControllerGetStateAction } from "@metamask/preferences-controller"; -+import type { AssetsContractController, StakedBalance } from "./AssetsContractController.cjs"; - /** - * The name of the {@link AccountTrackerController}. - */ -@@ -10,10 +11,12 @@ declare const controllerName = "AccountTrackerController"; - * @type AccountInformation - * - * Account information object -- * @property balance - Hex string of an account balancec in wei -+ * @property balance - Hex string of an account balance in wei -+ * @property stakedBalance - Hex string of an account staked balance in wei - */ - export type AccountInformation = { - balance: string; -+ stakedBalance?: string; - }; - /** - * @type AccountTrackerControllerState -@@ -62,18 +65,15 @@ type AccountTrackerPollingInput = { - networkClientId: NetworkClientId; - }; - declare const AccountTrackerController_base: (abstract new (...args: any[]) => { -- readonly "__#787890@#intervalIds": Record; -- "__#787890@#intervalLength": number | undefined; -+ readonly "__#784968@#intervalIds": Record; -+ "__#784968@#intervalLength": number | undefined; - setIntervalLength(intervalLength: number): void; - getIntervalLength(): number | undefined; - _startPolling(input: AccountTrackerPollingInput): void; - _stopPollingByPollingTokenSetId(key: string): void; -- readonly "__#787882@#pollingTokenSets": Map>; -- "__#787882@#callbacks": Map void>>; -+ readonly "__#784960@#pollingTokenSets": Map>; -+ "__#784960@#callbacks": Map void>>; - _executePoll(input: AccountTrackerPollingInput): Promise; -- /** -- * The action that can be performed to get the state of the {@link AccountTrackerController}. -- */ - startPolling(input: AccountTrackerPollingInput): string; - stopAllPolling(): void; - stopPollingByPollingToken(pollingToken: string): void; -@@ -91,11 +91,15 @@ export declare class AccountTrackerController extends AccountTrackerController_b - * @param options.interval - Polling interval used to fetch new account balances. - * @param options.state - Initial state to set on this controller. - * @param options.messenger - The controller messaging system. -+ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. -+ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. - */ -- constructor({ interval, state, messenger, }: { -+ constructor({ interval, state, messenger, getStakedBalanceForChain, includeStakedAssets, }: { - interval?: number; - state?: Partial; - messenger: AccountTrackerControllerMessenger; -+ getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain']; -+ includeStakedAssets?: boolean; - }); - private syncAccounts; - /** -@@ -128,6 +132,7 @@ export declare class AccountTrackerController extends AccountTrackerController_b - */ - syncBalanceWithAddresses(addresses: string[], networkClientId?: NetworkClientId): Promise>; - } - export default AccountTrackerController; -diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts -index b25212c..20d566f 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts -+++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.d.mts -@@ -2,6 +2,7 @@ import type { AccountsControllerSelectedEvmAccountChangeEvent, AccountsControlle - import type { ControllerStateChangeEvent, ControllerGetStateAction, RestrictedControllerMessenger } from "@metamask/base-controller"; - import type { NetworkClientId, NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller"; - import type { PreferencesControllerGetStateAction } from "@metamask/preferences-controller"; -+import type { AssetsContractController, StakedBalance } from "./AssetsContractController.mjs"; - /** - * The name of the {@link AccountTrackerController}. - */ -@@ -10,10 +11,12 @@ declare const controllerName = "AccountTrackerController"; - * @type AccountInformation - * - * Account information object -- * @property balance - Hex string of an account balancec in wei -+ * @property balance - Hex string of an account balance in wei -+ * @property stakedBalance - Hex string of an account staked balance in wei - */ - export type AccountInformation = { - balance: string; -+ stakedBalance?: string; - }; - /** - * @type AccountTrackerControllerState -@@ -62,18 +65,15 @@ type AccountTrackerPollingInput = { - networkClientId: NetworkClientId; - }; - declare const AccountTrackerController_base: (abstract new (...args: any[]) => { -- readonly "__#787890@#intervalIds": Record; -- "__#787890@#intervalLength": number | undefined; -+ readonly "__#784968@#intervalIds": Record; -+ "__#784968@#intervalLength": number | undefined; - setIntervalLength(intervalLength: number): void; - getIntervalLength(): number | undefined; - _startPolling(input: AccountTrackerPollingInput): void; - _stopPollingByPollingTokenSetId(key: string): void; -- readonly "__#787882@#pollingTokenSets": Map>; -- "__#787882@#callbacks": Map void>>; -+ readonly "__#784960@#pollingTokenSets": Map>; -+ "__#784960@#callbacks": Map void>>; - _executePoll(input: AccountTrackerPollingInput): Promise; -- /** -- * The action that can be performed to get the state of the {@link AccountTrackerController}. -- */ - startPolling(input: AccountTrackerPollingInput): string; - stopAllPolling(): void; - stopPollingByPollingToken(pollingToken: string): void; -@@ -91,11 +91,15 @@ export declare class AccountTrackerController extends AccountTrackerController_b - * @param options.interval - Polling interval used to fetch new account balances. - * @param options.state - Initial state to set on this controller. - * @param options.messenger - The controller messaging system. -+ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. -+ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. - */ -- constructor({ interval, state, messenger, }: { -+ constructor({ interval, state, messenger, getStakedBalanceForChain, includeStakedAssets, }: { - interval?: number; - state?: Partial; - messenger: AccountTrackerControllerMessenger; -+ getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain']; -+ includeStakedAssets?: boolean; - }); - private syncAccounts; - /** -@@ -128,6 +132,7 @@ export declare class AccountTrackerController extends AccountTrackerController_b - */ - syncBalanceWithAddresses(addresses: string[], networkClientId?: NetworkClientId): Promise>; - } - export default AccountTrackerController; -diff --git a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs -index 4c4bf70..48cb006 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs -+++ b/node_modules/@metamask/assets-controllers/dist/AccountTrackerController.mjs -@@ -1,15 +1,15 @@ --var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { -- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); -- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); -- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); --}; - var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { - if (kind === "m") throw new TypeError("Private method is not writable"); - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); - return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; - }; --var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; -+var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { -+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); -+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); -+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); -+}; -+var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_handle, _AccountTrackerController_getCurrentChainId, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getBalanceFromChain; - function $importDefault(module) { - if (module?.__esModule) { - return module.default; -@@ -49,8 +49,10 @@ export class AccountTrackerController extends StaticIntervalPollingController() - * @param options.interval - Polling interval used to fetch new account balances. - * @param options.state - Initial state to set on this controller. - * @param options.messenger - The controller messaging system. -+ * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain. -+ * @param options.includeStakedAssets - Whether to include staked assets in the account balances. - */ -- constructor({ interval = 10000, state, messenger, }) { -+ constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) { - const { selectedNetworkClientId } = messenger.call('NetworkController:getState'); - const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - super({ -@@ -67,7 +69,11 @@ export class AccountTrackerController extends StaticIntervalPollingController() - }); - _AccountTrackerController_instances.add(this); - _AccountTrackerController_refreshMutex.set(this, new Mutex()); -+ _AccountTrackerController_includeStakedAssets.set(this, void 0); -+ _AccountTrackerController_getStakedBalanceForChain.set(this, void 0); - _AccountTrackerController_handle.set(this, void 0); -+ __classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f"); -+ __classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f"); - this.setIntervalLength(interval); - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/no-floating-promises -@@ -173,6 +179,15 @@ export class AccountTrackerController extends StaticIntervalPollingController() - balance, - }; - } -+ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { -+ const stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); -+ if (stakedBalance) { -+ accountsForChain[address] = { -+ ...accountsForChain[address], -+ stakedBalance, -+ }; -+ } -+ } - } - this.update((state) => { - if (chainId === __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getCurrentChainId).call(this)) { -@@ -198,25 +213,30 @@ export class AccountTrackerController extends StaticIntervalPollingController() - return safelyExecuteWithTimeout(async () => { - assert(ethQuery, 'Provider not set.'); - const balance = await query(ethQuery, 'getBalance', [address]); -- return [address, balance]; -+ let stakedBalance; -+ if (__classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")) { -+ stakedBalance = await __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, address, networkClientId); -+ } -+ return [address, balance, stakedBalance]; - }); - })).then((value) => { - return value.reduce((obj, item) => { - if (!item) { - return obj; - } -- const [address, balance] = item; -+ const [address, balance, stakedBalance] = item; - return { - ...obj, - [address]: { - balance, -+ stakedBalance, - }, - }; - }, {}); - }); - } - } --_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { -+_AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_handle = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCurrentChainId = function _AccountTrackerController_getCurrentChainId() { - const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState'); - const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - return chainId; -diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs -index a8ba346..6b39491 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.cjs -@@ -15,7 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { - }; - var _AssetsContractController_instances, _AssetsContractController_provider, _AssetsContractController_ipfsGateway, _AssetsContractController_chainId, _AssetsContractController_registerActionHandlers, _AssetsContractController_registerEventSubscriptions, _AssetsContractController_getCorrectProvider, _AssetsContractController_getCorrectChainId; - Object.defineProperty(exports, "__esModule", { value: true }); --exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; -+exports.AssetsContractController = exports.MISSING_PROVIDER_ERROR = exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID = exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = void 0; -+// import { BigNumber } from '@ethersproject/bignumber'; -+const bignumber_1 = require("@ethersproject/bignumber"); - const contracts_1 = require("@ethersproject/contracts"); - const providers_1 = require("@ethersproject/providers"); - const controller_utils_1 = require("@metamask/controller-utils"); -@@ -51,6 +53,10 @@ exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { - [assetsUtil_1.SupportedTokenDetectionNetworks.moonbeam]: '0x6aa75276052d96696134252587894ef5ffa520af', - [assetsUtil_1.SupportedTokenDetectionNetworks.moonriver]: '0x6aa75276052d96696134252587894ef5ffa520af', - }; -+exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID = { -+ [assetsUtil_1.SupportedStakedBalanceNetworks.mainnet]: '0x4fef9d741011476750a243ac70b9789a63dd47df', -+ [assetsUtil_1.SupportedStakedBalanceNetworks.holesky]: '0x37bf0883c27365cffcd0c4202918df930989891f', -+}; - exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; - /** - * The name of the {@link AssetsContractController} -@@ -333,6 +339,60 @@ class AssetsContractController { - } - return nonZeroBalances; - } -+ /** -+ * Get the staked ethereum balance for an address in a single call. -+ * -+ * @param address - The address to check staked ethereum balance for. -+ * @param networkClientId - Network Client ID to fetch the provider with. -+ * @returns The hex staked ethereum balance for address. -+ */ -+ async getStakedBalanceForChain(address, networkClientId) { -+ const chainId = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectChainId).call(this, networkClientId); -+ const provider = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectProvider).call(this, networkClientId); -+ // balance defaults to zero -+ let balance = bignumber_1.BigNumber.from(0); -+ // Only fetch staked balance on supported networks -+ if (![ -+ assetsUtil_1.SupportedStakedBalanceNetworks.mainnet, -+ assetsUtil_1.SupportedStakedBalanceNetworks.holesky, -+ ].includes(chainId)) { -+ return undefined; -+ } -+ // Only fetch staked balance if contract address exists -+ if (!((id) => id in exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID)(chainId)) { -+ return undefined; -+ } -+ const contractAddress = exports.STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId]; -+ const abi = [ -+ { -+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }], -+ name: 'getShares', -+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], -+ stateMutability: 'view', -+ type: 'function', -+ }, -+ { -+ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], -+ name: 'convertToAssets', -+ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], -+ stateMutability: 'view', -+ type: 'function', -+ }, -+ ]; -+ try { -+ const contract = new contracts_1.Contract(contractAddress, abi, provider); -+ const userShares = await contract.getShares(address); -+ // convert shares to assets only if address shares > 0 else return default balance -+ if (!userShares.lte(0)) { -+ balance = await contract.convertToAssets(userShares.toString()); -+ } -+ } -+ catch (error) { -+ // if we get an error, log and return the default value -+ console.error(error); -+ } -+ return balance.toHexString(); -+ } - } - exports.AssetsContractController = AssetsContractController; - _AssetsContractController_provider = new WeakMap(), _AssetsContractController_ipfsGateway = new WeakMap(), _AssetsContractController_chainId = new WeakMap(), _AssetsContractController_instances = new WeakSet(), _AssetsContractController_registerActionHandlers = function _AssetsContractController_registerActionHandlers() { -diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts -index d7d9b61..b7bf69d 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.cts -@@ -32,6 +32,10 @@ export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: { - readonly "0x504": "0x6aa75276052d96696134252587894ef5ffa520af"; - readonly "0x505": "0x6aa75276052d96696134252587894ef5ffa520af"; - }; -+export declare const STAKING_CONTRACT_ADDRESS_BY_CHAINID: { -+ readonly "0x1": "0x4fef9d741011476750a243ac70b9789a63dd47df"; -+ readonly "0x4268": "0x37bf0883c27365cffcd0c4202918df930989891f"; -+}; - export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; - /** - * @type BalanceMap -@@ -98,6 +102,7 @@ export type AllowedEvents = PreferencesControllerStateChangeEvent | NetworkContr - * The messenger of the {@link AssetsContractController}. - */ - export type AssetsContractControllerMessenger = RestrictedControllerMessenger; -+export type StakedBalance = string | undefined; - /** - * Controller that interacts with contracts on mainnet through web3 - */ -@@ -272,6 +277,14 @@ export declare class AssetsContractController { - * @returns The list of non-zero token balances. - */ - getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[], networkClientId?: NetworkClientId): Promise; -+ /** -+ * Get the staked ethereum balance for an address in a single call. -+ * -+ * @param address - The address to check staked ethereum balance for. -+ * @param networkClientId - Network Client ID to fetch the provider with. -+ * @returns The hex staked ethereum balance for address. -+ */ -+ getStakedBalanceForChain(address: string, networkClientId?: NetworkClientId): Promise; - } - export default AssetsContractController; - //# sourceMappingURL=AssetsContractController.d.cts.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts -index a7916fb..9cbee8c 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts -+++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.d.mts -@@ -32,6 +32,10 @@ export declare const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: { - readonly "0x504": "0x6aa75276052d96696134252587894ef5ffa520af"; - readonly "0x505": "0x6aa75276052d96696134252587894ef5ffa520af"; - }; -+export declare const STAKING_CONTRACT_ADDRESS_BY_CHAINID: { -+ readonly "0x1": "0x4fef9d741011476750a243ac70b9789a63dd47df"; -+ readonly "0x4268": "0x37bf0883c27365cffcd0c4202918df930989891f"; -+}; - export declare const MISSING_PROVIDER_ERROR = "AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available"; - /** - * @type BalanceMap -@@ -98,6 +102,7 @@ export type AllowedEvents = PreferencesControllerStateChangeEvent | NetworkContr - * The messenger of the {@link AssetsContractController}. - */ - export type AssetsContractControllerMessenger = RestrictedControllerMessenger; -+export type StakedBalance = string | undefined; - /** - * Controller that interacts with contracts on mainnet through web3 - */ -@@ -272,6 +277,14 @@ export declare class AssetsContractController { - * @returns The list of non-zero token balances. - */ - getBalancesInSingleCall(selectedAddress: string, tokensToDetect: string[], networkClientId?: NetworkClientId): Promise; -+ /** -+ * Get the staked ethereum balance for an address in a single call. -+ * -+ * @param address - The address to check staked ethereum balance for. -+ * @param networkClientId - Network Client ID to fetch the provider with. -+ * @returns The hex staked ethereum balance for address. -+ */ -+ getStakedBalanceForChain(address: string, networkClientId?: NetworkClientId): Promise; - } - export default AssetsContractController; - //# sourceMappingURL=AssetsContractController.d.mts.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs -index 15a0e56..deeb1eb 100644 ---- a/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs -+++ b/node_modules/@metamask/assets-controllers/dist/AssetsContractController.mjs -@@ -16,13 +16,15 @@ function $importDefault(module) { - } - return module; - } -+// import { BigNumber } from '@ethersproject/bignumber'; -+import { BigNumber } from "@ethersproject/bignumber"; - import { Contract } from "@ethersproject/contracts"; - import { Web3Provider } from "@ethersproject/providers"; - import { IPFS_DEFAULT_GATEWAY_URL } from "@metamask/controller-utils"; - import { getKnownPropertyNames } from "@metamask/utils"; - import $abiSingleCallBalancesContract from "single-call-balance-checker-abi"; - const abiSingleCallBalancesContract = $importDefault($abiSingleCallBalancesContract); --import { SupportedTokenDetectionNetworks } from "./assetsUtil.mjs"; -+import { SupportedStakedBalanceNetworks, SupportedTokenDetectionNetworks } from "./assetsUtil.mjs"; - import { ERC20Standard } from "./Standards/ERC20Standard.mjs"; - import { ERC1155Standard } from "./Standards/NftStandards/ERC1155/ERC1155Standard.mjs"; - import { ERC721Standard } from "./Standards/NftStandards/ERC721/ERC721Standard.mjs"; -@@ -52,6 +54,10 @@ export const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { - [SupportedTokenDetectionNetworks.moonbeam]: '0x6aa75276052d96696134252587894ef5ffa520af', - [SupportedTokenDetectionNetworks.moonriver]: '0x6aa75276052d96696134252587894ef5ffa520af', - }; -+export const STAKING_CONTRACT_ADDRESS_BY_CHAINID = { -+ [SupportedStakedBalanceNetworks.mainnet]: '0x4fef9d741011476750a243ac70b9789a63dd47df', -+ [SupportedStakedBalanceNetworks.holesky]: '0x37bf0883c27365cffcd0c4202918df930989891f', -+}; - export const MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; - /** - * The name of the {@link AssetsContractController} -@@ -334,6 +340,60 @@ export class AssetsContractController { - } - return nonZeroBalances; - } -+ /** -+ * Get the staked ethereum balance for an address in a single call. -+ * -+ * @param address - The address to check staked ethereum balance for. -+ * @param networkClientId - Network Client ID to fetch the provider with. -+ * @returns The hex staked ethereum balance for address. -+ */ -+ async getStakedBalanceForChain(address, networkClientId) { -+ const chainId = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectChainId).call(this, networkClientId); -+ const provider = __classPrivateFieldGet(this, _AssetsContractController_instances, "m", _AssetsContractController_getCorrectProvider).call(this, networkClientId); -+ // balance defaults to zero -+ let balance = BigNumber.from(0); -+ // Only fetch staked balance on supported networks -+ if (![ -+ SupportedStakedBalanceNetworks.mainnet, -+ SupportedStakedBalanceNetworks.holesky, -+ ].includes(chainId)) { -+ return undefined; -+ } -+ // Only fetch staked balance if contract address exists -+ if (!((id) => id in STAKING_CONTRACT_ADDRESS_BY_CHAINID)(chainId)) { -+ return undefined; -+ } -+ const contractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId]; -+ const abi = [ -+ { -+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }], -+ name: 'getShares', -+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], -+ stateMutability: 'view', -+ type: 'function', -+ }, -+ { -+ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], -+ name: 'convertToAssets', -+ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }], -+ stateMutability: 'view', -+ type: 'function', -+ }, -+ ]; -+ try { -+ const contract = new Contract(contractAddress, abi, provider); -+ const userShares = await contract.getShares(address); -+ // convert shares to assets only if address shares > 0 else return default balance -+ if (!userShares.lte(0)) { -+ balance = await contract.convertToAssets(userShares.toString()); -+ } -+ } -+ catch (error) { -+ // if we get an error, log and return the default value -+ console.error(error); -+ } -+ return balance.toHexString(); -+ } - } - _AssetsContractController_provider = new WeakMap(), _AssetsContractController_ipfsGateway = new WeakMap(), _AssetsContractController_chainId = new WeakMap(), _AssetsContractController_instances = new WeakSet(), _AssetsContractController_registerActionHandlers = function _AssetsContractController_registerActionHandlers() { - const methodsExcludedFromMessenger = [ -diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.cjs b/node_modules/@metamask/assets-controllers/dist/NftController.cjs -index 82613cf..f7fd41e 100644 ---- a/node_modules/@metamask/assets-controllers/dist/NftController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/NftController.cjs -@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( - var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; - }; --var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_openSeaEnabled, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; -+var _NftController_instances, _NftController_mutex, _NftController_selectedAccountId, _NftController_chainId, _NftController_ipfsGateway, _NftController_displayNftMedia, _NftController_useIpfsSubdomains, _NftController_isIpfsGatewayEnabled, _NftController_onNftAdded, _NftController_onNetworkControllerNetworkDidChange, _NftController_onPreferencesControllerStateChange, _NftController_onSelectedAccountChange, _NftController_updateNestedNftState, _NftController_getNftCollectionApi, _NftController_getNftInformationFromApi, _NftController_getNftInformationFromTokenURI, _NftController_getNftURIAndStandard, _NftController_getNftInformation, _NftController_getNftContractInformationFromContract, _NftController_getNftContractInformation, _NftController_addIndividualNft, _NftController_addNftContract, _NftController_removeAndIgnoreIndividualNft, _NftController_removeIndividualNft, _NftController_removeNftContract, _NftController_validateWatchNft, _NftController_getCorrectChainId, _NftController_getAddressOrSelectedAddress, _NftController_updateNftUpdateForAccount; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.NftController = exports.getDefaultNftControllerState = void 0; - const address_1 = require("@ethersproject/address"); -@@ -53,7 +53,7 @@ class NftController extends base_controller_1.BaseController { - * @param options - The controller options. - * @param options.chainId - The chain ID of the current network. - * @param options.ipfsGateway - The configured IPFS gateway. -- * @param options.openSeaEnabled - Controls whether the OpenSea API is used. -+ * @param options.displayNftMedia - Controls whether the OpenSea API is used. - * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. - * @param options.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. - * @param options.onNftAdded - Callback that is called when an NFT is added. Currently used pass data -@@ -61,7 +61,7 @@ class NftController extends base_controller_1.BaseController { - * @param options.messenger - The controller messenger. - * @param options.state - Initial state to set on this controller. - */ -- constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, openSeaEnabled = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { -+ constructor({ chainId: initialChainId, ipfsGateway = controller_utils_1.IPFS_DEFAULT_GATEWAY_URL, displayNftMedia = false, useIpfsSubdomains = true, isIpfsGatewayEnabled = true, onNftAdded, messenger, state = {}, }) { - super({ - name: controllerName, - metadata: nftControllerMetadata, -@@ -76,14 +76,14 @@ class NftController extends base_controller_1.BaseController { - _NftController_selectedAccountId.set(this, void 0); - _NftController_chainId.set(this, void 0); - _NftController_ipfsGateway.set(this, void 0); -- _NftController_openSeaEnabled.set(this, void 0); -+ _NftController_displayNftMedia.set(this, void 0); - _NftController_useIpfsSubdomains.set(this, void 0); - _NftController_isIpfsGatewayEnabled.set(this, void 0); - _NftController_onNftAdded.set(this, void 0); - __classPrivateFieldSet(this, _NftController_selectedAccountId, this.messagingSystem.call('AccountsController:getSelectedAccount').id, "f"); - __classPrivateFieldSet(this, _NftController_chainId, initialChainId, "f"); - __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); -- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); -+ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); - __classPrivateFieldSet(this, _NftController_useIpfsSubdomains, useIpfsSubdomains, "f"); - __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); - __classPrivateFieldSet(this, _NftController_onNftAdded, onNftAdded, "f"); -@@ -97,6 +97,19 @@ class NftController extends base_controller_1.BaseController { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_onSelectedAccountChange).bind(this)); - } -+ -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset() { -+ this.update((state) => { -+ state.allNftContracts = {}; -+ state.allNfts = {}; -+ state.ignoredNfts = []; -+ }); -+ } -+ - getNftApi() { - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -@@ -581,7 +594,7 @@ class NftController extends base_controller_1.BaseController { - } - } - exports.NftController = NftController; --_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_openSeaEnabled = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { -+_NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new WeakMap(), _NftController_chainId = new WeakMap(), _NftController_ipfsGateway = new WeakMap(), _NftController_displayNftMedia = new WeakMap(), _NftController_useIpfsSubdomains = new WeakMap(), _NftController_isIpfsGatewayEnabled = new WeakMap(), _NftController_onNftAdded = new WeakMap(), _NftController_instances = new WeakSet(), _NftController_onNetworkControllerNetworkDidChange = function _NftController_onNetworkControllerNetworkDidChange({ selectedNetworkClientId, }) { - const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId); - __classPrivateFieldSet(this, _NftController_chainId, chainId, "f"); - }, _NftController_onPreferencesControllerStateChange = -@@ -589,16 +602,16 @@ _NftController_mutex = new WeakMap(), _NftController_selectedAccountId = new Wea - * Handles the state change of the preference controller. - * @param preferencesState - The new state of the preference controller. - * @param preferencesState.ipfsGateway - The configured IPFS gateway. -- * @param preferencesState.openSeaEnabled - Controls whether the OpenSea API is used. -+ * @param preferencesState.displayNftMedia - Controls whether the OpenSea API is used. - * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. - */ --async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, openSeaEnabled, isIpfsGatewayEnabled, }) { -+async function _NftController_onPreferencesControllerStateChange({ ipfsGateway, displayNftMedia, isIpfsGatewayEnabled, }) { - const selectedAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); - __classPrivateFieldSet(this, _NftController_selectedAccountId, selectedAccount.id, "f"); - __classPrivateFieldSet(this, _NftController_ipfsGateway, ipfsGateway, "f"); -- __classPrivateFieldSet(this, _NftController_openSeaEnabled, openSeaEnabled, "f"); -+ __classPrivateFieldSet(this, _NftController_displayNftMedia, displayNftMedia, "f"); - __classPrivateFieldSet(this, _NftController_isIpfsGatewayEnabled, isIpfsGatewayEnabled, "f"); -- const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || openSeaEnabled; -+ const needsUpdateNftMetadata = (isIpfsGatewayEnabled && ipfsGateway !== '') || displayNftMedia; - if (needsUpdateNftMetadata && selectedAccount) { - await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, selectedAccount); - } -@@ -611,7 +624,7 @@ async function _NftController_onSelectedAccountChange(internalAccount) { - const oldSelectedAccountId = __classPrivateFieldGet(this, _NftController_selectedAccountId, "f"); - __classPrivateFieldSet(this, _NftController_selectedAccountId, internalAccount.id, "f"); - const needsUpdateNftMetadata = ((__classPrivateFieldGet(this, _NftController_isIpfsGatewayEnabled, "f") && __classPrivateFieldGet(this, _NftController_ipfsGateway, "f") !== '') || -- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f")) && -+ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f")) && - oldSelectedAccountId !== internalAccount.id; - if (needsUpdateNftMetadata) { - await __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_updateNftUpdateForAccount).call(this, internalAccount); -@@ -686,6 +699,7 @@ async function _NftController_getNftInformationFromApi(contractAddress, tokenId) - description: null, - image: null, - standard: null, -+ error: 'Opensea import error', - }; - } - // if we've reached this point, we have successfully fetched some data for nftInformation -@@ -730,7 +744,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - tokenURI: tokenURI ?? null, - }; - } -- const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f"); -+ const isDisplayNFTMediaToggleEnabled = __classPrivateFieldGet(this, _NftController_displayNftMedia, "f"); - if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { - return { - image: null, -@@ -739,6 +753,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - standard: standard || null, - favorite: false, - tokenURI: tokenURI ?? null, -+ error: 'URI import error', - }; - } - if (hasIpfsTokenURI) { -@@ -777,6 +792,7 @@ async function _NftController_getNftInformationFromTokenURI(contractAddress, tok - standard: standard || null, - favorite: false, - tokenURI: tokenURI ?? null, -+ error: 'URI import error', - }; - } - }, _NftController_getNftURIAndStandard = -@@ -832,10 +848,21 @@ async function _NftController_getNftInformation(contractAddress, tokenId, networ - }); - const [blockchainMetadata, nftApiMetadata] = await Promise.all([ - (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromTokenURI).call(this, contractAddress, tokenId, networkClientId)), -- __classPrivateFieldGet(this, _NftController_openSeaEnabled, "f") && chainId === '0x1' -+ __classPrivateFieldGet(this, _NftController_displayNftMedia, "f") && chainId === '0x1' - ? (0, controller_utils_1.safelyExecute)(() => __classPrivateFieldGet(this, _NftController_instances, "m", _NftController_getNftInformationFromApi).call(this, contractAddress, tokenId)) - : undefined, - ]); -+ if (blockchainMetadata?.error && nftApiMetadata?.error) { -+ return { -+ image: null, -+ name: null, -+ description: null, -+ standard: blockchainMetadata.standard ?? null, -+ favorite: false, -+ tokenURI: blockchainMetadata.tokenURI ?? null, -+ error: 'Both import failed', -+ }; -+ } - return { - ...nftApiMetadata, - name: blockchainMetadata?.name ?? nftApiMetadata?.name ?? null, -@@ -977,6 +1004,7 @@ async function _NftController_addIndividualNft(tokenAddress, tokenId, nftMetadat - tokenId: tokenId.toString(), - standard: nftMetadata.standard, - source, -+ tokenURI: nftMetadata.tokenURI, - }); - } - } -diff --git a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -index 6306bb6..7e9bfec 100644 ---- a/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/NftController.d.cts -@@ -108,6 +108,7 @@ export type NftMetadata = { - creator?: string; - transactionId?: string; - tokenURI?: string | null; -+ error?: string; - collection?: Collection; - address?: string; - attributes?: Attributes[]; -@@ -419,6 +420,11 @@ export declare class NftController extends BaseController; - _requestApproval(suggestedNftMeta: SuggestedNftMeta): Promise; -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset(): void; - } - export default NftController; - //# sourceMappingURL=NftController.d.cts.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -index f7b75d7..bae1292 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs -@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function ( - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); - return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); - }; --var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled; -+var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled, _TokenBalancesController_updateInProgress; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.TokenBalancesController = exports.getDefaultTokenBalancesState = void 0; - const base_controller_1 = require("@metamask/base-controller"); -@@ -60,9 +60,11 @@ class TokenBalancesController extends base_controller_1.BaseController { - _TokenBalancesController_interval.set(this, void 0); - _TokenBalancesController_tokens.set(this, void 0); - _TokenBalancesController_disabled.set(this, void 0); -+ _TokenBalancesController_updateInProgress.set(this, void 0); - __classPrivateFieldSet(this, _TokenBalancesController_disabled, disabled, "f"); - __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); - __classPrivateFieldSet(this, _TokenBalancesController_tokens, tokens, "f"); -+ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, false, "f"); - this.messagingSystem.subscribe('TokensController:stateChange', ({ tokens: newTokens, detectedTokens }) => { - __classPrivateFieldSet(this, _TokenBalancesController_tokens, [...newTokens, ...detectedTokens], "f"); - // TODO: Either fix this lint violation or explain why it's necessary to ignore. -@@ -73,6 +75,18 @@ class TokenBalancesController extends base_controller_1.BaseController { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.poll(); - } -+ -+ -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset() { -+ this.update((state) => { -+ state.contractBalances = {}; -+ }); -+ } -+ - /** - * Allows controller to update tracked tokens contract balances. - */ -@@ -113,24 +127,31 @@ class TokenBalancesController extends base_controller_1.BaseController { - } - const selectedInternalAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); - const newContractBalances = {}; -- for (const token of __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f")) { -+ const balancePromises = __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f").map((token) => { - const { address } = token; -- try { -- const balance = await this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address); -+ return this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address) -+ .then((balance) => { - newContractBalances[address] = (0, controller_utils_1.toHex)(balance); -- token.hasBalanceError = false; -- } -- catch (error) { -+ token = { -+ ...token, -+ hasBalanceError: false -+ } -+ }).catch((error) => { - newContractBalances[address] = (0, controller_utils_1.toHex)(0); -- token.hasBalanceError = true; -- } -- } -+ token = { -+ ...token, -+ hasBalanceError: true -+ } -+ }) -+ }); -+ await Promise.all(balancePromises); - this.update((state) => { - state.contractBalances = newContractBalances; - }); -+ __classPrivateFieldSet(this, _TokenBalancesController_updateInProgress, updateInProgress, "f"); - } - } - exports.TokenBalancesController = TokenBalancesController; --_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(); -+_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(), _TokenBalancesController_updateInProgress = new WeakMap(); - exports.default = TokenBalancesController; - //# sourceMappingURL=TokenBalancesController.cjs.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts -index d252cab..ccc3d82 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts -@@ -77,6 +77,11 @@ export declare class TokenBalancesController extends BaseController; -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset(): void; - } - export default TokenBalancesController; - //# sourceMappingURL=TokenBalancesController.d.cts.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs -index beb4f95..6aab19a 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.cjs -@@ -117,6 +117,16 @@ class TokenRatesController extends (0, polling_controller_1.StaticIntervalPollin - __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_subscribeToTokensStateChange).call(this); - __classPrivateFieldGet(this, _TokenRatesController_instances, "m", _TokenRatesController_subscribeToNetworkStateChange).call(this); - } -+ -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset() { -+ this.update((state) => { -+ state.marketData = {}; -+ }); -+ } - /** - * Allows controller to make active and passive polling requests - */ -diff --git a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts -index a5d6404..0d3d017 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/TokenRatesController.d.cts -@@ -183,6 +183,11 @@ export declare class TokenRatesController extends TokenRatesController_base; -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset(): void; - } - export default TokenRatesController; - //# sourceMappingURL=TokenRatesController.d.cts.map -\ No newline at end of file -diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs -index 742de5b..651d1dd 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs -+++ b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs -@@ -109,6 +109,19 @@ class TokensController extends base_controller_1.BaseController { - } - }); - } -+ -+ /** -+ * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO -+ * Resets to the default state -+ */ -+ reset() { -+ this.update((state) => { -+ state.allTokens = {}; -+ state.allIgnoredTokens = {}; -+ state.ignoredTokens = []; -+ state.tokens = []; -+ }); -+ } - /** - * Adds a token to the stored token list. - * -diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts -index 3fbbc66..4f99591 100644 ---- a/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts -+++ b/node_modules/@metamask/assets-controllers/dist/TokensController.d.cts -@@ -160,6 +160,11 @@ export declare class TokensController extends BaseController { +- __classPrivateFieldSet(this, _TokenBalancesController_tokens, [...newTokens, ...detectedTokens], "f"); +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.updateBalances(); ++ _TokenBalancesController_instances.add(this); ++ _TokenBalancesController_queryMultipleAccounts.set(this, void 0); ++ _TokenBalancesController_allTokens.set(this, void 0); ++ _TokenBalancesController_allDetectedTokens.set(this, void 0); ++ /** ++ * Determines whether to query all accounts, or just the selected account. ++ * @param preferences - The preferences state. ++ * @param preferences.isMultiAccountBalancesEnabled - whether to query all accounts (mobile). ++ * @param preferences.useMultiAccountBalanceChecker - whether to query all accounts (extension). ++ * @returns true if all accounts should be queried. ++ */ ++ _TokenBalancesController_calculateQueryMultipleAccounts.set(this, ({ isMultiAccountBalancesEnabled, useMultiAccountBalanceChecker, }) => { ++ return Boolean( ++ // Note: These settings have different names on extension vs mobile ++ isMultiAccountBalancesEnabled || useMultiAccountBalanceChecker); + }); +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.poll(); +- } +- /** +- * Allows controller to update tracked tokens contract balances. +- */ +- enable() { +- __classPrivateFieldSet(this, _TokenBalancesController_disabled, false, "f"); ++ /** ++ * Handles the event for preferences state changes. ++ * @param preferences - The preferences state. ++ */ ++ _TokenBalancesController_onPreferencesStateChange.set(this, (preferences) => { ++ // Update the user preference for whether to query multiple accounts. ++ const queryMultipleAccounts = __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, preferences); ++ // Refresh when flipped off -> on ++ const refresh = queryMultipleAccounts && !__classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f"); ++ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, queryMultipleAccounts, "f"); ++ if (refresh) { ++ this.updateBalances().catch(console.error); ++ } ++ }); ++ /** ++ * Handles the event for tokens state changes. ++ * @param state - The token state. ++ * @param state.allTokens - The state for imported tokens across all chains. ++ * @param state.allDetectedTokens - The state for detected tokens across all chains. ++ */ ++ _TokenBalancesController_onTokensStateChange.set(this, ({ allTokens, allDetectedTokens, }) => { ++ // Refresh token balances on chains whose tokens have changed. ++ const chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, allTokens, allDetectedTokens); ++ const chainIdsToUpdate = chainIds.filter((chainId) => !(0, lodash_1.isEqual)(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId], allTokens[chainId]) || ++ !(0, lodash_1.isEqual)(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId], allDetectedTokens[chainId])); ++ __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f"); ++ __classPrivateFieldSet(this, _TokenBalancesController_allDetectedTokens, allDetectedTokens, "f"); ++ this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error); ++ }); ++ /** ++ * Returns an array of chain ids that have tokens. ++ * @param allTokens - The state for imported tokens across all chains. ++ * @param allDetectedTokens - The state for detected tokens across all chains. ++ * @returns An array of chain ids that have tokens. ++ */ ++ _TokenBalancesController_getChainIds.set(this, (allTokens, allDetectedTokens) => [ ++ ...new Set([ ++ ...Object.keys(allTokens), ++ ...Object.keys(allDetectedTokens), ++ ]), ++ ]); ++ this.setIntervalLength(interval); ++ // Set initial preference for querying multiple accounts, and subscribe to changes ++ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, this.messagingSystem.call('PreferencesController:getState')), "f"); ++ this.messagingSystem.subscribe('PreferencesController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onPreferencesStateChange, "f").bind(this)); ++ // Set initial tokens, and subscribe to changes ++ (_a = this, _b = this, { ++ allTokens: ({ set value(_c) { __classPrivateFieldSet(_a, _TokenBalancesController_allTokens, _c, "f"); } }).value, ++ allDetectedTokens: ({ set value(_c) { __classPrivateFieldSet(_b, _TokenBalancesController_allDetectedTokens, _c, "f"); } }).value, ++ } = this.messagingSystem.call('TokensController:getState')); ++ this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensStateChange, "f").bind(this)); ++ // Subscribe to network state changes ++ this.messagingSystem.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_onNetworkStateChange).bind(this)); + } + /** +- * Blocks controller from updating tracked tokens contract balances. ++ * Polls for erc20 token balances. ++ * @param input - The input for the poll. ++ * @param input.chainId - The chain id to poll token balances on. + */ +- disable() { +- __classPrivateFieldSet(this, _TokenBalancesController_disabled, true, "f"); ++ async _executePoll({ chainId }) { ++ await this.updateBalancesByChainId({ chainId }); + } + /** +- * Starts a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token balances. ++ * Updates the token balances for the given chain ids. ++ * @param input - The input for the update. ++ * @param input.chainIds - The chain ids to update token balances for. ++ * Or omitted to update all chains that contain tokens. + */ +- async poll(interval) { +- if (interval) { +- __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); +- } +- if (__classPrivateFieldGet(this, _TokenBalancesController_handle, "f")) { +- clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_handle, "f")); +- } +- await (0, controller_utils_1.safelyExecute)(() => this.updateBalances()); +- __classPrivateFieldSet(this, _TokenBalancesController_handle, setTimeout(() => { +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.poll(__classPrivateFieldGet(this, _TokenBalancesController_interval, "f")); +- }, __classPrivateFieldGet(this, _TokenBalancesController_interval, "f")), "f"); ++ async updateBalances({ chainIds } = {}) { ++ chainIds ?? (chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f"), __classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f"))); ++ await Promise.allSettled(chainIds.map((chainId) => this.updateBalancesByChainId({ chainId }))); + } + /** +- * Updates balances for all tokens. ++ * Updates token balances for the given chain id. ++ * @param input - The input for the update. ++ * @param input.chainId - The chain id to update token balances on. + */ +- async updateBalances() { +- if (__classPrivateFieldGet(this, _TokenBalancesController_disabled, "f")) { +- return; ++ async updateBalancesByChainId({ chainId }) { ++ const { address: selectedAccountAddress } = this.messagingSystem.call('AccountsController:getSelectedAccount'); ++ const isSelectedAccount = (accountAddress) => (0, controller_utils_1.toChecksumHexAddress)(accountAddress) === ++ (0, controller_utils_1.toChecksumHexAddress)(selectedAccountAddress); ++ const accountTokenPairs = []; ++ const addTokens = ([accountAddress, tokens]) => __classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f") || isSelectedAccount(accountAddress) ++ ? tokens.forEach((t) => accountTokenPairs.push({ ++ accountAddress: accountAddress, ++ tokenAddress: t.address, ++ })) ++ : undefined; ++ // Balances will be updated for both imported and detected tokens ++ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId] ?? {}).forEach(addTokens); ++ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId] ?? {}).forEach(addTokens); ++ let results = []; ++ if (accountTokenPairs.length > 0) { ++ const provider = new providers_1.Web3Provider(__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId).provider); ++ const calls = accountTokenPairs.map(({ accountAddress, tokenAddress }) => ({ ++ contract: new contracts_1.Contract(tokenAddress, metamask_eth_abis_1.abiERC20, provider), ++ functionSignature: 'balanceOf(address)', ++ arguments: [accountAddress], ++ })); ++ results = await (0, multicall_1.multicallOrFallback)(calls, chainId, provider); + } +- const selectedInternalAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); +- const newContractBalances = {}; +- for (const token of __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f")) { +- const { address } = token; +- try { +- const balance = await this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address); +- newContractBalances[address] = (0, controller_utils_1.toHex)(balance); +- token.hasBalanceError = false; ++ this.update((state) => { ++ var _a, _b; ++ // Reset so that when accounts or tokens are removed, ++ // their balances are removed rather than left stale. ++ for (const accountAddress of Object.keys(state.tokenBalances)) { ++ state.tokenBalances[accountAddress][chainId] = {}; + } +- catch (error) { +- newContractBalances[address] = (0, controller_utils_1.toHex)(0); +- token.hasBalanceError = true; ++ for (let i = 0; i < results.length; i++) { ++ const { success, value } = results[i]; ++ const { accountAddress, tokenAddress } = accountTokenPairs[i]; ++ if (success) { ++ ((_b = ((_a = state.tokenBalances)[accountAddress] ?? (_a[accountAddress] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = (0, controller_utils_1.toHex)(value); ++ } + } +- } +- this.update((state) => { +- state.contractBalances = newContractBalances; + }); + } + /** +@@ -139,6 +203,27 @@ class TokenBalancesController extends base_controller_1.BaseController { + } + } + exports.TokenBalancesController = TokenBalancesController; +-_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(); ++_TokenBalancesController_queryMultipleAccounts = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_allDetectedTokens = new WeakMap(), _TokenBalancesController_calculateQueryMultipleAccounts = new WeakMap(), _TokenBalancesController_onPreferencesStateChange = new WeakMap(), _TokenBalancesController_onTokensStateChange = new WeakMap(), _TokenBalancesController_getChainIds = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_onNetworkStateChange = function _TokenBalancesController_onNetworkStateChange(_, patches) { ++ // Remove state for deleted networks ++ for (const patch of patches) { ++ if (patch.op === 'remove' && ++ patch.path[0] === 'networkConfigurationsByChainId') { ++ const removedChainId = patch.path[1]; ++ this.update((state) => { ++ for (const accountAddress of Object.keys(state.tokenBalances)) { ++ delete state.tokenBalances[accountAddress][removedChainId]; ++ } ++ }); ++ } ++ } ++}, _TokenBalancesController_getNetworkClient = function _TokenBalancesController_getNetworkClient(chainId) { ++ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); ++ const networkConfiguration = networkConfigurationsByChainId[chainId]; ++ if (!networkConfiguration) { ++ throw new Error(`TokenBalancesController: No network configuration found for chainId ${chainId}`); ++ } ++ const { networkClientId } = networkConfiguration.rpcEndpoints[networkConfiguration.defaultRpcEndpointIndex]; ++ return this.messagingSystem.call(`NetworkController:getNetworkClientById`, networkClientId); ++}; + exports.default = TokenBalancesController; + //# sourceMappingURL=TokenBalancesController.cjs.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs.map b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs.map +index 26bd8c2..2931bdc 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs.map ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.cjs.map +@@ -1 +1 @@ +-{"version":3,"file":"TokenBalancesController.cjs","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { safelyExecute, toHex } from '@metamask/controller-utils';\n\nimport type { AssetsContractControllerGetERC20BalanceOfAction } from './AssetsContractController';\nimport type { Token } from './TokenRatesController';\nimport type { TokensControllerStateChangeEvent } from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\nconst controllerName = 'TokenBalancesController';\n\nconst metadata = {\n contractBalances: { persist: true, anonymous: false },\n};\n\n/**\n * Token balances controller options\n * @property interval - Polling interval used to fetch new token balances.\n * @property tokens - List of tokens to track balances for.\n * @property disabled - If set to true, all tracked tokens contract balances updates are blocked.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n tokens?: Token[];\n disabled?: boolean;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial;\n};\n\n/**\n * Represents a mapping of hash token contract addresses to their balances.\n */\ntype ContractBalances = Record;\n\n/**\n * Token balances controller state\n * @property contractBalances - Hash of token contract addresses to balances\n */\nexport type TokenBalancesControllerState = {\n contractBalances: ContractBalances;\n};\n\nexport type TokenBalancesControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenBalancesControllerState\n>;\n\nexport type TokenBalancesControllerActions =\n TokenBalancesControllerGetStateAction;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | AssetsContractControllerGetERC20BalanceOfAction;\n\nexport type TokenBalancesControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n TokenBalancesControllerState\n >;\n\nexport type TokenBalancesControllerEvents =\n TokenBalancesControllerStateChangeEvent;\n\nexport type AllowedEvents = TokensControllerStateChangeEvent;\n\nexport type TokenBalancesControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokenBalancesControllerActions | AllowedActions,\n TokenBalancesControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Get the default TokenBalancesController state.\n *\n * @returns The default TokenBalancesController state.\n */\nexport function getDefaultTokenBalancesState(): TokenBalancesControllerState {\n return {\n contractBalances: {},\n };\n}\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends BaseController<\n typeof controllerName,\n TokenBalancesControllerState,\n TokenBalancesControllerMessenger\n> {\n #handle?: ReturnType;\n\n #interval: number;\n\n #tokens: Token[];\n\n #disabled: boolean;\n\n /**\n * Construct a Token Balances Controller.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new token balances.\n * @param options.tokens - List of tokens to track balances for.\n * @param options.disabled - If set to true, all tracked tokens contract balances updates are blocked.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller restricted messenger.\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n tokens = [],\n disabled = false,\n messenger,\n state = {},\n }: TokenBalancesControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTokenBalancesState(),\n ...state,\n },\n });\n\n this.#disabled = disabled;\n this.#interval = interval;\n this.#tokens = tokens;\n\n this.messagingSystem.subscribe(\n 'TokensController:stateChange',\n ({ tokens: newTokens, detectedTokens }) => {\n this.#tokens = [...newTokens, ...detectedTokens];\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateBalances();\n },\n );\n\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.poll();\n }\n\n /**\n * Allows controller to update tracked tokens contract balances.\n */\n enable() {\n this.#disabled = false;\n }\n\n /**\n * Blocks controller from updating tracked tokens contract balances.\n */\n disable() {\n this.#disabled = true;\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token balances.\n */\n async poll(interval?: number): Promise {\n if (interval) {\n this.#interval = interval;\n }\n\n if (this.#handle) {\n clearTimeout(this.#handle);\n }\n\n await safelyExecute(() => this.updateBalances());\n\n this.#handle = setTimeout(() => {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.poll(this.#interval);\n }, this.#interval);\n }\n\n /**\n * Updates balances for all tokens.\n */\n async updateBalances() {\n if (this.#disabled) {\n return;\n }\n const selectedInternalAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n\n const newContractBalances: ContractBalances = {};\n for (const token of this.#tokens) {\n const { address } = token;\n try {\n const balance = await this.messagingSystem.call(\n 'AssetsContractController:getERC20BalanceOf',\n address,\n selectedInternalAccount.address,\n );\n newContractBalances[address] = toHex(balance);\n token.hasBalanceError = false;\n } catch (error) {\n newContractBalances[address] = toHex(0);\n token.hasBalanceError = true;\n }\n }\n\n this.update((state) => {\n state.contractBalances = newContractBalances;\n });\n }\n\n /**\n * Reset the controller state to the default state.\n */\n resetState() {\n this.update(() => {\n return getDefaultTokenBalancesState();\n });\n }\n}\n\nexport default TokenBalancesController;\n"]} +\ No newline at end of file ++{"version":3,"file":"TokenBalancesController.cjs","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { toChecksumHexAddress, toHex } from '@metamask/controller-utils';\nimport { abiERC20 } from '@metamask/metamask-eth-abis';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n NetworkState,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n PreferencesControllerGetStateAction,\n PreferencesControllerStateChangeEvent,\n PreferencesState,\n} from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\nimport type BN from 'bn.js';\nimport type { Patch } from 'immer';\nimport { isEqual } from 'lodash';\n\nimport type { MulticallResult } from './multicall';\nimport { multicallOrFallback } from './multicall';\nimport type { Token } from './TokenRatesController';\nimport type {\n TokensControllerGetStateAction,\n TokensControllerState,\n TokensControllerStateChangeEvent,\n} from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\nconst controllerName = 'TokenBalancesController';\n\nconst metadata = {\n tokenBalances: { persist: true, anonymous: false },\n};\n\n/**\n * Token balances controller options\n * @property interval - Polling interval used to fetch new token balances.\n * @property messenger - A controller messenger.\n * @property state - Initial state for the controller.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial;\n};\n\n/**\n * A mapping from account address to chain id to token address to balance.\n */\ntype TokenBalances = Record>>;\n\n/**\n * Token balances controller state\n * @property tokenBalances - A mapping from account address to chain id to token address to balance.\n */\nexport type TokenBalancesControllerState = {\n tokenBalances: TokenBalances;\n};\n\nexport type TokenBalancesControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenBalancesControllerState\n>;\n\nexport type TokenBalancesControllerActions =\n TokenBalancesControllerGetStateAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction\n | TokensControllerGetStateAction\n | PreferencesControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction;\n\nexport type TokenBalancesControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n TokenBalancesControllerState\n >;\n\nexport type TokenBalancesControllerEvents =\n TokenBalancesControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | TokensControllerStateChangeEvent\n | PreferencesControllerStateChangeEvent\n | NetworkControllerStateChangeEvent;\n\nexport type TokenBalancesControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokenBalancesControllerActions | AllowedActions,\n TokenBalancesControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Get the default TokenBalancesController state.\n *\n * @returns The default TokenBalancesController state.\n */\nexport function getDefaultTokenBalancesState(): TokenBalancesControllerState {\n return {\n tokenBalances: {},\n };\n}\n\n/** The input to start polling for the {@link TokenBalancesController} */\nexport type TokenBalancesPollingInput = {\n chainId: Hex;\n};\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends StaticIntervalPollingController()<\n typeof controllerName,\n TokenBalancesControllerState,\n TokenBalancesControllerMessenger\n> {\n #queryMultipleAccounts: boolean;\n\n #allTokens: TokensControllerState['allTokens'];\n\n #allDetectedTokens: TokensControllerState['allDetectedTokens'];\n\n /**\n * Construct a Token Balances Controller.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new token balances.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller restricted messenger.\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n messenger,\n state = {},\n }: TokenBalancesControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTokenBalancesState(),\n ...state,\n },\n });\n\n this.setIntervalLength(interval);\n\n // Set initial preference for querying multiple accounts, and subscribe to changes\n this.#queryMultipleAccounts = this.#calculateQueryMultipleAccounts(\n this.messagingSystem.call('PreferencesController:getState'),\n );\n this.messagingSystem.subscribe(\n 'PreferencesController:stateChange',\n this.#onPreferencesStateChange.bind(this),\n );\n\n // Set initial tokens, and subscribe to changes\n ({\n allTokens: this.#allTokens,\n allDetectedTokens: this.#allDetectedTokens,\n } = this.messagingSystem.call('TokensController:getState'));\n\n this.messagingSystem.subscribe(\n 'TokensController:stateChange',\n this.#onTokensStateChange.bind(this),\n );\n\n // Subscribe to network state changes\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n this.#onNetworkStateChange.bind(this),\n );\n }\n\n /**\n * Determines whether to query all accounts, or just the selected account.\n * @param preferences - The preferences state.\n * @param preferences.isMultiAccountBalancesEnabled - whether to query all accounts (mobile).\n * @param preferences.useMultiAccountBalanceChecker - whether to query all accounts (extension).\n * @returns true if all accounts should be queried.\n */\n #calculateQueryMultipleAccounts = ({\n isMultiAccountBalancesEnabled,\n useMultiAccountBalanceChecker,\n }: PreferencesState & { useMultiAccountBalanceChecker?: boolean }) => {\n return Boolean(\n // Note: These settings have different names on extension vs mobile\n isMultiAccountBalancesEnabled || useMultiAccountBalanceChecker,\n );\n };\n\n /**\n * Handles the event for preferences state changes.\n * @param preferences - The preferences state.\n */\n #onPreferencesStateChange = (preferences: PreferencesState) => {\n // Update the user preference for whether to query multiple accounts.\n const queryMultipleAccounts =\n this.#calculateQueryMultipleAccounts(preferences);\n\n // Refresh when flipped off -> on\n const refresh = queryMultipleAccounts && !this.#queryMultipleAccounts;\n this.#queryMultipleAccounts = queryMultipleAccounts;\n\n if (refresh) {\n this.updateBalances().catch(console.error);\n }\n };\n\n /**\n * Handles the event for tokens state changes.\n * @param state - The token state.\n * @param state.allTokens - The state for imported tokens across all chains.\n * @param state.allDetectedTokens - The state for detected tokens across all chains.\n */\n #onTokensStateChange = ({\n allTokens,\n allDetectedTokens,\n }: TokensControllerState) => {\n // Refresh token balances on chains whose tokens have changed.\n const chainIds = this.#getChainIds(allTokens, allDetectedTokens);\n const chainIdsToUpdate = chainIds.filter(\n (chainId) =>\n !isEqual(this.#allTokens[chainId], allTokens[chainId]) ||\n !isEqual(this.#allDetectedTokens[chainId], allDetectedTokens[chainId]),\n );\n\n this.#allTokens = allTokens;\n this.#allDetectedTokens = allDetectedTokens;\n\n this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error);\n };\n\n /**\n * Handles the event for network state changes.\n * @param _ - The network state.\n * @param patches - An array of patch operations performed on the network state.\n */\n #onNetworkStateChange(_: NetworkState, patches: Patch[]) {\n // Remove state for deleted networks\n for (const patch of patches) {\n if (\n patch.op === 'remove' &&\n patch.path[0] === 'networkConfigurationsByChainId'\n ) {\n const removedChainId = patch.path[1] as Hex;\n\n this.update((state) => {\n for (const accountAddress of Object.keys(state.tokenBalances)) {\n delete state.tokenBalances[accountAddress as Hex][removedChainId];\n }\n });\n }\n }\n }\n\n /**\n * Returns an array of chain ids that have tokens.\n * @param allTokens - The state for imported tokens across all chains.\n * @param allDetectedTokens - The state for detected tokens across all chains.\n * @returns An array of chain ids that have tokens.\n */\n #getChainIds = (\n allTokens: TokensControllerState['allTokens'],\n allDetectedTokens: TokensControllerState['allDetectedTokens'],\n ) =>\n [\n ...new Set([\n ...Object.keys(allTokens),\n ...Object.keys(allDetectedTokens),\n ]),\n ] as Hex[];\n\n /**\n * Polls for erc20 token balances.\n * @param input - The input for the poll.\n * @param input.chainId - The chain id to poll token balances on.\n */\n async _executePoll({ chainId }: TokenBalancesPollingInput) {\n await this.updateBalancesByChainId({ chainId });\n }\n\n /**\n * Updates the token balances for the given chain ids.\n * @param input - The input for the update.\n * @param input.chainIds - The chain ids to update token balances for.\n * Or omitted to update all chains that contain tokens.\n */\n async updateBalances({ chainIds }: { chainIds?: Hex[] } = {}) {\n chainIds ??= this.#getChainIds(this.#allTokens, this.#allDetectedTokens);\n\n await Promise.allSettled(\n chainIds.map((chainId) => this.updateBalancesByChainId({ chainId })),\n );\n }\n\n /**\n * Updates token balances for the given chain id.\n * @param input - The input for the update.\n * @param input.chainId - The chain id to update token balances on.\n */\n async updateBalancesByChainId({ chainId }: { chainId: Hex }) {\n const { address: selectedAccountAddress } = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n\n const isSelectedAccount = (accountAddress: string) =>\n toChecksumHexAddress(accountAddress) ===\n toChecksumHexAddress(selectedAccountAddress);\n\n const accountTokenPairs: { accountAddress: Hex; tokenAddress: Hex }[] = [];\n\n const addTokens = ([accountAddress, tokens]: [string, Token[]]) =>\n this.#queryMultipleAccounts || isSelectedAccount(accountAddress)\n ? tokens.forEach((t) =>\n accountTokenPairs.push({\n accountAddress: accountAddress as Hex,\n tokenAddress: t.address as Hex,\n }),\n )\n : undefined;\n\n // Balances will be updated for both imported and detected tokens\n Object.entries(this.#allTokens[chainId] ?? {}).forEach(addTokens);\n Object.entries(this.#allDetectedTokens[chainId] ?? {}).forEach(addTokens);\n\n let results: MulticallResult[] = [];\n\n if (accountTokenPairs.length > 0) {\n const provider = new Web3Provider(\n this.#getNetworkClient(chainId).provider,\n );\n\n const calls = accountTokenPairs.map(\n ({ accountAddress, tokenAddress }) => ({\n contract: new Contract(tokenAddress, abiERC20, provider),\n functionSignature: 'balanceOf(address)',\n arguments: [accountAddress],\n }),\n );\n\n results = await multicallOrFallback(calls, chainId, provider);\n }\n\n this.update((state) => {\n // Reset so that when accounts or tokens are removed,\n // their balances are removed rather than left stale.\n for (const accountAddress of Object.keys(state.tokenBalances)) {\n state.tokenBalances[accountAddress as Hex][chainId] = {};\n }\n\n for (let i = 0; i < results.length; i++) {\n const { success, value } = results[i];\n const { accountAddress, tokenAddress } = accountTokenPairs[i];\n\n if (success) {\n ((state.tokenBalances[accountAddress] ??= {})[chainId] ??= {})[\n tokenAddress\n ] = toHex(value as BN);\n }\n }\n });\n }\n\n /**\n * Reset the controller state to the default state.\n */\n resetState() {\n this.update(() => {\n return getDefaultTokenBalancesState();\n });\n }\n\n /**\n * Returns the network client for a given chain id\n * @param chainId - The chain id to get the network client for.\n * @returns The network client for the given chain id.\n */\n #getNetworkClient(chainId: Hex) {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n throw new Error(\n `TokenBalancesController: No network configuration found for chainId ${chainId}`,\n );\n }\n\n const { networkClientId } =\n networkConfiguration.rpcEndpoints[\n networkConfiguration.defaultRpcEndpointIndex\n ];\n\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n );\n }\n}\n\nexport default TokenBalancesController;\n"]} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts +index d5e18e6..f1e74fe 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts +@@ -1,40 +1,38 @@ + import type { AccountsControllerGetSelectedAccountAction } from "@metamask/accounts-controller"; + import type { RestrictedControllerMessenger, ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller"; +-import { BaseController } from "@metamask/base-controller"; +-import type { AssetsContractControllerGetERC20BalanceOfAction } from "./AssetsContractController.cjs"; +-import type { Token } from "./TokenRatesController.cjs"; +-import type { TokensControllerStateChangeEvent } from "./TokensController.cjs"; ++import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerStateChangeEvent } from "@metamask/network-controller"; ++import type { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent } from "@metamask/preferences-controller"; ++import type { Hex } from "@metamask/utils"; ++import type { TokensControllerGetStateAction, TokensControllerStateChangeEvent } from "./TokensController.cjs"; + declare const controllerName = "TokenBalancesController"; + /** + * Token balances controller options + * @property interval - Polling interval used to fetch new token balances. +- * @property tokens - List of tokens to track balances for. +- * @property disabled - If set to true, all tracked tokens contract balances updates are blocked. ++ * @property messenger - A controller messenger. ++ * @property state - Initial state for the controller. + */ + type TokenBalancesControllerOptions = { + interval?: number; +- tokens?: Token[]; +- disabled?: boolean; + messenger: TokenBalancesControllerMessenger; + state?: Partial; + }; + /** +- * Represents a mapping of hash token contract addresses to their balances. ++ * A mapping from account address to chain id to token address to balance. + */ +-type ContractBalances = Record; ++type TokenBalances = Record>>; + /** + * Token balances controller state +- * @property contractBalances - Hash of token contract addresses to balances ++ * @property tokenBalances - A mapping from account address to chain id to token address to balance. + */ + export type TokenBalancesControllerState = { +- contractBalances: ContractBalances; ++ tokenBalances: TokenBalances; + }; + export type TokenBalancesControllerGetStateAction = ControllerGetStateAction; + export type TokenBalancesControllerActions = TokenBalancesControllerGetStateAction; +-export type AllowedActions = AccountsControllerGetSelectedAccountAction | AssetsContractControllerGetERC20BalanceOfAction; ++export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction; + export type TokenBalancesControllerStateChangeEvent = ControllerStateChangeEvent; + export type TokenBalancesControllerEvents = TokenBalancesControllerStateChangeEvent; +-export type AllowedEvents = TokensControllerStateChangeEvent; ++export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent; + export type TokenBalancesControllerMessenger = RestrictedControllerMessenger; + /** + * Get the default TokenBalancesController state. +@@ -42,41 +40,63 @@ export type TokenBalancesControllerMessenger = RestrictedControllerMessenger { ++ readonly "__#787887@#intervalIds": Record; ++ "__#787887@#intervalLength": number | undefined; ++ setIntervalLength(intervalLength: number): void; ++ getIntervalLength(): number | undefined; ++ _startPolling(input: TokenBalancesPollingInput): void; ++ _stopPollingByPollingTokenSetId(key: string): void; ++ readonly "__#787879@#pollingTokenSets": Map>; ++ "__#787879@#callbacks": Map void>>; ++ _executePoll(input: TokenBalancesPollingInput): Promise; ++ startPolling(input: TokenBalancesPollingInput): string; ++ stopAllPolling(): void; ++ stopPollingByPollingToken(pollingToken: string): void; ++ onPollingComplete(input: TokenBalancesPollingInput, callback: (input: TokenBalancesPollingInput) => void): void; ++}) & typeof import("@metamask/base-controller").BaseController; + /** + * Controller that passively polls on a set interval token balances + * for tokens stored in the TokensController + */ +-export declare class TokenBalancesController extends BaseController { ++export declare class TokenBalancesController extends TokenBalancesController_base { + #private; + /** + * Construct a Token Balances Controller. + * + * @param options - The controller options. + * @param options.interval - Polling interval used to fetch new token balances. +- * @param options.tokens - List of tokens to track balances for. +- * @param options.disabled - If set to true, all tracked tokens contract balances updates are blocked. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller restricted messenger. + */ +- constructor({ interval, tokens, disabled, messenger, state, }: TokenBalancesControllerOptions); +- /** +- * Allows controller to update tracked tokens contract balances. +- */ +- enable(): void; ++ constructor({ interval, messenger, state, }: TokenBalancesControllerOptions); + /** +- * Blocks controller from updating tracked tokens contract balances. ++ * Polls for erc20 token balances. ++ * @param input - The input for the poll. ++ * @param input.chainId - The chain id to poll token balances on. + */ +- disable(): void; ++ _executePoll({ chainId }: TokenBalancesPollingInput): Promise; + /** +- * Starts a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token balances. ++ * Updates the token balances for the given chain ids. ++ * @param input - The input for the update. ++ * @param input.chainIds - The chain ids to update token balances for. ++ * Or omitted to update all chains that contain tokens. + */ +- poll(interval?: number): Promise; ++ updateBalances({ chainIds }?: { ++ chainIds?: Hex[]; ++ }): Promise; + /** +- * Updates balances for all tokens. ++ * Updates token balances for the given chain id. ++ * @param input - The input for the update. ++ * @param input.chainId - The chain id to update token balances on. + */ +- updateBalances(): Promise; ++ updateBalancesByChainId({ chainId }: { ++ chainId: Hex; ++ }): Promise; + /** + * Reset the controller state to the default state. + */ +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts.map b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts.map +index 1340bd2..1f8480a 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts.map ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.cts.map +@@ -1 +1 @@ +-{"version":3,"file":"TokenBalancesController.d.cts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAG3D,OAAO,KAAK,EAAE,+CAA+C,EAAE,uCAAmC;AAClG,OAAO,KAAK,EAAE,KAAK,EAAE,mCAA+B;AACpD,OAAO,KAAK,EAAE,gCAAgC,EAAE,+BAA2B;AAI3E,QAAA,MAAM,cAAc,4BAA4B,CAAC;AAMjD;;;;;GAKG;AACH,KAAK,8BAA8B,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,gCAAgC,CAAC;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG,wBAAwB,CAC1E,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GACxC,qCAAqC,CAAC;AAExC,MAAM,MAAM,cAAc,GACtB,0CAA0C,GAC1C,+CAA+C,CAAC;AAEpD,MAAM,MAAM,uCAAuC,GACjD,0BAA0B,CACxB,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEJ,MAAM,MAAM,6BAA6B,GACvC,uCAAuC,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAE7D,MAAM,MAAM,gCAAgC,GAAG,6BAA6B,CAC1E,OAAO,cAAc,EACrB,8BAA8B,GAAG,cAAc,EAC/C,6BAA6B,GAAG,aAAa,EAC7C,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,4BAA4B,CAI3E;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,cAAc,CACzD,OAAO,cAAc,EACrB,4BAA4B,EAC5B,gCAAgC,CACjC;;IASC;;;;;;;;;OASG;gBACS,EACV,QAA2B,EAC3B,MAAW,EACX,QAAgB,EAChB,SAAS,EACT,KAAU,GACX,EAAE,8BAA8B;IA8BjC;;OAEG;IACH,MAAM;IAIN;;OAEG;IACH,OAAO;IAIP;;;;OAIG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5C;;OAEG;IACG,cAAc;IA8BpB;;OAEG;IACH,UAAU;CAKX;AAED,eAAe,uBAAuB,CAAC"} +\ No newline at end of file ++{"version":3,"file":"TokenBalancesController.d.cts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AAGnC,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAC/B,iCAAiC,EAElC,qCAAqC;AAEtC,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EAEtC,yCAAyC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAQ3C,OAAO,KAAK,EACV,8BAA8B,EAE9B,gCAAgC,EACjC,+BAA2B;AAI5B,QAAA,MAAM,cAAc,4BAA4B,CAAC;AAMjD;;;;;GAKG;AACH,KAAK,8BAA8B,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,gCAAgC,CAAC;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,KAAK,aAAa,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG,wBAAwB,CAC1E,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GACxC,qCAAqC,CAAC;AAExC,MAAM,MAAM,cAAc,GACtB,2CAA2C,GAC3C,+BAA+B,GAC/B,8BAA8B,GAC9B,mCAAmC,GACnC,0CAA0C,CAAC;AAE/C,MAAM,MAAM,uCAAuC,GACjD,0BAA0B,CACxB,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEJ,MAAM,MAAM,6BAA6B,GACvC,uCAAuC,CAAC;AAE1C,MAAM,MAAM,aAAa,GACrB,gCAAgC,GAChC,qCAAqC,GACrC,iCAAiC,CAAC;AAEtC,MAAM,MAAM,gCAAgC,GAAG,6BAA6B,CAC1E,OAAO,cAAc,EACrB,8BAA8B,GAAG,cAAc,EAC/C,6BAA6B,GAAG,aAAa,EAC7C,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,4BAA4B,CAI3E;AAED,yEAAyE;AACzE,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,GAAG,CAAC;CACd,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,6BAC3C,OAAO,cAAc,EACrB,4BAA4B,EAC5B,gCAAgC,CACjC;;IAOC;;;;;;;OAOG;gBACS,EACV,QAA2B,EAC3B,SAAS,EACT,KAAU,GACX,EAAE,8BAA8B;IA2IjC;;;;OAIG;IACG,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,yBAAyB;IAIzD;;;;;OAKG;IACG,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAA;KAAO;IAQ5D;;;;OAIG;IACG,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE;IA+D3D;;OAEG;IACH,UAAU;CAiCX;AAED,eAAe,uBAAuB,CAAC"} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts +index 7695335..169b9d1 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts +@@ -1,40 +1,38 @@ + import type { AccountsControllerGetSelectedAccountAction } from "@metamask/accounts-controller"; + import type { RestrictedControllerMessenger, ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller"; +-import { BaseController } from "@metamask/base-controller"; +-import type { AssetsContractControllerGetERC20BalanceOfAction } from "./AssetsContractController.mjs"; +-import type { Token } from "./TokenRatesController.mjs"; +-import type { TokensControllerStateChangeEvent } from "./TokensController.mjs"; ++import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerStateChangeEvent } from "@metamask/network-controller"; ++import type { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent } from "@metamask/preferences-controller"; ++import type { Hex } from "@metamask/utils"; ++import type { TokensControllerGetStateAction, TokensControllerStateChangeEvent } from "./TokensController.mjs"; + declare const controllerName = "TokenBalancesController"; + /** + * Token balances controller options + * @property interval - Polling interval used to fetch new token balances. +- * @property tokens - List of tokens to track balances for. +- * @property disabled - If set to true, all tracked tokens contract balances updates are blocked. ++ * @property messenger - A controller messenger. ++ * @property state - Initial state for the controller. + */ + type TokenBalancesControllerOptions = { + interval?: number; +- tokens?: Token[]; +- disabled?: boolean; + messenger: TokenBalancesControllerMessenger; + state?: Partial; + }; + /** +- * Represents a mapping of hash token contract addresses to their balances. ++ * A mapping from account address to chain id to token address to balance. + */ +-type ContractBalances = Record; ++type TokenBalances = Record>>; + /** + * Token balances controller state +- * @property contractBalances - Hash of token contract addresses to balances ++ * @property tokenBalances - A mapping from account address to chain id to token address to balance. + */ + export type TokenBalancesControllerState = { +- contractBalances: ContractBalances; ++ tokenBalances: TokenBalances; + }; + export type TokenBalancesControllerGetStateAction = ControllerGetStateAction; + export type TokenBalancesControllerActions = TokenBalancesControllerGetStateAction; +-export type AllowedActions = AccountsControllerGetSelectedAccountAction | AssetsContractControllerGetERC20BalanceOfAction; ++export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction; + export type TokenBalancesControllerStateChangeEvent = ControllerStateChangeEvent; + export type TokenBalancesControllerEvents = TokenBalancesControllerStateChangeEvent; +-export type AllowedEvents = TokensControllerStateChangeEvent; ++export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent; + export type TokenBalancesControllerMessenger = RestrictedControllerMessenger; + /** + * Get the default TokenBalancesController state. +@@ -42,41 +40,63 @@ export type TokenBalancesControllerMessenger = RestrictedControllerMessenger { ++ readonly "__#787887@#intervalIds": Record; ++ "__#787887@#intervalLength": number | undefined; ++ setIntervalLength(intervalLength: number): void; ++ getIntervalLength(): number | undefined; ++ _startPolling(input: TokenBalancesPollingInput): void; ++ _stopPollingByPollingTokenSetId(key: string): void; ++ readonly "__#787879@#pollingTokenSets": Map>; ++ "__#787879@#callbacks": Map void>>; ++ _executePoll(input: TokenBalancesPollingInput): Promise; ++ startPolling(input: TokenBalancesPollingInput): string; ++ stopAllPolling(): void; ++ stopPollingByPollingToken(pollingToken: string): void; ++ onPollingComplete(input: TokenBalancesPollingInput, callback: (input: TokenBalancesPollingInput) => void): void; ++}) & typeof import("@metamask/base-controller").BaseController; + /** + * Controller that passively polls on a set interval token balances + * for tokens stored in the TokensController + */ +-export declare class TokenBalancesController extends BaseController { ++export declare class TokenBalancesController extends TokenBalancesController_base { + #private; + /** + * Construct a Token Balances Controller. + * + * @param options - The controller options. + * @param options.interval - Polling interval used to fetch new token balances. +- * @param options.tokens - List of tokens to track balances for. +- * @param options.disabled - If set to true, all tracked tokens contract balances updates are blocked. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller restricted messenger. + */ +- constructor({ interval, tokens, disabled, messenger, state, }: TokenBalancesControllerOptions); +- /** +- * Allows controller to update tracked tokens contract balances. +- */ +- enable(): void; ++ constructor({ interval, messenger, state, }: TokenBalancesControllerOptions); + /** +- * Blocks controller from updating tracked tokens contract balances. ++ * Polls for erc20 token balances. ++ * @param input - The input for the poll. ++ * @param input.chainId - The chain id to poll token balances on. + */ +- disable(): void; ++ _executePoll({ chainId }: TokenBalancesPollingInput): Promise; + /** +- * Starts a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token balances. ++ * Updates the token balances for the given chain ids. ++ * @param input - The input for the update. ++ * @param input.chainIds - The chain ids to update token balances for. ++ * Or omitted to update all chains that contain tokens. + */ +- poll(interval?: number): Promise; ++ updateBalances({ chainIds }?: { ++ chainIds?: Hex[]; ++ }): Promise; + /** +- * Updates balances for all tokens. ++ * Updates token balances for the given chain id. ++ * @param input - The input for the update. ++ * @param input.chainId - The chain id to update token balances on. + */ +- updateBalances(): Promise; ++ updateBalancesByChainId({ chainId }: { ++ chainId: Hex; ++ }): Promise; + /** + * Reset the controller state to the default state. + */ +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts.map b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts.map +index 1ec203c..a4baad6 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts.map ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.d.mts.map +@@ -1 +1 @@ +-{"version":3,"file":"TokenBalancesController.d.mts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAG3D,OAAO,KAAK,EAAE,+CAA+C,EAAE,uCAAmC;AAClG,OAAO,KAAK,EAAE,KAAK,EAAE,mCAA+B;AACpD,OAAO,KAAK,EAAE,gCAAgC,EAAE,+BAA2B;AAI3E,QAAA,MAAM,cAAc,4BAA4B,CAAC;AAMjD;;;;;GAKG;AACH,KAAK,8BAA8B,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,gCAAgC,CAAC;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG,wBAAwB,CAC1E,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GACxC,qCAAqC,CAAC;AAExC,MAAM,MAAM,cAAc,GACtB,0CAA0C,GAC1C,+CAA+C,CAAC;AAEpD,MAAM,MAAM,uCAAuC,GACjD,0BAA0B,CACxB,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEJ,MAAM,MAAM,6BAA6B,GACvC,uCAAuC,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAE7D,MAAM,MAAM,gCAAgC,GAAG,6BAA6B,CAC1E,OAAO,cAAc,EACrB,8BAA8B,GAAG,cAAc,EAC/C,6BAA6B,GAAG,aAAa,EAC7C,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,4BAA4B,CAI3E;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,cAAc,CACzD,OAAO,cAAc,EACrB,4BAA4B,EAC5B,gCAAgC,CACjC;;IASC;;;;;;;;;OASG;gBACS,EACV,QAA2B,EAC3B,MAAW,EACX,QAAgB,EAChB,SAAS,EACT,KAAU,GACX,EAAE,8BAA8B;IA8BjC;;OAEG;IACH,MAAM;IAIN;;OAEG;IACH,OAAO;IAIP;;;;OAIG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5C;;OAEG;IACG,cAAc;IA8BpB;;OAEG;IACH,UAAU;CAKX;AAED,eAAe,uBAAuB,CAAC"} +\ No newline at end of file ++{"version":3,"file":"TokenBalancesController.d.mts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AAGnC,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAC/B,iCAAiC,EAElC,qCAAqC;AAEtC,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EAEtC,yCAAyC;AAC1C,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAQ3C,OAAO,KAAK,EACV,8BAA8B,EAE9B,gCAAgC,EACjC,+BAA2B;AAI5B,QAAA,MAAM,cAAc,4BAA4B,CAAC;AAMjD;;;;;GAKG;AACH,KAAK,8BAA8B,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,gCAAgC,CAAC;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,KAAK,aAAa,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG,wBAAwB,CAC1E,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GACxC,qCAAqC,CAAC;AAExC,MAAM,MAAM,cAAc,GACtB,2CAA2C,GAC3C,+BAA+B,GAC/B,8BAA8B,GAC9B,mCAAmC,GACnC,0CAA0C,CAAC;AAE/C,MAAM,MAAM,uCAAuC,GACjD,0BAA0B,CACxB,OAAO,cAAc,EACrB,4BAA4B,CAC7B,CAAC;AAEJ,MAAM,MAAM,6BAA6B,GACvC,uCAAuC,CAAC;AAE1C,MAAM,MAAM,aAAa,GACrB,gCAAgC,GAChC,qCAAqC,GACrC,iCAAiC,CAAC;AAEtC,MAAM,MAAM,gCAAgC,GAAG,6BAA6B,CAC1E,OAAO,cAAc,EACrB,8BAA8B,GAAG,cAAc,EAC/C,6BAA6B,GAAG,aAAa,EAC7C,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,4BAA4B,IAAI,4BAA4B,CAI3E;AAED,yEAAyE;AACzE,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,GAAG,CAAC;CACd,CAAC;;;;;;;;;;;;;;;;AAEF;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,6BAC3C,OAAO,cAAc,EACrB,4BAA4B,EAC5B,gCAAgC,CACjC;;IAOC;;;;;;;OAOG;gBACS,EACV,QAA2B,EAC3B,SAAS,EACT,KAAU,GACX,EAAE,8BAA8B;IA2IjC;;;;OAIG;IACG,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,yBAAyB;IAIzD;;;;;OAKG;IACG,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAA;KAAO;IAQ5D;;;;OAIG;IACG,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE;IA+D3D;;OAEG;IACH,UAAU;CAiCX;AAED,eAAe,uBAAuB,CAAC"} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs +index a87eb7a..f228960 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs +@@ -1,21 +1,27 @@ ++var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { ++ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); ++ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); ++ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); ++}; + var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; + }; +-var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { +- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); +- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); +- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +-}; +-var _TokenBalancesController_handle, _TokenBalancesController_interval, _TokenBalancesController_tokens, _TokenBalancesController_disabled; +-import { BaseController } from "@metamask/base-controller"; +-import { safelyExecute, toHex } from "@metamask/controller-utils"; ++var _TokenBalancesController_instances, _TokenBalancesController_queryMultipleAccounts, _TokenBalancesController_allTokens, _TokenBalancesController_allDetectedTokens, _TokenBalancesController_calculateQueryMultipleAccounts, _TokenBalancesController_onPreferencesStateChange, _TokenBalancesController_onTokensStateChange, _TokenBalancesController_onNetworkStateChange, _TokenBalancesController_getChainIds, _TokenBalancesController_getNetworkClient; ++import { Contract } from "@ethersproject/contracts"; ++import { Web3Provider } from "@ethersproject/providers"; ++import { toChecksumHexAddress, toHex } from "@metamask/controller-utils"; ++import { abiERC20 } from "@metamask/metamask-eth-abis"; ++import { StaticIntervalPollingController } from "@metamask/polling-controller"; ++import $lodash from "lodash"; ++const { isEqual } = $lodash; ++import { multicallOrFallback } from "./multicall.mjs"; + const DEFAULT_INTERVAL = 180000; + const controllerName = 'TokenBalancesController'; + const metadata = { +- contractBalances: { persist: true, anonymous: false }, ++ tokenBalances: { persist: true, anonymous: false }, + }; + /** + * Get the default TokenBalancesController state. +@@ -24,25 +30,24 @@ const metadata = { + */ + export function getDefaultTokenBalancesState() { + return { +- contractBalances: {}, ++ tokenBalances: {}, + }; + } + /** + * Controller that passively polls on a set interval token balances + * for tokens stored in the TokensController + */ +-export class TokenBalancesController extends BaseController { ++export class TokenBalancesController extends StaticIntervalPollingController() { + /** + * Construct a Token Balances Controller. + * + * @param options - The controller options. + * @param options.interval - Polling interval used to fetch new token balances. +- * @param options.tokens - List of tokens to track balances for. +- * @param options.disabled - If set to true, all tracked tokens contract balances updates are blocked. + * @param options.state - Initial state to set on this controller. + * @param options.messenger - The controller restricted messenger. + */ +- constructor({ interval = DEFAULT_INTERVAL, tokens = [], disabled = false, messenger, state = {}, }) { ++ constructor({ interval = DEFAULT_INTERVAL, messenger, state = {}, }) { ++ var _a, _b; + super({ + name: controllerName, + metadata, +@@ -52,77 +57,137 @@ export class TokenBalancesController extends BaseController { + ...state, + }, + }); +- _TokenBalancesController_handle.set(this, void 0); +- _TokenBalancesController_interval.set(this, void 0); +- _TokenBalancesController_tokens.set(this, void 0); +- _TokenBalancesController_disabled.set(this, void 0); +- __classPrivateFieldSet(this, _TokenBalancesController_disabled, disabled, "f"); +- __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); +- __classPrivateFieldSet(this, _TokenBalancesController_tokens, tokens, "f"); +- this.messagingSystem.subscribe('TokensController:stateChange', ({ tokens: newTokens, detectedTokens }) => { +- __classPrivateFieldSet(this, _TokenBalancesController_tokens, [...newTokens, ...detectedTokens], "f"); +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.updateBalances(); ++ _TokenBalancesController_instances.add(this); ++ _TokenBalancesController_queryMultipleAccounts.set(this, void 0); ++ _TokenBalancesController_allTokens.set(this, void 0); ++ _TokenBalancesController_allDetectedTokens.set(this, void 0); ++ /** ++ * Determines whether to query all accounts, or just the selected account. ++ * @param preferences - The preferences state. ++ * @param preferences.isMultiAccountBalancesEnabled - whether to query all accounts (mobile). ++ * @param preferences.useMultiAccountBalanceChecker - whether to query all accounts (extension). ++ * @returns true if all accounts should be queried. ++ */ ++ _TokenBalancesController_calculateQueryMultipleAccounts.set(this, ({ isMultiAccountBalancesEnabled, useMultiAccountBalanceChecker, }) => { ++ return Boolean( ++ // Note: These settings have different names on extension vs mobile ++ isMultiAccountBalancesEnabled || useMultiAccountBalanceChecker); + }); +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.poll(); +- } +- /** +- * Allows controller to update tracked tokens contract balances. +- */ +- enable() { +- __classPrivateFieldSet(this, _TokenBalancesController_disabled, false, "f"); ++ /** ++ * Handles the event for preferences state changes. ++ * @param preferences - The preferences state. ++ */ ++ _TokenBalancesController_onPreferencesStateChange.set(this, (preferences) => { ++ // Update the user preference for whether to query multiple accounts. ++ const queryMultipleAccounts = __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, preferences); ++ // Refresh when flipped off -> on ++ const refresh = queryMultipleAccounts && !__classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f"); ++ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, queryMultipleAccounts, "f"); ++ if (refresh) { ++ this.updateBalances().catch(console.error); ++ } ++ }); ++ /** ++ * Handles the event for tokens state changes. ++ * @param state - The token state. ++ * @param state.allTokens - The state for imported tokens across all chains. ++ * @param state.allDetectedTokens - The state for detected tokens across all chains. ++ */ ++ _TokenBalancesController_onTokensStateChange.set(this, ({ allTokens, allDetectedTokens, }) => { ++ // Refresh token balances on chains whose tokens have changed. ++ const chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, allTokens, allDetectedTokens); ++ const chainIdsToUpdate = chainIds.filter((chainId) => !isEqual(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId], allTokens[chainId]) || ++ !isEqual(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId], allDetectedTokens[chainId])); ++ __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f"); ++ __classPrivateFieldSet(this, _TokenBalancesController_allDetectedTokens, allDetectedTokens, "f"); ++ this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error); ++ }); ++ /** ++ * Returns an array of chain ids that have tokens. ++ * @param allTokens - The state for imported tokens across all chains. ++ * @param allDetectedTokens - The state for detected tokens across all chains. ++ * @returns An array of chain ids that have tokens. ++ */ ++ _TokenBalancesController_getChainIds.set(this, (allTokens, allDetectedTokens) => [ ++ ...new Set([ ++ ...Object.keys(allTokens), ++ ...Object.keys(allDetectedTokens), ++ ]), ++ ]); ++ this.setIntervalLength(interval); ++ // Set initial preference for querying multiple accounts, and subscribe to changes ++ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, this.messagingSystem.call('PreferencesController:getState')), "f"); ++ this.messagingSystem.subscribe('PreferencesController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onPreferencesStateChange, "f").bind(this)); ++ // Set initial tokens, and subscribe to changes ++ (_a = this, _b = this, { ++ allTokens: ({ set value(_c) { __classPrivateFieldSet(_a, _TokenBalancesController_allTokens, _c, "f"); } }).value, ++ allDetectedTokens: ({ set value(_c) { __classPrivateFieldSet(_b, _TokenBalancesController_allDetectedTokens, _c, "f"); } }).value, ++ } = this.messagingSystem.call('TokensController:getState')); ++ this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensStateChange, "f").bind(this)); ++ // Subscribe to network state changes ++ this.messagingSystem.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_onNetworkStateChange).bind(this)); + } + /** +- * Blocks controller from updating tracked tokens contract balances. ++ * Polls for erc20 token balances. ++ * @param input - The input for the poll. ++ * @param input.chainId - The chain id to poll token balances on. + */ +- disable() { +- __classPrivateFieldSet(this, _TokenBalancesController_disabled, true, "f"); ++ async _executePoll({ chainId }) { ++ await this.updateBalancesByChainId({ chainId }); + } + /** +- * Starts a new polling interval. +- * +- * @param interval - Polling interval used to fetch new token balances. ++ * Updates the token balances for the given chain ids. ++ * @param input - The input for the update. ++ * @param input.chainIds - The chain ids to update token balances for. ++ * Or omitted to update all chains that contain tokens. + */ +- async poll(interval) { +- if (interval) { +- __classPrivateFieldSet(this, _TokenBalancesController_interval, interval, "f"); +- } +- if (__classPrivateFieldGet(this, _TokenBalancesController_handle, "f")) { +- clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_handle, "f")); +- } +- await safelyExecute(() => this.updateBalances()); +- __classPrivateFieldSet(this, _TokenBalancesController_handle, setTimeout(() => { +- // TODO: Either fix this lint violation or explain why it's necessary to ignore. +- // eslint-disable-next-line @typescript-eslint/no-floating-promises +- this.poll(__classPrivateFieldGet(this, _TokenBalancesController_interval, "f")); +- }, __classPrivateFieldGet(this, _TokenBalancesController_interval, "f")), "f"); ++ async updateBalances({ chainIds } = {}) { ++ chainIds ?? (chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f"), __classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f"))); ++ await Promise.allSettled(chainIds.map((chainId) => this.updateBalancesByChainId({ chainId }))); + } + /** +- * Updates balances for all tokens. ++ * Updates token balances for the given chain id. ++ * @param input - The input for the update. ++ * @param input.chainId - The chain id to update token balances on. + */ +- async updateBalances() { +- if (__classPrivateFieldGet(this, _TokenBalancesController_disabled, "f")) { +- return; ++ async updateBalancesByChainId({ chainId }) { ++ const { address: selectedAccountAddress } = this.messagingSystem.call('AccountsController:getSelectedAccount'); ++ const isSelectedAccount = (accountAddress) => toChecksumHexAddress(accountAddress) === ++ toChecksumHexAddress(selectedAccountAddress); ++ const accountTokenPairs = []; ++ const addTokens = ([accountAddress, tokens]) => __classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f") || isSelectedAccount(accountAddress) ++ ? tokens.forEach((t) => accountTokenPairs.push({ ++ accountAddress: accountAddress, ++ tokenAddress: t.address, ++ })) ++ : undefined; ++ // Balances will be updated for both imported and detected tokens ++ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId] ?? {}).forEach(addTokens); ++ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId] ?? {}).forEach(addTokens); ++ let results = []; ++ if (accountTokenPairs.length > 0) { ++ const provider = new Web3Provider(__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId).provider); ++ const calls = accountTokenPairs.map(({ accountAddress, tokenAddress }) => ({ ++ contract: new Contract(tokenAddress, abiERC20, provider), ++ functionSignature: 'balanceOf(address)', ++ arguments: [accountAddress], ++ })); ++ results = await multicallOrFallback(calls, chainId, provider); + } +- const selectedInternalAccount = this.messagingSystem.call('AccountsController:getSelectedAccount'); +- const newContractBalances = {}; +- for (const token of __classPrivateFieldGet(this, _TokenBalancesController_tokens, "f")) { +- const { address } = token; +- try { +- const balance = await this.messagingSystem.call('AssetsContractController:getERC20BalanceOf', address, selectedInternalAccount.address); +- newContractBalances[address] = toHex(balance); +- token.hasBalanceError = false; ++ this.update((state) => { ++ var _a, _b; ++ // Reset so that when accounts or tokens are removed, ++ // their balances are removed rather than left stale. ++ for (const accountAddress of Object.keys(state.tokenBalances)) { ++ state.tokenBalances[accountAddress][chainId] = {}; + } +- catch (error) { +- newContractBalances[address] = toHex(0); +- token.hasBalanceError = true; ++ for (let i = 0; i < results.length; i++) { ++ const { success, value } = results[i]; ++ const { accountAddress, tokenAddress } = accountTokenPairs[i]; ++ if (success) { ++ ((_b = ((_a = state.tokenBalances)[accountAddress] ?? (_a[accountAddress] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = toHex(value); ++ } + } +- } +- this.update((state) => { +- state.contractBalances = newContractBalances; + }); + } + /** +@@ -134,6 +199,27 @@ export class TokenBalancesController extends BaseController { + }); + } + } +-_TokenBalancesController_handle = new WeakMap(), _TokenBalancesController_interval = new WeakMap(), _TokenBalancesController_tokens = new WeakMap(), _TokenBalancesController_disabled = new WeakMap(); ++_TokenBalancesController_queryMultipleAccounts = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_allDetectedTokens = new WeakMap(), _TokenBalancesController_calculateQueryMultipleAccounts = new WeakMap(), _TokenBalancesController_onPreferencesStateChange = new WeakMap(), _TokenBalancesController_onTokensStateChange = new WeakMap(), _TokenBalancesController_getChainIds = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_onNetworkStateChange = function _TokenBalancesController_onNetworkStateChange(_, patches) { ++ // Remove state for deleted networks ++ for (const patch of patches) { ++ if (patch.op === 'remove' && ++ patch.path[0] === 'networkConfigurationsByChainId') { ++ const removedChainId = patch.path[1]; ++ this.update((state) => { ++ for (const accountAddress of Object.keys(state.tokenBalances)) { ++ delete state.tokenBalances[accountAddress][removedChainId]; ++ } ++ }); ++ } ++ } ++}, _TokenBalancesController_getNetworkClient = function _TokenBalancesController_getNetworkClient(chainId) { ++ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState'); ++ const networkConfiguration = networkConfigurationsByChainId[chainId]; ++ if (!networkConfiguration) { ++ throw new Error(`TokenBalancesController: No network configuration found for chainId ${chainId}`); ++ } ++ const { networkClientId } = networkConfiguration.rpcEndpoints[networkConfiguration.defaultRpcEndpointIndex]; ++ return this.messagingSystem.call(`NetworkController:getNetworkClientById`, networkClientId); ++}; + export default TokenBalancesController; + //# sourceMappingURL=TokenBalancesController.mjs.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs.map b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs.map +index a95bf2d..276bc2d 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs.map ++++ b/node_modules/@metamask/assets-controllers/dist/TokenBalancesController.mjs.map +@@ -1 +1 @@ +-{"version":3,"file":"TokenBalancesController.mjs","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { safelyExecute, toHex } from '@metamask/controller-utils';\n\nimport type { AssetsContractControllerGetERC20BalanceOfAction } from './AssetsContractController';\nimport type { Token } from './TokenRatesController';\nimport type { TokensControllerStateChangeEvent } from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\nconst controllerName = 'TokenBalancesController';\n\nconst metadata = {\n contractBalances: { persist: true, anonymous: false },\n};\n\n/**\n * Token balances controller options\n * @property interval - Polling interval used to fetch new token balances.\n * @property tokens - List of tokens to track balances for.\n * @property disabled - If set to true, all tracked tokens contract balances updates are blocked.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n tokens?: Token[];\n disabled?: boolean;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial;\n};\n\n/**\n * Represents a mapping of hash token contract addresses to their balances.\n */\ntype ContractBalances = Record;\n\n/**\n * Token balances controller state\n * @property contractBalances - Hash of token contract addresses to balances\n */\nexport type TokenBalancesControllerState = {\n contractBalances: ContractBalances;\n};\n\nexport type TokenBalancesControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenBalancesControllerState\n>;\n\nexport type TokenBalancesControllerActions =\n TokenBalancesControllerGetStateAction;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | AssetsContractControllerGetERC20BalanceOfAction;\n\nexport type TokenBalancesControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n TokenBalancesControllerState\n >;\n\nexport type TokenBalancesControllerEvents =\n TokenBalancesControllerStateChangeEvent;\n\nexport type AllowedEvents = TokensControllerStateChangeEvent;\n\nexport type TokenBalancesControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokenBalancesControllerActions | AllowedActions,\n TokenBalancesControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Get the default TokenBalancesController state.\n *\n * @returns The default TokenBalancesController state.\n */\nexport function getDefaultTokenBalancesState(): TokenBalancesControllerState {\n return {\n contractBalances: {},\n };\n}\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends BaseController<\n typeof controllerName,\n TokenBalancesControllerState,\n TokenBalancesControllerMessenger\n> {\n #handle?: ReturnType;\n\n #interval: number;\n\n #tokens: Token[];\n\n #disabled: boolean;\n\n /**\n * Construct a Token Balances Controller.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new token balances.\n * @param options.tokens - List of tokens to track balances for.\n * @param options.disabled - If set to true, all tracked tokens contract balances updates are blocked.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller restricted messenger.\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n tokens = [],\n disabled = false,\n messenger,\n state = {},\n }: TokenBalancesControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTokenBalancesState(),\n ...state,\n },\n });\n\n this.#disabled = disabled;\n this.#interval = interval;\n this.#tokens = tokens;\n\n this.messagingSystem.subscribe(\n 'TokensController:stateChange',\n ({ tokens: newTokens, detectedTokens }) => {\n this.#tokens = [...newTokens, ...detectedTokens];\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.updateBalances();\n },\n );\n\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.poll();\n }\n\n /**\n * Allows controller to update tracked tokens contract balances.\n */\n enable() {\n this.#disabled = false;\n }\n\n /**\n * Blocks controller from updating tracked tokens contract balances.\n */\n disable() {\n this.#disabled = true;\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - Polling interval used to fetch new token balances.\n */\n async poll(interval?: number): Promise {\n if (interval) {\n this.#interval = interval;\n }\n\n if (this.#handle) {\n clearTimeout(this.#handle);\n }\n\n await safelyExecute(() => this.updateBalances());\n\n this.#handle = setTimeout(() => {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.poll(this.#interval);\n }, this.#interval);\n }\n\n /**\n * Updates balances for all tokens.\n */\n async updateBalances() {\n if (this.#disabled) {\n return;\n }\n const selectedInternalAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n\n const newContractBalances: ContractBalances = {};\n for (const token of this.#tokens) {\n const { address } = token;\n try {\n const balance = await this.messagingSystem.call(\n 'AssetsContractController:getERC20BalanceOf',\n address,\n selectedInternalAccount.address,\n );\n newContractBalances[address] = toHex(balance);\n token.hasBalanceError = false;\n } catch (error) {\n newContractBalances[address] = toHex(0);\n token.hasBalanceError = true;\n }\n }\n\n this.update((state) => {\n state.contractBalances = newContractBalances;\n });\n }\n\n /**\n * Reset the controller state to the default state.\n */\n resetState() {\n this.update(() => {\n return getDefaultTokenBalancesState();\n });\n }\n}\n\nexport default TokenBalancesController;\n"]} +\ No newline at end of file ++{"version":3,"file":"TokenBalancesController.mjs","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';\nimport type {\n RestrictedControllerMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport { toChecksumHexAddress, toHex } from '@metamask/controller-utils';\nimport { abiERC20 } from '@metamask/metamask-eth-abis';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n NetworkControllerStateChangeEvent,\n NetworkState,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n PreferencesControllerGetStateAction,\n PreferencesControllerStateChangeEvent,\n PreferencesState,\n} from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\nimport type BN from 'bn.js';\nimport type { Patch } from 'immer';\nimport { isEqual } from 'lodash';\n\nimport type { MulticallResult } from './multicall';\nimport { multicallOrFallback } from './multicall';\nimport type { Token } from './TokenRatesController';\nimport type {\n TokensControllerGetStateAction,\n TokensControllerState,\n TokensControllerStateChangeEvent,\n} from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\nconst controllerName = 'TokenBalancesController';\n\nconst metadata = {\n tokenBalances: { persist: true, anonymous: false },\n};\n\n/**\n * Token balances controller options\n * @property interval - Polling interval used to fetch new token balances.\n * @property messenger - A controller messenger.\n * @property state - Initial state for the controller.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial;\n};\n\n/**\n * A mapping from account address to chain id to token address to balance.\n */\ntype TokenBalances = Record>>;\n\n/**\n * Token balances controller state\n * @property tokenBalances - A mapping from account address to chain id to token address to balance.\n */\nexport type TokenBalancesControllerState = {\n tokenBalances: TokenBalances;\n};\n\nexport type TokenBalancesControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenBalancesControllerState\n>;\n\nexport type TokenBalancesControllerActions =\n TokenBalancesControllerGetStateAction;\n\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction\n | TokensControllerGetStateAction\n | PreferencesControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction;\n\nexport type TokenBalancesControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n TokenBalancesControllerState\n >;\n\nexport type TokenBalancesControllerEvents =\n TokenBalancesControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | TokensControllerStateChangeEvent\n | PreferencesControllerStateChangeEvent\n | NetworkControllerStateChangeEvent;\n\nexport type TokenBalancesControllerMessenger = RestrictedControllerMessenger<\n typeof controllerName,\n TokenBalancesControllerActions | AllowedActions,\n TokenBalancesControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/**\n * Get the default TokenBalancesController state.\n *\n * @returns The default TokenBalancesController state.\n */\nexport function getDefaultTokenBalancesState(): TokenBalancesControllerState {\n return {\n tokenBalances: {},\n };\n}\n\n/** The input to start polling for the {@link TokenBalancesController} */\nexport type TokenBalancesPollingInput = {\n chainId: Hex;\n};\n\n/**\n * Controller that passively polls on a set interval token balances\n * for tokens stored in the TokensController\n */\nexport class TokenBalancesController extends StaticIntervalPollingController()<\n typeof controllerName,\n TokenBalancesControllerState,\n TokenBalancesControllerMessenger\n> {\n #queryMultipleAccounts: boolean;\n\n #allTokens: TokensControllerState['allTokens'];\n\n #allDetectedTokens: TokensControllerState['allDetectedTokens'];\n\n /**\n * Construct a Token Balances Controller.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new token balances.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller restricted messenger.\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n messenger,\n state = {},\n }: TokenBalancesControllerOptions) {\n super({\n name: controllerName,\n metadata,\n messenger,\n state: {\n ...getDefaultTokenBalancesState(),\n ...state,\n },\n });\n\n this.setIntervalLength(interval);\n\n // Set initial preference for querying multiple accounts, and subscribe to changes\n this.#queryMultipleAccounts = this.#calculateQueryMultipleAccounts(\n this.messagingSystem.call('PreferencesController:getState'),\n );\n this.messagingSystem.subscribe(\n 'PreferencesController:stateChange',\n this.#onPreferencesStateChange.bind(this),\n );\n\n // Set initial tokens, and subscribe to changes\n ({\n allTokens: this.#allTokens,\n allDetectedTokens: this.#allDetectedTokens,\n } = this.messagingSystem.call('TokensController:getState'));\n\n this.messagingSystem.subscribe(\n 'TokensController:stateChange',\n this.#onTokensStateChange.bind(this),\n );\n\n // Subscribe to network state changes\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n this.#onNetworkStateChange.bind(this),\n );\n }\n\n /**\n * Determines whether to query all accounts, or just the selected account.\n * @param preferences - The preferences state.\n * @param preferences.isMultiAccountBalancesEnabled - whether to query all accounts (mobile).\n * @param preferences.useMultiAccountBalanceChecker - whether to query all accounts (extension).\n * @returns true if all accounts should be queried.\n */\n #calculateQueryMultipleAccounts = ({\n isMultiAccountBalancesEnabled,\n useMultiAccountBalanceChecker,\n }: PreferencesState & { useMultiAccountBalanceChecker?: boolean }) => {\n return Boolean(\n // Note: These settings have different names on extension vs mobile\n isMultiAccountBalancesEnabled || useMultiAccountBalanceChecker,\n );\n };\n\n /**\n * Handles the event for preferences state changes.\n * @param preferences - The preferences state.\n */\n #onPreferencesStateChange = (preferences: PreferencesState) => {\n // Update the user preference for whether to query multiple accounts.\n const queryMultipleAccounts =\n this.#calculateQueryMultipleAccounts(preferences);\n\n // Refresh when flipped off -> on\n const refresh = queryMultipleAccounts && !this.#queryMultipleAccounts;\n this.#queryMultipleAccounts = queryMultipleAccounts;\n\n if (refresh) {\n this.updateBalances().catch(console.error);\n }\n };\n\n /**\n * Handles the event for tokens state changes.\n * @param state - The token state.\n * @param state.allTokens - The state for imported tokens across all chains.\n * @param state.allDetectedTokens - The state for detected tokens across all chains.\n */\n #onTokensStateChange = ({\n allTokens,\n allDetectedTokens,\n }: TokensControllerState) => {\n // Refresh token balances on chains whose tokens have changed.\n const chainIds = this.#getChainIds(allTokens, allDetectedTokens);\n const chainIdsToUpdate = chainIds.filter(\n (chainId) =>\n !isEqual(this.#allTokens[chainId], allTokens[chainId]) ||\n !isEqual(this.#allDetectedTokens[chainId], allDetectedTokens[chainId]),\n );\n\n this.#allTokens = allTokens;\n this.#allDetectedTokens = allDetectedTokens;\n\n this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error);\n };\n\n /**\n * Handles the event for network state changes.\n * @param _ - The network state.\n * @param patches - An array of patch operations performed on the network state.\n */\n #onNetworkStateChange(_: NetworkState, patches: Patch[]) {\n // Remove state for deleted networks\n for (const patch of patches) {\n if (\n patch.op === 'remove' &&\n patch.path[0] === 'networkConfigurationsByChainId'\n ) {\n const removedChainId = patch.path[1] as Hex;\n\n this.update((state) => {\n for (const accountAddress of Object.keys(state.tokenBalances)) {\n delete state.tokenBalances[accountAddress as Hex][removedChainId];\n }\n });\n }\n }\n }\n\n /**\n * Returns an array of chain ids that have tokens.\n * @param allTokens - The state for imported tokens across all chains.\n * @param allDetectedTokens - The state for detected tokens across all chains.\n * @returns An array of chain ids that have tokens.\n */\n #getChainIds = (\n allTokens: TokensControllerState['allTokens'],\n allDetectedTokens: TokensControllerState['allDetectedTokens'],\n ) =>\n [\n ...new Set([\n ...Object.keys(allTokens),\n ...Object.keys(allDetectedTokens),\n ]),\n ] as Hex[];\n\n /**\n * Polls for erc20 token balances.\n * @param input - The input for the poll.\n * @param input.chainId - The chain id to poll token balances on.\n */\n async _executePoll({ chainId }: TokenBalancesPollingInput) {\n await this.updateBalancesByChainId({ chainId });\n }\n\n /**\n * Updates the token balances for the given chain ids.\n * @param input - The input for the update.\n * @param input.chainIds - The chain ids to update token balances for.\n * Or omitted to update all chains that contain tokens.\n */\n async updateBalances({ chainIds }: { chainIds?: Hex[] } = {}) {\n chainIds ??= this.#getChainIds(this.#allTokens, this.#allDetectedTokens);\n\n await Promise.allSettled(\n chainIds.map((chainId) => this.updateBalancesByChainId({ chainId })),\n );\n }\n\n /**\n * Updates token balances for the given chain id.\n * @param input - The input for the update.\n * @param input.chainId - The chain id to update token balances on.\n */\n async updateBalancesByChainId({ chainId }: { chainId: Hex }) {\n const { address: selectedAccountAddress } = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n\n const isSelectedAccount = (accountAddress: string) =>\n toChecksumHexAddress(accountAddress) ===\n toChecksumHexAddress(selectedAccountAddress);\n\n const accountTokenPairs: { accountAddress: Hex; tokenAddress: Hex }[] = [];\n\n const addTokens = ([accountAddress, tokens]: [string, Token[]]) =>\n this.#queryMultipleAccounts || isSelectedAccount(accountAddress)\n ? tokens.forEach((t) =>\n accountTokenPairs.push({\n accountAddress: accountAddress as Hex,\n tokenAddress: t.address as Hex,\n }),\n )\n : undefined;\n\n // Balances will be updated for both imported and detected tokens\n Object.entries(this.#allTokens[chainId] ?? {}).forEach(addTokens);\n Object.entries(this.#allDetectedTokens[chainId] ?? {}).forEach(addTokens);\n\n let results: MulticallResult[] = [];\n\n if (accountTokenPairs.length > 0) {\n const provider = new Web3Provider(\n this.#getNetworkClient(chainId).provider,\n );\n\n const calls = accountTokenPairs.map(\n ({ accountAddress, tokenAddress }) => ({\n contract: new Contract(tokenAddress, abiERC20, provider),\n functionSignature: 'balanceOf(address)',\n arguments: [accountAddress],\n }),\n );\n\n results = await multicallOrFallback(calls, chainId, provider);\n }\n\n this.update((state) => {\n // Reset so that when accounts or tokens are removed,\n // their balances are removed rather than left stale.\n for (const accountAddress of Object.keys(state.tokenBalances)) {\n state.tokenBalances[accountAddress as Hex][chainId] = {};\n }\n\n for (let i = 0; i < results.length; i++) {\n const { success, value } = results[i];\n const { accountAddress, tokenAddress } = accountTokenPairs[i];\n\n if (success) {\n ((state.tokenBalances[accountAddress] ??= {})[chainId] ??= {})[\n tokenAddress\n ] = toHex(value as BN);\n }\n }\n });\n }\n\n /**\n * Reset the controller state to the default state.\n */\n resetState() {\n this.update(() => {\n return getDefaultTokenBalancesState();\n });\n }\n\n /**\n * Returns the network client for a given chain id\n * @param chainId - The chain id to get the network client for.\n * @returns The network client for the given chain id.\n */\n #getNetworkClient(chainId: Hex) {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n\n const networkConfiguration = networkConfigurationsByChainId[chainId];\n if (!networkConfiguration) {\n throw new Error(\n `TokenBalancesController: No network configuration found for chainId ${chainId}`,\n );\n }\n\n const { networkClientId } =\n networkConfiguration.rpcEndpoints[\n networkConfiguration.defaultRpcEndpointIndex\n ];\n\n return this.messagingSystem.call(\n `NetworkController:getNetworkClientById`,\n networkClientId,\n );\n }\n}\n\nexport default TokenBalancesController;\n"]} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs +index ab23c95..c6f9cbb 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokenDetectionController.cjs +@@ -273,9 +273,12 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_ + * Starts a new polling interval. + */ + async function _TokenDetectionController_startPolling() { ++ console.log('startPolling ......', !this.isActive); + if (!this.isActive) { + return; + } ++ console.log('startPolling 2 ......'); ++ + __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_stopPolling).call(this); + await this.detectTokens(); + // TODO: Either fix this lint violation or explain why it's necessary to ignore. +diff --git a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs +index a66ad22..36ba967 100644 +--- a/node_modules/@metamask/assets-controllers/dist/TokensController.cjs ++++ b/node_modules/@metamask/assets-controllers/dist/TokensController.cjs +@@ -209,9 +209,13 @@ class TokensController extends base_controller_1.BaseController { + const releaseLock = await __classPrivateFieldGet(this, _TokensController_mutex, "f").acquire(); + const { allTokens, ignoredTokens, allDetectedTokens } = this.state; + const importedTokensMap = {}; ++ let interactingChainId; ++ if (networkClientId) { ++ interactingChainId = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId).configuration.chainId; ++ } + // Used later to dedupe imported tokens + const newTokensMap = [ +- ...(allTokens[__classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAccount).call(this).address] || []), ++ ...(allTokens[interactingChainId ?? __classPrivateFieldGet(this, _TokensController_chainId, "f")]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAccount).call(this).address] || []), + ...tokensToImport, + ].reduce((output, token) => { + output[token.address] = token; +@@ -235,10 +239,6 @@ class TokensController extends base_controller_1.BaseController { + }); + const newTokens = Object.values(newTokensMap); + const newIgnoredTokens = ignoredTokens.filter((tokenAddress) => !newTokensMap[tokenAddress.toLowerCase()]); +- let interactingChainId; +- if (networkClientId) { +- interactingChainId = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId).configuration.chainId; +- } + const detectedTokensForGivenChain = interactingChainId + ? allDetectedTokens?.[interactingChainId]?.[__classPrivateFieldGet(this, _TokensController_instances, "m", _TokensController_getSelectedAddress).call(this)] + : []; +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.cjs b/node_modules/@metamask/assets-controllers/dist/multicall.cjs +new file mode 100644 +index 0000000..bf9aa5e +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.cjs +@@ -0,0 +1,350 @@ ++"use strict"; ++Object.defineProperty(exports, "__esModule", { value: true }); ++exports.multicallOrFallback = void 0; ++const contracts_1 = require("@ethersproject/contracts"); ++const assetsUtil_1 = require("./assetsUtil.cjs"); ++// https://github.com/mds1/multicall/blob/main/deployments.json ++const MULTICALL_CONTRACT_BY_CHAINID = { ++ '0x1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaa36a7': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4268': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5e9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b6e6': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x18fc4a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaa37dc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4ba': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eed': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eee': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eeb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15f2249': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x89': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13881': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13882': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x44d': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x5a2': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x98a': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x64': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x27d8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa86a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa869': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfa2': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfa': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfaf0': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x38': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x61': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15eb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xcc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x504': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x505': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x507': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2a15c308d': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2a15c3083': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x63564c40': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x19': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x152': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5535072': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6c1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x10': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x72': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x120': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4e454152': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x250': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5c2359': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xec0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x42': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x80': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x440': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x257': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9fe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd3a0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x84444': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2329': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2328': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6c': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x12': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa516': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5afe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4ec': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaef3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x116ea': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x116e9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2019': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3e9': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x7d1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x141': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4d2': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e14': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e15': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1251': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7f08': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8ae': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x138b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1389': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1388': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f92': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x14a33': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x14a34': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2105': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x936': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xff': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x46a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x46b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x14f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd2af': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9ac0ce': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe705': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xe704': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe708': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2b6f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x39': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x23a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1644': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdea8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3af': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x171': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3e7': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x76adf1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3b9ac9ff': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2c': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2e': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x15b3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x82751': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8274f': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x82750': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x96f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3cc5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4571': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe99': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7d0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1297': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1d5e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3a14269b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x561bf78b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x235ddd0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3cd156dc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5d456c62': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x79f99296': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x585eb4b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x507aaa2a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1fc3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x32d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a73': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a72': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a71': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9ac0d6': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1069': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7e5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x53': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x52': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xe298': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x94': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2c6': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2803': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2802': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c5f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c60': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4cb2f': '0xdbfa261cd7d17bb40479a0493ad6c0fee435859e', ++ '0x7f93': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xb660': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xb02113d3f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdad': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdae': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15b38': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15b32': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45c': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x41a6ace': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa729': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f47b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b59': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1b58': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc3': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x16fd8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc7': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x405': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x334': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1ce': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1cf': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xa70e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x868b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa0c71fd': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13e31': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa1337': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f2b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xf63': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x144': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x118': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x2b74': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0xfc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x9da': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x137': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13ed': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x24b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xba9302': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7c8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x138d5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x343b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x34a1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3109': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x91b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa96': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x22c3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2be3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xbf03': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b254': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa7b14': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2276': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b9e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6a63bb8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15af3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15af1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xae3f3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x531': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c61': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c58': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1d88': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5b9b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4c7e1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa53b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a2b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x406': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2cef': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x18b2': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x182a9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfdd': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfde': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x99c0a0f': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x22cf': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x310c5': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x46f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x659': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x139c968f9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xed88': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd036': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x31bf8c3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1cbc67bfdc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x98967f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4f588': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x16db': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x3a': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x59': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1e0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2eb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x221': '0xcA11bde05977b3631167028862bE2a173976CA11', ++}; ++const multicallAbi = [ ++ { ++ name: 'tryAggregate', ++ type: 'function', ++ stateMutability: 'payable', ++ inputs: [ ++ { name: 'requireSuccess', type: 'bool' }, ++ { ++ name: 'calls', ++ type: 'tuple[]', ++ components: [ ++ { name: 'target', type: 'address' }, ++ { name: 'callData', type: 'bytes' }, ++ ], ++ }, ++ ], ++ outputs: [ ++ { ++ name: 'returnData', ++ type: 'tuple[]', ++ components: [ ++ { name: 'success', type: 'bool' }, ++ { name: 'returnData', type: 'bytes' }, ++ ], ++ }, ++ ], ++ }, ++]; ++const multicall = async (calls, multicallAddress, provider, maxCallsPerMulticall) => { ++ const multicallContract = new contracts_1.Contract(multicallAddress, multicallAbi, provider); ++ return await (0, assetsUtil_1.reduceInBatchesSerially)({ ++ values: calls, ++ batchSize: maxCallsPerMulticall, ++ initialResult: [], ++ eachBatch: async (workingResult, batch) => { ++ const calldata = batch.map((call) => ({ ++ target: call.contract.address, ++ callData: call.contract.interface.encodeFunctionData(call.contract.interface.functions[call.functionSignature], call.arguments), ++ })); ++ const results = await multicallContract.callStatic.tryAggregate(false, calldata); ++ return [ ++ ...workingResult, ++ ...results.map((r, i) => ({ ++ success: r.success, ++ value: r.success ++ ? batch[i].contract.interface.decodeFunctionResult(batch[i].functionSignature, r.returnData)[0] ++ : undefined, ++ })), ++ ]; ++ }, ++ }); ++}; ++const fallback = async (calls, maxCallsParallel) => { ++ return await (0, assetsUtil_1.reduceInBatchesSerially)({ ++ values: calls, ++ batchSize: maxCallsParallel, ++ initialResult: [], ++ eachBatch: async (workingResult, batch) => { ++ const results = await Promise.allSettled(batch.map((call) => call.contract[call.functionSignature](...call.arguments))); ++ return [ ++ ...workingResult, ++ ...results.map((p) => ({ ++ success: p.status === 'fulfilled', ++ value: p.status === 'fulfilled' ? p.value : undefined, ++ })), ++ ]; ++ }, ++ }); ++}; ++/** ++ * Executes an array of contract calls. If the chain supports multicalls, ++ * the calls will be executed in single RPC requests (up to maxCallsPerMulticall). ++ * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel). ++ * @param calls - An array of contract calls to execute. ++ * @param chainId - The hexadecimal chain id. ++ * @param provider - An ethers rpc provider. ++ * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall. ++ * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel. ++ * @returns An array of results, with a success boolean and value for each call. ++ */ ++const multicallOrFallback = async (calls, chainId, provider, maxCallsPerMulticall = 300, maxCallsParallel = 20) => { ++ if (calls.length === 0) { ++ return []; ++ } ++ const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; ++ return await (multicallAddress ++ ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) ++ : fallback(calls, maxCallsParallel)); ++}; ++exports.multicallOrFallback = multicallOrFallback; ++//# sourceMappingURL=multicall.cjs.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.cjs.map b/node_modules/@metamask/assets-controllers/dist/multicall.cjs.map +new file mode 100644 +index 0000000..147eb33 +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.cjs.map +@@ -0,0 +1 @@ ++{"version":3,"file":"multicall.cjs","sourceRoot":"","sources":["../src/multicall.ts"],"names":[],"mappings":"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","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport type { Web3Provider } from '@ethersproject/providers';\nimport type { Hex } from '@metamask/utils';\n\nimport { reduceInBatchesSerially } from './assetsUtil';\n\n// https://github.com/mds1/multicall/blob/main/deployments.json\nconst MULTICALL_CONTRACT_BY_CHAINID = {\n '0x1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaa36a7': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4268': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5e9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b6e6': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x18fc4a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaa37dc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4ba': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eed': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eee': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eeb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15f2249': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x89': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13881': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13882': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x44d': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x5a2': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x98a': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x64': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x27d8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa86a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa869': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfa2': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfa': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfaf0': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x38': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x61': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15eb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xcc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x504': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x505': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x507': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2a15c308d': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2a15c3083': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x63564c40': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x19': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x152': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5535072': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6c1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x10': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x72': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x120': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4e454152': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x250': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5c2359': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xec0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x42': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x80': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x440': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x257': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9fe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd3a0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x84444': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2329': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2328': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6c': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x12': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa516': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5afe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4ec': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaef3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x116ea': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x116e9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2019': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3e9': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x7d1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x141': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4d2': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e14': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e15': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1251': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7f08': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8ae': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x138b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1389': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1388': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f92': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x14a33': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x14a34': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2105': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x936': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xff': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x46a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x46b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x14f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd2af': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9ac0ce': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe705': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xe704': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe708': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2b6f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x39': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x23a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1644': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdea8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3af': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x171': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3e7': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x76adf1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3b9ac9ff': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2c': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2e': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x15b3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x82751': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8274f': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x82750': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x96f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3cc5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4571': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe99': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7d0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1297': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1d5e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3a14269b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x561bf78b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x235ddd0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3cd156dc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5d456c62': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x79f99296': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x585eb4b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x507aaa2a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1fc3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x32d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a73': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a72': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a71': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9ac0d6': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1069': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7e5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x53': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x52': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xe298': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x94': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2c6': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2803': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2802': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c5f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c60': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4cb2f': '0xdbfa261cd7d17bb40479a0493ad6c0fee435859e',\n '0x7f93': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xb660': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xb02113d3f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdad': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdae': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15b38': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15b32': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45c': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x41a6ace': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa729': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f47b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b59': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1b58': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc3': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x16fd8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc7': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x405': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x334': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1ce': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1cf': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xa70e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x868b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa0c71fd': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13e31': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa1337': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f2b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xf63': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x144': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x118': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x2b74': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0xfc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x9da': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x137': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13ed': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x24b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xba9302': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7c8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x138d5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x343b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x34a1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3109': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x91b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa96': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x22c3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2be3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xbf03': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b254': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa7b14': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2276': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b9e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6a63bb8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15af3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15af1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xae3f3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x531': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c61': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c58': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1d88': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5b9b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4c7e1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa53b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a2b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x406': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2cef': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x18b2': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x182a9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfdd': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfde': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x99c0a0f': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x22cf': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x310c5': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x46f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x659': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x139c968f9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xed88': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd036': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x31bf8c3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1cbc67bfdc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x98967f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4f588': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x16db': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x3a': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x59': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1e0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2eb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x221': '0xcA11bde05977b3631167028862bE2a173976CA11',\n} as Record;\n\nconst multicallAbi = [\n {\n name: 'tryAggregate',\n type: 'function',\n stateMutability: 'payable',\n inputs: [\n { name: 'requireSuccess', type: 'bool' },\n {\n name: 'calls',\n type: 'tuple[]',\n components: [\n { name: 'target', type: 'address' },\n { name: 'callData', type: 'bytes' },\n ],\n },\n ],\n outputs: [\n {\n name: 'returnData',\n type: 'tuple[]',\n components: [\n { name: 'success', type: 'bool' },\n { name: 'returnData', type: 'bytes' },\n ],\n },\n ],\n },\n];\n\ntype Call = {\n contract: Contract;\n functionSignature: string;\n arguments: unknown[];\n};\n\nexport type MulticallResult = { success: boolean; value: unknown };\n\nconst multicall = async (\n calls: Call[],\n multicallAddress: Hex,\n provider: Web3Provider,\n maxCallsPerMulticall: number,\n): Promise => {\n const multicallContract = new Contract(\n multicallAddress,\n multicallAbi,\n provider,\n );\n\n return await reduceInBatchesSerially({\n values: calls,\n batchSize: maxCallsPerMulticall,\n initialResult: [],\n eachBatch: async (workingResult, batch) => {\n const calldata = batch.map((call) => ({\n target: call.contract.address,\n callData: call.contract.interface.encodeFunctionData(\n call.contract.interface.functions[call.functionSignature],\n call.arguments,\n ),\n }));\n\n const results = await multicallContract.callStatic.tryAggregate(\n false,\n calldata,\n );\n\n return [\n ...workingResult,\n ...results.map(\n (r: { success: boolean; returnData: string }, i: number) => ({\n success: r.success,\n value: r.success\n ? batch[i].contract.interface.decodeFunctionResult(\n batch[i].functionSignature,\n r.returnData,\n )[0]\n : undefined,\n }),\n ),\n ];\n },\n });\n};\n\nconst fallback = async (\n calls: Call[],\n maxCallsParallel: number,\n): Promise => {\n return await reduceInBatchesSerially({\n values: calls,\n batchSize: maxCallsParallel,\n initialResult: [],\n eachBatch: async (workingResult, batch) => {\n const results = await Promise.allSettled(\n batch.map((call) =>\n call.contract[call.functionSignature](...call.arguments),\n ),\n );\n return [\n ...workingResult,\n ...results.map((p) => ({\n success: p.status === 'fulfilled',\n value: p.status === 'fulfilled' ? p.value : undefined,\n })),\n ];\n },\n });\n};\n\n/**\n * Executes an array of contract calls. If the chain supports multicalls,\n * the calls will be executed in single RPC requests (up to maxCallsPerMulticall).\n * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel).\n * @param calls - An array of contract calls to execute.\n * @param chainId - The hexadecimal chain id.\n * @param provider - An ethers rpc provider.\n * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall.\n * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel.\n * @returns An array of results, with a success boolean and value for each call.\n */\nexport const multicallOrFallback = async (\n calls: Call[],\n chainId: Hex,\n provider: Web3Provider,\n maxCallsPerMulticall = 300,\n maxCallsParallel = 20,\n): Promise => {\n if (calls.length === 0) {\n return [];\n }\n\n const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId];\n return await (multicallAddress\n ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall)\n : fallback(calls, maxCallsParallel));\n};\n"]} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.d.cts b/node_modules/@metamask/assets-controllers/dist/multicall.d.cts +new file mode 100644 +index 0000000..530da8d +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.d.cts +@@ -0,0 +1,26 @@ ++import { Contract } from "@ethersproject/contracts"; ++import type { Web3Provider } from "@ethersproject/providers"; ++import type { Hex } from "@metamask/utils"; ++type Call = { ++ contract: Contract; ++ functionSignature: string; ++ arguments: unknown[]; ++}; ++export type MulticallResult = { ++ success: boolean; ++ value: unknown; ++}; ++/** ++ * Executes an array of contract calls. If the chain supports multicalls, ++ * the calls will be executed in single RPC requests (up to maxCallsPerMulticall). ++ * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel). ++ * @param calls - An array of contract calls to execute. ++ * @param chainId - The hexadecimal chain id. ++ * @param provider - An ethers rpc provider. ++ * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall. ++ * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel. ++ * @returns An array of results, with a success boolean and value for each call. ++ */ ++export declare const multicallOrFallback: (calls: Call[], chainId: Hex, provider: Web3Provider, maxCallsPerMulticall?: number, maxCallsParallel?: number) => Promise; ++export {}; ++//# sourceMappingURL=multicall.d.cts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.d.cts.map b/node_modules/@metamask/assets-controllers/dist/multicall.d.cts.map +new file mode 100644 +index 0000000..4664714 +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.d.cts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"multicall.d.cts","sourceRoot":"","sources":["../src/multicall.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iCAAiC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,iCAAiC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAgS3C,KAAK,IAAI,GAAG;IACV,QAAQ,EAAE,QAAQ,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AA2EnE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,UACvB,IAAI,EAAE,WACJ,GAAG,YACF,YAAY,+DAGrB,QAAQ,eAAe,EAAE,CAS3B,CAAC"} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.d.mts b/node_modules/@metamask/assets-controllers/dist/multicall.d.mts +new file mode 100644 +index 0000000..7a7c199 +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.d.mts +@@ -0,0 +1,26 @@ ++import { Contract } from "@ethersproject/contracts"; ++import type { Web3Provider } from "@ethersproject/providers"; ++import type { Hex } from "@metamask/utils"; ++type Call = { ++ contract: Contract; ++ functionSignature: string; ++ arguments: unknown[]; ++}; ++export type MulticallResult = { ++ success: boolean; ++ value: unknown; ++}; ++/** ++ * Executes an array of contract calls. If the chain supports multicalls, ++ * the calls will be executed in single RPC requests (up to maxCallsPerMulticall). ++ * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel). ++ * @param calls - An array of contract calls to execute. ++ * @param chainId - The hexadecimal chain id. ++ * @param provider - An ethers rpc provider. ++ * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall. ++ * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel. ++ * @returns An array of results, with a success boolean and value for each call. ++ */ ++export declare const multicallOrFallback: (calls: Call[], chainId: Hex, provider: Web3Provider, maxCallsPerMulticall?: number, maxCallsParallel?: number) => Promise; ++export {}; ++//# sourceMappingURL=multicall.d.mts.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.d.mts.map b/node_modules/@metamask/assets-controllers/dist/multicall.d.mts.map +new file mode 100644 +index 0000000..73e9b1b +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.d.mts.map +@@ -0,0 +1 @@ ++{"version":3,"file":"multicall.d.mts","sourceRoot":"","sources":["../src/multicall.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iCAAiC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,iCAAiC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAgS3C,KAAK,IAAI,GAAG;IACV,QAAQ,EAAE,QAAQ,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AA2EnE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,UACvB,IAAI,EAAE,WACJ,GAAG,YACF,YAAY,+DAGrB,QAAQ,eAAe,EAAE,CAS3B,CAAC"} +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.mjs b/node_modules/@metamask/assets-controllers/dist/multicall.mjs +new file mode 100644 +index 0000000..8fbe011 +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.mjs +@@ -0,0 +1,346 @@ ++import { Contract } from "@ethersproject/contracts"; ++import { reduceInBatchesSerially } from "./assetsUtil.mjs"; ++// https://github.com/mds1/multicall/blob/main/deployments.json ++const MULTICALL_CONTRACT_BY_CHAINID = { ++ '0x1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaa36a7': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4268': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5e9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b6e6': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x18fc4a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaa37dc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4ba': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eed': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eee': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x66eeb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15f2249': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x89': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13881': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13882': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x44d': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x5a2': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x98a': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x64': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x27d8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa86a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa869': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfa2': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfa': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfaf0': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x38': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x61': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15eb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xcc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x504': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x505': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x507': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2a15c308d': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2a15c3083': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x63564c40': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x19': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x152': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5535072': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6c1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x10': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x72': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x120': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4e454152': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x250': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5c2359': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xec0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x42': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x80': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x440': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x257': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9fe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd3a0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x84444': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2329': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2328': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6c': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x12': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa516': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5afe': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa4ec': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xaef3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x116ea': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x116e9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2019': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3e9': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x7d1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x141': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4d2': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e14': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1e15': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1251': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7f08': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8ae': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x138b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1389': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1388': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f92': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x14a33': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x14a34': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2105': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x936': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xff': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x46a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x46b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x14f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd2af': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9ac0ce': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe705': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xe704': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe708': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2b6f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x39': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x23a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1644': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdea8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3af': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x171': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3e7': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x76adf1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3b9ac9ff': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2c': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2e': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x15b3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x82751': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8274f': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x82750': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x96f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3cc5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4571': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe99': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7d0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1297': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1d5e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3a14269b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x561bf78b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x235ddd0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3cd156dc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5d456c62': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x79f99296': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x585eb4b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x507aaa2a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1fc3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x32d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a73': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a72': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x8a71': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xe9ac0d6': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1069': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7e5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x53': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x52': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xe298': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x94': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2c6': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2803': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2802': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c5f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c60': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13a': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4cb2f': '0xdbfa261cd7d17bb40479a0493ad6c0fee435859e', ++ '0x7f93': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xb660': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xb02113d3f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdad': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xdae': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15b38': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15b32': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45c': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x45b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x41a6ace': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa729': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f47b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b59': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1b58': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc3': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x16fd8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc7': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x405': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x334': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1ce': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1cf': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0xa70e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x868b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa0c71fd': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13e31': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa1337': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f2b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xf63': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x144': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x118': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0x2b74': '0xF9cda624FBC7e059355ce98a31693d299FACd963', ++ '0xfc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x9da': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x137': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x13ed': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x24b1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xba9302': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x7c8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x138d5': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6d': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x343b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x34a1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x3109': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x91b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa96': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x22c3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2be3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xbf03': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b254': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa7b14': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2276': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1b9e': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x6a63bb8': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15af3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x15af1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xae3f3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x531': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c61': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x28c58': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1d88': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x5b9b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4c7e1': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xa53b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1a2b': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x406': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x2cef': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x18b2': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x182a9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xc4': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfdd': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xfde': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x99c0a0f': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x22cf': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x310c5': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x46f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x659': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x139c968f9': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xed88': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0xd036': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1f3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x31bf8c3': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x1cbc67bfdc': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x98967f': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x4f588': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x16db': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x3a': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x59': '0xca11bde05977b3631167028862be2a173976ca11', ++ '0x1e0': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x2eb': '0xcA11bde05977b3631167028862bE2a173976CA11', ++ '0x221': '0xcA11bde05977b3631167028862bE2a173976CA11', ++}; ++const multicallAbi = [ ++ { ++ name: 'tryAggregate', ++ type: 'function', ++ stateMutability: 'payable', ++ inputs: [ ++ { name: 'requireSuccess', type: 'bool' }, ++ { ++ name: 'calls', ++ type: 'tuple[]', ++ components: [ ++ { name: 'target', type: 'address' }, ++ { name: 'callData', type: 'bytes' }, ++ ], ++ }, ++ ], ++ outputs: [ ++ { ++ name: 'returnData', ++ type: 'tuple[]', ++ components: [ ++ { name: 'success', type: 'bool' }, ++ { name: 'returnData', type: 'bytes' }, ++ ], ++ }, ++ ], ++ }, ++]; ++const multicall = async (calls, multicallAddress, provider, maxCallsPerMulticall) => { ++ const multicallContract = new Contract(multicallAddress, multicallAbi, provider); ++ return await reduceInBatchesSerially({ ++ values: calls, ++ batchSize: maxCallsPerMulticall, ++ initialResult: [], ++ eachBatch: async (workingResult, batch) => { ++ const calldata = batch.map((call) => ({ ++ target: call.contract.address, ++ callData: call.contract.interface.encodeFunctionData(call.contract.interface.functions[call.functionSignature], call.arguments), ++ })); ++ const results = await multicallContract.callStatic.tryAggregate(false, calldata); ++ return [ ++ ...workingResult, ++ ...results.map((r, i) => ({ ++ success: r.success, ++ value: r.success ++ ? batch[i].contract.interface.decodeFunctionResult(batch[i].functionSignature, r.returnData)[0] ++ : undefined, ++ })), ++ ]; ++ }, ++ }); ++}; ++const fallback = async (calls, maxCallsParallel) => { ++ return await reduceInBatchesSerially({ ++ values: calls, ++ batchSize: maxCallsParallel, ++ initialResult: [], ++ eachBatch: async (workingResult, batch) => { ++ const results = await Promise.allSettled(batch.map((call) => call.contract[call.functionSignature](...call.arguments))); ++ return [ ++ ...workingResult, ++ ...results.map((p) => ({ ++ success: p.status === 'fulfilled', ++ value: p.status === 'fulfilled' ? p.value : undefined, ++ })), ++ ]; ++ }, ++ }); ++}; ++/** ++ * Executes an array of contract calls. If the chain supports multicalls, ++ * the calls will be executed in single RPC requests (up to maxCallsPerMulticall). ++ * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel). ++ * @param calls - An array of contract calls to execute. ++ * @param chainId - The hexadecimal chain id. ++ * @param provider - An ethers rpc provider. ++ * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall. ++ * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel. ++ * @returns An array of results, with a success boolean and value for each call. ++ */ ++export const multicallOrFallback = async (calls, chainId, provider, maxCallsPerMulticall = 300, maxCallsParallel = 20) => { ++ if (calls.length === 0) { ++ return []; ++ } ++ const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; ++ return await (multicallAddress ++ ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) ++ : fallback(calls, maxCallsParallel)); ++}; ++//# sourceMappingURL=multicall.mjs.map +\ No newline at end of file +diff --git a/node_modules/@metamask/assets-controllers/dist/multicall.mjs.map b/node_modules/@metamask/assets-controllers/dist/multicall.mjs.map +new file mode 100644 +index 0000000..568f618 +--- /dev/null ++++ b/node_modules/@metamask/assets-controllers/dist/multicall.mjs.map +@@ -0,0 +1 @@ ++{"version":3,"file":"multicall.mjs","sourceRoot":"","sources":["../src/multicall.ts"],"names":[],"mappings":"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","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport type { Web3Provider } from '@ethersproject/providers';\nimport type { Hex } from '@metamask/utils';\n\nimport { reduceInBatchesSerially } from './assetsUtil';\n\n// https://github.com/mds1/multicall/blob/main/deployments.json\nconst MULTICALL_CONTRACT_BY_CHAINID = {\n '0x1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaa36a7': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4268': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5e9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b6e6': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x18fc4a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaa37dc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4ba': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eed': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eee': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x66eeb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15f2249': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x89': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13881': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13882': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x44d': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x5a2': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x98a': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x64': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x27d8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa86a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa869': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfa2': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfa': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfaf0': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x38': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x61': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15eb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xcc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x504': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x505': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x507': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2a15c308d': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2a15c3083': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x63564c40': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x19': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x152': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5535072': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6c1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x10': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x72': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x120': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4e454152': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x250': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5c2359': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xec0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x42': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x80': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x440': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x257': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9fe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd3a0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x84444': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2329': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2328': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6c': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x12': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa516': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5afe': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa4ec': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xaef3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x116ea': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x116e9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2019': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3e9': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x7d1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x141': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4d2': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e14': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1e15': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1251': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7f08': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8ae': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x138b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1389': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1388': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f92': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x14a33': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x14a34': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2105': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x936': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xff': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x46a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x46b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x14f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd2af': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9ac0ce': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe705': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xe704': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe708': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2b6f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x39': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x23a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1644': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdea8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3af': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x171': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3e7': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x76adf1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3b9ac9ff': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2c': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2e': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x15b3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x82751': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8274f': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x82750': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x96f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3cc5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4571': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe99': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7d0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1297': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1d5e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3a14269b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x561bf78b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x235ddd0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3cd156dc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5d456c62': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x79f99296': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x585eb4b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x507aaa2a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1fc3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x32d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a73': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a72': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x8a71': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xe9ac0d6': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1069': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7e5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x53': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x52': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xe298': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x94': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2c6': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2803': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2802': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c5f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c60': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13a': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4cb2f': '0xdbfa261cd7d17bb40479a0493ad6c0fee435859e',\n '0x7f93': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xb660': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xb02113d3f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdad': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xdae': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15b38': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15b32': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45c': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x45b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x41a6ace': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa729': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f47b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b59': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1b58': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc3': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x16fd8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc7': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x405': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x334': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1ce': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1cf': '0xca11bde05977b3631167028862be2a173976ca11',\n '0xa70e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x868b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa0c71fd': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13e31': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa1337': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f2b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xf63': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x144': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x118': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0x2b74': '0xF9cda624FBC7e059355ce98a31693d299FACd963',\n '0xfc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x9da': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x137': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x13ed': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x24b1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xba9302': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x7c8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x138d5': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6d': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x343b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x34a1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x3109': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x91b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa96': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x22c3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2be3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xbf03': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b254': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa7b14': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2276': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1b9e': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x6a63bb8': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15af3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x15af1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xae3f3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x531': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c61': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x28c58': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1d88': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x5b9b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4c7e1': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xa53b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1a2b': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x406': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x2cef': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x18b2': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x182a9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xc4': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfdd': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xfde': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x99c0a0f': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x22cf': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x310c5': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x46f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x659': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x139c968f9': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xed88': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0xd036': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1f3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x31bf8c3': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x1cbc67bfdc': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x98967f': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x4f588': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x16db': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x3a': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x59': '0xca11bde05977b3631167028862be2a173976ca11',\n '0x1e0': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x2eb': '0xcA11bde05977b3631167028862bE2a173976CA11',\n '0x221': '0xcA11bde05977b3631167028862bE2a173976CA11',\n} as Record;\n\nconst multicallAbi = [\n {\n name: 'tryAggregate',\n type: 'function',\n stateMutability: 'payable',\n inputs: [\n { name: 'requireSuccess', type: 'bool' },\n {\n name: 'calls',\n type: 'tuple[]',\n components: [\n { name: 'target', type: 'address' },\n { name: 'callData', type: 'bytes' },\n ],\n },\n ],\n outputs: [\n {\n name: 'returnData',\n type: 'tuple[]',\n components: [\n { name: 'success', type: 'bool' },\n { name: 'returnData', type: 'bytes' },\n ],\n },\n ],\n },\n];\n\ntype Call = {\n contract: Contract;\n functionSignature: string;\n arguments: unknown[];\n};\n\nexport type MulticallResult = { success: boolean; value: unknown };\n\nconst multicall = async (\n calls: Call[],\n multicallAddress: Hex,\n provider: Web3Provider,\n maxCallsPerMulticall: number,\n): Promise => {\n const multicallContract = new Contract(\n multicallAddress,\n multicallAbi,\n provider,\n );\n\n return await reduceInBatchesSerially({\n values: calls,\n batchSize: maxCallsPerMulticall,\n initialResult: [],\n eachBatch: async (workingResult, batch) => {\n const calldata = batch.map((call) => ({\n target: call.contract.address,\n callData: call.contract.interface.encodeFunctionData(\n call.contract.interface.functions[call.functionSignature],\n call.arguments,\n ),\n }));\n\n const results = await multicallContract.callStatic.tryAggregate(\n false,\n calldata,\n );\n\n return [\n ...workingResult,\n ...results.map(\n (r: { success: boolean; returnData: string }, i: number) => ({\n success: r.success,\n value: r.success\n ? batch[i].contract.interface.decodeFunctionResult(\n batch[i].functionSignature,\n r.returnData,\n )[0]\n : undefined,\n }),\n ),\n ];\n },\n });\n};\n\nconst fallback = async (\n calls: Call[],\n maxCallsParallel: number,\n): Promise => {\n return await reduceInBatchesSerially({\n values: calls,\n batchSize: maxCallsParallel,\n initialResult: [],\n eachBatch: async (workingResult, batch) => {\n const results = await Promise.allSettled(\n batch.map((call) =>\n call.contract[call.functionSignature](...call.arguments),\n ),\n );\n return [\n ...workingResult,\n ...results.map((p) => ({\n success: p.status === 'fulfilled',\n value: p.status === 'fulfilled' ? p.value : undefined,\n })),\n ];\n },\n });\n};\n\n/**\n * Executes an array of contract calls. If the chain supports multicalls,\n * the calls will be executed in single RPC requests (up to maxCallsPerMulticall).\n * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel).\n * @param calls - An array of contract calls to execute.\n * @param chainId - The hexadecimal chain id.\n * @param provider - An ethers rpc provider.\n * @param maxCallsPerMulticall - If multicall is supported, the maximum number of calls to exeute in each multicall.\n * @param maxCallsParallel - If multicall is not supported, the maximum number of calls to execute in parallel.\n * @returns An array of results, with a success boolean and value for each call.\n */\nexport const multicallOrFallback = async (\n calls: Call[],\n chainId: Hex,\n provider: Web3Provider,\n maxCallsPerMulticall = 300,\n maxCallsParallel = 20,\n): Promise => {\n if (calls.length === 0) {\n return [];\n }\n\n const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId];\n return await (multicallAddress\n ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall)\n : fallback(calls, maxCallsParallel));\n};\n"]} +\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e78d681dea2..3536345a3c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4411,10 +4411,10 @@ "@metamask/utils" "^9.1.0" nanoid "^3.1.31" -"@metamask/assets-controllers@^41.0.0": - version "41.0.0" - resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-41.0.0.tgz#b6d5b0064015bbd45bf9a85370c5c4ffd486de1d" - integrity sha512-FwnkCxV8IZPN8aCb2xWj4lB2hwVwW4UKkU+0XnD236EAUcWsWYUMf9Z1NNBE2t/NlOKs9xXjfdJRNG3dPJ71dg== +"@metamask/assets-controllers@^43.1.1": + version "43.1.1" + resolved "https://registry.yarnpkg.com/@metamask/assets-controllers/-/assets-controllers-43.1.1.tgz#f9c8b97765f06653ca45271ead5b7acea2a169e4" + integrity sha512-dR5XmIGRN9nMW7gPtl83iwhFF5MQzOz+Jv1tN/e5v+uNHZ6f6aYLCvESEL9fDgGOeJn81g2P39RH/fT/QpcbRA== dependencies: "@ethereumjs/util" "^8.1.0" "@ethersproject/address" "^5.7.0" @@ -4424,7 +4424,7 @@ "@metamask/abi-utils" "^2.0.3" "@metamask/base-controller" "^7.0.2" "@metamask/contract-metadata" "^2.4.0" - "@metamask/controller-utils" "^11.4.2" + "@metamask/controller-utils" "^11.4.3" "@metamask/eth-query" "^4.0.0" "@metamask/metamask-eth-abis" "^3.1.1" "@metamask/polling-controller" "^12.0.1" @@ -4523,6 +4523,22 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" +"@metamask/controller-utils@^11.4.3": + version "11.4.3" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.4.3.tgz#5763f0bbee2f3770c1ba42dd4869786afef849bd" + integrity sha512-shrVCHFwIbt8qVcKbxe/mp5tOxjz6905/7ZIAnwUJKHYv7iEqfjyO1ibPoOknrZCF2vbXtP21b435g3v9DBNTQ== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/utils" "^10.0.0" + "@spruceid/siwe-parser" "2.1.0" + "@types/bn.js" "^5.1.5" + bignumber.js "^9.1.2" + bn.js "^5.2.1" + eth-ens-namehash "^2.0.8" + fast-deep-equal "^3.1.3" + "@metamask/controller-utils@^8.0.1", "@metamask/controller-utils@^8.0.2", "@metamask/controller-utils@^8.0.4": version "8.0.4" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-8.0.4.tgz#78a952301ff4b2a501b31865ab0de434c6ea3cd2"