@metamask/assets-controllers 63.1.0 → 65.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [65.0.0]
11
+
12
+ ### Added
13
+
14
+ - **BREAKING:** Add event listener for `TransactionController:transactionConfirmed` on `TokenDetectionController` to trigger token detection ([#5859](https://github.com/MetaMask/core/pull/5859))
15
+
16
+ ### Changed
17
+
18
+ - **BREAKING:** Add event listener for `KeyringController:accountRemoved` instead of `AccountsController:accountRemoved` in `TokenBalancesController` and `TokensController` ([#5859](https://github.com/MetaMask/core/pull/5859))
19
+
20
+ ## [64.0.0]
21
+
22
+ ### Added
23
+
24
+ - **BREAKING:** Add event listener for `AccountsController:accountRemoved` on `TokenBalancesController` to remove token balances for the removed account ([#5726](https://github.com/MetaMask/core/pull/5726))
25
+
26
+ - **BREAKING:** Add event listener for `AccountsController:accountRemoved` on `TokensController` to remove tokens for the removed account ([#5726](https://github.com/MetaMask/core/pull/5726))
27
+
28
+ - **BREAKING:** Add `listAccounts` action to `TokensController` ([#5726](https://github.com/MetaMask/core/pull/5726))
29
+
30
+ - **BREAKING:** Add `listAccounts` action to `TokenBalancesController` ([#5726](https://github.com/MetaMask/core/pull/5726))
31
+
32
+ ### Changed
33
+
34
+ - TokenBalancesController will now check if balances has changed before updating the state ([#5726](https://github.com/MetaMask/core/pull/5726))
35
+
10
36
  ## [63.1.0]
11
37
 
12
38
  ### Changed
@@ -1638,7 +1664,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1638
1664
 
1639
1665
  - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845))
1640
1666
 
1641
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@63.1.0...HEAD
1667
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@65.0.0...HEAD
1668
+ [65.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@64.0.0...@metamask/assets-controllers@65.0.0
1669
+ [64.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@63.1.0...@metamask/assets-controllers@64.0.0
1642
1670
  [63.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@63.0.0...@metamask/assets-controllers@63.1.0
1643
1671
  [63.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@62.0.0...@metamask/assets-controllers@63.0.0
1644
1672
  [62.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@61.1.0...@metamask/assets-controllers@62.0.0
@@ -10,7 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
10
10
  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");
11
11
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
12
  };
13
- var _TokenBalancesController_instances, _TokenBalancesController_queryMultipleAccounts, _TokenBalancesController_allTokens, _TokenBalancesController_allDetectedTokens, _TokenBalancesController_calculateQueryMultipleAccounts, _TokenBalancesController_onPreferencesStateChange, _TokenBalancesController_onTokensStateChange, _TokenBalancesController_onNetworkStateChange, _TokenBalancesController_getChainIds, _TokenBalancesController_getNetworkClient;
13
+ var _TokenBalancesController_instances, _TokenBalancesController_queryMultipleAccounts, _TokenBalancesController_allTokens, _TokenBalancesController_allDetectedTokens, _TokenBalancesController_calculateQueryMultipleAccounts, _TokenBalancesController_onPreferencesStateChange, _TokenBalancesController_onTokensStateChange, _TokenBalancesController_onNetworkStateChange, _TokenBalancesController_handleOnAccountRemoved, _TokenBalancesController_getChainIds, _TokenBalancesController_handleTokensControllerStateChange, _TokenBalancesController_getNetworkClient;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.TokenBalancesController = exports.getDefaultTokenBalancesState = void 0;
16
16
  const contracts_1 = require("@ethersproject/contracts");
@@ -18,6 +18,7 @@ const providers_1 = require("@ethersproject/providers");
18
18
  const controller_utils_1 = require("@metamask/controller-utils");
19
19
  const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis");
20
20
  const polling_controller_1 = require("@metamask/polling-controller");
21
+ const utils_1 = require("@metamask/utils");
21
22
  const lodash_1 = require("lodash");
22
23
  const multicall_1 = require("./multicall.cjs");
23
24
  const DEFAULT_INTERVAL = 180000;
@@ -103,7 +104,9 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
103
104
  !(0, lodash_1.isEqual)(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId], allDetectedTokens[chainId]));
104
105
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
105
106
  __classPrivateFieldSet(this, _TokenBalancesController_allDetectedTokens, allDetectedTokens, "f");
106
- this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error);
107
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_handleTokensControllerStateChange).call(this, {
108
+ chainIds: chainIdsToUpdate,
109
+ }).catch(console.error);
107
110
  });
108
111
  /**
109
112
  * Returns an array of chain ids that have tokens.
@@ -129,6 +132,8 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
129
132
  this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensStateChange, "f").bind(this));
130
133
  // Subscribe to network state changes
131
134
  this.messagingSystem.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_onNetworkStateChange).bind(this));
135
+ // subscribe to account removed event to cleanup stale balances
136
+ this.messagingSystem.subscribe('KeyringController:accountRemoved', (accountAddress) => __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_handleOnAccountRemoved).call(this, accountAddress));
132
137
  }
133
138
  /**
134
139
  * Polls for erc20 token balances.
@@ -168,6 +173,7 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
168
173
  Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId] ?? {}).forEach(addTokens);
169
174
  Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId] ?? {}).forEach(addTokens);
170
175
  let results = [];
176
+ const currentTokenBalances = this.messagingSystem.call('TokenBalancesController:getState');
171
177
  if (accountTokenPairs.length > 0) {
172
178
  const provider = new providers_1.Web3Provider(__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId).provider);
173
179
  const calls = accountTokenPairs.map(({ accountAddress, tokenAddress }) => ({
@@ -177,17 +183,26 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
177
183
  }));
178
184
  results = await (0, multicall_1.multicallOrFallback)(calls, chainId, provider);
179
185
  }
186
+ const updatedResults = results.map((res, i) => {
187
+ const { value } = res;
188
+ const { accountAddress, tokenAddress } = accountTokenPairs[i];
189
+ const currentTokenBalanceValueForAccount = currentTokenBalances.tokenBalances?.[accountAddress]?.[chainId]?.[tokenAddress];
190
+ const isTokenBalanceValueChanged = currentTokenBalanceValueForAccount !== (0, controller_utils_1.toHex)(value);
191
+ return {
192
+ ...res,
193
+ isTokenBalanceValueChanged,
194
+ };
195
+ });
196
+ // if all values of isTokenBalanceValueChanged are false, return
197
+ if (updatedResults.every((result) => !result.isTokenBalanceValueChanged)) {
198
+ return;
199
+ }
180
200
  this.update((state) => {
181
201
  var _a, _b;
182
- // Reset so that when accounts or tokens are removed,
183
- // their balances are removed rather than left stale.
184
- for (const accountAddress of Object.keys(state.tokenBalances)) {
185
- state.tokenBalances[accountAddress][chainId] = {};
186
- }
187
- for (let i = 0; i < results.length; i++) {
188
- const { success, value } = results[i];
202
+ for (let i = 0; i < updatedResults.length; i++) {
203
+ const { success, value, isTokenBalanceValueChanged } = updatedResults[i];
189
204
  const { accountAddress, tokenAddress } = accountTokenPairs[i];
190
- if (success) {
205
+ if (success && isTokenBalanceValueChanged) {
191
206
  ((_b = ((_a = state.tokenBalances)[accountAddress] ?? (_a[accountAddress] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = (0, controller_utils_1.toHex)(value);
192
207
  }
193
208
  }
@@ -216,6 +231,61 @@ _TokenBalancesController_queryMultipleAccounts = new WeakMap(), _TokenBalancesCo
216
231
  });
217
232
  }
218
233
  }
234
+ }, _TokenBalancesController_handleOnAccountRemoved = function _TokenBalancesController_handleOnAccountRemoved(accountAddress) {
235
+ const isEthAddress = (0, utils_1.isStrictHexString)(accountAddress.toLowerCase()) &&
236
+ (0, controller_utils_1.isValidHexAddress)(accountAddress);
237
+ if (!isEthAddress) {
238
+ return;
239
+ }
240
+ this.update((state) => {
241
+ delete state.tokenBalances[accountAddress];
242
+ });
243
+ }, _TokenBalancesController_handleTokensControllerStateChange = async function _TokenBalancesController_handleTokensControllerStateChange({ chainIds, } = {}) {
244
+ const currentTokenBalancesState = this.messagingSystem.call('TokenBalancesController:getState');
245
+ const currentTokenBalances = currentTokenBalancesState.tokenBalances;
246
+ const currentAllTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f");
247
+ const chainIdsSet = new Set(chainIds);
248
+ // first we check if the state change was due to a token being removed
249
+ for (const currentAccount of Object.keys(currentTokenBalances)) {
250
+ const allChains = currentTokenBalances[currentAccount];
251
+ for (const currentChain of Object.keys(allChains)) {
252
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
253
+ continue;
254
+ }
255
+ const tokensObject = allChains[currentChain];
256
+ const allCurrentTokens = Object.keys(tokensObject);
257
+ const existingTokensInState = currentAllTokens[currentChain]?.[currentAccount] || [];
258
+ const existingSet = new Set(existingTokensInState.map((elm) => elm.address));
259
+ for (const singleToken of allCurrentTokens) {
260
+ if (!existingSet.has(singleToken)) {
261
+ this.update((state) => {
262
+ delete state.tokenBalances[currentAccount][currentChain][singleToken];
263
+ });
264
+ }
265
+ }
266
+ }
267
+ }
268
+ // then we check if the state change was due to a token being added
269
+ let shouldUpdate = false;
270
+ for (const currentChain of Object.keys(currentAllTokens)) {
271
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
272
+ continue;
273
+ }
274
+ const accountsPerChain = currentAllTokens[currentChain];
275
+ for (const currentAccount of Object.keys(accountsPerChain)) {
276
+ const tokensList = accountsPerChain[currentAccount];
277
+ const tokenBalancesObject = currentTokenBalances[currentAccount]?.[currentChain] || {};
278
+ for (const singleToken of tokensList) {
279
+ if (!tokenBalancesObject?.[singleToken.address]) {
280
+ shouldUpdate = true;
281
+ break;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ if (shouldUpdate) {
287
+ await this.updateBalances({ chainIds }).catch(console.error);
288
+ }
219
289
  }, _TokenBalancesController_getNetworkClient = function _TokenBalancesController_getNetworkClient(chainId) {
220
290
  const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
221
291
  const networkConfiguration = networkConfigurationsByChainId[chainId];
@@ -1 +1 @@
1
- {"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 RestrictedMessenger,\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 messenger.\n * @property state - Initial state for the controller.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial<TokenBalancesControllerState>;\n};\n\n/**\n * A mapping from account address to chain id to token address to balance.\n */\ntype TokenBalances = Record<Hex, Record<Hex, Record<Hex, Hex>>>;\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 = RestrictedMessenger<\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<TokenBalancesPollingInput>()<\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"]}
1
+ {"version":3,"file":"TokenBalancesController.cjs","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,wDAAoD;AACpD,wDAAwD;AAUxD,iEAIoC;AAEpC,mEAAuD;AAOvD,qEAA+E;AAM/E,2CAA8D;AAG9D,mCAAiC;AAGjC,+CAAkD;AAQlD,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC,MAAM,cAAc,GAAG,yBAAyB,CAAC;AAEjD,MAAM,QAAQ,GAAG;IACf,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE;CACnD,CAAC;AAkEF;;;;GAIG;AACH,SAAgB,4BAA4B;IAC1C,OAAO;QACL,aAAa,EAAE,EAAE;KAClB,CAAC;AACJ,CAAC;AAJD,oEAIC;AAOD;;;GAGG;AACH,MAAa,uBAAwB,SAAQ,IAAA,oDAA+B,GAI3E;IAOC;;;;;;;OAOG;IACH,YAAY,EACV,QAAQ,GAAG,gBAAgB,EAC3B,SAAS,EACT,KAAK,GAAG,EAAE,GACqB;;QAC/B,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ;YACR,SAAS;YACT,KAAK,EAAE;gBACL,GAAG,4BAA4B,EAAE;gBACjC,GAAG,KAAK;aACT;SACF,CAAC,CAAC;;QA3BL,iEAAgC;QAEhC,qDAA+C;QAE/C,6DAA+D;QA6D/D;;;;;;WAMG;QACH,kEAAkC,CAAC,EACjC,6BAA6B,EAC7B,6BAA6B,GACkC,EAAE,EAAE;YACnE,OAAO,OAAO;YACZ,mEAAmE;YACnE,6BAA6B,IAAI,6BAA6B,CAC/D,CAAC;QACJ,CAAC,EAAC;QAEF;;;WAGG;QACH,4DAA4B,CAAC,WAA6B,EAAE,EAAE;YAC5D,qEAAqE;YACrE,MAAM,qBAAqB,GACzB,uBAAA,IAAI,+DAAgC,MAApC,IAAI,EAAiC,WAAW,CAAC,CAAC;YAEpD,iCAAiC;YACjC,MAAM,OAAO,GAAG,qBAAqB,IAAI,CAAC,uBAAA,IAAI,sDAAuB,CAAC;YACtE,uBAAA,IAAI,kDAA0B,qBAAqB,MAAA,CAAC;YAEpD,IAAI,OAAO,EAAE;gBACX,IAAI,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;aAC5C;QACH,CAAC,EAAC;QAEF;;;;;WAKG;QACH,uDAAuB,CAAC,EACtB,SAAS,EACT,iBAAiB,GACK,EAAE,EAAE;YAC1B,8DAA8D;YAC9D,MAAM,QAAQ,GAAG,uBAAA,IAAI,4CAAa,MAAjB,IAAI,EAAc,SAAS,EAAE,iBAAiB,CAAC,CAAC;YACjE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CACtC,CAAC,OAAO,EAAE,EAAE,CACV,CAAC,IAAA,gBAAO,EAAC,uBAAA,IAAI,0CAAW,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;gBACtD,CAAC,IAAA,gBAAO,EAAC,uBAAA,IAAI,kDAAmB,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC,CACzE,CAAC;YAEF,uBAAA,IAAI,sCAAc,SAAS,MAAA,CAAC;YAC5B,uBAAA,IAAI,8CAAsB,iBAAiB,MAAA,CAAC;YAC5C,uBAAA,IAAI,sGAAmC,MAAvC,IAAI,EAAoC;gBACtC,QAAQ,EAAE,gBAAgB;aAC3B,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,EAAC;QA2CF;;;;;WAKG;QACH,+CAAe,CACb,SAA6C,EAC7C,iBAA6D,EAC7D,EAAE,CACF;YACE,GAAG,IAAI,GAAG,CAAC;gBACT,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;gBACzB,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC;aAClC,CAAC;SACM,EAAC;QAxJX,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAEjC,kFAAkF;QAClF,uBAAA,IAAI,kDAA0B,uBAAA,IAAI,+DAAgC,MAApC,IAAI,EAChC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAC5D,MAAA,CAAC;QACF,IAAI,CAAC,eAAe,CAAC,SAAS,CAC5B,mCAAmC,EACnC,uBAAA,IAAI,yDAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAC1C,CAAC;QAEF,+CAA+C;QAC/C,MACa,IAAI,OACI,IAAI,EAFxB;YACC,SAAS,wGAAiB;YAC1B,iBAAiB,gHAAyB;SAC3C,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;QAE5D,IAAI,CAAC,eAAe,CAAC,SAAS,CAC5B,8BAA8B,EAC9B,uBAAA,IAAI,oDAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CACrC,CAAC;QAEF,qCAAqC;QACrC,IAAI,CAAC,eAAe,CAAC,SAAS,CAC5B,+BAA+B,EAC/B,uBAAA,IAAI,yFAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CACtC,CAAC;QAEF,+DAA+D;QAE/D,IAAI,CAAC,eAAe,CAAC,SAAS,CAC5B,kCAAkC,EAClC,CAAC,cAAsB,EAAE,EAAE,CAAC,uBAAA,IAAI,2FAAwB,MAA5B,IAAI,EAAyB,cAAc,CAAC,CACzE,CAAC;IACJ,CAAC;IAwHD;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,EAA6B;QACvD,MAAM,IAAI,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAAC,EAAE,QAAQ,KAA2B,EAAE;QAC1D,QAAQ,KAAR,QAAQ,GAAK,uBAAA,IAAI,4CAAa,MAAjB,IAAI,EAAc,uBAAA,IAAI,0CAAW,EAAE,uBAAA,IAAI,kDAAmB,CAAC,EAAC;QAEzE,MAAM,OAAO,CAAC,UAAU,CACtB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CACrE,CAAC;IACJ,CAAC;IAoED;;;;OAIG;IACH,KAAK,CAAC,uBAAuB,CAAC,EAAE,OAAO,EAAoB;QACzD,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CACnE,uCAAuC,CACxC,CAAC;QAEF,MAAM,iBAAiB,GAAG,CAAC,cAAsB,EAAE,EAAE,CACnD,IAAA,uCAAoB,EAAC,cAAc,CAAC;YACpC,IAAA,uCAAoB,EAAC,sBAAsB,CAAC,CAAC;QAE/C,MAAM,iBAAiB,GAAiD,EAAE,CAAC;QAE3E,MAAM,SAAS,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,CAAoB,EAAE,EAAE,CAChE,uBAAA,IAAI,sDAAuB,IAAI,iBAAiB,CAAC,cAAc,CAAC;YAC9D,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACnB,iBAAiB,CAAC,IAAI,CAAC;gBACrB,cAAc,EAAE,cAAqB;gBACrC,YAAY,EAAE,CAAC,CAAC,OAAc;aAC/B,CAAC,CACH;YACH,CAAC,CAAC,SAAS,CAAC;QAEhB,iEAAiE;QACjE,MAAM,CAAC,OAAO,CAAC,uBAAA,IAAI,0CAAW,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAClE,MAAM,CAAC,OAAO,CAAC,uBAAA,IAAI,kDAAmB,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE1E,IAAI,OAAO,GAAsB,EAAE,CAAC;QAEpC,MAAM,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CACpD,kCAAkC,CACnC,CAAC;QAEF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE;YAChC,MAAM,QAAQ,GAAG,IAAI,wBAAY,CAC/B,uBAAA,IAAI,qFAAkB,MAAtB,IAAI,EAAmB,OAAO,CAAC,CAAC,QAAQ,CACzC,CAAC;YAEF,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CACjC,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;gBACrC,QAAQ,EAAE,IAAI,oBAAQ,CAAC,YAAY,EAAE,4BAAQ,EAAE,QAAQ,CAAC;gBACxD,iBAAiB,EAAE,oBAAoB;gBACvC,SAAS,EAAE,CAAC,cAAc,CAAC;aAC5B,CAAC,CACH,CAAC;YAEF,OAAO,GAAG,MAAM,IAAA,+BAAmB,EAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;SAC/D;QAED,MAAM,cAAc,GAEb,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;YAC5B,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;YACtB,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;YAC9D,MAAM,kCAAkC,GACtC,oBAAoB,CAAC,aAAa,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAC/D,YAAY,CACb,CAAC;YACJ,MAAM,0BAA0B,GAC9B,kCAAkC,KAAK,IAAA,wBAAK,EAAC,KAAW,CAAC,CAAC;YAC5D,OAAO;gBACL,GAAG,GAAG;gBACN,0BAA0B;aAC3B,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,gEAAgE;QAChE,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,0BAA0B,CAAC,EAAE;YACxE,OAAO;SACR;QAED,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;;YACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;gBAC9C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,GAClD,cAAc,CAAC,CAAC,CAAC,CAAC;gBACpB,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBAC9D,IAAI,OAAO,IAAI,0BAA0B,EAAE;oBACzC,OAAC,OAAC,KAAK,CAAC,aAAa,EAAC,cAAc,SAAd,cAAc,IAAM,EAAE,EAAC,EAAC,OAAO,SAAP,OAAO,IAAM,EAAE,EAAC,CAC5D,YAAY,CACb,GAAG,IAAA,wBAAK,EAAC,KAAW,CAAC,CAAC;iBACxB;aACF;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE;YACf,OAAO,4BAA4B,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;CA6BF;AAjZD,0DAiZC;2kBA1QuB,CAAe,EAAE,OAAgB;IACrD,oCAAoC;IACpC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;QAC3B,IACE,KAAK,CAAC,EAAE,KAAK,QAAQ;YACrB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,gCAAgC,EAClD;YACA,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAQ,CAAC;YAE5C,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpB,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE;oBAC7D,OAAO,KAAK,CAAC,aAAa,CAAC,cAAqB,CAAC,CAAC,cAAc,CAAC,CAAC;iBACnE;YACH,CAAC,CAAC,CAAC;SACJ;KACF;AACH,CAAC,6GAOuB,cAAsB;IAC5C,MAAM,YAAY,GAChB,IAAA,yBAAiB,EAAC,cAAc,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAA,oCAAiB,EAAC,cAAc,CAAC,CAAC;IACpC,IAAI,CAAC,YAAY,EAAE;QACjB,OAAO;KACR;IAED,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QACpB,OAAO,KAAK,CAAC,aAAa,CAAC,cAA+B,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,+DA0CD,KAAK,qEAAoC,EACvC,QAAQ,MACgB,EAAE;IAC1B,MAAM,yBAAyB,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CACzD,kCAAkC,CACnC,CAAC;IACF,MAAM,oBAAoB,GAAG,yBAAyB,CAAC,aAAa,CAAC;IACrE,MAAM,gBAAgB,GAAG,uBAAA,IAAI,0CAAW,CAAC;IACzC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtC,sEAAsE;IACtE,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE;QAC9D,MAAM,SAAS,GAAG,oBAAoB,CAAC,cAA+B,CAAC,CAAC;QACxE,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;YACjD,IAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAmB,CAAC,EAAE;gBAC7D,SAAS;aACV;YACD,MAAM,YAAY,GAAG,SAAS,CAAC,YAAmB,CAAC,CAAC;YACpD,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnD,MAAM,qBAAqB,GACzB,gBAAgB,CAAC,YAAmB,CAAC,EAAE,CACrC,cAA+B,CAChC,IAAI,EAAE,CAAC;YACV,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,qBAAqB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAChD,CAAC;YAEF,KAAK,MAAM,WAAW,IAAI,gBAAgB,EAAE;gBAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;oBACjC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;wBACpB,OAAO,KAAK,CAAC,aAAa,CAAC,cAAqB,CAAC,CAC/C,YAAmB,CACpB,CAAC,WAA4B,CAAC,CAAC;oBAClC,CAAC,CAAC,CAAC;iBACJ;aACF;SACF;KACF;IAED,mEAAmE;IACnE,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE;QACxD,IAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAmB,CAAC,EAAE;YAC7D,SAAS;SACV;QACD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,YAAmB,CAAC,CAAC;QAE/D,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE;YAC1D,MAAM,UAAU,GAAG,gBAAgB,CAAC,cAA+B,CAAC,CAAC;YACrE,MAAM,mBAAmB,GACvB,oBAAoB,CAAC,cAA+B,CAAC,EAAE,CACrD,YAAmB,CACpB,IAAI,EAAE,CAAC;YACV,KAAK,MAAM,WAAW,IAAI,UAAU,EAAE;gBACpC,IAAI,CAAC,mBAAmB,EAAE,CAAC,WAAW,CAAC,OAAwB,CAAC,EAAE;oBAChE,YAAY,GAAG,IAAI,CAAC;oBACpB,MAAM;iBACP;aACF;SACF;KACF;IACD,IAAI,YAAY,EAAE;QAChB,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;KAC9D;AACH,CAAC,iGAwGiB,OAAY;IAC5B,MAAM,EAAE,8BAA8B,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAClE,4BAA4B,CAC7B,CAAC;IAEF,MAAM,oBAAoB,GAAG,8BAA8B,CAAC,OAAO,CAAC,CAAC;IACrE,IAAI,CAAC,oBAAoB,EAAE;QACzB,MAAM,IAAI,KAAK,CACb,uEAAuE,OAAO,EAAE,CACjF,CAAC;KACH;IAED,MAAM,EAAE,eAAe,EAAE,GACvB,oBAAoB,CAAC,YAAY,CAC/B,oBAAoB,CAAC,uBAAuB,CAC7C,CAAC;IAEJ,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAC9B,wCAAwC,EACxC,eAAe,CAChB,CAAC;AACJ,CAAC;AAGH,kBAAe,uBAAuB,CAAC","sourcesContent":["import { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerListAccountsAction,\n} from '@metamask/accounts-controller';\nimport type {\n RestrictedMessenger,\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport {\n isValidHexAddress,\n toChecksumHexAddress,\n toHex,\n} from '@metamask/controller-utils';\nimport type { KeyringControllerAccountRemovedEvent } from '@metamask/keyring-controller';\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 { isStrictHexString, 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 messenger.\n * @property state - Initial state for the controller.\n */\ntype TokenBalancesControllerOptions = {\n interval?: number;\n messenger: TokenBalancesControllerMessenger;\n state?: Partial<TokenBalancesControllerState>;\n};\n\n/**\n * A mapping from account address to chain id to token address to balance.\n */\ntype TokenBalances = Record<Hex, Record<Hex, Record<Hex, Hex>>>;\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 | AccountsControllerListAccountsAction;\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 | KeyringControllerAccountRemovedEvent;\n\nexport type TokenBalancesControllerMessenger = RestrictedMessenger<\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<TokenBalancesPollingInput>()<\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 // subscribe to account removed event to cleanup stale balances\n\n this.messagingSystem.subscribe(\n 'KeyringController:accountRemoved',\n (accountAddress: string) => this.#handleOnAccountRemoved(accountAddress),\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 this.#handleTokensControllerStateChange({\n chainIds: chainIdsToUpdate,\n }).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 * Handles changes when an account has been removed.\n *\n * @param accountAddress - The account address being removed.\n */\n #handleOnAccountRemoved(accountAddress: string) {\n const isEthAddress =\n isStrictHexString(accountAddress.toLowerCase()) &&\n isValidHexAddress(accountAddress);\n if (!isEthAddress) {\n return;\n }\n\n this.update((state) => {\n delete state.tokenBalances[accountAddress as `0x${string}`];\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 async #handleTokensControllerStateChange({\n chainIds,\n }: { chainIds?: Hex[] } = {}) {\n const currentTokenBalancesState = this.messagingSystem.call(\n 'TokenBalancesController:getState',\n );\n const currentTokenBalances = currentTokenBalancesState.tokenBalances;\n const currentAllTokens = this.#allTokens;\n const chainIdsSet = new Set(chainIds);\n\n // first we check if the state change was due to a token being removed\n for (const currentAccount of Object.keys(currentTokenBalances)) {\n const allChains = currentTokenBalances[currentAccount as `0x${string}`];\n for (const currentChain of Object.keys(allChains)) {\n if (chainIds?.length && !chainIdsSet.has(currentChain as Hex)) {\n continue;\n }\n const tokensObject = allChains[currentChain as Hex];\n const allCurrentTokens = Object.keys(tokensObject);\n const existingTokensInState =\n currentAllTokens[currentChain as Hex]?.[\n currentAccount as `0x${string}`\n ] || [];\n const existingSet = new Set(\n existingTokensInState.map((elm) => elm.address),\n );\n\n for (const singleToken of allCurrentTokens) {\n if (!existingSet.has(singleToken)) {\n this.update((state) => {\n delete state.tokenBalances[currentAccount as Hex][\n currentChain as Hex\n ][singleToken as `0x${string}`];\n });\n }\n }\n }\n }\n\n // then we check if the state change was due to a token being added\n let shouldUpdate = false;\n for (const currentChain of Object.keys(currentAllTokens)) {\n if (chainIds?.length && !chainIdsSet.has(currentChain as Hex)) {\n continue;\n }\n const accountsPerChain = currentAllTokens[currentChain as Hex];\n\n for (const currentAccount of Object.keys(accountsPerChain)) {\n const tokensList = accountsPerChain[currentAccount as `0x${string}`];\n const tokenBalancesObject =\n currentTokenBalances[currentAccount as `0x${string}`]?.[\n currentChain as Hex\n ] || {};\n for (const singleToken of tokensList) {\n if (!tokenBalancesObject?.[singleToken.address as `0x${string}`]) {\n shouldUpdate = true;\n break;\n }\n }\n }\n }\n if (shouldUpdate) {\n await this.updateBalances({ chainIds }).catch(console.error);\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 const currentTokenBalances = this.messagingSystem.call(\n 'TokenBalancesController:getState',\n );\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 const updatedResults: (MulticallResult & {\n isTokenBalanceValueChanged?: boolean;\n })[] = results.map((res, i) => {\n const { value } = res;\n const { accountAddress, tokenAddress } = accountTokenPairs[i];\n const currentTokenBalanceValueForAccount =\n currentTokenBalances.tokenBalances?.[accountAddress]?.[chainId]?.[\n tokenAddress\n ];\n const isTokenBalanceValueChanged =\n currentTokenBalanceValueForAccount !== toHex(value as BN);\n return {\n ...res,\n isTokenBalanceValueChanged,\n };\n });\n\n // if all values of isTokenBalanceValueChanged are false, return\n if (updatedResults.every((result) => !result.isTokenBalanceValueChanged)) {\n return;\n }\n\n this.update((state) => {\n for (let i = 0; i < updatedResults.length; i++) {\n const { success, value, isTokenBalanceValueChanged } =\n updatedResults[i];\n const { accountAddress, tokenAddress } = accountTokenPairs[i];\n if (success && isTokenBalanceValueChanged) {\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"]}
@@ -1,8 +1,9 @@
1
- import type { AccountsControllerGetSelectedAccountAction } from "@metamask/accounts-controller";
1
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerListAccountsAction } from "@metamask/accounts-controller";
2
2
  import type { RestrictedMessenger, ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
3
+ import type { KeyringControllerAccountRemovedEvent } from "@metamask/keyring-controller";
3
4
  import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerStateChangeEvent } from "@metamask/network-controller";
4
5
  import type { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent } from "@metamask/preferences-controller";
5
- import type { Hex } from "@metamask/utils";
6
+ import { type Hex } from "@metamask/utils";
6
7
  import type { TokensControllerGetStateAction, TokensControllerStateChangeEvent } from "./TokensController.cjs";
7
8
  declare const controllerName = "TokenBalancesController";
8
9
  /**
@@ -29,10 +30,10 @@ export type TokenBalancesControllerState = {
29
30
  };
30
31
  export type TokenBalancesControllerGetStateAction = ControllerGetStateAction<typeof controllerName, TokenBalancesControllerState>;
31
32
  export type TokenBalancesControllerActions = TokenBalancesControllerGetStateAction;
32
- export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction;
33
+ export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction | AccountsControllerListAccountsAction;
33
34
  export type TokenBalancesControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, TokenBalancesControllerState>;
34
35
  export type TokenBalancesControllerEvents = TokenBalancesControllerStateChangeEvent;
35
- export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent;
36
+ export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent | KeyringControllerAccountRemovedEvent;
36
37
  export type TokenBalancesControllerMessenger = RestrictedMessenger<typeof controllerName, TokenBalancesControllerActions | AllowedActions, TokenBalancesControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>;
37
38
  /**
38
39
  * Get the default TokenBalancesController state.
@@ -56,9 +57,7 @@ declare const TokenBalancesController_base: (abstract new (...args: any[]) => {
56
57
  _executePoll(input: TokenBalancesPollingInput): Promise<void>;
57
58
  startPolling(input: TokenBalancesPollingInput): string;
58
59
  stopAllPolling(): void;
59
- stopPollingByPollingToken(pollingToken: string): void; /**
60
- * A mapping from account address to chain id to token address to balance.
61
- */
60
+ stopPollingByPollingToken(pollingToken: string): void;
62
61
  onPollingComplete(input: TokenBalancesPollingInput, callback: (input: TokenBalancesPollingInput) => void): void;
63
62
  }) & typeof import("@metamask/base-controller").BaseController;
64
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TokenBalancesController.d.cts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,mBAAmB,EACnB,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,mBAAmB,CAChE,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;;;;;;;;;;;;;2DAhEF;;OAEG;;;AAgEH;;;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"}
1
+ {"version":3,"file":"TokenBalancesController.d.cts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,0CAA0C,EAC1C,oCAAoC,EACrC,sCAAsC;AACvC,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AAMnC,OAAO,KAAK,EAAE,oCAAoC,EAAE,qCAAqC;AAEzF,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAC/B,iCAAiC,EAElC,qCAAqC;AAEtC,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EAEtC,yCAAyC;AAC1C,OAAO,EAAqB,KAAK,GAAG,EAAE,wBAAwB;AAQ9D,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,GAC1C,oCAAoC,CAAC;AAEzC,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,GACjC,oCAAoC,CAAC;AAEzC,MAAM,MAAM,gCAAgC,GAAG,mBAAmB,CAChE,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;IAqKjC;;;;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;IA0E5D;;;;OAIG;IACG,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE;IAmF3D;;OAEG;IACH,UAAU;CAiCX;AAED,eAAe,uBAAuB,CAAC"}
@@ -1,8 +1,9 @@
1
- import type { AccountsControllerGetSelectedAccountAction } from "@metamask/accounts-controller";
1
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerListAccountsAction } from "@metamask/accounts-controller";
2
2
  import type { RestrictedMessenger, ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
3
+ import type { KeyringControllerAccountRemovedEvent } from "@metamask/keyring-controller";
3
4
  import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction, NetworkControllerStateChangeEvent } from "@metamask/network-controller";
4
5
  import type { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent } from "@metamask/preferences-controller";
5
- import type { Hex } from "@metamask/utils";
6
+ import { type Hex } from "@metamask/utils";
6
7
  import type { TokensControllerGetStateAction, TokensControllerStateChangeEvent } from "./TokensController.mjs";
7
8
  declare const controllerName = "TokenBalancesController";
8
9
  /**
@@ -29,10 +30,10 @@ export type TokenBalancesControllerState = {
29
30
  };
30
31
  export type TokenBalancesControllerGetStateAction = ControllerGetStateAction<typeof controllerName, TokenBalancesControllerState>;
31
32
  export type TokenBalancesControllerActions = TokenBalancesControllerGetStateAction;
32
- export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction;
33
+ export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | TokensControllerGetStateAction | PreferencesControllerGetStateAction | AccountsControllerGetSelectedAccountAction | AccountsControllerListAccountsAction;
33
34
  export type TokenBalancesControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, TokenBalancesControllerState>;
34
35
  export type TokenBalancesControllerEvents = TokenBalancesControllerStateChangeEvent;
35
- export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent;
36
+ export type AllowedEvents = TokensControllerStateChangeEvent | PreferencesControllerStateChangeEvent | NetworkControllerStateChangeEvent | KeyringControllerAccountRemovedEvent;
36
37
  export type TokenBalancesControllerMessenger = RestrictedMessenger<typeof controllerName, TokenBalancesControllerActions | AllowedActions, TokenBalancesControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>;
37
38
  /**
38
39
  * Get the default TokenBalancesController state.
@@ -56,9 +57,7 @@ declare const TokenBalancesController_base: (abstract new (...args: any[]) => {
56
57
  _executePoll(input: TokenBalancesPollingInput): Promise<void>;
57
58
  startPolling(input: TokenBalancesPollingInput): string;
58
59
  stopAllPolling(): void;
59
- stopPollingByPollingToken(pollingToken: string): void; /**
60
- * A mapping from account address to chain id to token address to balance.
61
- */
60
+ stopPollingByPollingToken(pollingToken: string): void;
62
61
  onPollingComplete(input: TokenBalancesPollingInput, callback: (input: TokenBalancesPollingInput) => void): void;
63
62
  }) & typeof import("@metamask/base-controller").BaseController;
64
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"TokenBalancesController.d.mts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0CAA0C,EAAE,sCAAsC;AAChG,OAAO,KAAK,EACV,mBAAmB,EACnB,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,mBAAmB,CAChE,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;;;;;;;;;;;;;2DAhEF;;OAEG;;;AAgEH;;;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"}
1
+ {"version":3,"file":"TokenBalancesController.d.mts","sourceRoot":"","sources":["../src/TokenBalancesController.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,0CAA0C,EAC1C,oCAAoC,EACrC,sCAAsC;AACvC,OAAO,KAAK,EACV,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AAMnC,OAAO,KAAK,EAAE,oCAAoC,EAAE,qCAAqC;AAEzF,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAC/B,iCAAiC,EAElC,qCAAqC;AAEtC,OAAO,KAAK,EACV,mCAAmC,EACnC,qCAAqC,EAEtC,yCAAyC;AAC1C,OAAO,EAAqB,KAAK,GAAG,EAAE,wBAAwB;AAQ9D,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,GAC1C,oCAAoC,CAAC;AAEzC,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,GACjC,oCAAoC,CAAC;AAEzC,MAAM,MAAM,gCAAgC,GAAG,mBAAmB,CAChE,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;IAqKjC;;;;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;IA0E5D;;;;OAIG;IACG,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE;IAmF3D;;OAEG;IACH,UAAU;CAiCX;AAED,eAAe,uBAAuB,CAAC"}
@@ -9,12 +9,13 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  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");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _TokenBalancesController_instances, _TokenBalancesController_queryMultipleAccounts, _TokenBalancesController_allTokens, _TokenBalancesController_allDetectedTokens, _TokenBalancesController_calculateQueryMultipleAccounts, _TokenBalancesController_onPreferencesStateChange, _TokenBalancesController_onTokensStateChange, _TokenBalancesController_onNetworkStateChange, _TokenBalancesController_getChainIds, _TokenBalancesController_getNetworkClient;
12
+ var _TokenBalancesController_instances, _TokenBalancesController_queryMultipleAccounts, _TokenBalancesController_allTokens, _TokenBalancesController_allDetectedTokens, _TokenBalancesController_calculateQueryMultipleAccounts, _TokenBalancesController_onPreferencesStateChange, _TokenBalancesController_onTokensStateChange, _TokenBalancesController_onNetworkStateChange, _TokenBalancesController_handleOnAccountRemoved, _TokenBalancesController_getChainIds, _TokenBalancesController_handleTokensControllerStateChange, _TokenBalancesController_getNetworkClient;
13
13
  import { Contract } from "@ethersproject/contracts";
14
14
  import { Web3Provider } from "@ethersproject/providers";
15
- import { toChecksumHexAddress, toHex } from "@metamask/controller-utils";
15
+ import { isValidHexAddress, toChecksumHexAddress, toHex } from "@metamask/controller-utils";
16
16
  import { abiERC20 } from "@metamask/metamask-eth-abis";
17
17
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
18
+ import { isStrictHexString } from "@metamask/utils";
18
19
  import $lodash from "lodash";
19
20
  const { isEqual } = $lodash;
20
21
  import { multicallOrFallback } from "./multicall.mjs";
@@ -100,7 +101,9 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
100
101
  !isEqual(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId], allDetectedTokens[chainId]));
101
102
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
102
103
  __classPrivateFieldSet(this, _TokenBalancesController_allDetectedTokens, allDetectedTokens, "f");
103
- this.updateBalances({ chainIds: chainIdsToUpdate }).catch(console.error);
104
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_handleTokensControllerStateChange).call(this, {
105
+ chainIds: chainIdsToUpdate,
106
+ }).catch(console.error);
104
107
  });
