@metamask-previews/assets-controllers 73.0.1-preview-bf50b46b → 73.0.1-preview-e4e5ca5c

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +1 -13
  2. package/dist/AccountTrackerController.cjs +5 -167
  3. package/dist/AccountTrackerController.cjs.map +1 -1
  4. package/dist/AccountTrackerController.d.cts +2 -14
  5. package/dist/AccountTrackerController.d.cts.map +1 -1
  6. package/dist/AccountTrackerController.d.mts +2 -14
  7. package/dist/AccountTrackerController.d.mts.map +1 -1
  8. package/dist/AccountTrackerController.mjs +5 -167
  9. package/dist/AccountTrackerController.mjs.map +1 -1
  10. package/dist/TokenBalancesController.cjs +321 -267
  11. package/dist/TokenBalancesController.cjs.map +1 -1
  12. package/dist/TokenBalancesController.d.cts +93 -51
  13. package/dist/TokenBalancesController.d.cts.map +1 -1
  14. package/dist/TokenBalancesController.d.mts +93 -51
  15. package/dist/TokenBalancesController.d.mts.map +1 -1
  16. package/dist/TokenBalancesController.mjs +320 -270
  17. package/dist/TokenBalancesController.mjs.map +1 -1
  18. package/dist/assetsUtil.cjs +1 -13
  19. package/dist/assetsUtil.cjs.map +1 -1
  20. package/dist/assetsUtil.d.cts +0 -8
  21. package/dist/assetsUtil.d.cts.map +1 -1
  22. package/dist/assetsUtil.d.mts +0 -8
  23. package/dist/assetsUtil.d.mts.map +1 -1
  24. package/dist/assetsUtil.mjs +1 -12
  25. package/dist/assetsUtil.mjs.map +1 -1
  26. package/dist/constants.cjs +1 -12
  27. package/dist/constants.cjs.map +1 -1
  28. package/dist/constants.d.cts +0 -1
  29. package/dist/constants.d.cts.map +1 -1
  30. package/dist/constants.d.mts +0 -1
  31. package/dist/constants.d.mts.map +1 -1
  32. package/dist/constants.mjs +0 -11
  33. package/dist/constants.mjs.map +1 -1
  34. package/dist/index.cjs +3 -3
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +3 -2
  37. package/dist/index.d.cts.map +1 -1
  38. package/dist/index.d.mts +3 -2
  39. package/dist/index.d.mts.map +1 -1
  40. package/dist/index.mjs +1 -1
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/multi-chain-accounts-service/multi-chain-accounts.cjs +1 -35
  43. package/dist/multi-chain-accounts-service/multi-chain-accounts.cjs.map +1 -1
  44. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.cts +0 -16
  45. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.cts.map +1 -1
  46. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.mts +0 -16
  47. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.mts.map +1 -1
  48. package/dist/multi-chain-accounts-service/multi-chain-accounts.mjs +0 -33
  49. package/dist/multi-chain-accounts-service/multi-chain-accounts.mjs.map +1 -1
  50. package/dist/multi-chain-accounts-service/types.cjs.map +1 -1
  51. package/dist/multi-chain-accounts-service/types.d.cts +0 -8
  52. package/dist/multi-chain-accounts-service/types.d.cts.map +1 -1
  53. package/dist/multi-chain-accounts-service/types.d.mts +0 -8
  54. package/dist/multi-chain-accounts-service/types.d.mts.map +1 -1
  55. package/dist/multi-chain-accounts-service/types.mjs.map +1 -1
  56. package/dist/multicall.cjs +22 -397
  57. package/dist/multicall.cjs.map +1 -1
  58. package/dist/multicall.d.cts +0 -39
  59. package/dist/multicall.d.cts.map +1 -1
  60. package/dist/multicall.d.mts +0 -39
  61. package/dist/multicall.d.mts.map +1 -1
  62. package/dist/multicall.mjs +21 -398
  63. package/dist/multicall.mjs.map +1 -1
  64. package/dist/selectors/balanceSelectors.cjs +275 -0
  65. package/dist/selectors/balanceSelectors.cjs.map +1 -0
  66. package/dist/selectors/balanceSelectors.d.cts +248 -0
  67. package/dist/selectors/balanceSelectors.d.cts.map +1 -0
  68. package/dist/selectors/balanceSelectors.d.mts +248 -0
  69. package/dist/selectors/balanceSelectors.d.mts.map +1 -0
  70. package/dist/selectors/balanceSelectors.mjs +268 -0
  71. package/dist/selectors/balanceSelectors.mjs.map +1 -0
  72. package/package.json +6 -3
  73. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +0 -98
  74. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +0 -1
  75. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts +0 -28
  76. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +0 -1
  77. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts +0 -28
  78. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +0 -1
  79. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +0 -98
  80. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +0 -1
  81. package/dist/rpc-service/rpc-balance-fetcher.cjs +0 -128
  82. package/dist/rpc-service/rpc-balance-fetcher.cjs.map +0 -1
  83. package/dist/rpc-service/rpc-balance-fetcher.d.cts +0 -34
  84. package/dist/rpc-service/rpc-balance-fetcher.d.cts.map +0 -1
  85. package/dist/rpc-service/rpc-balance-fetcher.d.mts +0 -34
  86. package/dist/rpc-service/rpc-balance-fetcher.d.mts.map +0 -1
  87. package/dist/rpc-service/rpc-balance-fetcher.mjs +0 -124
  88. package/dist/rpc-service/rpc-balance-fetcher.mjs.map +0 -1
  89. package/dist/selectors.cjs +0 -64
  90. package/dist/selectors.cjs.map +0 -1
  91. package/dist/selectors.d.cts +0 -51
  92. package/dist/selectors.d.cts.map +0 -1
  93. package/dist/selectors.d.mts +0 -51
  94. package/dist/selectors.d.mts.map +0 -1
  95. package/dist/selectors.mjs +0 -61
  96. package/dist/selectors.mjs.map +0 -1
