@metamask/assets-controllers 74.0.0 → 74.1.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,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [74.1.0]
11
+
12
+ ### Added
13
+
14
+ - Enable `AccountTrackerController` to fetch native balances using AccountsAPI when `allowExternalServices` is enabled ([#6369](https://github.com/MetaMask/core/pull/6369))
15
+
16
+ - Implement native balance fetching via AccountsAPI when `useAccountsAPI` and `allowExternalServices` are both true
17
+ - Add fallback to RPC balance fetching when external services are disabled
18
+ - Add comprehensive test coverage for both AccountsAPI and RPC balance fetching scenarios
19
+
20
+ ### Changed
21
+
22
+ - Bump `@metamask/base-controller` from `^8.1.0` to `^8.2.0` ([#6355](https://github.com/MetaMask/core/pull/6355))
23
+
24
+ - Add new `accountId` field to the `Asset` type ([#6358](https://github.com/MetaMask/core/pull/6358))
25
+
26
+ ### Fixed
27
+
28
+ - Uses `InternalAccount['type']` for the `Asset['type']` property ([#6358](https://github.com/MetaMask/core/pull/6358))
29
+
30
+ - Ensure that the evm addresses used to fetch balances from AccountTrackerController state is lowercase, in order to account for discrepancies between clients ([#6358](https://github.com/MetaMask/core/pull/6358))
31
+
32
+ - Prevents mutation of memoized fields used inside selectors ([#6358](https://github.com/MetaMask/core/pull/6358))
33
+
34
+ - Fix duplicate token balance entries caused by case-sensitive address comparison in `TokenBalancesController.updateBalances` ([#6354](https://github.com/MetaMask/core/pull/6354))
35
+
36
+ - Normalize token addresses to proper EIP-55 checksum format before using as object keys to prevent the same token from appearing multiple times with different cases
37
+ - Add comprehensive unit tests for token address normalization scenarios
38
+
39
+ - Fix TokenBalancesController timeout handling by replacing `safelyExecuteWithTimeout` with proper `Promise.race` implementation ([#6365](https://github.com/MetaMask/core/pull/6365))
40
+
41
+ - Replace `safelyExecuteWithTimeout` which was silently swallowing timeout errors with direct `Promise.race` that properly throws
42
+ - Reduce RPC timeout from 3 minutes to 15 seconds for better responsiveness and batch size
43
+ - Enable proper fallback between API and RPC balance fetchers when timeouts occur
44
+
10
45
  ## [74.0.0]
11
46
 
12
47
  ### Added
@@ -1879,7 +1914,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1879
1914
 
1880
1915
  - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845))
1881
1916
 
1882
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.0.0...HEAD
1917
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.1.0...HEAD
1918
+ [74.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.0.0...@metamask/assets-controllers@74.1.0
1883
1919
  [74.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@73.3.0...@metamask/assets-controllers@74.0.0
1884
1920
  [73.3.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@73.2.0...@metamask/assets-controllers@73.3.0
1885
1921
  [73.2.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@73.1.0...@metamask/assets-controllers@73.2.0
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- var _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getNetworkClientIds, _AccountTrackerController_getBalanceFromChain, _AccountTrackerController_registerMessageHandlers;
16
+ var _AccountTrackerRpcBalanceFetcher_instances, _AccountTrackerRpcBalanceFetcher_getProvider, _AccountTrackerRpcBalanceFetcher_getNetworkClient, _AccountTrackerRpcBalanceFetcher_includeStakedAssets, _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain, _AccountTrackerRpcBalanceFetcher_getBalanceFromChain, _AccountTrackerController_instances, _AccountTrackerController_refreshMutex, _AccountTrackerController_includeStakedAssets, _AccountTrackerController_getStakedBalanceForChain, _AccountTrackerController_balanceFetchers, _AccountTrackerController_getProvider, _AccountTrackerController_getNetworkClient, _AccountTrackerController_getCorrectNetworkClient, _AccountTrackerController_getNetworkClientIds, _AccountTrackerController_registerMessageHandlers;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.AccountTrackerController = void 0;
19
19
  const contracts_1 = require("@ethersproject/contracts");
@@ -23,14 +23,136 @@ const eth_query_1 = __importDefault(require("@metamask/eth-query"));
23
23
  const polling_controller_1 = require("@metamask/polling-controller");
24
24
  const utils_1 = require("@metamask/utils");
25
25
  const async_mutex_1 = require("async-mutex");
26
+ const bn_js_1 = __importDefault(require("bn.js"));
26
27
  const lodash_1 = require("lodash");
27
28
  const single_call_balance_checker_abi_1 = __importDefault(require("single-call-balance-checker-abi"));
28
29
  const AssetsContractController_1 = require("./AssetsContractController.cjs");
29
30
  const assetsUtil_1 = require("./assetsUtil.cjs");
31
+ const api_balance_fetcher_1 = require("./multi-chain-accounts-service/api-balance-fetcher.cjs");
30
32
  /**
31
33
  * The name of the {@link AccountTrackerController}.
32
34
  */
33
35
  const controllerName = 'AccountTrackerController';
36
+ const DEFAULT_TIMEOUT_MS = 15000;
37
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
38
+ /**
39
+ * RPC-based balance fetcher for AccountTrackerController.
40
+ * Fetches only native balances and staked balances (no token balances).
41
+ */
42
+ class AccountTrackerRpcBalanceFetcher {
43
+ constructor(getProvider, getNetworkClient, includeStakedAssets, getStakedBalanceForChain) {
44
+ _AccountTrackerRpcBalanceFetcher_instances.add(this);
45
+ _AccountTrackerRpcBalanceFetcher_getProvider.set(this, void 0);
46
+ _AccountTrackerRpcBalanceFetcher_getNetworkClient.set(this, void 0);
47
+ _AccountTrackerRpcBalanceFetcher_includeStakedAssets.set(this, void 0);
48
+ _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain.set(this, void 0);
49
+ __classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getProvider, getProvider, "f");
50
+ __classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getNetworkClient, getNetworkClient, "f");
51
+ __classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_includeStakedAssets, includeStakedAssets, "f");
52
+ __classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain, getStakedBalanceForChain, "f");
53
+ }
54
+ supports() {
55
+ return true; // fallback – supports every chain
56
+ }
57
+ async fetch({ chainIds, queryAllAccounts, selectedAccount, allAccounts, }) {
58
+ const results = [];
59
+ for (const chainId of chainIds) {
60
+ const accountsToUpdate = queryAllAccounts
61
+ ? Object.values(allAccounts).map((account) => (0, controller_utils_1.toChecksumHexAddress)(account.address))
62
+ : [selectedAccount];
63
+ const { provider, blockTracker } = __classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_getNetworkClient, "f").call(this, chainId);
64
+ const ethQuery = new eth_query_1.default(provider);
65
+ // Force fresh block data before multicall
66
+ await (0, controller_utils_1.safelyExecuteWithTimeout)(() => blockTracker?.checkForLatestBlock?.());
67
+ // Fetch native balances
68
+ if ((0, utils_1.hasProperty)(AssetsContractController_1.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID, chainId)) {
69
+ const contractAddress = AssetsContractController_1.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[chainId];
70
+ const contract = new contracts_1.Contract(contractAddress, single_call_balance_checker_abi_1.default, __classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_getProvider, "f").call(this, chainId));
71
+ const nativeBalances = await (0, controller_utils_1.safelyExecuteWithTimeout)(() => contract.balances(accountsToUpdate, [ZERO_ADDRESS]), false, 3000);
72
+ if (nativeBalances) {
73
+ accountsToUpdate.forEach((address, index) => {
74
+ results.push({
75
+ success: true,
76
+ value: new bn_js_1.default(nativeBalances[index].toString()),
77
+ account: address,
78
+ token: ZERO_ADDRESS,
79
+ chainId,
80
+ });
81
+ });
82
+ }
83
+ }
84
+ else {
85
+ // Process accounts in batches using reduceInBatchesSerially
86
+ await (0, assetsUtil_1.reduceInBatchesSerially)({
87
+ values: accountsToUpdate,
88
+ batchSize: assetsUtil_1.TOKEN_PRICES_BATCH_SIZE,
89
+ initialResult: undefined,
90
+ eachBatch: async (workingResult, batch) => {
91
+ const balancePromises = batch.map(async (address) => {
92
+ const balanceResult = await __classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_instances, "m", _AccountTrackerRpcBalanceFetcher_getBalanceFromChain).call(this, address, ethQuery).catch(() => null);
93
+ if (balanceResult) {
94
+ results.push({
95
+ success: true,
96
+ value: new bn_js_1.default(balanceResult.replace('0x', ''), 16),
97
+ account: address,
98
+ token: ZERO_ADDRESS,
99
+ chainId,
100
+ });
101
+ }
102
+ else {
103
+ results.push({
104
+ success: false,
105
+ account: address,
106
+ token: ZERO_ADDRESS,
107
+ chainId,
108
+ });
109
+ }
110
+ });
111
+ await Promise.allSettled(balancePromises);
112
+ return workingResult;
113
+ },
114
+ });
115
+ }
116
+ // Fetch staked balances if enabled
117
+ if (__classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_includeStakedAssets, "f")) {
118
+ const stakedBalancesPromise = __classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain, "f").call(this, accountsToUpdate, chainId);
119
+ const stakedBalanceResult = await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => (await stakedBalancesPromise));
120
+ if (stakedBalanceResult) {
121
+ // Find the staking contract address for this chain
122
+ const stakingContractAddress = AssetsContractController_1.STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId];
123
+ if (stakingContractAddress) {
124
+ Object.entries(stakedBalanceResult).forEach(([address, balance]) => {
125
+ results.push({
126
+ success: true,
127
+ value: balance
128
+ ? new bn_js_1.default(balance.replace('0x', ''), 16)
129
+ : new bn_js_1.default('0'),
130
+ account: address,
131
+ token: (0, controller_utils_1.toChecksumHexAddress)(stakingContractAddress),
132
+ chainId,
133
+ });
134
+ });
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return results;
140
+ }
141
+ }
142
+ _AccountTrackerRpcBalanceFetcher_getProvider = new WeakMap(), _AccountTrackerRpcBalanceFetcher_getNetworkClient = new WeakMap(), _AccountTrackerRpcBalanceFetcher_includeStakedAssets = new WeakMap(), _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain = new WeakMap(), _AccountTrackerRpcBalanceFetcher_instances = new WeakSet(), _AccountTrackerRpcBalanceFetcher_getBalanceFromChain =
143
+ /**
144
+ * Fetches the balance of a given address from the blockchain.
145
+ *
146
+ * @param address - The account address to fetch the balance for.
147
+ * @param ethQuery - The EthQuery instance to query getBalance with.
148
+ * @returns A promise that resolves to the balance in a hex string format.
149
+ */
150
+ async function _AccountTrackerRpcBalanceFetcher_getBalanceFromChain(address, ethQuery) {
151
+ return await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => {
152
+ (0, utils_1.assert)(ethQuery, 'Provider not set.');
153
+ return await (0, controller_utils_1.query)(ethQuery, 'getBalance', [address]);
154
+ });
155
+ };
34
156
  const accountTrackerMetadata = {
35
157
  accountsByChainId: {
36
158
  persist: true,
@@ -50,8 +172,10 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
50
172
  * @param options.messenger - The controller messaging system.
51
173
  * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain.
52
174
  * @param options.includeStakedAssets - Whether to include staked assets in the account balances.
175
+ * @param options.useAccountsAPI - Enable Accounts‑API strategy (if supported chain).
176
+ * @param options.allowExternalServices - Disable external HTTP calls (privacy / offline mode).
53
177
  */
54
- constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) {
178
+ constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, useAccountsAPI = false, allowExternalServices = () => true, }) {
55
179
  const { selectedNetworkClientId } = messenger.call('NetworkController:getState');
56
180
  const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
57
181
  super({
@@ -69,8 +193,29 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
69
193
  _AccountTrackerController_refreshMutex.set(this, new async_mutex_1.Mutex());
70
194
  _AccountTrackerController_includeStakedAssets.set(this, void 0);
71
195
  _AccountTrackerController_getStakedBalanceForChain.set(this, void 0);
196
+ _AccountTrackerController_balanceFetchers.set(this, void 0);
197
+ _AccountTrackerController_getProvider.set(this, (chainId) => {
198
+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
199
+ const cfg = networkConfigurationsByChainId[chainId];
200
+ const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
201
+ const client = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
202
+ return new providers_1.Web3Provider(client.provider);
203
+ });
204
+ _AccountTrackerController_getNetworkClient.set(this, (chainId) => {
205
+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
206
+ const cfg = networkConfigurationsByChainId[chainId];
207
+ const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
208
+ return this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
209
+ });
72
210
  __classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f");
73
211
  __classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f");
212
+ // Initialize balance fetchers - Strategy order: API first, then RPC fallback
213
+ __classPrivateFieldSet(this, _AccountTrackerController_balanceFetchers, [
214
+ ...(useAccountsAPI && allowExternalServices()
215
+ ? [new api_balance_fetcher_1.AccountsApiBalanceFetcher('extension', __classPrivateFieldGet(this, _AccountTrackerController_getProvider, "f"))]
216
+ : []),
217
+ new AccountTrackerRpcBalanceFetcher(__classPrivateFieldGet(this, _AccountTrackerController_getProvider, "f"), __classPrivateFieldGet(this, _AccountTrackerController_getNetworkClient, "f"), includeStakedAssets, getStakedBalanceForChain),
218
+ ], "f");
74
219
  this.setIntervalLength(interval);
75
220
  this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange', (newAddress, prevAddress) => {
76
221
  if (newAddress !== prevAddress) {
@@ -140,6 +285,8 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
140
285
  */
141
286
  async refresh(networkClientIds) {
142
287
  const selectedAccount = this.messagingSystem.call('AccountsController:getSelectedAccount');
288
+ const allAccounts = this.messagingSystem.call('AccountsController:listAccounts');
289
+ const { isMultiAccountBalancesEnabled } = this.messagingSystem.call('PreferencesController:getState');
143
290
  const releaseLock = await __classPrivateFieldGet(this, _AccountTrackerController_refreshMutex, "f").acquire();
144
291
  try {
145
292
  const chainIds = networkClientIds.map((networkClientId) => {
@@ -147,83 +294,79 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
147
294
  return chainId;
148
295
  });
149
296
  this.syncAccounts(chainIds);
150
- // Create an array of promises for each networkClientId
151
- const updatePromises = networkClientIds.map(async (networkClientId) => {
152
- const { chainId, ethQuery, provider, blockTracker } = __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getCorrectNetworkClient).call(this, networkClientId);
153
- const { accountsByChainId } = this.state;
154
- const { isMultiAccountBalancesEnabled } = this.messagingSystem.call('PreferencesController:getState');
155
- const accountsToUpdate = isMultiAccountBalancesEnabled
156
- ? Object.keys(accountsByChainId[chainId])
157
- : [(0, controller_utils_1.toChecksumHexAddress)(selectedAccount.address)];
158
- const accountsForChain = { ...accountsByChainId[chainId] };
159
- // Force fresh block data before multicall
160
- // TODO: This is a temporary fix to ensure that the block number is up to date.
161
- // We should remove this once we have a better solution for this on the block tracker controller.
162
- await (0, controller_utils_1.safelyExecuteWithTimeout)(() => blockTracker?.checkForLatestBlock?.());
163
- const stakedBalancesPromise = __classPrivateFieldGet(this, _AccountTrackerController_includeStakedAssets, "f")
164
- ? __classPrivateFieldGet(this, _AccountTrackerController_getStakedBalanceForChain, "f").call(this, accountsToUpdate, networkClientId)
165
- : Promise.resolve({});
166
- if ((0, utils_1.hasProperty)(AssetsContractController_1.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID, chainId)) {
167
- const contractAddress = AssetsContractController_1.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[chainId];
168
- const contract = new contracts_1.Contract(contractAddress, single_call_balance_checker_abi_1.default, new providers_1.Web3Provider(provider));
169
- const nativeBalances = await (0, controller_utils_1.safelyExecuteWithTimeout)(() => contract.balances(accountsToUpdate, [
170
- '0x0000000000000000000000000000000000000000',
171
- ]), false, 3000);
172
- if (nativeBalances) {
173
- accountsToUpdate.forEach((address, index) => {
174
- accountsForChain[address] = {
175
- balance: nativeBalances[index].toHexString(),
176
- };
177
- });
297
+ // Use balance fetchers with fallback strategy
298
+ const aggregated = [];
299
+ let remainingChains = [...chainIds];
300
+ // Try each fetcher in order, removing successfully processed chains
301
+ for (const fetcher of __classPrivateFieldGet(this, _AccountTrackerController_balanceFetchers, "f")) {
302
+ const supportedChains = remainingChains.filter((c) => fetcher.supports(c));
303
+ if (!supportedChains.length) {
304
+ continue;
305
+ }
306
+ try {
307
+ const balances = await Promise.race([
308
+ fetcher.fetch({
309
+ chainIds: supportedChains,
310
+ queryAllAccounts: isMultiAccountBalancesEnabled,
311
+ selectedAccount: (0, controller_utils_1.toChecksumHexAddress)(selectedAccount.address),
312
+ allAccounts,
313
+ }),
314
+ new Promise((_resolve, reject) => setTimeout(() => {
315
+ reject(new Error(`Timeout after ${DEFAULT_TIMEOUT_MS}ms`));
316
+ }, DEFAULT_TIMEOUT_MS)),
317
+ ]);
318
+ if (balances && balances.length > 0) {
319
+ aggregated.push(...balances);
320
+ // Remove chains that were successfully processed
321
+ const processedChains = new Set(balances.map((b) => b.chainId));
322
+ remainingChains = remainingChains.filter((chain) => !processedChains.has(chain));
178
323
  }
179
324
  }
180
- else {
181
- // Process accounts in batches using reduceInBatchesSerially
182
- await (0, assetsUtil_1.reduceInBatchesSerially)({
183
- values: accountsToUpdate,
184
- batchSize: assetsUtil_1.TOKEN_PRICES_BATCH_SIZE,
185
- initialResult: undefined,
186
- eachBatch: async (workingResult, batch) => {
187
- const balancePromises = batch.map(async (address) => {
188
- const balanceResult = await __classPrivateFieldGet(this, _AccountTrackerController_instances, "m", _AccountTrackerController_getBalanceFromChain).call(this, address, ethQuery).catch(() => null);
189
- // Update account balances
190
- if (balanceResult) {
191
- accountsForChain[address] = {
192
- balance: balanceResult,
193
- };
194
- }
195
- });
196
- await Promise.allSettled(balancePromises);
197
- return workingResult;
198
- },
199
- });
325
+ catch (error) {
326
+ console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
327
+ // Continue to next fetcher (fallback)
200
328
  }
201
- const stakedBalanceResult = await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => (await stakedBalancesPromise));
202
- Object.entries(stakedBalanceResult ?? {}).forEach(([address, balance]) => {
203
- accountsForChain[address] = {
204
- ...accountsForChain[address],
205
- stakedBalance: balance,
206
- };
207
- });
208
- // After all batches are processed, return the updated data
209
- return { chainId, accountsForChain };
210
- });
211
- // Wait for all networkClientId updates to settle in parallel
212
- const allResults = await Promise.allSettled(updatePromises);
329
+ // If all chains have been processed, break early
330
+ if (remainingChains.length === 0) {
331
+ break;
332
+ }
333
+ }
213
334
  // Build a _copy_ of the current state and track whether anything changed
214
335
  const nextAccountsByChainId = (0, lodash_1.cloneDeep)(this.state.accountsByChainId);
215
336
  let hasChanges = false;
216
- allResults.forEach((result) => {
217
- if (result.status === 'fulfilled') {
218
- const { chainId, accountsForChain } = result.value;
219
- // Only mark as changed if the incoming data differs
220
- if (!(0, lodash_1.isEqual)(nextAccountsByChainId[chainId], accountsForChain)) {
221
- nextAccountsByChainId[chainId] = accountsForChain;
222
- hasChanges = true;
337
+ // Process the aggregated balance results
338
+ const stakedBalancesByChainAndAddress = {};
339
+ aggregated.forEach(({ success, value, account, token, chainId }) => {
340
+ if (success && value !== undefined) {
341
+ const hexValue = `0x${value.toString(16)}`;
342
+ if (token === ZERO_ADDRESS) {
343
+ // Native balance
344
+ if (nextAccountsByChainId[chainId][account].balance !== hexValue) {
345
+ nextAccountsByChainId[chainId][account].balance = hexValue;
346
+ hasChanges = true;
347
+ }
348
+ }
349
+ else {
350
+ // Staked balance (from staking contract address)
351
+ if (!stakedBalancesByChainAndAddress[chainId]) {
352
+ stakedBalancesByChainAndAddress[chainId] = {};
353
+ }
354
+ stakedBalancesByChainAndAddress[chainId][account] = hexValue;
223
355
  }
224
356
  }
225
357
  });
226
- // 👇🏻 call `update` only when something is new / different
358
+ // Apply staked balances
359
+ Object.entries(stakedBalancesByChainAndAddress).forEach(([chainId, balancesByAddress]) => {
360
+ Object.entries(balancesByAddress).forEach(([address, stakedBalance]) => {
361
+ if (nextAccountsByChainId[chainId][address].stakedBalance !==
362
+ stakedBalance) {
363
+ nextAccountsByChainId[chainId][address].stakedBalance =
364
+ stakedBalance;
365
+ hasChanges = true;
366
+ }
367
+ });
368
+ });
369
+ // Only update state if something changed
227
370
  if (hasChanges) {
228
371
  this.update((state) => {
229
372
  state.accountsByChainId = nextAccountsByChainId;
@@ -318,7 +461,7 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
318
461
  }
319
462
  }
320
463
  exports.AccountTrackerController = AccountTrackerController;
321
- _AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCorrectNetworkClient = function _AccountTrackerController_getCorrectNetworkClient(networkClientId) {
464
+ _AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerController_includeStakedAssets = new WeakMap(), _AccountTrackerController_getStakedBalanceForChain = new WeakMap(), _AccountTrackerController_balanceFetchers = new WeakMap(), _AccountTrackerController_getProvider = new WeakMap(), _AccountTrackerController_getNetworkClient = new WeakMap(), _AccountTrackerController_instances = new WeakSet(), _AccountTrackerController_getCorrectNetworkClient = function _AccountTrackerController_getCorrectNetworkClient(networkClientId) {
322
465
  const selectedNetworkClientId = networkClientId ??
323
466
  this.messagingSystem.call('NetworkController:getState')
324
467
  .selectedNetworkClientId;
@@ -332,19 +475,6 @@ _AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerControlle
332
475
  }, _AccountTrackerController_getNetworkClientIds = function _AccountTrackerController_getNetworkClientIds() {
333
476
  const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
334
477
  return Object.values(networkConfigurationsByChainId).flatMap((networkConfiguration) => networkConfiguration.rpcEndpoints.map((rpcEndpoint) => rpcEndpoint.networkClientId));
335
- }, _AccountTrackerController_getBalanceFromChain =
336
- /**
337
- * Fetches the balance of a given address from the blockchain.
338
- *
339
- * @param address - The account address to fetch the balance for.
340
- * @param ethQuery - The EthQuery instance to query getBalnce with.
341
- * @returns A promise that resolves to the balance in a hex string format.
342
- */
343
- async function _AccountTrackerController_getBalanceFromChain(address, ethQuery) {
344
- return await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => {
345
- (0, utils_1.assert)(ethQuery, 'Provider not set.');
346
- return await (0, controller_utils_1.query)(ethQuery, 'getBalance', [address]);
347
- });
348
478
  }, _AccountTrackerController_registerMessageHandlers = function _AccountTrackerController_registerMessageHandlers() {
349
479
  this.messagingSystem.registerActionHandler(`${controllerName}:updateNativeBalances`, this.updateNativeBalances.bind(this));
350
480
  this.messagingSystem.registerActionHandler(`${controllerName}:updateStakedBalances`, this.updateStakedBalances.bind(this));
@@ -1 +1 @@
1
- {"version":3,"file":"AccountTrackerController.cjs","sourceRoot":"","sources":["../src/AccountTrackerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BigNumber } from '@ethersproject/bignumber';\nimport { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerSelectedEvmAccountChangeEvent,\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerListAccountsAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerStateChangeEvent,\n ControllerGetStateAction,\n RestrictedMessenger,\n} from '@metamask/base-controller';\nimport {\n query,\n safelyExecuteWithTimeout,\n toChecksumHexAddress,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { PreferencesControllerGetStateAction } from '@metamask/preferences-controller';\nimport { assert, hasProperty, type Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport { cloneDeep, isEqual } from 'lodash';\nimport abiSingleCallBalancesContract from 'single-call-balance-checker-abi';\n\nimport {\n SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID,\n type AssetsContractController,\n type StakedBalance,\n} from './AssetsContractController';\nimport { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';\n\n/**\n * The name of the {@link AccountTrackerController}.\n */\nconst controllerName = 'AccountTrackerController';\n\n/**\n * AccountInformation\n *\n * Account information object\n *\n * balance - Hex string of an account balance in wei\n *\n * stakedBalance - Hex string of an account staked balance in wei\n */\nexport type AccountInformation = {\n balance: string;\n stakedBalance?: string;\n};\n\n/**\n * AccountTrackerControllerState\n *\n * Account tracker controller state\n *\n * accountsByChainId - Map of addresses to account information by chain\n */\nexport type AccountTrackerControllerState = {\n accountsByChainId: Record<string, { [address: string]: AccountInformation }>;\n};\n\nconst accountTrackerMetadata = {\n accountsByChainId: {\n persist: true,\n anonymous: false,\n },\n};\n\n/**\n * The action that can be performed to get the state of the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n AccountTrackerControllerState\n>;\n\n/**\n * The action that can be performed to update multiple native token balances in batch.\n */\nexport type AccountTrackerUpdateNativeBalancesAction = {\n type: `${typeof controllerName}:updateNativeBalances`;\n handler: AccountTrackerController['updateNativeBalances'];\n};\n\n/**\n * The action that can be performed to update multiple staked balances in batch.\n */\nexport type AccountTrackerUpdateStakedBalancesAction = {\n type: `${typeof controllerName}:updateStakedBalances`;\n handler: AccountTrackerController['updateStakedBalances'];\n};\n\n/**\n * The actions that can be performed using the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerActions =\n | AccountTrackerControllerGetStateAction\n | AccountTrackerUpdateNativeBalancesAction\n | AccountTrackerUpdateStakedBalancesAction;\n\n/**\n * The messenger of the {@link AccountTrackerController} for communication.\n */\nexport type AllowedActions =\n | AccountsControllerListAccountsAction\n | PreferencesControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction;\n\n/**\n * The event that {@link AccountTrackerController} can emit.\n */\nexport type AccountTrackerControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n AccountTrackerControllerState\n >;\n\n/**\n * The events that {@link AccountTrackerController} can emit.\n */\nexport type AccountTrackerControllerEvents =\n AccountTrackerControllerStateChangeEvent;\n\n/**\n * The external events available to the {@link AccountTrackerController}.\n */\nexport type AllowedEvents =\n | AccountsControllerSelectedEvmAccountChangeEvent\n | AccountsControllerSelectedAccountChangeEvent;\n\n/**\n * The messenger of the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerMessenger = RestrictedMessenger<\n typeof controllerName,\n AccountTrackerControllerActions | AllowedActions,\n AccountTrackerControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/** The input to start polling for the {@link AccountTrackerController} */\ntype AccountTrackerPollingInput = {\n networkClientIds: NetworkClientId[];\n};\n\n/**\n * Controller that tracks the network balances for all user accounts.\n */\nexport class AccountTrackerController extends StaticIntervalPollingController<AccountTrackerPollingInput>()<\n typeof controllerName,\n AccountTrackerControllerState,\n AccountTrackerControllerMessenger\n> {\n readonly #refreshMutex = new Mutex();\n\n readonly #includeStakedAssets: boolean;\n\n readonly #getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'];\n\n /**\n * Creates an AccountTracker instance.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new account balances.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller messaging system.\n * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain.\n * @param options.includeStakedAssets - Whether to include staked assets in the account balances.\n */\n constructor({\n interval = 10000,\n state,\n messenger,\n getStakedBalanceForChain,\n includeStakedAssets = false,\n }: {\n interval?: number;\n state?: Partial<AccountTrackerControllerState>;\n messenger: AccountTrackerControllerMessenger;\n getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'];\n includeStakedAssets?: boolean;\n }) {\n const { selectedNetworkClientId } = messenger.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId },\n } = messenger.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n super({\n name: controllerName,\n messenger,\n state: {\n accountsByChainId: {\n [chainId]: {},\n },\n ...state,\n },\n metadata: accountTrackerMetadata,\n });\n this.#getStakedBalanceForChain = getStakedBalanceForChain;\n\n this.#includeStakedAssets = includeStakedAssets;\n\n this.setIntervalLength(interval);\n\n this.messagingSystem.subscribe(\n 'AccountsController:selectedEvmAccountChange',\n (newAddress, prevAddress) => {\n if (newAddress !== prevAddress) {\n // Making an async call for this new event\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.refresh(this.#getNetworkClientIds());\n }\n },\n (event): string => event.address,\n );\n\n this.#registerMessageHandlers();\n }\n\n private syncAccounts(newChainIds: string[]) {\n const accountsByChainId = cloneDeep(this.state.accountsByChainId);\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId: currentChainId },\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n const existing = Object.keys(accountsByChainId?.[currentChainId] ?? {});\n\n // Initialize new chain IDs if they don't exist\n newChainIds.forEach((newChainId) => {\n if (!accountsByChainId[newChainId]) {\n accountsByChainId[newChainId] = {};\n existing.forEach((address) => {\n accountsByChainId[newChainId][address] = { balance: '0x0' };\n });\n }\n });\n\n // Note: The address from the preferences controller are checksummed\n // The addresses from the accounts controller are lowercased\n const addresses = Object.values(\n this.messagingSystem\n .call('AccountsController:listAccounts')\n .map((internalAccount) =>\n toChecksumHexAddress(internalAccount.address),\n ),\n );\n const newAddresses = addresses.filter(\n (address) => !existing.includes(address),\n );\n const oldAddresses = existing.filter(\n (address) => !addresses.includes(address),\n );\n Object.keys(accountsByChainId).forEach((chainId) => {\n newAddresses.forEach((address) => {\n accountsByChainId[chainId][address] = {\n balance: '0x0',\n };\n });\n });\n\n Object.keys(accountsByChainId).forEach((chainId) => {\n oldAddresses.forEach((address) => {\n delete accountsByChainId[chainId][address];\n });\n });\n\n if (!isEqual(this.state.accountsByChainId, accountsByChainId)) {\n this.update((state) => {\n state.accountsByChainId = accountsByChainId;\n });\n }\n }\n\n /**\n * Resolves a networkClientId to a network client config\n * or globally selected network config if not provided\n *\n * @param networkClientId - Optional networkClientId to fetch a network client with\n * @returns network client config\n */\n #getCorrectNetworkClient(networkClientId?: NetworkClientId) {\n const selectedNetworkClientId =\n networkClientId ??\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId;\n const {\n configuration: { chainId },\n provider,\n blockTracker,\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n return {\n chainId,\n provider,\n ethQuery: new EthQuery(provider),\n blockTracker,\n };\n }\n\n /**\n * Retrieves the list of network client IDs.\n *\n * @returns An array of network client IDs.\n */\n #getNetworkClientIds(): NetworkClientId[] {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n return Object.values(networkConfigurationsByChainId).flatMap(\n (networkConfiguration) =>\n networkConfiguration.rpcEndpoints.map(\n (rpcEndpoint) => rpcEndpoint.networkClientId,\n ),\n );\n }\n\n /**\n * Refreshes the balances of the accounts using the networkClientId\n *\n * @param input - The input for the poll.\n * @param input.networkClientIds - The network client IDs used to get balances.\n */\n async _executePoll({\n networkClientIds,\n }: AccountTrackerPollingInput): Promise<void> {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.refresh(networkClientIds);\n }\n\n /**\n * Refreshes the balances of the accounts depending on the multi-account setting.\n * If multi-account is disabled, only updates the selected account balance.\n * If multi-account is enabled, updates balances for all accounts.\n *\n * @param networkClientIds - Optional network client IDs to fetch a network client with\n */\n async refresh(networkClientIds: NetworkClientId[]) {\n const selectedAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n const releaseLock = await this.#refreshMutex.acquire();\n try {\n const chainIds = networkClientIds.map((networkClientId) => {\n const { chainId } = this.#getCorrectNetworkClient(networkClientId);\n return chainId;\n });\n\n this.syncAccounts(chainIds);\n\n // Create an array of promises for each networkClientId\n const updatePromises = networkClientIds.map(async (networkClientId) => {\n const { chainId, ethQuery, provider, blockTracker } =\n this.#getCorrectNetworkClient(networkClientId);\n const { accountsByChainId } = this.state;\n const { isMultiAccountBalancesEnabled } = this.messagingSystem.call(\n 'PreferencesController:getState',\n );\n\n const accountsToUpdate = isMultiAccountBalancesEnabled\n ? Object.keys(accountsByChainId[chainId])\n : [toChecksumHexAddress(selectedAccount.address)];\n\n const accountsForChain = { ...accountsByChainId[chainId] };\n\n // Force fresh block data before multicall\n // TODO: This is a temporary fix to ensure that the block number is up to date.\n // We should remove this once we have a better solution for this on the block tracker controller.\n await safelyExecuteWithTimeout(() =>\n blockTracker?.checkForLatestBlock?.(),\n );\n\n const stakedBalancesPromise = this.#includeStakedAssets\n ? this.#getStakedBalanceForChain(accountsToUpdate, networkClientId)\n : Promise.resolve({});\n\n if (hasProperty(SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID, chainId)) {\n const contractAddress = SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[\n chainId\n ] as string;\n\n const contract = new Contract(\n contractAddress,\n abiSingleCallBalancesContract,\n new Web3Provider(provider),\n );\n\n const nativeBalances = await safelyExecuteWithTimeout(\n () =>\n contract.balances(accountsToUpdate, [\n '0x0000000000000000000000000000000000000000',\n ]) as Promise<BigNumber[]>,\n false,\n 3_000, // 3s max call for multicall contract call\n );\n\n if (nativeBalances) {\n accountsToUpdate.forEach((address, index) => {\n accountsForChain[address] = {\n balance: nativeBalances[index].toHexString(),\n };\n });\n }\n } else {\n // Process accounts in batches using reduceInBatchesSerially\n await reduceInBatchesSerially<string, void>({\n values: accountsToUpdate,\n batchSize: TOKEN_PRICES_BATCH_SIZE,\n initialResult: undefined,\n eachBatch: async (workingResult: void, batch: string[]) => {\n const balancePromises = batch.map(async (address: string) => {\n const balanceResult = await this.#getBalanceFromChain(\n address,\n ethQuery,\n ).catch(() => null);\n\n // Update account balances\n if (balanceResult) {\n accountsForChain[address] = {\n balance: balanceResult,\n };\n }\n });\n\n await Promise.allSettled(balancePromises);\n return workingResult;\n },\n });\n }\n\n const stakedBalanceResult = await safelyExecuteWithTimeout(\n async () =>\n (await stakedBalancesPromise) as Record<string, StakedBalance>,\n );\n\n Object.entries(stakedBalanceResult ?? {}).forEach(\n ([address, balance]) => {\n accountsForChain[address] = {\n ...accountsForChain[address],\n stakedBalance: balance,\n };\n },\n );\n\n // After all batches are processed, return the updated data\n return { chainId, accountsForChain };\n });\n\n // Wait for all networkClientId updates to settle in parallel\n const allResults = await Promise.allSettled(updatePromises);\n\n // Build a _copy_ of the current state and track whether anything changed\n const nextAccountsByChainId: AccountTrackerControllerState['accountsByChainId'] =\n cloneDeep(this.state.accountsByChainId);\n let hasChanges = false;\n\n allResults.forEach((result) => {\n if (result.status === 'fulfilled') {\n const { chainId, accountsForChain } = result.value;\n // Only mark as changed if the incoming data differs\n if (!isEqual(nextAccountsByChainId[chainId], accountsForChain)) {\n nextAccountsByChainId[chainId] = accountsForChain;\n hasChanges = true;\n }\n }\n });\n\n // 👇🏻 call `update` only when something is new / different\n if (hasChanges) {\n this.update((state) => {\n state.accountsByChainId = nextAccountsByChainId;\n });\n }\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Fetches the balance of a given address from the blockchain.\n *\n * @param address - The account address to fetch the balance for.\n * @param ethQuery - The EthQuery instance to query getBalnce with.\n * @returns A promise that resolves to the balance in a hex string format.\n */\n async #getBalanceFromChain(\n address: string,\n ethQuery?: EthQuery,\n ): Promise<string | undefined> {\n return await safelyExecuteWithTimeout(async () => {\n assert(ethQuery, 'Provider not set.');\n return await query(ethQuery, 'getBalance', [address]);\n });\n }\n\n /**\n * Sync accounts balances with some additional addresses.\n *\n * @param addresses - the additional addresses, may be hardware wallet addresses.\n * @param networkClientId - Optional networkClientId to fetch a network client with.\n * @returns accounts - addresses with synced balance\n */\n async syncBalanceWithAddresses(\n addresses: string[],\n networkClientId?: NetworkClientId,\n ): Promise<\n Record<string, { balance: string; stakedBalance?: StakedBalance }>\n > {\n const { ethQuery } = this.#getCorrectNetworkClient(networkClientId);\n\n // TODO: This should use multicall when enabled by the user.\n return await Promise.all(\n addresses.map(\n (address): Promise<[string, string, StakedBalance] | undefined> => {\n return safelyExecuteWithTimeout(async () => {\n assert(ethQuery, 'Provider not set.');\n const balance = await query(ethQuery, 'getBalance', [address]);\n\n let stakedBalance: StakedBalance;\n if (this.#includeStakedAssets) {\n stakedBalance = (\n await this.#getStakedBalanceForChain([address], networkClientId)\n )[address];\n }\n return [address, balance, stakedBalance];\n });\n },\n ),\n ).then((value) => {\n return value.reduce((obj, item) => {\n if (!item) {\n return obj;\n }\n\n const [address, balance, stakedBalance] = item;\n return {\n ...obj,\n [address]: {\n balance,\n stakedBalance,\n },\n };\n }, {});\n });\n }\n\n /**\n * Updates the balances of multiple native tokens in a single batch operation.\n * This is more efficient than calling updateNativeToken multiple times as it\n * triggers only one state update.\n *\n * @param balances - Array of balance updates, each containing address, chainId, and balance.\n */\n updateNativeBalances(\n balances: { address: string; chainId: Hex; balance: string }[],\n ) {\n this.update((state) => {\n balances.forEach(({ address, chainId, balance }) => {\n // Ensure the chainId exists in the state\n if (!state.accountsByChainId[chainId]) {\n state.accountsByChainId[chainId] = {};\n }\n\n // Ensure the address exists for this chain\n if (!state.accountsByChainId[chainId][address]) {\n state.accountsByChainId[chainId][address] = { balance: '0x0' };\n }\n\n // Update the balance\n state.accountsByChainId[chainId][address].balance = balance;\n });\n });\n }\n\n /**\n * Updates the staked balances of multiple accounts in a single batch operation.\n * This is more efficient than updating staked balances individually as it\n * triggers only one state update.\n *\n * @param stakedBalances - Array of staked balance updates, each containing address, chainId, and stakedBalance.\n */\n updateStakedBalances(\n stakedBalances: {\n address: string;\n chainId: Hex;\n stakedBalance: StakedBalance;\n }[],\n ) {\n this.update((state) => {\n stakedBalances.forEach(({ address, chainId, stakedBalance }) => {\n // Ensure the chainId exists in the state\n if (!state.accountsByChainId[chainId]) {\n state.accountsByChainId[chainId] = {};\n }\n\n // Ensure the address exists for this chain\n if (!state.accountsByChainId[chainId][address]) {\n state.accountsByChainId[chainId][address] = { balance: '0x0' };\n }\n\n // Update the staked balance\n state.accountsByChainId[chainId][address].stakedBalance = stakedBalance;\n });\n });\n }\n\n #registerMessageHandlers() {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateNativeBalances` as const,\n this.updateNativeBalances.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateStakedBalances` as const,\n this.updateStakedBalances.bind(this),\n );\n }\n}\n\nexport default AccountTrackerController;\n"]}
1
+ {"version":3,"file":"AccountTrackerController.cjs","sourceRoot":"","sources":["../src/AccountTrackerController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BigNumber } from '@ethersproject/bignumber';\nimport { Contract } from '@ethersproject/contracts';\nimport { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerSelectedEvmAccountChangeEvent,\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerListAccountsAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerStateChangeEvent,\n ControllerGetStateAction,\n RestrictedMessenger,\n} from '@metamask/base-controller';\nimport {\n query,\n safelyExecuteWithTimeout,\n toChecksumHexAddress,\n} from '@metamask/controller-utils';\nimport EthQuery from '@metamask/eth-query';\nimport type {\n NetworkClient,\n NetworkClientId,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type { PreferencesControllerGetStateAction } from '@metamask/preferences-controller';\nimport { assert, hasProperty, type Hex } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\nimport BN from 'bn.js';\nimport { cloneDeep, isEqual } from 'lodash';\nimport abiSingleCallBalancesContract from 'single-call-balance-checker-abi';\n\nimport {\n SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID,\n STAKING_CONTRACT_ADDRESS_BY_CHAINID,\n type AssetsContractController,\n type StakedBalance,\n} from './AssetsContractController';\nimport { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil';\nimport {\n AccountsApiBalanceFetcher,\n type BalanceFetcher,\n type ProcessedBalance,\n} from './multi-chain-accounts-service/api-balance-fetcher';\n\n/**\n * The name of the {@link AccountTrackerController}.\n */\nconst controllerName = 'AccountTrackerController';\n\nexport type ChainIdHex = Hex;\nexport type ChecksumAddress = Hex;\n\nconst DEFAULT_TIMEOUT_MS = 15000;\nconst ZERO_ADDRESS =\n '0x0000000000000000000000000000000000000000' as ChecksumAddress;\n\n/**\n * RPC-based balance fetcher for AccountTrackerController.\n * Fetches only native balances and staked balances (no token balances).\n */\nclass AccountTrackerRpcBalanceFetcher implements BalanceFetcher {\n readonly #getProvider: (chainId: Hex) => Web3Provider;\n\n readonly #getNetworkClient: (chainId: Hex) => NetworkClient;\n\n readonly #includeStakedAssets: boolean;\n\n readonly #getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'];\n\n constructor(\n getProvider: (chainId: Hex) => Web3Provider,\n getNetworkClient: (chainId: Hex) => NetworkClient,\n includeStakedAssets: boolean,\n getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'],\n ) {\n this.#getProvider = getProvider;\n this.#getNetworkClient = getNetworkClient;\n this.#includeStakedAssets = includeStakedAssets;\n this.#getStakedBalanceForChain = getStakedBalanceForChain;\n }\n\n supports(): boolean {\n return true; // fallback – supports every chain\n }\n\n async fetch({\n chainIds,\n queryAllAccounts,\n selectedAccount,\n allAccounts,\n }: Parameters<BalanceFetcher['fetch']>[0]): Promise<ProcessedBalance[]> {\n const results: ProcessedBalance[] = [];\n\n for (const chainId of chainIds) {\n const accountsToUpdate = queryAllAccounts\n ? Object.values(allAccounts).map(\n (account) =>\n toChecksumHexAddress(account.address) as ChecksumAddress,\n )\n : [selectedAccount];\n\n const { provider, blockTracker } = this.#getNetworkClient(chainId);\n const ethQuery = new EthQuery(provider);\n\n // Force fresh block data before multicall\n await safelyExecuteWithTimeout(() =>\n blockTracker?.checkForLatestBlock?.(),\n );\n\n // Fetch native balances\n if (hasProperty(SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID, chainId)) {\n const contractAddress = SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID[\n chainId\n ] as string;\n\n const contract = new Contract(\n contractAddress,\n abiSingleCallBalancesContract,\n this.#getProvider(chainId),\n );\n\n const nativeBalances = await safelyExecuteWithTimeout(\n () =>\n contract.balances(accountsToUpdate, [ZERO_ADDRESS]) as Promise<\n BigNumber[]\n >,\n false,\n 3_000, // 3s max call for multicall contract call\n );\n\n if (nativeBalances) {\n accountsToUpdate.forEach((address, index) => {\n results.push({\n success: true,\n value: new BN(nativeBalances[index].toString()),\n account: address,\n token: ZERO_ADDRESS,\n chainId,\n });\n });\n }\n } else {\n // Process accounts in batches using reduceInBatchesSerially\n await reduceInBatchesSerially<string, void>({\n values: accountsToUpdate,\n batchSize: TOKEN_PRICES_BATCH_SIZE,\n initialResult: undefined,\n eachBatch: async (workingResult: void, batch: string[]) => {\n const balancePromises = batch.map(async (address: string) => {\n const balanceResult = await this.#getBalanceFromChain(\n address,\n ethQuery,\n ).catch(() => null);\n\n if (balanceResult) {\n results.push({\n success: true,\n value: new BN(balanceResult.replace('0x', ''), 16),\n account: address as ChecksumAddress,\n token: ZERO_ADDRESS,\n chainId,\n });\n } else {\n results.push({\n success: false,\n account: address as ChecksumAddress,\n token: ZERO_ADDRESS,\n chainId,\n });\n }\n });\n\n await Promise.allSettled(balancePromises);\n return workingResult;\n },\n });\n }\n\n // Fetch staked balances if enabled\n if (this.#includeStakedAssets) {\n const stakedBalancesPromise = this.#getStakedBalanceForChain(\n accountsToUpdate,\n chainId,\n );\n\n const stakedBalanceResult = await safelyExecuteWithTimeout(\n async () =>\n (await stakedBalancesPromise) as Record<string, StakedBalance>,\n );\n\n if (stakedBalanceResult) {\n // Find the staking contract address for this chain\n const stakingContractAddress =\n STAKING_CONTRACT_ADDRESS_BY_CHAINID[\n chainId as keyof typeof STAKING_CONTRACT_ADDRESS_BY_CHAINID\n ];\n\n if (stakingContractAddress) {\n Object.entries(stakedBalanceResult).forEach(\n ([address, balance]) => {\n results.push({\n success: true,\n value: balance\n ? new BN(balance.replace('0x', ''), 16)\n : new BN('0'),\n account: address as ChecksumAddress,\n token: toChecksumHexAddress(\n stakingContractAddress,\n ) as ChecksumAddress,\n chainId,\n });\n },\n );\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Fetches the balance of a given address from the blockchain.\n *\n * @param address - The account address to fetch the balance for.\n * @param ethQuery - The EthQuery instance to query getBalance with.\n * @returns A promise that resolves to the balance in a hex string format.\n */\n async #getBalanceFromChain(\n address: string,\n ethQuery?: EthQuery,\n ): Promise<string | undefined> {\n return await safelyExecuteWithTimeout(async () => {\n assert(ethQuery, 'Provider not set.');\n return await query(ethQuery, 'getBalance', [address]);\n });\n }\n}\n\n/**\n * AccountInformation\n *\n * Account information object\n *\n * balance - Hex string of an account balance in wei\n *\n * stakedBalance - Hex string of an account staked balance in wei\n */\nexport type AccountInformation = {\n balance: string;\n stakedBalance?: string;\n};\n\n/**\n * AccountTrackerControllerState\n *\n * Account tracker controller state\n *\n * accountsByChainId - Map of addresses to account information by chain\n */\nexport type AccountTrackerControllerState = {\n accountsByChainId: Record<string, { [address: string]: AccountInformation }>;\n};\n\nconst accountTrackerMetadata = {\n accountsByChainId: {\n persist: true,\n anonymous: false,\n },\n};\n\n/**\n * The action that can be performed to get the state of the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n AccountTrackerControllerState\n>;\n\n/**\n * The action that can be performed to update multiple native token balances in batch.\n */\nexport type AccountTrackerUpdateNativeBalancesAction = {\n type: `${typeof controllerName}:updateNativeBalances`;\n handler: AccountTrackerController['updateNativeBalances'];\n};\n\n/**\n * The action that can be performed to update multiple staked balances in batch.\n */\nexport type AccountTrackerUpdateStakedBalancesAction = {\n type: `${typeof controllerName}:updateStakedBalances`;\n handler: AccountTrackerController['updateStakedBalances'];\n};\n\n/**\n * The actions that can be performed using the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerActions =\n | AccountTrackerControllerGetStateAction\n | AccountTrackerUpdateNativeBalancesAction\n | AccountTrackerUpdateStakedBalancesAction;\n\n/**\n * The messenger of the {@link AccountTrackerController} for communication.\n */\nexport type AllowedActions =\n | AccountsControllerListAccountsAction\n | PreferencesControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction\n | NetworkControllerGetStateAction\n | NetworkControllerGetNetworkClientByIdAction;\n\n/**\n * The event that {@link AccountTrackerController} can emit.\n */\nexport type AccountTrackerControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n AccountTrackerControllerState\n >;\n\n/**\n * The events that {@link AccountTrackerController} can emit.\n */\nexport type AccountTrackerControllerEvents =\n AccountTrackerControllerStateChangeEvent;\n\n/**\n * The external events available to the {@link AccountTrackerController}.\n */\nexport type AllowedEvents =\n | AccountsControllerSelectedEvmAccountChangeEvent\n | AccountsControllerSelectedAccountChangeEvent;\n\n/**\n * The messenger of the {@link AccountTrackerController}.\n */\nexport type AccountTrackerControllerMessenger = RestrictedMessenger<\n typeof controllerName,\n AccountTrackerControllerActions | AllowedActions,\n AccountTrackerControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n/** The input to start polling for the {@link AccountTrackerController} */\ntype AccountTrackerPollingInput = {\n networkClientIds: NetworkClientId[];\n};\n\n/**\n * Controller that tracks the network balances for all user accounts.\n */\nexport class AccountTrackerController extends StaticIntervalPollingController<AccountTrackerPollingInput>()<\n typeof controllerName,\n AccountTrackerControllerState,\n AccountTrackerControllerMessenger\n> {\n readonly #refreshMutex = new Mutex();\n\n readonly #includeStakedAssets: boolean;\n\n readonly #getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'];\n\n readonly #balanceFetchers: BalanceFetcher[];\n\n /**\n * Creates an AccountTracker instance.\n *\n * @param options - The controller options.\n * @param options.interval - Polling interval used to fetch new account balances.\n * @param options.state - Initial state to set on this controller.\n * @param options.messenger - The controller messaging system.\n * @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain.\n * @param options.includeStakedAssets - Whether to include staked assets in the account balances.\n * @param options.useAccountsAPI - Enable Accounts‑API strategy (if supported chain).\n * @param options.allowExternalServices - Disable external HTTP calls (privacy / offline mode).\n */\n constructor({\n interval = 10000,\n state,\n messenger,\n getStakedBalanceForChain,\n includeStakedAssets = false,\n useAccountsAPI = false,\n allowExternalServices = () => true,\n }: {\n interval?: number;\n state?: Partial<AccountTrackerControllerState>;\n messenger: AccountTrackerControllerMessenger;\n getStakedBalanceForChain: AssetsContractController['getStakedBalanceForChain'];\n includeStakedAssets?: boolean;\n useAccountsAPI?: boolean;\n allowExternalServices?: () => boolean;\n }) {\n const { selectedNetworkClientId } = messenger.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId },\n } = messenger.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n super({\n name: controllerName,\n messenger,\n state: {\n accountsByChainId: {\n [chainId]: {},\n },\n ...state,\n },\n metadata: accountTrackerMetadata,\n });\n this.#getStakedBalanceForChain = getStakedBalanceForChain;\n\n this.#includeStakedAssets = includeStakedAssets;\n\n // Initialize balance fetchers - Strategy order: API first, then RPC fallback\n this.#balanceFetchers = [\n ...(useAccountsAPI && allowExternalServices()\n ? [new AccountsApiBalanceFetcher('extension', this.#getProvider)]\n : []),\n new AccountTrackerRpcBalanceFetcher(\n this.#getProvider,\n this.#getNetworkClient,\n includeStakedAssets,\n getStakedBalanceForChain,\n ),\n ];\n\n this.setIntervalLength(interval);\n\n this.messagingSystem.subscribe(\n 'AccountsController:selectedEvmAccountChange',\n (newAddress, prevAddress) => {\n if (newAddress !== prevAddress) {\n // Making an async call for this new event\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.refresh(this.#getNetworkClientIds());\n }\n },\n (event): string => event.address,\n );\n\n this.#registerMessageHandlers();\n }\n\n private syncAccounts(newChainIds: string[]) {\n const accountsByChainId = cloneDeep(this.state.accountsByChainId);\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId: currentChainId },\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n const existing = Object.keys(accountsByChainId?.[currentChainId] ?? {});\n\n // Initialize new chain IDs if they don't exist\n newChainIds.forEach((newChainId) => {\n if (!accountsByChainId[newChainId]) {\n accountsByChainId[newChainId] = {};\n existing.forEach((address) => {\n accountsByChainId[newChainId][address] = { balance: '0x0' };\n });\n }\n });\n\n // Note: The address from the preferences controller are checksummed\n // The addresses from the accounts controller are lowercased\n const addresses = Object.values(\n this.messagingSystem\n .call('AccountsController:listAccounts')\n .map((internalAccount) =>\n toChecksumHexAddress(internalAccount.address),\n ),\n );\n const newAddresses = addresses.filter(\n (address) => !existing.includes(address),\n );\n const oldAddresses = existing.filter(\n (address) => !addresses.includes(address),\n );\n Object.keys(accountsByChainId).forEach((chainId) => {\n newAddresses.forEach((address) => {\n accountsByChainId[chainId][address] = {\n balance: '0x0',\n };\n });\n });\n\n Object.keys(accountsByChainId).forEach((chainId) => {\n oldAddresses.forEach((address) => {\n delete accountsByChainId[chainId][address];\n });\n });\n\n if (!isEqual(this.state.accountsByChainId, accountsByChainId)) {\n this.update((state) => {\n state.accountsByChainId = accountsByChainId;\n });\n }\n }\n\n readonly #getProvider = (chainId: Hex): Web3Provider => {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const cfg = networkConfigurationsByChainId[chainId];\n const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];\n const client = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n return new Web3Provider(client.provider);\n };\n\n readonly #getNetworkClient = (chainId: Hex) => {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const cfg = networkConfigurationsByChainId[chainId];\n const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];\n return this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n networkClientId,\n );\n };\n\n /**\n * Resolves a networkClientId to a network client config\n * or globally selected network config if not provided\n *\n * @param networkClientId - Optional networkClientId to fetch a network client with\n * @returns network client config\n */\n #getCorrectNetworkClient(networkClientId?: NetworkClientId) {\n const selectedNetworkClientId =\n networkClientId ??\n this.messagingSystem.call('NetworkController:getState')\n .selectedNetworkClientId;\n const {\n configuration: { chainId },\n provider,\n blockTracker,\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n return {\n chainId,\n provider,\n ethQuery: new EthQuery(provider),\n blockTracker,\n };\n }\n\n /**\n * Retrieves the list of network client IDs.\n *\n * @returns An array of network client IDs.\n */\n #getNetworkClientIds(): NetworkClientId[] {\n const { networkConfigurationsByChainId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n return Object.values(networkConfigurationsByChainId).flatMap(\n (networkConfiguration) =>\n networkConfiguration.rpcEndpoints.map(\n (rpcEndpoint) => rpcEndpoint.networkClientId,\n ),\n );\n }\n\n /**\n * Refreshes the balances of the accounts using the networkClientId\n *\n * @param input - The input for the poll.\n * @param input.networkClientIds - The network client IDs used to get balances.\n */\n async _executePoll({\n networkClientIds,\n }: AccountTrackerPollingInput): Promise<void> {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.refresh(networkClientIds);\n }\n\n /**\n * Refreshes the balances of the accounts depending on the multi-account setting.\n * If multi-account is disabled, only updates the selected account balance.\n * If multi-account is enabled, updates balances for all accounts.\n *\n * @param networkClientIds - Optional network client IDs to fetch a network client with\n */\n async refresh(networkClientIds: NetworkClientId[]) {\n const selectedAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n const allAccounts = this.messagingSystem.call(\n 'AccountsController:listAccounts',\n );\n const { isMultiAccountBalancesEnabled } = this.messagingSystem.call(\n 'PreferencesController:getState',\n );\n\n const releaseLock = await this.#refreshMutex.acquire();\n try {\n const chainIds = networkClientIds.map((networkClientId) => {\n const { chainId } = this.#getCorrectNetworkClient(networkClientId);\n return chainId;\n });\n\n this.syncAccounts(chainIds);\n\n // Use balance fetchers with fallback strategy\n const aggregated: ProcessedBalance[] = [];\n let remainingChains = [...chainIds] as ChainIdHex[];\n\n // Try each fetcher in order, removing successfully processed chains\n for (const fetcher of this.#balanceFetchers) {\n const supportedChains = remainingChains.filter((c) =>\n fetcher.supports(c),\n );\n if (!supportedChains.length) {\n continue;\n }\n\n try {\n const balances = await Promise.race([\n fetcher.fetch({\n chainIds: supportedChains,\n queryAllAccounts: isMultiAccountBalancesEnabled,\n selectedAccount: toChecksumHexAddress(\n selectedAccount.address,\n ) as ChecksumAddress,\n allAccounts,\n }),\n new Promise<never>((_resolve, reject) =>\n setTimeout(() => {\n reject(new Error(`Timeout after ${DEFAULT_TIMEOUT_MS}ms`));\n }, DEFAULT_TIMEOUT_MS),\n ),\n ]);\n\n if (balances && balances.length > 0) {\n aggregated.push(...balances);\n // Remove chains that were successfully processed\n const processedChains = new Set(balances.map((b) => b.chainId));\n remainingChains = remainingChains.filter(\n (chain) => !processedChains.has(chain),\n );\n }\n } catch (error) {\n console.warn(\n `Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`,\n );\n // Continue to next fetcher (fallback)\n }\n\n // If all chains have been processed, break early\n if (remainingChains.length === 0) {\n break;\n }\n }\n\n // Build a _copy_ of the current state and track whether anything changed\n const nextAccountsByChainId: AccountTrackerControllerState['accountsByChainId'] =\n cloneDeep(this.state.accountsByChainId);\n let hasChanges = false;\n\n // Process the aggregated balance results\n const stakedBalancesByChainAndAddress: Record<\n string,\n Record<string, string>\n > = {};\n\n aggregated.forEach(({ success, value, account, token, chainId }) => {\n if (success && value !== undefined) {\n const hexValue = `0x${value.toString(16)}`;\n\n if (token === ZERO_ADDRESS) {\n // Native balance\n if (nextAccountsByChainId[chainId][account].balance !== hexValue) {\n nextAccountsByChainId[chainId][account].balance = hexValue;\n hasChanges = true;\n }\n } else {\n // Staked balance (from staking contract address)\n if (!stakedBalancesByChainAndAddress[chainId]) {\n stakedBalancesByChainAndAddress[chainId] = {};\n }\n stakedBalancesByChainAndAddress[chainId][account] = hexValue;\n }\n }\n });\n\n // Apply staked balances\n Object.entries(stakedBalancesByChainAndAddress).forEach(\n ([chainId, balancesByAddress]) => {\n Object.entries(balancesByAddress).forEach(\n ([address, stakedBalance]) => {\n if (\n nextAccountsByChainId[chainId][address].stakedBalance !==\n stakedBalance\n ) {\n nextAccountsByChainId[chainId][address].stakedBalance =\n stakedBalance;\n hasChanges = true;\n }\n },\n );\n },\n );\n\n // Only update state if something changed\n if (hasChanges) {\n this.update((state) => {\n state.accountsByChainId = nextAccountsByChainId;\n });\n }\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Sync accounts balances with some additional addresses.\n *\n * @param addresses - the additional addresses, may be hardware wallet addresses.\n * @param networkClientId - Optional networkClientId to fetch a network client with.\n * @returns accounts - addresses with synced balance\n */\n async syncBalanceWithAddresses(\n addresses: string[],\n networkClientId?: NetworkClientId,\n ): Promise<\n Record<string, { balance: string; stakedBalance?: StakedBalance }>\n > {\n const { ethQuery } = this.#getCorrectNetworkClient(networkClientId);\n\n // TODO: This should use multicall when enabled by the user.\n return await Promise.all(\n addresses.map(\n (address): Promise<[string, string, StakedBalance] | undefined> => {\n return safelyExecuteWithTimeout(async () => {\n assert(ethQuery, 'Provider not set.');\n const balance = await query(ethQuery, 'getBalance', [address]);\n\n let stakedBalance: StakedBalance;\n if (this.#includeStakedAssets) {\n stakedBalance = (\n await this.#getStakedBalanceForChain([address], networkClientId)\n )[address];\n }\n return [address, balance, stakedBalance];\n });\n },\n ),\n ).then((value) => {\n return value.reduce((obj, item) => {\n if (!item) {\n return obj;\n }\n\n const [address, balance, stakedBalance] = item;\n return {\n ...obj,\n [address]: {\n balance,\n stakedBalance,\n },\n };\n }, {});\n });\n }\n\n /**\n * Updates the balances of multiple native tokens in a single batch operation.\n * This is more efficient than calling updateNativeToken multiple times as it\n * triggers only one state update.\n *\n * @param balances - Array of balance updates, each containing address, chainId, and balance.\n */\n updateNativeBalances(\n balances: { address: string; chainId: Hex; balance: string }[],\n ) {\n this.update((state) => {\n balances.forEach(({ address, chainId, balance }) => {\n // Ensure the chainId exists in the state\n if (!state.accountsByChainId[chainId]) {\n state.accountsByChainId[chainId] = {};\n }\n\n // Ensure the address exists for this chain\n if (!state.accountsByChainId[chainId][address]) {\n state.accountsByChainId[chainId][address] = { balance: '0x0' };\n }\n\n // Update the balance\n state.accountsByChainId[chainId][address].balance = balance;\n });\n });\n }\n\n /**\n * Updates the staked balances of multiple accounts in a single batch operation.\n * This is more efficient than updating staked balances individually as it\n * triggers only one state update.\n *\n * @param stakedBalances - Array of staked balance updates, each containing address, chainId, and stakedBalance.\n */\n updateStakedBalances(\n stakedBalances: {\n address: string;\n chainId: Hex;\n stakedBalance: StakedBalance;\n }[],\n ) {\n this.update((state) => {\n stakedBalances.forEach(({ address, chainId, stakedBalance }) => {\n // Ensure the chainId exists in the state\n if (!state.accountsByChainId[chainId]) {\n state.accountsByChainId[chainId] = {};\n }\n\n // Ensure the address exists for this chain\n if (!state.accountsByChainId[chainId][address]) {\n state.accountsByChainId[chainId][address] = { balance: '0x0' };\n }\n\n // Update the staked balance\n state.accountsByChainId[chainId][address].stakedBalance = stakedBalance;\n });\n });\n }\n\n #registerMessageHandlers() {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateNativeBalances` as const,\n this.updateNativeBalances.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:updateStakedBalances` as const,\n this.updateStakedBalances.bind(this),\n );\n }\n}\n\nexport default AccountTrackerController;\n"]}