105
108
  /**
106
109
  * Returns an array of chain ids that have tokens.
@@ -126,6 +129,8 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
126
129
  this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensStateChange, "f").bind(this));
127
130
  // Subscribe to network state changes
128
131
  this.messagingSystem.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_onNetworkStateChange).bind(this));
132
+ // subscribe to account removed event to cleanup stale balances
133
+ this.messagingSystem.subscribe('KeyringController:accountRemoved', (accountAddress) => __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_handleOnAccountRemoved).call(this, accountAddress));
129
134
  }
130
135
  /**
131
136
  * Polls for erc20 token balances.
@@ -165,6 +170,7 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
165
170
  Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId] ?? {}).forEach(addTokens);
166
171
  Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId] ?? {}).forEach(addTokens);
167
172
  let results = [];
173
+ const currentTokenBalances = this.messagingSystem.call('TokenBalancesController:getState');
168
174
  if (accountTokenPairs.length > 0) {
169
175
  const provider = new Web3Provider(__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId).provider);
170
176
  const calls = accountTokenPairs.map(({ accountAddress, tokenAddress }) => ({
@@ -174,17 +180,26 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
174
180
  }));
175
181
  results = await multicallOrFallback(calls, chainId, provider);
176
182
  }
183
+ const updatedResults = results.map((res, i) => {
184
+ const { value } = res;
185
+ const { accountAddress, tokenAddress } = accountTokenPairs[i];
186
+ const currentTokenBalanceValueForAccount = currentTokenBalances.tokenBalances?.[accountAddress]?.[chainId]?.[tokenAddress];
187
+ const isTokenBalanceValueChanged = currentTokenBalanceValueForAccount !== toHex(value);
188
+ return {
189
+ ...res,
190
+ isTokenBalanceValueChanged,
191
+ };
192
+ });
193
+ // if all values of isTokenBalanceValueChanged are false, return
194
+ if (updatedResults.every((result) => !result.isTokenBalanceValueChanged)) {
195
+ return;
196
+ }
177
197
  this.update((state) => {
178
198
  var _a, _b;
179
- // Reset so that when accounts or tokens are removed,
180
- // their balances are removed rather than left stale.
181
- for (const accountAddress of Object.keys(state.tokenBalances)) {
182
- state.tokenBalances[accountAddress][chainId] = {};
183
- }
184
- for (let i = 0; i < results.length; i++) {
185
- const { success, value } = results[i];
199
+ for (let i = 0; i < updatedResults.length; i++) {
200
+ const { success, value, isTokenBalanceValueChanged } = updatedResults[i];
186
201
  const { accountAddress, tokenAddress } = accountTokenPairs[i];
187
- if (success) {
202
+ if (success && isTokenBalanceValueChanged) {
188
203
  ((_b = ((_a = state.tokenBalances)[accountAddress] ?? (_a[accountAddress] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = toHex(value);
189
204
  }
190
205
  }
@@ -212,6 +227,61 @@ _TokenBalancesController_queryMultipleAccounts = new WeakMap(), _TokenBalancesCo
212
227
  });
213
228
  }
214
229
  }
230
+ }, _TokenBalancesController_handleOnAccountRemoved = function _TokenBalancesController_handleOnAccountRemoved(accountAddress) {
231
+ const isEthAddress = isStrictHexString(accountAddress.toLowerCase()) &&
232
+ isValidHexAddress(accountAddress);
233
+ if (!isEthAddress) {
234
+ return;
235
+ }
236
+ this.update((state) => {
237
+ delete state.tokenBalances[accountAddress];
238
+ });
239
+ }, _TokenBalancesController_handleTokensControllerStateChange = async function _TokenBalancesController_handleTokensControllerStateChange({ chainIds, } = {}) {
240
+ const currentTokenBalancesState = this.messagingSystem.call('TokenBalancesController:getState');
241
+ const currentTokenBalances = currentTokenBalancesState.tokenBalances;
242
+ const currentAllTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f");
243
+ const chainIdsSet = new Set(chainIds);
244
+ // first we check if the state change was due to a token being removed
245
+ for (const currentAccount of Object.keys(currentTokenBalances)) {
246
+ const allChains = currentTokenBalances[currentAccount];
247
+ for (const currentChain of Object.keys(allChains)) {
248
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
249
+ continue;
250
+ }
251
+ const tokensObject = allChains[currentChain];
252
+ const allCurrentTokens = Object.keys(tokensObject);
253
+ const existingTokensInState = currentAllTokens[currentChain]?.[currentAccount] || [];
254
+ const existingSet = new Set(existingTokensInState.map((elm) => elm.address));
255
+ for (const singleToken of allCurrentTokens) {
256
+ if (!existingSet.has(singleToken)) {
257
+ this.update((state) => {
258
+ delete state.tokenBalances[currentAccount][currentChain][singleToken];
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ // then we check if the state change was due to a token being added
265
+ let shouldUpdate = false;
266
+ for (const currentChain of Object.keys(currentAllTokens)) {
267
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
268
+ continue;
269
+ }
270
+ const accountsPerChain = currentAllTokens[currentChain];
271
+ for (const currentAccount of Object.keys(accountsPerChain)) {
272
+ const tokensList = accountsPerChain[currentAccount];
273
+ const tokenBalancesObject = currentTokenBalances[currentAccount]?.[currentChain] || {};
274
+ for (const singleToken of tokensList) {
275
+ if (!tokenBalancesObject?.[singleToken.address]) {
276
+ shouldUpdate = true;
277
+ break;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ if (shouldUpdate) {
283
+ await this.updateBalances({ chainIds }).catch(console.error);
284
+ }
215
285
  }, _TokenBalancesController_getNetworkClient = function _TokenBalancesController_getNetworkClient(chainId) {
216
286
  const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
217
287
  const networkConfiguration = networkConfigurationsByChainId[chainId];