@@ -1,310 +1,364 @@
1
1
  "use strict";
2
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
- if (kind === "m") throw new TypeError("Private method is not writable");
4
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
- 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");
6
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
- };
8
2
  var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
3
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
4
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
5
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
6
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
8
+ if (kind === "m") throw new TypeError("Private method is not writable");
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
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
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
15
12
  };
16
- var _TokenBalancesController_instances, _TokenBalancesController_log, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_chainIdsWithTokens_get, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved;
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_getProvider, _TokenBalancesController_ensureFreshBlockData, _TokenBalancesController_batchBalanceOf, _TokenBalancesController_getNetworkClient;
17
14
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.TokenBalancesController = void 0;
15
+ exports.TokenBalancesController = exports.getDefaultTokenBalancesState = void 0;
16
+ const contracts_1 = require("@ethersproject/contracts");
19
17
  const providers_1 = require("@ethersproject/providers");
20
18
  const controller_utils_1 = require("@metamask/controller-utils");
19
+ const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis");
21
20
  const polling_controller_1 = require("@metamask/polling-controller");
22
21
  const utils_1 = require("@metamask/utils");
23
- const immer_1 = require("immer");
24
- const isEqual_1 = __importDefault(require("lodash/isEqual.js"));
25
- const api_balance_fetcher_1 = require("./multi-chain-accounts-service/api-balance-fetcher.cjs");
26
- const rpc_balance_fetcher_1 = require("./rpc-service/rpc-balance-fetcher.cjs");
27
- const CONTROLLER = 'TokenBalancesController';
28
- const DEFAULT_INTERVAL_MS = 180000; // 3 minutes
22
+ const lodash_1 = require("lodash");
23
+ const multicall_1 = require("./multicall.cjs");
24
+ const DEFAULT_INTERVAL = 180000;
25
+ const controllerName = 'TokenBalancesController';
29
26
  const metadata = {
30
27
  tokenBalances: { persist: true, anonymous: false },
31
28
  };
