diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 9eacec10f97e..65f8fa8ea2ae 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -792,20 +792,6 @@ const state = { swapsWelcomeMessageHasBeenShown: true, defaultHomeActiveTabName: 'Tokens', network: '5', - accounts: { - '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': { - address: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', - balance: '0x176e5b6f173ebe66', - }, - '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e': { - address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', - balance: '0x2d3142f5000', - }, - '0x9d0ba4ddac06032527b140912ec808ab9451b788': { - address: '0x9d0ba4ddac06032527b140912ec808ab9451b788', - balance: '0x15f6f0b9d4f8d000', - }, - }, accountsByChainId: { '0x1': { '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': { balance: '0x0' }, @@ -829,10 +815,6 @@ const state = { }, }, }, - currentBlockGasLimit: '0x793af4', - currentBlockGasLimitByChainId: { - '0x5': '0x793af4', - }, transactions: [ { chainId: '0x38', diff --git a/app/scripts/background.js b/app/scripts/background.js index 449c23cfac32..864e74980f35 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -556,10 +556,7 @@ function saveTimestamp() { * @property {boolean} welcomeScreen - True if welcome screen should be shown. * @property {string} currentLocale - A locale string matching the user's preferred display language. * @property {string} networkStatus - Either "unknown", "available", "unavailable", or "blocked", depending on the status of the currently selected network. - * @property {object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values. * @property {object} accountsByChainId - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values keyed by chain id. - * @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string. - * @property {object} currentBlockGasLimitByChainId - The most recently seen block gas limit, in a lower case hex prefixed string keyed by chain id. * @property {object} unapprovedPersonalMsgs - An object of messages pending approval, mapping a unique ID to the options. * @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs. * @property {object} unapprovedEncryptionPublicKeyMsgs - An object of messages pending approval, mapping a unique ID to the options. diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index e89bf7138305..92d871ef3c2e 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -14,10 +14,7 @@ export const SENTRY_BACKGROUND_STATE = { }, }, AccountTracker: { - accounts: false, accountsByChainId: false, - currentBlockGasLimit: true, - currentBlockGasLimitByChainId: true, }, AddressBookController: { addressBook: false, diff --git a/app/scripts/controller-init/account-tracker-controller-init.test.ts b/app/scripts/controller-init/account-tracker-controller-init.test.ts index 6febf7a666b6..aaecc483c922 100644 --- a/app/scripts/controller-init/account-tracker-controller-init.test.ts +++ b/app/scripts/controller-init/account-tracker-controller-init.test.ts @@ -4,7 +4,7 @@ import { NetworkControllerNetworkDidChangeEvent, } from '@metamask/network-controller'; import { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; -import AccountTrackerController from '../controllers/account-tracker-controller'; +import AccountTrackerController from '@metamask/assets-controllers'; import { ControllerInitRequest } from './types'; import { buildControllerInitRequestMock } from './test/utils'; import { diff --git a/app/scripts/controller-init/account-tracker-controller-init.ts b/app/scripts/controller-init/account-tracker-controller-init.ts index 786935455685..4f0c894675ab 100644 --- a/app/scripts/controller-init/account-tracker-controller-init.ts +++ b/app/scripts/controller-init/account-tracker-controller-init.ts @@ -1,56 +1,38 @@ -import { assert } from '@metamask/utils'; -import { getProviderConfig } from '../../../shared/modules/selectors/networks'; -import { NETWORK_TYPES } from '../../../shared/constants/network'; -import AccountTrackerController from '../controllers/account-tracker-controller'; +import { AccountTrackerController } from '@metamask/assets-controllers'; +import { NetworkClientId } from '@metamask/network-controller'; import { ControllerInitFunction } from './types'; import { AccountTrackerControllerInitMessenger, AccountTrackerControllerMessenger, } from './messengers'; -/** - * Initialize the account tracker controller. - * - * @param request - The request object. - * @param request.controllerMessenger - The messenger to use for the controller. - * @param request.initMessenger - The messenger to use for initialization. - * @returns The initialized controller. - */ export const AccountTrackerControllerInit: ControllerInitFunction< AccountTrackerController, AccountTrackerControllerMessenger, AccountTrackerControllerInitMessenger -> = ({ controllerMessenger, initMessenger }) => { - const { provider, blockTracker } = - initMessenger.call('NetworkController:getSelectedNetworkClient') ?? {}; - - assert( - provider, - 'Provider is required to initialize AccountTrackerController.', - ); - - assert( - blockTracker, - 'Block tracker is required to initialize AccountTrackerController.', - ); +> = ({ controllerMessenger, initMessenger, persistedState, getController }) => { + const getAssetsContractController = () => + getController('AssetsContractController'); const controller = new AccountTrackerController({ - state: { accounts: {} }, + state: persistedState.AccountTrackerController, messenger: controllerMessenger, - provider, - blockTracker, - getNetworkIdentifier: (providerConfig): string => { - const metamask = initMessenger.call('NetworkController:getState'); - - const config = - providerConfig ?? - getProviderConfig({ - metamask, - }); - - return config.type === NETWORK_TYPES.RPC && config.rpcUrl - ? config.rpcUrl - : config.type; + getStakedBalanceForChain: ( + addresses: string[], + networkClientId?: NetworkClientId, + ) => { + const assetsContractController = getAssetsContractController(); + return assetsContractController.getStakedBalanceForChain( + addresses, + networkClientId, + ); + }, + includeStakedAssets: false, + allowExternalServices: () => { + const { useExternalServices } = initMessenger.call( + 'PreferencesController:getState', + ); + return useExternalServices; }, accountsApiChainIds: () => { const state = initMessenger.call('RemoteFeatureFlagController:getState'); @@ -59,16 +41,11 @@ export const AccountTrackerControllerInit: ControllerInitFunction< state?.remoteFeatureFlags?.assetsAccountApiBalances; return Array.isArray(featureFlagForAccountApiBalances) - ? (featureFlagForAccountApiBalances as string[]) + ? (featureFlagForAccountApiBalances as `0x${string}`[]) : []; }, }); - // Ensure `AccountTrackerController` updates balances after network change. - initMessenger.subscribe('NetworkController:networkDidChange', () => { - controller.updateAccounts(); - }); - return { persistedStateKey: null, memStateKey: null, diff --git a/app/scripts/controller-init/controller-list.ts b/app/scripts/controller-init/controller-list.ts index 3a4509b860eb..3f0115974817 100644 --- a/app/scripts/controller-init/controller-list.ts +++ b/app/scripts/controller-init/controller-list.ts @@ -13,6 +13,7 @@ import { SmartTransactionsController } from '@metamask/smart-transactions-contro import { TransactionController } from '@metamask/transaction-controller'; import { AccountsController } from '@metamask/accounts-controller'; import { + AccountTrackerController, AssetsContractController, CurrencyRateController, DeFiPositionsController, @@ -88,7 +89,6 @@ import { NetworkOrderController } from '../controllers/network-order'; import OAuthService from '../services/oauth/oauth-service'; import MetaMetricsController from '../controllers/metametrics-controller'; import { SnapsNameProvider } from '../lib/SnapsNameProvider'; -import AccountTrackerController from '../controllers/account-tracker-controller'; import { AppStateController } from '../controllers/app-state-controller'; import { SnapKeyringBuilder } from '../lib/snap-keyring/snap-keyring'; import { SubscriptionService } from '../services/subscription/subscription-service'; @@ -257,4 +257,5 @@ export type ControllerFlatState = AccountOrderController['state'] & TokenRatesController['state'] & NftController['state'] & NftDetectionController['state'] & - NetworkEnablementController['state']; + NetworkEnablementController['state'] & + AccountTrackerController['state']; diff --git a/app/scripts/controller-init/messengers/account-tracker-controller-messenger.ts b/app/scripts/controller-init/messengers/account-tracker-controller-messenger.ts index b6834f170655..be4e49f100fd 100644 --- a/app/scripts/controller-init/messengers/account-tracker-controller-messenger.ts +++ b/app/scripts/controller-init/messengers/account-tracker-controller-messenger.ts @@ -1,20 +1,34 @@ import { Messenger } from '@metamask/base-controller'; import { - NetworkControllerGetSelectedNetworkClientAction, + NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerNetworkDidChangeEvent, } from '@metamask/network-controller'; import { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; -import { PreferencesControllerGetStateAction } from '../../controllers/preferences-controller'; import { - AllowedActions, - AllowedEvents, -} from '../../controllers/account-tracker-controller'; + AccountsControllerGetSelectedAccountAction, + AccountsControllerListAccountsAction, + AccountsControllerSelectedAccountChangeEvent, + AccountsControllerSelectedEvmAccountChangeEvent, +} from '@metamask/accounts-controller'; +import { PreferencesControllerGetStateAction } from '@metamask/preferences-controller'; +import { PreferencesControllerGetStateAction as InternalPreferencesControllerGetStateAction } from '../../controllers/preferences-controller'; export type AccountTrackerControllerMessenger = ReturnType< typeof getAccountTrackerControllerMessenger >; +type AllowedActions = + | AccountsControllerGetSelectedAccountAction + | AccountsControllerListAccountsAction + | NetworkControllerGetNetworkClientByIdAction + | NetworkControllerGetStateAction + | PreferencesControllerGetStateAction; + +type AllowedEvents = + | AccountsControllerSelectedAccountChangeEvent + | AccountsControllerSelectedEvmAccountChangeEvent; + /** * Create a messenger restricted to the allowed actions and events of the * account tracker controller. @@ -29,25 +43,21 @@ export function getAccountTrackerControllerMessenger( name: 'AccountTrackerController', allowedActions: [ 'AccountsController:getSelectedAccount', - 'NetworkController:getState', + 'AccountsController:listAccounts', 'NetworkController:getNetworkClientById', - 'OnboardingController:getState', + 'NetworkController:getState', 'PreferencesController:getState', - 'RemoteFeatureFlagController:getState', ], allowedEvents: [ + 'AccountsController:selectedAccountChange', 'AccountsController:selectedEvmAccountChange', - 'OnboardingController:stateChange', - 'KeyringController:accountRemoved', ], }); } type AllowedInitializationActions = - | NetworkControllerGetSelectedNetworkClientAction - | NetworkControllerGetStateAction | RemoteFeatureFlagControllerGetStateAction - | PreferencesControllerGetStateAction; + | InternalPreferencesControllerGetStateAction; type AllowedInitializationEvents = NetworkControllerNetworkDidChangeEvent; @@ -71,8 +81,6 @@ export function getAccountTrackerControllerInitMessenger( return messenger.getRestricted({ name: 'AccountTrackerControllerInit', allowedActions: [ - 'NetworkController:getSelectedNetworkClient', - 'NetworkController:getState', 'RemoteFeatureFlagController:getState', 'PreferencesController:getState', ], diff --git a/app/scripts/controller-init/messengers/keyring-controller-messenger.ts b/app/scripts/controller-init/messengers/keyring-controller-messenger.ts index 74c0519e4107..8194c6800ed5 100644 --- a/app/scripts/controller-init/messengers/keyring-controller-messenger.ts +++ b/app/scripts/controller-init/messengers/keyring-controller-messenger.ts @@ -1,8 +1,4 @@ import { Messenger } from '@metamask/base-controller'; -import { - AllowedActions, - AllowedEvents, -} from '../../controllers/account-tracker-controller'; import { AppStateControllerRequestQrCodeScanAction } from '../../controllers/app-state-controller'; export type KeyringControllerMessenger = ReturnType< @@ -17,7 +13,7 @@ export type KeyringControllerMessenger = ReturnType< * messenger. */ export function getKeyringControllerMessenger( - messenger: Messenger, + messenger: Messenger, ) { return messenger.getRestricted({ name: 'KeyringController', diff --git a/app/scripts/controller-init/messengers/token-balances-controller-messenger.ts b/app/scripts/controller-init/messengers/token-balances-controller-messenger.ts index 827528b30b55..da0ad51ac5a6 100644 --- a/app/scripts/controller-init/messengers/token-balances-controller-messenger.ts +++ b/app/scripts/controller-init/messengers/token-balances-controller-messenger.ts @@ -15,7 +15,9 @@ import { import { AccountTrackerUpdateNativeBalancesAction, AccountTrackerUpdateStakedBalancesAction, + AccountTrackerControllerGetStateAction, TokensControllerState, + type TokenDetectionControllerAddDetectedTokensViaWsAction, } from '@metamask/assets-controllers'; import { KeyringControllerAccountRemovedEvent } from '@metamask/keyring-controller'; import { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; @@ -23,8 +25,6 @@ import type { AccountActivityServiceStatusChangedEvent, AccountActivityServiceBalanceUpdatedEvent, } from '@metamask/core-backend'; -import type { TokenDetectionControllerAddDetectedTokensViaWsAction } from '@metamask/assets-controllers'; -import { AccountTrackerControllerGetStateAction } from '../../controllers/account-tracker-controller'; import { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent, diff --git a/app/scripts/controller-init/utils.ts b/app/scripts/controller-init/utils.ts index 8cf0e4834637..55782d3d443d 100644 --- a/app/scripts/controller-init/utils.ts +++ b/app/scripts/controller-init/utils.ts @@ -37,6 +37,7 @@ type ControllerMessengerCallback = ( ) => BaseRestrictedControllerMessenger; export type ControllersToInitialize = + | 'AccountTrackerController' | 'AuthenticationController' | 'CronjobController' | 'DeFiPositionsController' diff --git a/app/scripts/controllers/account-tracker-controller.ts b/app/scripts/controllers/account-tracker-controller-bk.ts similarity index 100% rename from app/scripts/controllers/account-tracker-controller.ts rename to app/scripts/controllers/account-tracker-controller-bk.ts diff --git a/app/scripts/controllers/account-tracker-controller.test.ts b/app/scripts/controllers/account-tracker-controller.test.ts deleted file mode 100644 index 61b4a25963eb..000000000000 --- a/app/scripts/controllers/account-tracker-controller.test.ts +++ /dev/null @@ -1,1609 +0,0 @@ -import EventEmitter from 'events'; -import { Messenger, deriveStateFromMetadata } from '@metamask/base-controller'; -import { InternalAccount } from '@metamask/keyring-internal-api'; -import { BlockTracker, Provider } from '@metamask/network-controller'; - -import { flushPromises } from '../../../test/lib/timer-helpers'; -import { createTestProviderTools } from '../../../test/stub/provider'; -import type { - AccountTrackerControllerOptions, - AllowedActions, - AllowedEvents, -} from './account-tracker-controller'; -import AccountTrackerController, { - getDefaultAccountTrackerControllerState, -} from './account-tracker-controller'; - -const noop = () => true; -const currentNetworkId = '5'; -const currentChainId = '0x5'; -const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; -const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; - -const SELECTED_ADDRESS = '0x123'; - -const INITIAL_BALANCE_1 = '0x1'; -const INITIAL_BALANCE_2 = '0x2'; -const UPDATE_BALANCE = '0xabc'; -const UPDATE_BALANCE_HOOK = '0xabcd'; - -const GAS_LIMIT = '0x111111'; -const GAS_LIMIT_HOOK = '0x222222'; - -// The below three values were generated by running MetaMask in the browser -// The response to eth_call, which is called via `ethContract.balances` -// in `#updateAccountsViaBalanceChecker` of account-tracker-controller.ts, needs to be properly -// formatted or else ethers will throw an error. -const ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN = - '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000038d7ea4c6800600000000000000000000000000000000000000000000000000000000000186a0'; -const EXPECTED_CONTRACT_BALANCE_1 = '0x038d7ea4c68006'; -const EXPECTED_CONTRACT_BALANCE_2 = '0x0186a0'; - -// Mock Account API response - this will be created dynamically in tests to match actual addresses -const createMockAccountApiResponse = (addresses: string[]) => ({ - balances: Object.fromEntries( - addresses.map((address, index) => [ - `token_${index}`, - { - object: 'token', - balance: '1000000000000000000', - accountAddress: `eip155:1:${address}`, - type: 'native' as const, - address: '0x0000000000000000000000000000000000000000', - symbol: 'ETH', - name: 'Ethereum', - decimals: 18, - chainId: 1, - }, - ]), - ), -}); - -const mockAccounts = { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: INITIAL_BALANCE_1 }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: INITIAL_BALANCE_2, - }, -}; - -class MockBlockTracker extends EventEmitter { - getCurrentBlock = noop; - - getLatestBlock = noop; -} - -function buildMockBlockTracker({ shouldStubListeners = true } = {}) { - const blockTrackerStub = new MockBlockTracker(); - if (shouldStubListeners) { - jest.spyOn(blockTrackerStub, 'addListener').mockImplementation(); - jest.spyOn(blockTrackerStub, 'removeListener').mockImplementation(); - } - return blockTrackerStub; -} - -type WithControllerOptions = { - completedOnboarding?: boolean; - useMultiAccountBalanceChecker?: boolean; - useExternalServices?: boolean; - getNetworkClientById?: jest.Mock; - getSelectedAccount?: jest.Mock; - accountsApiChainIds?: () => string[]; -} & Partial; - -type WithControllerCallback = ({ - controller, - blockTrackerFromHookStub, - blockTrackerStub, - triggerAccountRemoved, -}: { - controller: AccountTrackerController; - blockTrackerFromHookStub: MockBlockTracker; - blockTrackerStub: MockBlockTracker; - triggerAccountRemoved: (address: string) => void; -}) => ReturnValue; - -type WithControllerArgs = - | [WithControllerCallback] - | [WithControllerOptions, WithControllerCallback]; - -async function withController( - ...args: WithControllerArgs -): Promise { - const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; - const { - completedOnboarding = false, - useMultiAccountBalanceChecker = false, - useExternalServices = false, - getNetworkClientById, - getSelectedAccount, - accountsApiChainIds = () => ['0x1'], - ...accountTrackerOptions - } = rest; - const { provider } = createTestProviderTools({ - scaffold: { - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBalance: UPDATE_BALANCE, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_call: ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBlockByNumber: { gasLimit: GAS_LIMIT }, - }, - networkId: currentNetworkId, - chainId: currentNetworkId, - }); - const blockTrackerStub = buildMockBlockTracker(); - - const messenger = new Messenger(); - const getSelectedAccountStub = () => - ({ - id: 'accountId', - address: SELECTED_ADDRESS, - }) as InternalAccount; - messenger.registerActionHandler( - 'AccountsController:getSelectedAccount', - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31880 - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getSelectedAccount || getSelectedAccountStub, - ); - - const { provider: providerFromHook } = createTestProviderTools({ - scaffold: { - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBalance: UPDATE_BALANCE_HOOK, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_call: ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBlockByNumber: { gasLimit: GAS_LIMIT_HOOK }, - }, - networkId: 'selectedNetworkId', - chainId: currentChainId, - }); - - const getNetworkStateStub = jest.fn().mockReturnValue({ - selectedNetworkClientId: 'selectedNetworkClientId', - }); - messenger.registerActionHandler( - 'NetworkController:getState', - getNetworkStateStub, - ); - - const blockTrackerFromHookStub = buildMockBlockTracker(); - const getNetworkClientByIdStub = jest.fn().mockReturnValue({ - configuration: { - chainId: currentChainId, - }, - blockTracker: blockTrackerFromHookStub, - provider: providerFromHook, - }); - messenger.registerActionHandler( - 'NetworkController:getNetworkClientById', - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31880 - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - getNetworkClientById || getNetworkClientByIdStub, - ); - - const getOnboardingControllerState = jest.fn().mockReturnValue({ - completedOnboarding, - }); - messenger.registerActionHandler( - 'OnboardingController:getState', - getOnboardingControllerState, - ); - - const getPreferencesControllerState = jest.fn().mockReturnValue({ - useMultiAccountBalanceChecker, - useExternalServices, - }); - messenger.registerActionHandler( - 'PreferencesController:getState', - getPreferencesControllerState, - ); - - const controller = new AccountTrackerController({ - state: getDefaultAccountTrackerControllerState(), - provider: provider as Provider, - blockTracker: blockTrackerStub as unknown as BlockTracker, - getNetworkIdentifier: jest.fn(), - accountsApiChainIds, - messenger: messenger.getRestricted({ - name: 'AccountTrackerController', - allowedActions: [ - 'AccountsController:getSelectedAccount', - 'NetworkController:getState', - 'NetworkController:getNetworkClientById', - 'OnboardingController:getState', - 'PreferencesController:getState', - ], - allowedEvents: [ - 'AccountsController:selectedEvmAccountChange', - 'OnboardingController:stateChange', - 'KeyringController:accountRemoved', - ], - }), - ...accountTrackerOptions, - }); - - return await fn({ - controller, - blockTrackerFromHookStub, - blockTrackerStub, - triggerAccountRemoved: (address: string) => { - messenger.publish('KeyringController:accountRemoved', address); - }, - }); -} - -describe('AccountTrackerController', () => { - describe('start', () => { - it('restarts the subscription to the block tracker and update accounts', async () => { - await withController(({ controller, blockTrackerStub }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - controller.start(); - - expect(blockTrackerStub.removeListener).toHaveBeenNthCalledWith( - 1, - 'latest', - expect.any(Function), - ); - expect(blockTrackerStub.addListener).toHaveBeenNthCalledWith( - 1, - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenNthCalledWith(1); // called first time with no args - - controller.start(); - - expect(blockTrackerStub.removeListener).toHaveBeenNthCalledWith( - 2, - 'latest', - expect.any(Function), - ); - expect(blockTrackerStub.addListener).toHaveBeenNthCalledWith( - 2, - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenNthCalledWith(2); // called second time with no args - - controller.stop(); - }); - }); - }); - - describe('stop', () => { - it('ends the subscription to the block tracker', async () => { - await withController(({ controller, blockTrackerStub }) => { - controller.stop(); - - expect(blockTrackerStub.removeListener).toHaveBeenNthCalledWith( - 1, - 'latest', - expect.any(Function), - ); - }); - }); - }); - - describe('startPollingByNetworkClientId', () => { - it('should subscribe to the block tracker and update accounts if not already using the networkClientId', async () => { - await withController(({ controller, blockTrackerFromHookStub }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - controller.startPollingByNetworkClientId('mainnet'); - - expect(blockTrackerFromHookStub.addListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenCalledWith('mainnet'); - - controller.startPollingByNetworkClientId('mainnet'); - - expect(blockTrackerFromHookStub.addListener).toHaveBeenCalledTimes(1); - expect(updateAccountsSpy).toHaveBeenCalledTimes(1); - - controller.stopAllPolling(); - }); - }); - - it('should subscribe to the block tracker and update accounts for each networkClientId', async () => { - const blockTrackerFromHookStub1 = buildMockBlockTracker(); - const blockTrackerFromHookStub2 = buildMockBlockTracker(); - const blockTrackerFromHookStub3 = buildMockBlockTracker(); - await withController( - { - getNetworkClientById: jest - .fn() - .mockImplementation((networkClientId) => { - switch (networkClientId) { - case 'mainnet': - return { - configuration: { - chainId: '0x1', - }, - blockTracker: blockTrackerFromHookStub1, - }; - case 'goerli': - return { - configuration: { - chainId: '0x5', - }, - blockTracker: blockTrackerFromHookStub2, - }; - case 'networkClientId1': - return { - configuration: { - chainId: '0xa', - }, - blockTracker: blockTrackerFromHookStub3, - }; - default: - throw new Error('unexpected networkClientId'); - } - }), - }, - ({ controller }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - controller.startPollingByNetworkClientId('mainnet'); - - expect(blockTrackerFromHookStub1.addListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenCalledWith('mainnet'); - - controller.startPollingByNetworkClientId('goerli'); - - expect(blockTrackerFromHookStub2.addListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenCalledWith('goerli'); - - controller.startPollingByNetworkClientId('networkClientId1'); - - expect(blockTrackerFromHookStub3.addListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(updateAccountsSpy).toHaveBeenCalledWith('networkClientId1'); - - controller.stopAllPolling(); - }, - ); - }); - }); - - describe('stopPollingByPollingToken', () => { - it('should unsubscribe from the block tracker when called with a valid polling that was the only active pollingToken for a given networkClient', async () => { - await withController(({ controller, blockTrackerFromHookStub }) => { - jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); - - const pollingToken = - controller.startPollingByNetworkClientId('mainnet'); - - controller.stopPollingByPollingToken(pollingToken); - - expect(blockTrackerFromHookStub.removeListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - }); - }); - - it('should gracefully handle unknown polling tokens', async () => { - await withController(({ controller, blockTrackerFromHookStub }) => { - jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); - - const pollingToken = - controller.startPollingByNetworkClientId('mainnet'); - - controller.stopPollingByPollingToken('unknown-token'); - controller.stopPollingByPollingToken(pollingToken); - - expect(blockTrackerFromHookStub.removeListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - }); - }); - - it('should not unsubscribe from the block tracker if called with one of multiple active polling tokens for a given networkClient', async () => { - await withController(({ controller, blockTrackerFromHookStub }) => { - jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); - - const pollingToken1 = - controller.startPollingByNetworkClientId('mainnet'); - controller.startPollingByNetworkClientId('mainnet'); - - controller.stopPollingByPollingToken(pollingToken1); - - expect(blockTrackerFromHookStub.removeListener).not.toHaveBeenCalled(); - - controller.stopAllPolling(); - }); - }); - - it('should error if no pollingToken is passed', async () => { - await withController(({ controller }) => { - expect(() => { - controller.stopPollingByPollingToken(undefined); - }).toThrow('pollingToken required'); - }); - }); - }); - - describe('stopAll', () => { - it('should end all subscriptions', async () => { - const blockTrackerFromHookStub1 = buildMockBlockTracker(); - const blockTrackerFromHookStub2 = buildMockBlockTracker(); - const getNetworkClientByIdStub = jest - .fn() - .mockImplementation((networkClientId) => { - switch (networkClientId) { - case 'mainnet': - return { - configuration: { - chainId: '0x1', - }, - blockTracker: blockTrackerFromHookStub1, - }; - case 'goerli': - return { - configuration: { - chainId: '0x5', - }, - blockTracker: blockTrackerFromHookStub2, - }; - default: - throw new Error('unexpected networkClientId'); - } - }); - await withController( - { - getNetworkClientById: getNetworkClientByIdStub, - }, - ({ controller, blockTrackerStub }) => { - jest.spyOn(controller, 'updateAccounts').mockResolvedValue(); - - controller.startPollingByNetworkClientId('mainnet'); - - controller.startPollingByNetworkClientId('goerli'); - - controller.stopAllPolling(); - - expect(blockTrackerStub.removeListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(blockTrackerFromHookStub1.removeListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - expect(blockTrackerFromHookStub2.removeListener).toHaveBeenCalledWith( - 'latest', - expect.any(Function), - ); - }, - ); - }); - }); - - describe('blockTracker "latest" events', () => { - it('updates currentBlockGasLimit, currentBlockGasLimitByChainId, and accounts when polling is initiated via `start`', async () => { - const blockTrackerStub = buildMockBlockTracker({ - shouldStubListeners: false, - }); - await withController( - { - blockTracker: blockTrackerStub as unknown as BlockTracker, - }, - async ({ controller }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - controller.start(); - blockTrackerStub.emit('latest', 'blockNumber'); - - await flushPromises(); - - expect(updateAccountsSpy).toHaveBeenCalledWith(undefined); - - expect(controller.state).toStrictEqual({ - accounts: {}, - accountsByChainId: {}, - currentBlockGasLimit: GAS_LIMIT, - currentBlockGasLimitByChainId: { - [currentChainId]: GAS_LIMIT, - }, - }); - - controller.stop(); - }, - ); - }); - - it('updates only the currentBlockGasLimitByChainId and accounts when polling is initiated via `startPollingByNetworkClientId`', async () => { - const blockTrackerFromHookStub = buildMockBlockTracker({ - shouldStubListeners: false, - }); - const providerFromHook = createTestProviderTools({ - scaffold: { - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBalance: UPDATE_BALANCE_HOOK, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_call: ETHERS_CONTRACT_BALANCES_ETH_CALL_RETURN, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 - // eslint-disable-next-line @typescript-eslint/naming-convention - eth_getBlockByNumber: { gasLimit: GAS_LIMIT_HOOK }, - }, - networkId: '0x1', - chainId: '0x1', - }).provider; - const getNetworkClientByIdStub = jest - .fn() - .mockImplementation((networkClientId) => { - switch (networkClientId) { - case 'mainnet': - return { - configuration: { - chainId: '0x1', - }, - blockTracker: blockTrackerFromHookStub, - provider: providerFromHook, - }; - case 'selectedNetworkClientId': - return { - configuration: { - chainId: currentChainId, - }, - }; - default: - throw new Error('unexpected networkClientId'); - } - }); - await withController( - { - getNetworkClientById: getNetworkClientByIdStub, - }, - async ({ controller }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - controller.startPollingByNetworkClientId('mainnet'); - - blockTrackerFromHookStub.emit('latest', 'blockNumber'); - - await flushPromises(); - - expect(updateAccountsSpy).toHaveBeenCalledWith('mainnet'); - - expect(controller.state).toStrictEqual({ - accounts: {}, - accountsByChainId: {}, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: { - '0x1': GAS_LIMIT_HOOK, - }, - }); - - controller.stopAllPolling(); - }, - ); - }); - }); - - describe('updateAccountsAllActiveNetworks', () => { - it('updates accounts for the globally selected network and all currently polling networks', async () => { - await withController( - { - // Enable onboarding completion and disable Account API to test the traditional individual network calls behavior - completedOnboarding: true, - useExternalServices: false, - useMultiAccountBalanceChecker: false, - accountsApiChainIds: () => [], - state: { - accounts: { - [SELECTED_ADDRESS]: { address: SELECTED_ADDRESS, balance: '0x0' }, - }, - }, - }, - async ({ controller }) => { - const updateAccountsSpy = jest - .spyOn(controller, 'updateAccounts') - .mockResolvedValue(); - - // Start polling for multiple networks - await controller.startPollingByNetworkClientId('networkClientId1'); - await controller.startPollingByNetworkClientId('networkClientId2'); - await controller.startPollingByNetworkClientId('networkClientId3'); - - expect(updateAccountsSpy).toHaveBeenCalledTimes(3); - - // This should make individual calls for each network since Account API is disabled - await controller.updateAccountsAllActiveNetworks(); - - // Should make 4 additional calls: 1 for global network + 3 for polling networks - expect(updateAccountsSpy).toHaveBeenCalledTimes(7); - expect(updateAccountsSpy).toHaveBeenNthCalledWith(4); // called with no args - expect(updateAccountsSpy).toHaveBeenNthCalledWith( - 5, - 'networkClientId1', - ); - expect(updateAccountsSpy).toHaveBeenNthCalledWith( - 6, - 'networkClientId2', - ); - expect(updateAccountsSpy).toHaveBeenNthCalledWith( - 7, - 'networkClientId3', - ); - }, - ); - }); - }); - - describe('updateAccounts', () => { - it('does not update accounts if completedOnBoarding is false', async () => { - await withController( - { - completedOnboarding: false, - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(controller.state).toStrictEqual({ - accounts: {}, - currentBlockGasLimit: '', - accountsByChainId: {}, - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - - describe('chain does not have single call balance address', () => { - const mockAccountsWithSelectedAddress = { - ...mockAccounts, - [SELECTED_ADDRESS]: { - address: SELECTED_ADDRESS, - balance: '0x0', - }, - }; - const mockInitialState = { - accounts: mockAccountsWithSelectedAddress, - accountsByChainId: { - '0x999': mockAccountsWithSelectedAddress, - }, - }; - - describe('when useMultiAccountBalanceChecker is true', () => { - it('updates all accounts directly', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - state: mockInitialState, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { - chainId: '0x999', - }, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - const accounts = { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: UPDATE_BALANCE, - }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: UPDATE_BALANCE, - }, - [SELECTED_ADDRESS]: { - address: SELECTED_ADDRESS, - balance: UPDATE_BALANCE, - }, - }; - - expect(controller.state).toStrictEqual({ - accounts, - accountsByChainId: { - '0x999': accounts, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - - describe('when useMultiAccountBalanceChecker is false', () => { - it('updates only the selectedAddress directly, setting other balances to null', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: false, - state: mockInitialState, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { - chainId: '0x999', - }, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - const accounts = { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: null }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: null, - }, - [SELECTED_ADDRESS]: { - address: SELECTED_ADDRESS, - balance: UPDATE_BALANCE, - }, - }; - - expect(controller.state).toStrictEqual({ - accounts, - accountsByChainId: { - '0x999': accounts, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - }); - - describe('chain does have single call balance address and network is not localhost', () => { - describe('when useMultiAccountBalanceChecker is true', () => { - it('updates all accounts via balance checker', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - getNetworkIdentifier: jest - .fn() - .mockReturnValue('http://not-localhost:8545'), - getSelectedAccount: jest.fn().mockReturnValue({ - id: 'accountId', - address: VALID_ADDRESS, - } as InternalAccount), - state: { - accounts: { ...mockAccounts }, - accountsByChainId: { - [currentChainId]: { ...mockAccounts }, - }, - }, - }, - async ({ controller }) => { - await controller.updateAccounts('mainnet'); - - const accounts = { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: EXPECTED_CONTRACT_BALANCE_1, - }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: EXPECTED_CONTRACT_BALANCE_2, - }, - }; - - expect(controller.state).toStrictEqual({ - accounts, - accountsByChainId: { - [currentChainId]: accounts, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - }); - }); - - describe('updateAccountByAddress', () => { - it('does not update account by address if completedOnboarding is false', async () => { - await withController( - { - completedOnboarding: false, - }, - async ({ controller }) => { - const VALID_ADDRESS_TO_UPDATE = '0x1234'; - await controller.updateAccountByAddress({ - address: VALID_ADDRESS_TO_UPDATE, - }); - - expect(controller.state).toStrictEqual({ - accounts: {}, - currentBlockGasLimit: '', - accountsByChainId: {}, - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - - it('updates an account by address if completedOnboarding is true', async () => { - const VALID_ADDRESS_TO_UPDATE = '0x1234'; - await withController( - { - completedOnboarding: true, - state: { - accounts: { - [VALID_ADDRESS_TO_UPDATE]: { - address: VALID_ADDRESS_TO_UPDATE, - balance: null, - }, - }, - }, - }, - async ({ controller }) => { - await controller.updateAccountByAddress({ - address: VALID_ADDRESS_TO_UPDATE, - }); - - expect(controller.state).toStrictEqual({ - accounts: { - [VALID_ADDRESS_TO_UPDATE]: { - address: VALID_ADDRESS_TO_UPDATE, - balance: '0xabc', - }, - }, - currentBlockGasLimit: '', - accountsByChainId: { - [currentChainId]: { - [VALID_ADDRESS_TO_UPDATE]: { - address: VALID_ADDRESS_TO_UPDATE, - balance: '0xabc', - }, - }, - }, - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - - it('updates an account for selected address if no address is provided', async () => { - await withController( - { - completedOnboarding: true, - state: { - accounts: { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: null, - }, - }, - accountsByChainId: { - [currentChainId]: { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: null, - }, - }, - }, - }, - }, - async ({ controller }) => { - expect(controller.state).toStrictEqual({ - accounts: { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: null, - }, - }, - accountsByChainId: { - [currentChainId]: { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: null, - }, - }, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - - describe('onAccountRemoved', () => { - it('should remove an account from state', async () => { - await withController( - { - state: { - accounts: { ...mockAccounts }, - accountsByChainId: { - [currentChainId]: { - ...mockAccounts, - }, - '0x1': { - ...mockAccounts, - }, - '0x2': { - ...mockAccounts, - }, - }, - }, - }, - ({ controller, triggerAccountRemoved }) => { - triggerAccountRemoved(VALID_ADDRESS); - - const accounts = { - [VALID_ADDRESS_TWO]: mockAccounts[VALID_ADDRESS_TWO], - }; - - expect(controller.state).toStrictEqual({ - accounts, - accountsByChainId: { - [currentChainId]: accounts, - '0x1': accounts, - '0x2': accounts, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - - describe('clearAccounts', () => { - it('should reset state', async () => { - await withController( - { - state: { - accounts: { ...mockAccounts }, - accountsByChainId: { - [currentChainId]: { - ...mockAccounts, - }, - '0x1': { - ...mockAccounts, - }, - '0x2': { - ...mockAccounts, - }, - }, - }, - }, - ({ controller }) => { - controller.clearAccounts(); - - expect(controller.state).toStrictEqual({ - accounts: {}, - accountsByChainId: { - [currentChainId]: {}, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }); - }, - ); - }); - }); - - describe('updateNativeBalances', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should update balances for multiple accounts across different chains', async () => { - await withController(({ controller }) => { - const balances = [ - { - address: VALID_ADDRESS, - chainId: '0x1' as const, - balance: '0x123456789', - }, - { - address: VALID_ADDRESS_TWO, - chainId: '0x1' as const, - balance: '0x987654321', - }, - { - address: VALID_ADDRESS, - chainId: '0x89' as const, - balance: '0xabcdef123', - }, - ]; - - controller.updateNativeBalances(balances); - - expect(controller.state.accountsByChainId).toStrictEqual({ - '0x1': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0x123456789', - }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: '0x987654321', - }, - }, - '0x89': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0xabcdef123', - }, - }, - }); - }); - }); - - it('should update existing balances without affecting other properties', async () => { - await withController( - { - state: { - accounts: {}, - accountsByChainId: { - '0x1': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0x111', - stakedBalance: '0xstaked123', - }, - }, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }, - }, - ({ controller }) => { - const balances = [ - { - address: VALID_ADDRESS, - chainId: '0x1' as const, - balance: '0x222', - }, - ]; - - controller.updateNativeBalances(balances); - - expect( - controller.state.accountsByChainId['0x1'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: '0x222', - stakedBalance: '0xstaked123', - }); - }, - ); - }); - }); - - describe('updateStakedBalances', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should update staked balances for multiple accounts across different chains', async () => { - await withController(({ controller }) => { - const stakedBalances = [ - { - address: VALID_ADDRESS, - chainId: '0x1' as const, - stakedBalance: '0x1', - }, - { - address: VALID_ADDRESS_TWO, - chainId: '0x1' as const, - stakedBalance: '0x1', - }, - { - address: VALID_ADDRESS, - chainId: '0x89' as const, - stakedBalance: '0x1', - }, - ]; - - controller.updateStakedBalances(stakedBalances); - - expect(controller.state.accountsByChainId).toStrictEqual({ - '0x1': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0x0', - stakedBalance: '0x1', - }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: '0x0', - stakedBalance: '0x1', - }, - }, - '0x89': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0x0', - stakedBalance: '0x1', - }, - }, - }); - }); - }); - - it('should update existing staked balances without affecting other properties', async () => { - await withController( - { - state: { - accounts: {}, - accountsByChainId: { - '0x1': { - [VALID_ADDRESS]: { - address: VALID_ADDRESS, - balance: '0x123', - stakedBalance: '0x1', - }, - }, - }, - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, - }, - }, - ({ controller }) => { - const stakedBalances = [ - { - address: VALID_ADDRESS, - chainId: '0x1' as const, - stakedBalance: '0x1', - }, - ]; - - controller.updateStakedBalances(stakedBalances); - - expect( - controller.state.accountsByChainId['0x1'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: '0x123', - stakedBalance: '0x1', - }); - }, - ); - }); - }); - - describe('Account API', () => { - let fetchMock: jest.MockedFunction; - - beforeEach(() => { - fetchMock = jest.fn(); - global.fetch = fetchMock; - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - describe('with Account API enabled and supported chain', () => { - it('should use Account API when conditions are met', async () => { - const mockResponse = createMockAccountApiResponse([ - VALID_ADDRESS, - VALID_ADDRESS_TWO, - ]); - fetchMock.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(mockResponse), - } as Response); - - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: '0x0', - }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).toHaveBeenCalled(); - }, - ); - }); - - it('should construct correct API URL with multiple addresses', async () => { - const mockResponse = createMockAccountApiResponse([ - VALID_ADDRESS, - VALID_ADDRESS_TWO, - ]); - fetchMock.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(mockResponse), - } as Response); - - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - [VALID_ADDRESS_TWO]: { - address: VALID_ADDRESS_TWO, - balance: '0x0', - }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).toHaveBeenCalled(); - expect(fetchMock).toHaveBeenCalled(); - }, - ); - }); - - it('should fallback to RPC when Account API returns non-ok response', async () => { - fetchMock.mockResolvedValueOnce({ - ok: false, - status: 500, - statusText: 'Internal Server Error', - } as Response); - - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).toHaveBeenCalled(); - // Should update balance via balance checker fallback (not RPC since useMultiAccountBalanceChecker=true) - expect( - controller.state.accountsByChainId['0x1'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: EXPECTED_CONTRACT_BALANCE_1, - }); - }, - ); - }); - - it('should fallback to RPC when Account API throws error', async () => { - fetchMock.mockRejectedValueOnce(new Error('Network error')); - - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).toHaveBeenCalled(); - // Should update balance via balance checker fallback (not RPC since useMultiAccountBalanceChecker=true) - expect( - controller.state.accountsByChainId['0x1'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: EXPECTED_CONTRACT_BALANCE_1, - }); - }, - ); - }); - }); - - describe('with Account API conditions not met', () => { - it('should not call Account API when external services disabled', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: false, // disabled - accountsApiChainIds: () => ['0x1'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).not.toHaveBeenCalled(); - // Should update balance via balance checker (not RPC since useMultiAccountBalanceChecker=true) - expect( - controller.state.accountsByChainId['0x1'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: EXPECTED_CONTRACT_BALANCE_1, - }); - }, - ); - }); - - it('should not call Account API when chain not supported', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], // Only Ethereum supported - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }, - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x89' }, // Polygon not supported - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - expect(fetchMock).not.toHaveBeenCalled(); - // Should update balance via balance checker fallback (not RPC since useMultiAccountBalanceChecker=true and Polygon has balance checker) - expect( - controller.state.accountsByChainId['0x89'][VALID_ADDRESS], - ).toStrictEqual({ - address: VALID_ADDRESS, - balance: EXPECTED_CONTRACT_BALANCE_1, - }); - }, - ); - }); - }); - - describe('Account API configuration', () => { - it('should support multiple chains configured via feature flag', async () => { - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1', '0x38', '0xe708'], - state: { - accounts: { - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }, - }, - }, - ({ controller }) => { - // Test that the controller is properly initialized with Account API configuration - // We verify this indirectly by checking the controller is created successfully - expect(controller).toBeDefined(); - expect(controller.state).toBeDefined(); - expect(controller.state.accounts).toStrictEqual({ - [VALID_ADDRESS]: { address: VALID_ADDRESS, balance: '0x0' }, - }); - }, - ); - }); - }); - - describe('Account API batching', () => { - it('should handle multiple batches with more than 50 accounts', async () => { - const addresses = Array.from( - { length: 125 }, // This should create 3 batches: 50, 50, 25 - (_, i) => `0x${i.toString().padStart(40, '0')}`, - ); - - // Mock response for each batch - const batchResponse = { - balances: { - '1': Object.fromEntries( - addresses.slice(0, 50).map((address) => [ - address, - { - balance: '1000000000000000000', - token: { - address: '0x0000000000000000000000000000000000000000', - symbol: 'ETH', - decimals: 18, - name: 'Ethereum', - type: 'native' as const, - }, - }, - ]), - ), - }, - }; - - fetchMock - .mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(batchResponse), - } as Response) - .mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(batchResponse), - } as Response) - .mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(batchResponse), - } as Response); - - await withController( - { - completedOnboarding: true, - useMultiAccountBalanceChecker: true, - useExternalServices: true, - accountsApiChainIds: () => ['0x1'], - state: { - accounts: Object.fromEntries( - addresses.map((address) => [ - address, - { address, balance: '0x0' }, - ]), - ), - }, - getNetworkClientById: jest.fn().mockReturnValue({ - configuration: { chainId: '0x1' }, - provider: {} as Provider, - }), - }, - async ({ controller }) => { - await controller.updateAccounts(); - - // Should make 3 API calls (batches of 50, 50, 25) - expect(fetchMock).toHaveBeenCalledTimes(3); - - // Each call should be to the correct endpoint - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - expect.stringContaining( - 'https://accounts.api.cx.metamask.io/v4/multiaccount/balances', - ), - expect.objectContaining({ - method: 'GET', - headers: { Accept: 'application/json' }, - }), - ); - }, - ); - }); - }); - }); - - describe('metadata', () => { - it('includes expected state in debug snapshots', async () => { - await withController(({ controller }) => { - expect( - deriveStateFromMetadata( - controller.state, - controller.metadata, - 'anonymous', - ), - ).toMatchInlineSnapshot(` - { - "currentBlockGasLimit": "", - "currentBlockGasLimitByChainId": {}, - } - `); - }); - }); - - it('includes expected state in state logs', async () => { - await withController(({ controller }) => { - expect( - deriveStateFromMetadata( - controller.state, - controller.metadata, - 'includeInStateLogs', - ), - ).toMatchInlineSnapshot(`{}`); - }); - }); - - it('persists expected state', async () => { - await withController(({ controller }) => { - expect( - deriveStateFromMetadata( - controller.state, - controller.metadata, - 'persist', - ), - ).toMatchInlineSnapshot(` - { - "accounts": {}, - "accountsByChainId": {}, - "currentBlockGasLimit": "", - "currentBlockGasLimitByChainId": {}, - } - `); - }); - }); - - it('exposes expected state to UI', async () => { - await withController(({ controller }) => { - expect( - deriveStateFromMetadata( - controller.state, - controller.metadata, - 'usedInUi', - ), - ).toMatchInlineSnapshot(` - { - "accounts": {}, - "accountsByChainId": {}, - "currentBlockGasLimit": "", - "currentBlockGasLimitByChainId": {}, - } - `); - }); - }); - }); -}); diff --git a/app/scripts/controllers/preferences-controller.test.ts b/app/scripts/controllers/preferences-controller.test.ts index 8766a24557d0..1a9dd4b35386 100644 --- a/app/scripts/controllers/preferences-controller.test.ts +++ b/app/scripts/controllers/preferences-controller.test.ts @@ -867,7 +867,6 @@ describe('preferences controller', () => { "enableMV3TimestampSave": true, "featureFlags": {}, "forgottenPassword": false, - "isMultiAccountBalancesEnabled": true, "ledgerTransportType": "u2f", "openSeaEnabled": true, "overrideContentSecurityPolicyHeader": true, @@ -902,6 +901,7 @@ describe('preferences controller', () => { "useAddressBarEnsResolution": true, "useBlockie": false, "useCurrencyRateCheck": true, + "isMultiAccountBalancesEnabled": true, "useMultiAccountBalanceChecker": true, "useNftDetection": true, "usePhishDetect": true, @@ -935,7 +935,6 @@ describe('preferences controller', () => { "identities": {}, "ipfsGateway": "dweb.link", "isIpfsGatewayEnabled": true, - "isMultiAccountBalancesEnabled": true, "knownMethodData": {}, "ledgerTransportType": "u2f", "lostIdentities": {}, @@ -983,6 +982,7 @@ describe('preferences controller', () => { "useCurrencyRateCheck": true, "useExternalNameSources": true, "useExternalServices": true, + "isMultiAccountBalancesEnabled": true, "useMultiAccountBalanceChecker": true, "useNftDetection": true, "usePhishDetect": true, @@ -1018,7 +1018,6 @@ describe('preferences controller', () => { "identities": {}, "ipfsGateway": "dweb.link", "isIpfsGatewayEnabled": true, - "isMultiAccountBalancesEnabled": true, "knownMethodData": {}, "ledgerTransportType": "u2f", "lostIdentities": {}, @@ -1066,6 +1065,7 @@ describe('preferences controller', () => { "useCurrencyRateCheck": true, "useExternalNameSources": true, "useExternalServices": true, + "isMultiAccountBalancesEnabled": true, "useMultiAccountBalanceChecker": true, "useNftDetection": true, "usePhishDetect": true, @@ -1101,7 +1101,6 @@ describe('preferences controller', () => { "identities": {}, "ipfsGateway": "dweb.link", "isIpfsGatewayEnabled": true, - "isMultiAccountBalancesEnabled": true, "knownMethodData": {}, "ledgerTransportType": "u2f", "lostIdentities": {}, @@ -1149,6 +1148,7 @@ describe('preferences controller', () => { "useCurrencyRateCheck": true, "useExternalNameSources": true, "useExternalServices": true, + "isMultiAccountBalancesEnabled": true, "useMultiAccountBalanceChecker": true, "useNftDetection": true, "usePhishDetect": true, diff --git a/app/scripts/controllers/preferences-controller.ts b/app/scripts/controllers/preferences-controller.ts index 23e6a42b05da..34e27f47506c 100644 --- a/app/scripts/controllers/preferences-controller.ts +++ b/app/scripts/controllers/preferences-controller.ts @@ -154,6 +154,7 @@ export type PreferencesControllerState = Omit< useCurrencyRateCheck: boolean; useExternalNameSources: boolean; useExternalServices: boolean; + isMultiAccountBalancesEnabled: boolean; useMultiAccountBalanceChecker: boolean; usePhishDetect: boolean; referrals: { @@ -183,8 +184,6 @@ export const getDefaultPreferencesControllerState = // ENS decentralized website resolution ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, isIpfsGatewayEnabled: true, - // from core PreferencesController - isMultiAccountBalancesEnabled: true, knownMethodData: {}, // Ledger transport type is deprecated. We currently only support webhid // on chrome, and u2f on firefox. @@ -237,6 +236,8 @@ export const getDefaultPreferencesControllerState = // Whenever useExternalServices is false, certain features will be disabled. // The flag is true by Default, meaning the toggle is ON by default. useExternalServices: true, + // from core PreferencesController + isMultiAccountBalancesEnabled: true, useMultiAccountBalanceChecker: true, useNftDetection: true, usePhishDetect: true, @@ -319,12 +320,6 @@ const controllerMetadata = { anonymous: false, usedInUi: true, }, - isMultiAccountBalancesEnabled: { - includeInStateLogs: true, - persist: true, - anonymous: true, - usedInUi: true, - }, knownMethodData: { includeInStateLogs: true, persist: true, @@ -440,6 +435,12 @@ const controllerMetadata = { anonymous: false, usedInUi: true, }, + isMultiAccountBalancesEnabled: { + includeInStateLogs: true, + persist: true, + anonymous: true, + usedInUi: true, + }, useMultiAccountBalanceChecker: { includeInStateLogs: true, persist: true, @@ -512,6 +513,11 @@ export class PreferencesController extends BaseController< ...defaultState.preferences, ...state?.preferences, }, + // TODO - These two properties are the same, we only need isMultiAccountBalancesEnabled to keep it compatible with core PreferencesController + // At some point we should completely remove all references and methods for useMultiAccountBalanceChecker and use isMultiAccountBalancesEnabled instead. + isMultiAccountBalancesEnabled: + state?.useMultiAccountBalanceChecker ?? + defaultState.isMultiAccountBalancesEnabled, }; super({ @@ -573,6 +579,7 @@ export class PreferencesController extends BaseController< setUseMultiAccountBalanceChecker(val: boolean): void { this.update((state) => { state.useMultiAccountBalanceChecker = val; + state.isMultiAccountBalancesEnabled = val; }); } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f4fc65cba4e5..76c4bfa055fd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1133,9 +1133,6 @@ export default class MetamaskController extends EventEmitter { // if this is the first time, clear the state of by calling these methods const resetMethods = [ - this.accountTrackerController.resetState.bind( - this.accountTrackerController, - ), this.decryptMessageController.resetState.bind( this.decryptMessageController, ), @@ -3094,10 +3091,9 @@ export default class MetamaskController extends EventEmitter { tokenRatesController.stopPollingByPollingToken.bind( tokenRatesController, ), - accountTrackerStartPolling: - accountTrackerController.startPollingByNetworkClientId.bind( - accountTrackerController, - ), + accountTrackerStartPolling: accountTrackerController.startPolling.bind( + accountTrackerController, + ), accountTrackerStopPollingByPollingToken: accountTrackerController.stopPollingByPollingToken.bind( accountTrackerController, @@ -4496,9 +4492,6 @@ export default class MetamaskController extends EventEmitter { // TODO: Update @metamask/accounts-controller to support this. this.accountOrderController.updateHiddenAccountsList([]); - // clear accounts in AccountTrackerController - this.accountTrackerController.clearAccounts(); - this.txController.clearUnapprovedTransactions(); if (completedOnboarding) { @@ -4809,10 +4802,12 @@ export default class MetamaskController extends EventEmitter { * @param {Provider} provider - The provider instance to use when asking the network */ async getBalance(address, provider) { + // TODO We should not rely on global chain id const accounts = this.accountTrackerController.state.accountsByChainId[ this.#getGlobalChainId() ]; + // TODO - Checksum? const cached = accounts?.[address]; if (cached && cached.balance) { @@ -4903,10 +4898,6 @@ export default class MetamaskController extends EventEmitter { try { // Automatic login via config password await this.submitPassword(password); - - // Updating accounts in this.accountTrackerController before starting UI syncing ensure that - // state has account balance before it is synced with UI - await this.accountTrackerController.updateAccountsAllActiveNetworks(); } finally { this._startUISync(); } @@ -5034,16 +5025,6 @@ export default class MetamaskController extends EventEmitter { accounts = await keyring.getFirstPage(); } - // Merge with existing accounts - // and make sure addresses are not repeated - const oldAccounts = await this.keyringController.getAccounts(); - - const accountsToTrack = [ - ...new Set( - oldAccounts.concat(accounts.map((a) => a.address.toLowerCase())), - ), - ]; - this.accountTrackerController.syncWithAddresses(accountsToTrack); return accounts; }, ); @@ -5339,6 +5320,7 @@ export default class MetamaskController extends EventEmitter { const internalAccountCount = internalAccounts.length; + // TODO We should not rely on global chain id const accountsForCurrentChain = this.accountTrackerController.state.accountsByChainId[ this.#getGlobalChainId() @@ -7509,7 +7491,8 @@ export default class MetamaskController extends EventEmitter { return; } - this.accountTrackerController.syncWithAddresses(addresses); + // TODO AccountTrackerController Migration: Review how this works before merging + // this.accountTrackerController.syncWithAddresses(addresses); } /** @@ -7700,6 +7683,7 @@ export default class MetamaskController extends EventEmitter { ), // Other dependencies getAccountBalance: (account, chainId) => + // TODO - Checksum? this.accountTrackerController.state.accountsByChainId?.[chainId]?.[ account ]?.balance, @@ -7797,10 +7781,11 @@ export default class MetamaskController extends EventEmitter { networkClientId, txParams: { from }, } = transactionMeta; - this.accountTrackerController.updateAccountByAddress({ - address: from, - networkClientId, - }); + // TODO AccountTrackerController Migration: Review how we handle this + // this.accountTrackerController.updateAccountByAddress({ + // address: from, + // networkClientId, + // }); } toggleExternalServices(useExternal) { diff --git a/shared/types/background.ts b/shared/types/background.ts index 1d12f9be9c33..26fe8a5763cc 100644 --- a/shared/types/background.ts +++ b/shared/types/background.ts @@ -12,6 +12,7 @@ import type { MultichainAssetsRatesControllerState, MultichainAssetsControllerState, DeFiPositionsControllerState, + AccountTrackerControllerState, } from '@metamask/assets-controllers'; import type { MultichainTransactionsControllerState } from '@metamask/multichain-transactions-controller'; import type { MultichainNetworkControllerState } from '@metamask/multichain-network-controller'; @@ -57,7 +58,6 @@ import type { } from '@metamask/notification-services-controller'; import type { SmartTransactionsControllerState } from '@metamask/smart-transactions-controller'; -import type { AccountTrackerControllerState } from '../../app/scripts/controllers/account-tracker-controller'; import type { NetworkOrderControllerState } from '../../app/scripts/controllers/network-order'; import type { AccountOrderControllerState } from '../../app/scripts/controllers/account-order'; import type { PreferencesControllerState } from '../../app/scripts/controllers/preferences-controller'; @@ -73,10 +73,7 @@ import type { SwapsControllerState } from '../../app/scripts/controllers/swaps/s export type ControllerStatePropertiesEnumerated = { internalAccounts: AccountsControllerState['internalAccounts']; - accounts: AccountTrackerControllerState['accounts']; accountsByChainId: AccountTrackerControllerState['accountsByChainId']; - currentBlockGasLimit: AccountTrackerControllerState['currentBlockGasLimit']; - currentBlockGasLimitByChainId: AccountTrackerControllerState['currentBlockGasLimitByChainId']; addressBook: AddressBookControllerState['addressBook']; alertEnabledness: AlertControllerState['alertEnabledness']; unconnectedAccountAlertShownOrigins: AlertControllerState['unconnectedAccountAlertShownOrigins']; diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 9266125a584a..ba058da758ab 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -1777,17 +1777,6 @@ class FixtureBuilder { withTrezorAccount() { return this.withAccountTracker({ - accounts: { - '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { - address: '0x5cfe73b6021e818b776b421b1c4db2474086a7e1', - balance: '0x15af1d78b58c40000', - }, - '0xf68464152d7289d7ea9a2bec2e0035c45188223c': { - address: '0xf68464152d7289d7ea9a2bec2e0035c45188223c', - balance: '0x100000000000000000000', - }, - }, - currentBlockGasLimit: '0x1c9c380', accountsByChainId: { '0x539': { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { @@ -1800,9 +1789,6 @@ class FixtureBuilder { }, }, }, - currentBlockGasLimitByChainId: { - '0x539': '0x1c9c380', - }, }) .withAccountsController({ internalAccounts: { diff --git a/test/e2e/tests/metrics/errors.spec.ts b/test/e2e/tests/metrics/errors.spec.ts index a435e8b0a573..7395f4bdc74a 100644 --- a/test/e2e/tests/metrics/errors.spec.ts +++ b/test/e2e/tests/metrics/errors.spec.ts @@ -54,9 +54,6 @@ const maskedBackgroundFields = [ const maskedUiFields = maskedBackgroundFields.map(backgroundToUiField); const removedBackgroundFields = [ - // This property is timing-dependent - 'AccountTracker.currentBlockGasLimit', - 'AccountTracker.currentBlockGasLimitByChainId', // These properties are set to undefined, causing inconsistencies between Chrome and Firefox 'AppStateController.currentPopupId', 'AppStateController.timeoutMinutes', diff --git a/test/integration/data/integration-init-state.json b/test/integration/data/integration-init-state.json index e50848f5df34..b5735703cf13 100644 --- a/test/integration/data/integration-init-state.json +++ b/test/integration/data/integration-init-state.json @@ -32,32 +32,6 @@ } } }, - "accounts": { - "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { - "balance": "0x346ba7725f412cbfdb", - "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" - }, - "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { - "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", - "balance": "0x0" - }, - "0xc42edfcc21ed14dda456aa0756c153f7985d8813": { - "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", - "balance": "0x0" - }, - "0xeb9e64b93097bc15f01f13eae97015c57ab64823": { - "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823", - "balance": "0x0" - }, - "0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281": { - "address": "0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281", - "balance": "0x0" - }, - "0xb552685e3d2790efd64a175b00d51f02cdafee5d": { - "address": "0xb552685e3d2790efd64a175b00d51f02cdafee5d", - "balance": "0x0" - } - }, "accountsByChainId": { "0xaa36a7": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { @@ -322,10 +296,6 @@ } }, "currentAppVersion": "11.14.5", - "currentBlockGasLimit": "0x1c9c380", - "currentBlockGasLimitByChainId": { - "0x1": "0x1c9c380" - }, "currentCurrency": "usd", "currentLocale": "en", "customNonceValue": "", diff --git a/test/integration/data/onboarding-completion-route.json b/test/integration/data/onboarding-completion-route.json index e5989a6195b2..32b2cf473d91 100644 --- a/test/integration/data/onboarding-completion-route.json +++ b/test/integration/data/onboarding-completion-route.json @@ -32,7 +32,6 @@ } } }, - "accounts": { "0x03cf1158b58ccdfc04dd518f11f85c3ee7fa0189": {} }, "accountsByChainId": {}, "addSnapAccountEnabled": false, "addressBook": { @@ -105,8 +104,6 @@ } }, "currentAppVersion": "11.14.4-flask.0", - "currentBlockGasLimit": "", - "currentBlockGasLimitByChainId": {}, "currentCurrency": "usd", "currentExtensionPopupId": 1715943310719, "currentLocale": "en", diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index af6f0dd772e9..2390c38ea1e4 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -36,8 +36,6 @@ const initialState = { useBlockie: false, featureFlags: {}, currentLocale: '', - currentBlockGasLimit: '', - currentBlockGasLimitByChainId: {}, preferences: { autoLockTimeLimit: DEFAULT_AUTO_LOCK_TIME_LIMIT, showExtensionInFullSizeView: false, @@ -299,10 +297,6 @@ export const getNftContracts = (state) => { return allNftContracts?.[selectedAddress]?.[chainId] ?? []; }; -export function getBlockGasLimit(state) { - return state.metamask.currentBlockGasLimit; -} - export function getNativeCurrency(state) { return getProviderConfig(state).ticker; } diff --git a/ui/ducks/metamask/metamask.test.js b/ui/ducks/metamask/metamask.test.js index 8848e2d6d283..d60e74d52aae 100644 --- a/ui/ducks/metamask/metamask.test.js +++ b/ui/ducks/metamask/metamask.test.js @@ -10,7 +10,6 @@ import { ETH_EOA_METHODS } from '../../../shared/constants/eth-methods'; import { CHAIN_IDS } from '../../../shared/constants/network'; import { mockNetworkState } from '../../../test/stub/networks'; import reduceMetamask, { - getBlockGasLimit, getConversionRate, getGasEstimateType, getGasEstimateTypeByChainId, @@ -119,10 +118,6 @@ describe('MetaMask Reducers', () => { selectedAccount: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', }, cachedBalances: {}, - currentBlockGasLimit: '0x4c1878', - currentBlockGasLimitByChainId: { - '0x5': '0x4c1878', - }, useCurrencyRateCheck: true, currencyRates: { GoerliETH: { @@ -131,32 +126,6 @@ describe('MetaMask Reducers', () => { }, currentCurrency: 'usd', ...mockNetworkState({ chainId: CHAIN_IDS.GOERLI }), - accounts: { - '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { - code: '0x', - balance: '0x47c9d71831c76efe', - nonce: '0x1b', - address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', - }, - '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': { - code: '0x', - balance: '0x37452b1315889f80', - nonce: '0xa', - address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', - }, - '0x2f8d4a878cfa04a6e60d46362f5644deab66572d': { - code: '0x', - balance: '0x30c9d71831c76efe', - nonce: '0x1c', - address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', - }, - '0xd85a4b6a394794842887b8284293d69163007bbb': { - code: '0x', - balance: '0x0', - nonce: '0x0', - address: '0xd85a4b6a394794842887b8284293d69163007bbb', - }, - }, accountsByChainId: { '0x5': { '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { @@ -289,12 +258,6 @@ describe('MetaMask Reducers', () => { }); describe('metamask state selectors', () => { - describe('getBlockGasLimit', () => { - it('should return the current block gas limit', () => { - expect(getBlockGasLimit(mockState)).toStrictEqual('0x4c1878'); - }); - }); - describe('getConversionRate()', () => { it('should return the eth conversion rate', () => { expect(getConversionRate(mockState)).toStrictEqual(1200.88200327); diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 3a3e135d27ad..c2b9d27c0d9c 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -598,6 +598,7 @@ export const computeEstimatedGasLimit = createAsyncThunk( ) { const gasLimit = await estimateGasLimitForSend({ gasPrice: draftTransaction.gas.gasPrice, + // ACCOUNTTRACKER TODO: Replace this blockGasLimit: metamask.currentBlockGasLimit, selectedAddress: selectedAccount.address, sendToken: draftTransaction.sendAsset.details, @@ -735,6 +736,7 @@ export const initializeSendState = createAsyncThunk( // required gas. If this value isn't nullish, set it as the new gasLimit const estimatedGasLimit = await estimateGasLimitForSend({ gasPrice, + // ACCOUNTTRACKER TODO: Replace this blockGasLimit: metamask.currentBlockGasLimit, selectedAddress: getSender(state), sendToken: draftTransaction.sendAsset.details, diff --git a/ui/hooks/useAccountTrackerPolling.ts b/ui/hooks/useAccountTrackerPolling.ts index 9780df2446fc..c3c8cf8dcff2 100644 --- a/ui/hooks/useAccountTrackerPolling.ts +++ b/ui/hooks/useAccountTrackerPolling.ts @@ -27,10 +27,10 @@ const useAccountTrackerPolling = () => { useMultiPolling({ startPolling: accountTrackerStartPolling, - // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31879 - // eslint-disable-next-line @typescript-eslint/no-misused-promises stopPollingByPollingToken: accountTrackerStopPollingByPollingToken, - input: canStartPolling ? pollableNetworkClientIds : [], + input: canStartPolling + ? [{ networkClientIds: pollableNetworkClientIds }] + : [{ networkClientIds: [] }], }); }; diff --git a/ui/pages/confirmations/confirmation/stories/util.js b/ui/pages/confirmations/confirmation/stories/util.js index 9a4173493f27..222b0f50e30c 100644 --- a/ui/pages/confirmations/confirmation/stories/util.js +++ b/ui/pages/confirmations/confirmation/stories/util.js @@ -58,7 +58,6 @@ const STORE_MOCK = { }, }, tokenList: {}, - accounts: testData.metamask.accounts, internalAccounts: testData.metamask.internalAccounts, accountsByChainId: testData.metamask.accountsByChainId, snaps: { diff --git a/ui/pages/confirmations/confirmation/templates/create-named-snap-account.test.js b/ui/pages/confirmations/confirmation/templates/create-named-snap-account.test.js index 2cb1480e8467..59f6f97f9f95 100644 --- a/ui/pages/confirmations/confirmation/templates/create-named-snap-account.test.js +++ b/ui/pages/confirmations/confirmation/templates/create-named-snap-account.test.js @@ -58,13 +58,6 @@ const mockBaseStore = { metamask: { ...mockState.metamask, keyrings: updatedKeyrings, - accounts: { - ...mockState.metamask.accounts, - [mockTemporaryAccount.address]: { - balance: mockTemporaryAccount.balance, - address: mockTemporaryAccount.address, - }, - }, internalAccounts: { ...mockState.metamask.internalAccounts, accounts: { diff --git a/ui/pages/create-account/connect-hardware/index.js b/ui/pages/create-account/connect-hardware/index.js index 8cfbcb5befd6..cf8660882070 100644 --- a/ui/pages/create-account/connect-hardware/index.js +++ b/ui/pages/create-account/connect-hardware/index.js @@ -110,6 +110,7 @@ class ConnectHardwareForm extends Component { isFirefox: false, }; + // ACCOUNTTRACKER TODO: Remove this UNSAFE_componentWillReceiveProps(nextProps) { const { accounts } = nextProps; const newAccounts = this.state.accounts.map((a) => { diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 66a73cd18b85..045583de7f23 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -662,6 +662,7 @@ export function getHDEntropyIndex(state) { return hdEntropyIndex === -1 ? undefined : hdEntropyIndex; } +// TODO ACT MIGRATION - CHECK FOR CASE ON REFERENCES /** * Get account balances state. * @@ -673,6 +674,7 @@ export function getMetaMaskAccountBalances(state) { return state.metamask?.accountsByChainId?.[currentChainId] ?? {}; } +// TODO ACT MIGRATION - CHECK FOR USAGE export function getMetaMaskCachedBalances(state, networkChainId) { const enabledNetworks = getEnabledNetworks(state); const eip155 = enabledNetworks?.eip155 ?? {}; @@ -705,6 +707,7 @@ export function getMetaMaskCachedBalances(state, networkChainId) { return {}; } +// TODO ACT MIGRATION - CHECK FOR USAGE export function getCrossChainMetaMaskCachedBalances(state) { const allAccountsByChainId = state.metamask.accountsByChainId; return Object.keys(allAccountsByChainId).reduce((acc, topLevelKey) => { @@ -721,6 +724,7 @@ export function getCrossChainMetaMaskCachedBalances(state) { }, {}); } +// TODO ACT MIGRATION - CHECK FOR USAGE /** * Based on the current account address, return the balance for the native token of all chain networks on that account * @@ -731,6 +735,11 @@ export function getSelectedAccountNativeTokenCachedBalanceByChainId(state) { const { accountsByChainId } = state.metamask; const { address: selectedAddress } = getSelectedEvmInternalAccount(state); + console.log('DEBUG XXX 3', { + accountsByChainId, + selectedAddress, + }); + const balancesByChainId = {}; for (const [chainId, accounts] of Object.entries(accountsByChainId || {})) { if (accounts[selectedAddress]) { @@ -766,6 +775,11 @@ export function getSelectedAccountTokensAcrossChains(state) { ...Object.keys(nativeTokenBalancesByChainId || {}), ]); + console.log('DEBUG XXX 2', { + nativeTokenBalancesByChainId, + chainIds, + }); + chainIds.forEach((chainId) => { if (!tokensByChain[chainId]) { tokensByChain[chainId] = []; @@ -826,6 +840,7 @@ export const getTokensAcrossChainsByAccountAddressSelector = createSelector( getTokensAcrossChainsByAccountAddress(state, accountAddress), ); +// TODO ACT MIGRATION - CHECK FOR USAGE /** * Get the native token balance for a given account address and chainId * diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 20dfeedec513..ef1a7f47a91e 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -5969,17 +5969,18 @@ export async function tokenRatesStopPollingByPollingToken( } /** - * Starts polling on accountTrackerController with the networkClientId + * Starts polling on accountTrackerController with the networkClientIds * - * @param networkClientId - The network client ID to pull balances for. + * @param input - The input for the poll. + * @param input.networkClientIds - The network client IDs to pull balances for. * @returns polling token used to stop polling */ -export async function accountTrackerStartPolling( - networkClientId: string, -): Promise { +export async function accountTrackerStartPolling(input: { + networkClientIds: NetworkClientId[]; +}): Promise { const pollingToken = await submitRequestToBackground( 'accountTrackerStartPolling', - [networkClientId], + [input], ); await addPollingTokenToAppState(pollingToken); return pollingToken;