32
- // endregion
33
- // ────────────────────────────────────────────────────────────────────────────
34
- // region: Helper utilities
35
- const draft = (base, fn) => (0, immer_1.produce)(base, fn);
36
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
37
- // endregion
38
- // ────────────────────────────────────────────────────────────────────────────
39
- // region: Main controller
29
+ /**
30
+ * Get the default TokenBalancesController state.
31
+ *
32
+ * @returns The default TokenBalancesController state.
33
+ */
34
+ function getDefaultTokenBalancesState() {
35
+ return {
36
+ tokenBalances: {},
37
+ };
38
+ }
39
+ exports.getDefaultTokenBalancesState = getDefaultTokenBalancesState;
40
+ /**
41
+ * Controller that passively polls on a set interval token balances
42
+ * for tokens stored in the TokensController
43
+ */
40
44
  class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPollingController)() {
41
- constructor({ messenger, interval = DEFAULT_INTERVAL_MS, state = {}, queryMultipleAccounts = true, useAccountsAPI = true, allowExternalServices = () => true, log = (...args) => console.warn(`[${CONTROLLER}]`, ...args), }) {
45
+ /**
46
+ * Construct a Token Balances Controller.
47
+ *
48
+ * @param options - The controller options.
49
+ * @param options.interval - Polling interval used to fetch new token balances.
50
+ * @param options.state - Initial state to set on this controller.
51
+ * @param options.messenger - The controller restricted messenger.
52
+ */
53
+ constructor({ interval = DEFAULT_INTERVAL, messenger, state = {}, }) {
54
+ var _a, _b;
42
55
  super({
43
- name: CONTROLLER,
44
- messenger,
56
+ name: controllerName,
45
57
  metadata,
46
- state: { tokenBalances: {}, ...state },
58
+ messenger,
59
+ state: {
60
+ ...getDefaultTokenBalancesState(),
61
+ ...state,
62
+ },
47
63
  });
48
64
  _TokenBalancesController_instances.add(this);
49
- _TokenBalancesController_log.set(this, void 0);
50
- _TokenBalancesController_queryAllAccounts.set(this, void 0);
51
- _TokenBalancesController_balanceFetchers.set(this, void 0);
52
- _TokenBalancesController_allTokens.set(this, {});
53
- _TokenBalancesController_detectedTokens.set(this, {});
54
- _TokenBalancesController_getProvider.set(this, (chainId) => {
55
- const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
56
- const cfg = networkConfigurationsByChainId[chainId];
57
- const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
58
- const client = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
59
- return new providers_1.Web3Provider(client.provider);
60
- });
61
- _TokenBalancesController_getNetworkClient.set(this, (chainId) => {
62
- const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
63
- const cfg = networkConfigurationsByChainId[chainId];
64
- const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
65
- return this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
66
- });
67
- // ───────── event handlers ─────────
68
- _TokenBalancesController_onTokensChanged.set(this, async (state) => {
69
- const changed = [];
70
- let hasChanges = false;
71
- // Get chains that have existing balances
72
- const chainsWithBalances = new Set();
73
- for (const address of Object.keys(this.state.tokenBalances)) {
74
- const addressKey = address;
75
- for (const chainId of Object.keys(this.state.tokenBalances[addressKey] || {})) {
76
- chainsWithBalances.add(chainId);
77
- }
78
- }
79
- // Only process chains that are explicitly mentioned in the incoming state change
80
- const incomingChainIds = new Set([
81
- ...Object.keys(state.allTokens),
82
- ...Object.keys(state.allDetectedTokens),
83
- ]);
84
- // Only proceed if there are actual changes to chains that have balances or are being added
85
- const relevantChainIds = Array.from(incomingChainIds).filter((chainId) => {
86
- const id = chainId;
87
- const hasTokensNow = (state.allTokens[id] && Object.keys(state.allTokens[id]).length > 0) ||
88
- (state.allDetectedTokens[id] &&
89
- Object.keys(state.allDetectedTokens[id]).length > 0);
90
- const hadTokensBefore = (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id] && Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
91
- (__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
92
- Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
93
- // Check if there's an actual change in token state
94
- const hasTokenChange = !(0, isEqual_1.default)(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
95
- !(0, isEqual_1.default)(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]);
96
- // Process chains that have actual changes OR are new chains getting tokens
97
- return hasTokenChange || (!hadTokensBefore && hasTokensNow);
98
- });
99
- if (relevantChainIds.length === 0) {
100
- // No relevant changes, just update internal state
101
- __classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
102
- __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
103
- return;
104
- }
105
- // Handle both cleanup and updates in a single state update
106
- this.update((s) => {
107
- for (const chainId of relevantChainIds) {
108
- const id = chainId;
109
- const hasTokensNow = (state.allTokens[id] &&
110
- Object.keys(state.allTokens[id]).length > 0) ||
111
- (state.allDetectedTokens[id] &&
112
- Object.keys(state.allDetectedTokens[id]).length > 0);
113
- const hadTokensBefore = (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id] &&
114
- Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
115
- (__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
116
- Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
117
- if (!(0, isEqual_1.default)(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
118
- !(0, isEqual_1.default)(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id])) {
119
- if (hasTokensNow) {
120
- // Chain still has tokens - mark for async balance update
121
- changed.push(id);
122
- }
123
- else if (hadTokensBefore) {
124
- // Chain had tokens before but doesn't now - clean up balances immediately
125
- for (const address of Object.keys(s.tokenBalances)) {
126
- const addressKey = address;
127
- if (s.tokenBalances[addressKey]?.[id]) {
128
- s.tokenBalances[addressKey][id] = {};
129
- hasChanges = true;
130
- }
131
- }
132
- }
133
- }
134
- }
135
- });
136
- __classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
137
- __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
138
- // Only update balances for chains that still have tokens (and only if we haven't already updated state)
139
- if (changed.length && !hasChanges) {
140
- this.updateBalances({ chainIds: changed }).catch((error) => {
141
- __classPrivateFieldGet(this, _TokenBalancesController_log, "f").call(this, 'Error updating balances after token change:', error);
142
- });
143
- }
65
+ _TokenBalancesController_queryMultipleAccounts.set(this, void 0);
66
+ _TokenBalancesController_allTokens.set(this, void 0);
67
+ _TokenBalancesController_allDetectedTokens.set(this, void 0);
68
+ /**
69
+ * Determines whether to query all accounts, or just the selected account.
70
+ * @param preferences - The preferences state.
71
+ * @param preferences.isMultiAccountBalancesEnabled - whether to query all accounts (mobile).
72
+ * @param preferences.useMultiAccountBalanceChecker - whether to query all accounts (extension).
73
+ * @returns true if all accounts should be queried.
74
+ */
75
+ _TokenBalancesController_calculateQueryMultipleAccounts.set(this, ({ isMultiAccountBalancesEnabled, useMultiAccountBalanceChecker, }) => {
76
+ return Boolean(
77
+ // Note: These settings have different names on extension vs mobile
78
+ isMultiAccountBalancesEnabled || useMultiAccountBalanceChecker);
144
79
  });
145
- _TokenBalancesController_onNetworkChanged.set(this, (state) => {
146
- // Check if any networks were removed by comparing with previous state
147
- const currentNetworks = new Set(Object.keys(state.networkConfigurationsByChainId));
148
- // Get all networks that currently have balances
149
- const networksWithBalances = new Set();
150
- for (const address of Object.keys(this.state.tokenBalances)) {
151
- const addressKey = address;
152
- for (const network of Object.keys(this.state.tokenBalances[addressKey] || {})) {
153
- networksWithBalances.add(network);
154
- }
155
- }
156
- // Find networks that were removed
157
- const removedNetworks = Array.from(networksWithBalances).filter((network) => !currentNetworks.has(network));
158
- if (removedNetworks.length > 0) {
159
- this.update((s) => {
160
- // Remove balances for all accounts on the deleted networks
161
- for (const address of Object.keys(s.tokenBalances)) {
162
- const addressKey = address;
163
- for (const removedNetwork of removedNetworks) {
164
- const networkKey = removedNetwork;
165
- if (s.tokenBalances[addressKey]?.[networkKey]) {
166
- delete s.tokenBalances[addressKey][networkKey];
167
- }
168
- }
169
- }
170
- });
80
+ /**
81
+ * Handles the event for preferences state changes.
82
+ * @param preferences - The preferences state.
83
+ */
84
+ _TokenBalancesController_onPreferencesStateChange.set(this, (preferences) => {
85
+ // Update the user preference for whether to query multiple accounts.
86
+ const queryMultipleAccounts = __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, preferences);
87
+ // Refresh when flipped off -> on
88
+ const refresh = queryMultipleAccounts && !__classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f");
89
+ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, queryMultipleAccounts, "f");
90
+ if (refresh) {
91
+ this.updateBalances().catch(console.error);
171
92
  }
172
93
  });
173
- _TokenBalancesController_onAccountRemoved.set(this, (addr) => {
174
- if (!(0, utils_1.isStrictHexString)(addr) || !(0, controller_utils_1.isValidHexAddress)(addr)) {
175
- return;
176
- }
177
- this.update((s) => {
178
- delete s.tokenBalances[addr];
179
- });
94
+ /**
95
+ * Handles the event for tokens state changes.
96
+ * @param state - The token state.
97
+ * @param state.allTokens - The state for imported tokens across all chains.
98
+ * @param state.allDetectedTokens - The state for detected tokens across all chains.
99
+ */
100
+ _TokenBalancesController_onTokensStateChange.set(this, ({ allTokens, allDetectedTokens, }) => {
101
+ // Refresh token balances on chains whose tokens have changed.
102
+ const chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, allTokens, allDetectedTokens);
103
+ const chainIdsToUpdate = chainIds.filter((chainId) => !(0, lodash_1.isEqual)(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId], allTokens[chainId]) ||
104
+ !(0, lodash_1.isEqual)(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId], allDetectedTokens[chainId]));
105
+ __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
106
+ __classPrivateFieldSet(this, _TokenBalancesController_allDetectedTokens, allDetectedTokens, "f");
107
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_handleTokensControllerStateChange).call(this, {
108
+ chainIds: chainIdsToUpdate,
109
+ }).catch(console.error);
180
110
  });
181
- __classPrivateFieldSet(this, _TokenBalancesController_log, log, "f");
182
- __classPrivateFieldSet(this, _TokenBalancesController_queryAllAccounts, queryMultipleAccounts, "f");
183
- // Strategy order: API first, then RPC fallback
184
- __classPrivateFieldSet(this, _TokenBalancesController_balanceFetchers, [
185
- ...(useAccountsAPI && allowExternalServices()
186
- ? [new api_balance_fetcher_1.AccountsApiBalanceFetcher()]
187
- : []),
188
- new rpc_balance_fetcher_1.RpcBalanceFetcher(__classPrivateFieldGet(this, _TokenBalancesController_getProvider, "f"), __classPrivateFieldGet(this, _TokenBalancesController_getNetworkClient, "f"), () => ({
189
- allTokens: __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f"),
190
- allDetectedTokens: __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f"),
191
- })),
192
- ], "f");
111
+ /**
112
+ * Returns an array of chain ids that have tokens.
113
+ * @param allTokens - The state for imported tokens across all chains.
114
+ * @param allDetectedTokens - The state for detected tokens across all chains.
115
+ * @returns An array of chain ids that have tokens.
116
+ */
117
+ _TokenBalancesController_getChainIds.set(this, (allTokens, allDetectedTokens) => [
118
+ ...new Set([
119
+ ...Object.keys(allTokens),
120
+ ...Object.keys(allDetectedTokens),
121
+ ]),
122
+ ]);
193
123
  this.setIntervalLength(interval);
194
- // initial token state & subscriptions
195
- const { allTokens, allDetectedTokens } = this.messagingSystem.call('TokensController:getState');
196
- __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
197
- __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, allDetectedTokens, "f");
198
- this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f"));
199
- this.messagingSystem.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onNetworkChanged, "f"));
200
- this.messagingSystem.subscribe('KeyringController:accountRemoved', __classPrivateFieldGet(this, _TokenBalancesController_onAccountRemoved, "f"));
124
+ // Set initial preference for querying multiple accounts, and subscribe to changes
125
+ __classPrivateFieldSet(this, _TokenBalancesController_queryMultipleAccounts, __classPrivateFieldGet(this, _TokenBalancesController_calculateQueryMultipleAccounts, "f").call(this, this.messagingSystem.call('PreferencesController:getState')), "f");
126
+ this.messagingSystem.subscribe('PreferencesController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onPreferencesStateChange, "f").bind(this));
127
+ // Set initial tokens, and subscribe to changes
128
+ (_a = this, _b = this, {
129
+ allTokens: ({ set value(_c) { __classPrivateFieldSet(_a, _TokenBalancesController_allTokens, _c, "f"); } }).value,
130
+ allDetectedTokens: ({ set value(_c) { __classPrivateFieldSet(_b, _TokenBalancesController_allDetectedTokens, _c, "f"); } }).value,
131
+ } = this.messagingSystem.call('TokensController:getState'));
132
+ this.messagingSystem.subscribe('TokensController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onTokensStateChange, "f").bind(this));
133
+ // Subscribe to network state changes
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));
201
137
  }
202
- // ───────── polling ─────────
203
- async _executePoll({ chainIds }) {
204
- await this.updateBalances({ chainIds });
138
+ /**
139
+ * Polls for erc20 token balances.
140
+ * @param input - The input for the poll.
141
+ * @param input.chainId - The chain id to poll token balances on.
142
+ */
143
+ async _executePoll({ chainId }) {
144
+ await this.updateBalancesByChainId({ chainId });
205
145
  }
206
- // ───────── public API ─────────
146
+ /**
147
+ * Updates the token balances for the given chain ids.
148
+ * @param input - The input for the update.
149
+ * @param input.chainIds - The chain ids to update token balances for.
150
+ * Or omitted to update all chains that contain tokens.
151
+ */
207
152
  async updateBalances({ chainIds } = {}) {
208
- const targetChains = chainIds ?? __classPrivateFieldGet(this, _TokenBalancesController_instances, "a", _TokenBalancesController_chainIdsWithTokens_get);
209
- if (!targetChains.length) {
153
+ chainIds ?? (chainIds = __classPrivateFieldGet(this, _TokenBalancesController_getChainIds, "f").call(this, __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f"), __classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")));
154
+ await Promise.allSettled(chainIds.map((chainId) => this.updateBalancesByChainId({ chainId })));
155
+ }
156
+ /**
157
+ * Returns ERC-20 balances for a single account on a single chain.
158
+ *
159
+ * @param params - The parameters for the balance fetch.
160
+ * @param params.chainId - The chain id to fetch balances on.
161
+ * @param params.accountAddress - The account address to fetch balances for.
162
+ * @param params.tokenAddresses - The token addresses to fetch balances for.
163
+ * @returns A mapping from token address to balance (hex) | null.
164
+ */
165
+ async getErc20Balances({ chainId, accountAddress, tokenAddresses, }) {
166
+ // Return early if no token addresses provided
167
+ if (tokenAddresses.length === 0) {
168
+ return {};
169
+ }
170
+ const pairs = tokenAddresses.map((tokenAddress) => ({
171
+ accountAddress,
172
+ tokenAddress,
173
+ }));
174
+ const results = await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_batchBalanceOf).call(this, { chainId, pairs });
175
+ const balances = {};
176
+ tokenAddresses.forEach((tokenAddress, i) => {
177
+ balances[tokenAddress] = results[i]?.success
178
+ ? (0, controller_utils_1.toHex)(results[i].value)
179
+ : null;
180
+ });
181
+ return balances;
182
+ }
183
+ /**
184
+ * Updates token balances for the given chain id.
185
+ * @param input - The input for the update.
186
+ * @param input.chainId - The chain id to update token balances on.
187
+ */
188
+ async updateBalancesByChainId({ chainId }) {
189
+ const { address: selectedAccountAddress } = this.messagingSystem.call('AccountsController:getSelectedAccount');
190
+ const isSelectedAccount = (accountAddress) => (0, controller_utils_1.toChecksumHexAddress)(accountAddress) ===
191
+ (0, controller_utils_1.toChecksumHexAddress)(selectedAccountAddress);
192
+ const accountTokenPairs = [];
193
+ const addTokens = ([accountAddress, tokens]) => __classPrivateFieldGet(this, _TokenBalancesController_queryMultipleAccounts, "f") || isSelectedAccount(accountAddress)
194
+ ? tokens.forEach((t) => accountTokenPairs.push({
195
+ accountAddress: accountAddress,
196
+ tokenAddress: t.address,
197
+ }))
198
+ : undefined;
199
+ // Balances will be updated for both imported and detected tokens
200
+ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId] ?? {}).forEach(addTokens);
201
+ Object.entries(__classPrivateFieldGet(this, _TokenBalancesController_allDetectedTokens, "f")[chainId] ?? {}).forEach(addTokens);
202
+ let results = [];
203
+ const currentTokenBalances = this.messagingSystem.call('TokenBalancesController:getState');
204
+ if (accountTokenPairs.length > 0) {
205
+ results = await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_batchBalanceOf).call(this, {
206
+ chainId,
207
+ pairs: accountTokenPairs,
208
+ });
209
+ }
210
+ const updatedResults = results.map((res, i) => {
211
+ const { value } = res;
212
+ const { accountAddress, tokenAddress } = accountTokenPairs[i];
213
+ const currentTokenBalanceValueForAccount = currentTokenBalances.tokenBalances?.[accountAddress]?.[chainId]?.[tokenAddress];
214
+ // `value` can be null or undefined if the multicall failed due to RPC issue.
215
+ // Please see packages/assets-controllers/src/multicall.ts#L365.
216
+ // Hence we should not update the balance in that case.
217
+ const isTokenBalanceValueChanged = res.success && value !== undefined && value !== null
218
+ ? currentTokenBalanceValueForAccount !== (0, controller_utils_1.toHex)(value)
219
+ : false;
220
+ return {
221
+ ...res,
222
+ isTokenBalanceValueChanged,
223
+ };
224
+ });
225
+ // if all values of isTokenBalanceValueChanged are false, return
226
+ if (updatedResults.every((result) => !result.isTokenBalanceValueChanged)) {
210
227
  return;
211
228
  }
212
- const { address: selected } = this.messagingSystem.call('AccountsController:getSelectedAccount');
213
- const allAccounts = this.messagingSystem.call('AccountsController:listAccounts');
214
- const aggregated = [];
215
- let remainingChains = [...targetChains];
216
- // Try each fetcher in order, removing successfully processed chains
217
- for (const fetcher of __classPrivateFieldGet(this, _TokenBalancesController_balanceFetchers, "f")) {
218
- const supportedChains = remainingChains.filter((c) => fetcher.supports(c));
219
- if (!supportedChains.length) {
220
- continue;
221
- }
222
- try {
223
- const balances = await fetcher.fetch({
224
- chainIds: supportedChains,
225
- queryAllAccounts: __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"),
226
- selectedAccount: selected,
227
- allAccounts,
228
- });
229
- if (balances.length > 0) {
230
- aggregated.push(...balances);
231
- // Remove chains that were successfully processed
232
- const processedChains = new Set(balances.map((b) => b.chainId));
233
- remainingChains = remainingChains.filter((chain) => !processedChains.has(chain));
229
+ this.update((state) => {
230
+ var _a, _b;
231
+ for (let i = 0; i < updatedResults.length; i++) {
232
+ const { success, value, isTokenBalanceValueChanged } = updatedResults[i];
233
+ const { accountAddress, tokenAddress } = accountTokenPairs[i];
234
+ if (success && isTokenBalanceValueChanged) {
235
+ ((_b = ((_a = state.tokenBalances)[accountAddress] ?? (_a[accountAddress] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = (0, controller_utils_1.toHex)(value);
234
236
  }
235
237
  }
236
- catch (error) {
237
- __classPrivateFieldGet(this, _TokenBalancesController_log, "f").call(this, `Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
238
- // Continue to next fetcher (fallback)
238
+ });
239
+ }
240
+ /**
241
+ * Reset the controller state to the default state.
242
+ */
243
+ resetState() {
244
+ this.update(() => {
245
+ return getDefaultTokenBalancesState();
246
+ });
247
+ }
248
+ }
249
+ exports.TokenBalancesController = TokenBalancesController;
250
+ _TokenBalancesController_queryMultipleAccounts = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_allDetectedTokens = new WeakMap(), _TokenBalancesController_calculateQueryMultipleAccounts = new WeakMap(), _TokenBalancesController_onPreferencesStateChange = new WeakMap(), _TokenBalancesController_onTokensStateChange = new WeakMap(), _TokenBalancesController_getChainIds = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_onNetworkStateChange = function _TokenBalancesController_onNetworkStateChange(_, patches) {
251
+ // Remove state for deleted networks
252
+ for (const patch of patches) {
253
+ if (patch.op === 'remove' &&
254
+ patch.path[0] === 'networkConfigurationsByChainId') {
255
+ const removedChainId = patch.path[1];
256
+ this.update((state) => {
257
+ for (const accountAddress of Object.keys(state.tokenBalances)) {
258
+ delete state.tokenBalances[accountAddress][removedChainId];
259
+ }
260
+ });
261
+ }
262
+ }
263
+ }, _TokenBalancesController_handleOnAccountRemoved = function _TokenBalancesController_handleOnAccountRemoved(accountAddress) {
264
+ const isEthAddress = (0, utils_1.isStrictHexString)(accountAddress.toLowerCase()) &&
265
+ (0, controller_utils_1.isValidHexAddress)(accountAddress);
266
+ if (!isEthAddress) {
267
+ return;
268
+ }
269
+ this.update((state) => {
270
+ delete state.tokenBalances[accountAddress];
271
+ });
272
+ }, _TokenBalancesController_handleTokensControllerStateChange = async function _TokenBalancesController_handleTokensControllerStateChange({ chainIds, } = {}) {
273
+ const currentTokenBalancesState = this.messagingSystem.call('TokenBalancesController:getState');
274
+ const currentTokenBalances = currentTokenBalancesState.tokenBalances;
275
+ const currentAllTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f");
276
+ const chainIdsSet = new Set(chainIds);
277
+ // first we check if the state change was due to a token being removed
278
+ for (const currentAccount of Object.keys(currentTokenBalances)) {
279
+ const allChains = currentTokenBalances[currentAccount];
280
+ for (const currentChain of Object.keys(allChains)) {
281
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
282
+ continue;
239
283
  }
240
- // If all chains have been processed, break early
241
- if (remainingChains.length === 0) {
242
- break;
284
+ const tokensObject = allChains[currentChain];
285
+ const allCurrentTokens = Object.keys(tokensObject);
286
+ const existingTokensInState = currentAllTokens[currentChain]?.[currentAccount] || [];
287
+ const existingSet = new Set(existingTokensInState.map((elm) => elm.address));
288
+ for (const singleToken of allCurrentTokens) {
289
+ if (!existingSet.has(singleToken)) {
290
+ this.update((state) => {
291
+ delete state.tokenBalances[currentAccount][currentChain][singleToken];
292
+ });
293
+ }
243
294
  }
244
295
  }
245
- // Determine which accounts to process
246
- const accountsToProcess = __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f")
247
- ? allAccounts.map((a) => a.address)
248
- : [selected];
249
- const prev = this.state;
250
- const next = draft(prev, (d) => {
251
- // First, initialize all tokens from allTokens state with balance 0
252
- // for the accounts and chains we're processing
253
- for (const chainId of targetChains) {
254
- for (const account of accountsToProcess) {
255
- // Initialize tokens from allTokens
256
- const chainTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId];
257
- if (chainTokens?.[account]) {
258
- Object.values(chainTokens[account]).forEach((token) => {
259
- var _a, _b;
260
- const tokenAddress = token.address;
261
- ((_b = ((_a = d.tokenBalances)[account] ?? (_a[account] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = '0x0';
262
- });
263
- }
264
- // Initialize tokens from allDetectedTokens
265
- const detectedChainTokens = __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[chainId];
266
- if (detectedChainTokens?.[account]) {
267
- Object.values(detectedChainTokens[account]).forEach((token) => {
268
- var _a, _b;
269
- const tokenAddress = token.address;
270
- ((_b = ((_a = d.tokenBalances)[account] ?? (_a[account] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = '0x0';
271
- });
272
- }
296
+ }
297
+ // then we check if the state change was due to a token being added
298
+ let shouldUpdate = false;
299
+ for (const currentChain of Object.keys(currentAllTokens)) {
300
+ if (chainIds?.length && !chainIdsSet.has(currentChain)) {
301
+ continue;
302
+ }
303
+ const accountsPerChain = currentAllTokens[currentChain];
304
+ for (const currentAccount of Object.keys(accountsPerChain)) {
305
+ const tokensList = accountsPerChain[currentAccount];
306
+ const tokenBalancesObject = currentTokenBalances[currentAccount]?.[currentChain] || {};
307
+ for (const singleToken of tokensList) {
308
+ if (!tokenBalancesObject?.[singleToken.address]) {
309
+ shouldUpdate = true;
310
+ break;
273
311
  }
274
312
  }
275
- // Then update with actual fetched balances where available
276
- aggregated.forEach(({ success, value, account, token, chainId }) => {
277
- var _a, _b;
278
- if (success && value !== undefined) {
279
- ((_b = ((_a = d.tokenBalances)[account] ?? (_a[account] = {})))[chainId] ?? (_b[chainId] = {}))[token] =
280
- (0, controller_utils_1.toHex)(value);
281
- }
282
- });
283
- });
284
- if (!(0, isEqual_1.default)(prev, next)) {
285
- this.update(() => next);
286
- aggregated.forEach((r) => {
287
- if (r.success && r.token === ZERO_ADDRESS) {
288
- this.messagingSystem.call('AccountTrackerController:updateNativeToken', r.account, r.chainId, r.value?.toString() ?? '0');
289
- }
290
- });
291
- }
292
- else {
293
- __classPrivateFieldGet(this, _TokenBalancesController_log, "f").call(this, 'No balance changes detected');
294
313
  }
295
314
  }
296
- resetState() {
297
- this.update(() => ({ tokenBalances: {} }));
315
+ if (shouldUpdate) {
316
+ await this.updateBalances({ chainIds }).catch(console.error);
298
317
  }
299
- }
300
- exports.TokenBalancesController = TokenBalancesController;
301
- _TokenBalancesController_log = new WeakMap(), _TokenBalancesController_queryAllAccounts = new WeakMap(), _TokenBalancesController_balanceFetchers = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_detectedTokens = new WeakMap(), _TokenBalancesController_getProvider = new WeakMap(), _TokenBalancesController_getNetworkClient = new WeakMap(), _TokenBalancesController_onTokensChanged = new WeakMap(), _TokenBalancesController_onNetworkChanged = new WeakMap(), _TokenBalancesController_onAccountRemoved = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_chainIdsWithTokens_get = function _TokenBalancesController_chainIdsWithTokens_get() {
302
- return [
303
- ...new Set([
304
- ...Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")),
305
- ...Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")),
306
- ]),
307
- ];
318
+ }, _TokenBalancesController_getProvider = function _TokenBalancesController_getProvider(chainId) {
319
+ return new providers_1.Web3Provider(__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId).provider);
320
+ }, _TokenBalancesController_ensureFreshBlockData =
321
+ /**
322
+ * Ensures that the block tracker has the latest block data before performing multicall operations.
323
+ * This is a temporary fix to ensure that the block number is up to date.
324
+ *
325
+ * @param chainId - The chain id to update block data for.
326
+ */
327
+ async function _TokenBalancesController_ensureFreshBlockData(chainId) {
328
+ // Force fresh block data before multicall
329
+ // TODO: This is a temporary fix to ensure that the block number is up to date.
330
+ // We should remove this once we have a better solution for this on the block tracker controller.
331
+ const networkClient = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getNetworkClient).call(this, chainId);
332
+ await networkClient.blockTracker?.checkForLatestBlock?.();
333
+ }, _TokenBalancesController_batchBalanceOf =
334
+ /**
335
+ * Internal util: run `balanceOf` for an arbitrary set of account/token pairs.
336
+ *
337
+ * @param params - The parameters for the balance fetch.
338
+ * @param params.chainId - The chain id to fetch balances on.
339
+ * @param params.pairs - The account/token pairs to fetch balances for.
340
+ * @returns The balances for the given token addresses.
341
+ */
342
+ async function _TokenBalancesController_batchBalanceOf({ chainId, pairs, }) {
343
+ if (!pairs.length) {
344
+ return [];
345
+ }
346
+ const provider = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getProvider).call(this, chainId);
347
+ const calls = pairs.map(({ accountAddress, tokenAddress }) => ({
348
+ contract: new contracts_1.Contract(tokenAddress, metamask_eth_abis_1.abiERC20, provider),
349
+ functionSignature: 'balanceOf(address)',
350
+ arguments: [accountAddress],
351
+ }));
352
+ await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_ensureFreshBlockData).call(this, chainId);
353
+ return (0, multicall_1.multicallOrFallback)(calls, chainId, provider);
354
+ }, _TokenBalancesController_getNetworkClient = function _TokenBalancesController_getNetworkClient(chainId) {
355
+ const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
356
+ const networkConfiguration = networkConfigurationsByChainId[chainId];
357
+ if (!networkConfiguration) {
358
+ throw new Error(`TokenBalancesController: No network configuration found for chainId ${chainId}`);
359
+ }
360
+ const { networkClientId } = networkConfiguration.rpcEndpoints[networkConfiguration.defaultRpcEndpointIndex];
361
+ return this.messagingSystem.call(`NetworkController:getNetworkClientById`, networkClientId);
308
362
  };
309
363
  exports.default = TokenBalancesController;
310
364
  //# sourceMappingURL=TokenBalancesController.cjs.map