@metamask/assets-controllers 74.0.0 → 74.1.1
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 +50 -1
- package/dist/AccountTrackerController.cjs +222 -84
- package/dist/AccountTrackerController.cjs.map +1 -1
- package/dist/AccountTrackerController.d.cts +7 -1
- package/dist/AccountTrackerController.d.cts.map +1 -1
- package/dist/AccountTrackerController.d.mts +7 -1
- package/dist/AccountTrackerController.d.mts.map +1 -1
- package/dist/AccountTrackerController.mjs +224 -85
- package/dist/AccountTrackerController.mjs.map +1 -1
- package/dist/TokenBalancesController.cjs +17 -14
- package/dist/TokenBalancesController.cjs.map +1 -1
- package/dist/TokenBalancesController.d.cts.map +1 -1
- package/dist/TokenBalancesController.d.mts.map +1 -1
- package/dist/TokenBalancesController.mjs +18 -15
- package/dist/TokenBalancesController.mjs.map +1 -1
- package/dist/rpc-service/rpc-balance-fetcher.cjs +31 -9
- package/dist/rpc-service/rpc-balance-fetcher.cjs.map +1 -1
- package/dist/rpc-service/rpc-balance-fetcher.d.cts.map +1 -1
- package/dist/rpc-service/rpc-balance-fetcher.d.mts.map +1 -1
- package/dist/rpc-service/rpc-balance-fetcher.mjs +32 -10
- package/dist/rpc-service/rpc-balance-fetcher.mjs.map +1 -1
- package/dist/selectors/token-selectors.cjs +24 -9
- package/dist/selectors/token-selectors.cjs.map +1 -1
- package/dist/selectors/token-selectors.d.cts +96 -20
- package/dist/selectors/token-selectors.d.cts.map +1 -1
- package/dist/selectors/token-selectors.d.mts +96 -20
- package/dist/selectors/token-selectors.d.mts.map +1 -1
- package/dist/selectors/token-selectors.mjs +24 -9
- package/dist/selectors/token-selectors.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [74.1.1]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Improve balance fetching performance and resilience by parallelizing multi-chain operations and moving timeout handling to fetchers ([#6390](https://github.com/MetaMask/core/pull/6390))
|
|
15
|
+
|
|
16
|
+
- Replace sequential `for` loops with `Promise.allSettled` in `RpcBalanceFetcher` and `AccountTrackerController` for parallel chain processing
|
|
17
|
+
- Move timeout handling from controller-level `Promise.race` to fetcher-level `safelyExecuteWithTimeout` for better error isolation
|
|
18
|
+
- Add `safelyExecuteWithTimeout` to both `RpcBalanceFetcher` and `AccountsApiBalanceFetcher` to prevent individual chain timeouts from blocking other chains
|
|
19
|
+
- Remove redundant timeout wrappers from `TokenBalancesController` and `AccountTrackerController`
|
|
20
|
+
- Improve test coverage for timeout and error handling scenarios in all balance fetchers
|
|
21
|
+
|
|
22
|
+
## [74.1.0]
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Enable `AccountTrackerController` to fetch native balances using AccountsAPI when `allowExternalServices` is enabled ([#6369](https://github.com/MetaMask/core/pull/6369))
|
|
27
|
+
|
|
28
|
+
- Implement native balance fetching via AccountsAPI when `useAccountsAPI` and `allowExternalServices` are both true
|
|
29
|
+
- Add fallback to RPC balance fetching when external services are disabled
|
|
30
|
+
- Add comprehensive test coverage for both AccountsAPI and RPC balance fetching scenarios
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Bump `@metamask/base-controller` from `^8.1.0` to `^8.2.0` ([#6355](https://github.com/MetaMask/core/pull/6355))
|
|
35
|
+
|
|
36
|
+
- Add new `accountId` field to the `Asset` type ([#6358](https://github.com/MetaMask/core/pull/6358))
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Uses `InternalAccount['type']` for the `Asset['type']` property ([#6358](https://github.com/MetaMask/core/pull/6358))
|
|
41
|
+
|
|
42
|
+
- 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))
|
|
43
|
+
|
|
44
|
+
- Prevents mutation of memoized fields used inside selectors ([#6358](https://github.com/MetaMask/core/pull/6358))
|
|
45
|
+
|
|
46
|
+
- Fix duplicate token balance entries caused by case-sensitive address comparison in `TokenBalancesController.updateBalances` ([#6354](https://github.com/MetaMask/core/pull/6354))
|
|
47
|
+
|
|
48
|
+
- 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
|
|
49
|
+
- Add comprehensive unit tests for token address normalization scenarios
|
|
50
|
+
|
|
51
|
+
- Fix TokenBalancesController timeout handling by replacing `safelyExecuteWithTimeout` with proper `Promise.race` implementation ([#6365](https://github.com/MetaMask/core/pull/6365))
|
|
52
|
+
|
|
53
|
+
- Replace `safelyExecuteWithTimeout` which was silently swallowing timeout errors with direct `Promise.race` that properly throws
|
|
54
|
+
- Reduce RPC timeout from 3 minutes to 15 seconds for better responsiveness and batch size
|
|
55
|
+
- Enable proper fallback between API and RPC balance fetchers when timeouts occur
|
|
56
|
+
|
|
10
57
|
## [74.0.0]
|
|
11
58
|
|
|
12
59
|
### Added
|
|
@@ -1879,7 +1926,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1879
1926
|
|
|
1880
1927
|
- Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845))
|
|
1881
1928
|
|
|
1882
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.
|
|
1929
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.1.1...HEAD
|
|
1930
|
+
[74.1.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.1.0...@metamask/assets-controllers@74.1.1
|
|
1931
|
+
[74.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@74.0.0...@metamask/assets-controllers@74.1.0
|
|
1883
1932
|
[74.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@73.3.0...@metamask/assets-controllers@74.0.0
|
|
1884
1933
|
[73.3.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@73.2.0...@metamask/assets-controllers@73.3.0
|
|
1885
1934
|
[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,
|
|
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,149 @@ 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 ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
37
|
+
/**
|
|
38
|
+
* RPC-based balance fetcher for AccountTrackerController.
|
|
39
|
+
* Fetches only native balances and staked balances (no token balances).
|
|
40
|
+
*/
|
|
41
|
+
class AccountTrackerRpcBalanceFetcher {
|
|
42
|
+
constructor(getProvider, getNetworkClient, includeStakedAssets, getStakedBalanceForChain) {
|
|
43
|
+
_AccountTrackerRpcBalanceFetcher_instances.add(this);
|
|
44
|
+
_AccountTrackerRpcBalanceFetcher_getProvider.set(this, void 0);
|
|
45
|
+
_AccountTrackerRpcBalanceFetcher_getNetworkClient.set(this, void 0);
|
|
46
|
+
_AccountTrackerRpcBalanceFetcher_includeStakedAssets.set(this, void 0);
|
|
47
|
+
_AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain.set(this, void 0);
|
|
48
|
+
__classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getProvider, getProvider, "f");
|
|
49
|
+
__classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getNetworkClient, getNetworkClient, "f");
|
|
50
|
+
__classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_includeStakedAssets, includeStakedAssets, "f");
|
|
51
|
+
__classPrivateFieldSet(this, _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain, getStakedBalanceForChain, "f");
|
|
52
|
+
}
|
|
53
|
+
supports() {
|
|
54
|
+
return true; // fallback – supports every chain
|
|
55
|
+
}
|
|
56
|
+
async fetch({ chainIds, queryAllAccounts, selectedAccount, allAccounts, }) {
|
|
57
|
+
// Process all chains in parallel for better performance
|
|
58
|
+
const chainProcessingPromises = chainIds.map(async (chainId) => {
|
|
59
|
+
const accountsToUpdate = queryAllAccounts
|
|
60
|
+
? Object.values(allAccounts).map((account) => (0, controller_utils_1.toChecksumHexAddress)(account.address))
|
|
61
|
+
: [selectedAccount];
|
|
62
|
+
const { provider, blockTracker } = __classPrivateFieldGet(this, _AccountTrackerRpcBalanceFetcher_getNetworkClient, "f").call(this, chainId);
|
|
63
|
+
const ethQuery = new eth_query_1.default(provider);
|
|
64
|
+
const chainResults = [];
|
|
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
|
+
chainResults.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
|
+
chainResults.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
|
+
chainResults.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
|
+
chainResults.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
|
+
return chainResults;
|
|
139
|
+
});
|
|
140
|
+
// Wait for all chains to complete (or fail) and collect results
|
|
141
|
+
const chainResultsArray = await Promise.allSettled(chainProcessingPromises);
|
|
142
|
+
const results = [];
|
|
143
|
+
chainResultsArray.forEach((chainResult) => {
|
|
144
|
+
if (chainResult.status === 'fulfilled') {
|
|
145
|
+
results.push(...chainResult.value);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Log error but continue with other chains
|
|
149
|
+
console.warn('Chain processing failed:', chainResult.reason);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_AccountTrackerRpcBalanceFetcher_getProvider = new WeakMap(), _AccountTrackerRpcBalanceFetcher_getNetworkClient = new WeakMap(), _AccountTrackerRpcBalanceFetcher_includeStakedAssets = new WeakMap(), _AccountTrackerRpcBalanceFetcher_getStakedBalanceForChain = new WeakMap(), _AccountTrackerRpcBalanceFetcher_instances = new WeakSet(), _AccountTrackerRpcBalanceFetcher_getBalanceFromChain =
|
|
156
|
+
/**
|
|
157
|
+
* Fetches the balance of a given address from the blockchain.
|
|
158
|
+
*
|
|
159
|
+
* @param address - The account address to fetch the balance for.
|
|
160
|
+
* @param ethQuery - The EthQuery instance to query getBalance with.
|
|
161
|
+
* @returns A promise that resolves to the balance in a hex string format.
|
|
162
|
+
*/
|
|
163
|
+
async function _AccountTrackerRpcBalanceFetcher_getBalanceFromChain(address, ethQuery) {
|
|
164
|
+
return await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => {
|
|
165
|
+
(0, utils_1.assert)(ethQuery, 'Provider not set.');
|
|
166
|
+
return await (0, controller_utils_1.query)(ethQuery, 'getBalance', [address]);
|
|
167
|
+
});
|
|
168
|
+
};
|
|
34
169
|
const accountTrackerMetadata = {
|
|
35
170
|
accountsByChainId: {
|
|
36
171
|
persist: true,
|
|
@@ -50,8 +185,10 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
|
|
|
50
185
|
* @param options.messenger - The controller messaging system.
|
|
51
186
|
* @param options.getStakedBalanceForChain - The function to get the staked native asset balance for a chain.
|
|
52
187
|
* @param options.includeStakedAssets - Whether to include staked assets in the account balances.
|
|
188
|
+
* @param options.useAccountsAPI - Enable Accounts‑API strategy (if supported chain).
|
|
189
|
+
* @param options.allowExternalServices - Disable external HTTP calls (privacy / offline mode).
|
|
53
190
|
*/
|
|
54
|
-
constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, }) {
|
|
191
|
+
constructor({ interval = 10000, state, messenger, getStakedBalanceForChain, includeStakedAssets = false, useAccountsAPI = false, allowExternalServices = () => true, }) {
|
|
55
192
|
const { selectedNetworkClientId } = messenger.call('NetworkController:getState');
|
|
56
193
|
const { configuration: { chainId }, } = messenger.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
|
|
57
194
|
super({
|
|
@@ -69,8 +206,29 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
|
|
|
69
206
|
_AccountTrackerController_refreshMutex.set(this, new async_mutex_1.Mutex());
|
|
70
207
|
_AccountTrackerController_includeStakedAssets.set(this, void 0);
|
|
71
208
|
_AccountTrackerController_getStakedBalanceForChain.set(this, void 0);
|
|
209
|
+
_AccountTrackerController_balanceFetchers.set(this, void 0);
|
|
210
|
+
_AccountTrackerController_getProvider.set(this, (chainId) => {
|
|
211
|
+
const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
|
|
212
|
+
const cfg = networkConfigurationsByChainId[chainId];
|
|
213
|
+
const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
|
|
214
|
+
const client = this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
|
|
215
|
+
return new providers_1.Web3Provider(client.provider);
|
|
216
|
+
});
|
|
217
|
+
_AccountTrackerController_getNetworkClient.set(this, (chainId) => {
|
|
218
|
+
const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
|
|
219
|
+
const cfg = networkConfigurationsByChainId[chainId];
|
|
220
|
+
const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
|
|
221
|
+
return this.messagingSystem.call('NetworkController:getNetworkClientById', networkClientId);
|
|
222
|
+
});
|
|
72
223
|
__classPrivateFieldSet(this, _AccountTrackerController_getStakedBalanceForChain, getStakedBalanceForChain, "f");
|
|
73
224
|
__classPrivateFieldSet(this, _AccountTrackerController_includeStakedAssets, includeStakedAssets, "f");
|
|
225
|
+
// Initialize balance fetchers - Strategy order: API first, then RPC fallback
|
|
226
|
+
__classPrivateFieldSet(this, _AccountTrackerController_balanceFetchers, [
|
|
227
|
+
...(useAccountsAPI && allowExternalServices()
|
|
228
|
+
? [new api_balance_fetcher_1.AccountsApiBalanceFetcher('extension', __classPrivateFieldGet(this, _AccountTrackerController_getProvider, "f"))]
|
|
229
|
+
: []),
|
|
230
|
+
new AccountTrackerRpcBalanceFetcher(__classPrivateFieldGet(this, _AccountTrackerController_getProvider, "f"), __classPrivateFieldGet(this, _AccountTrackerController_getNetworkClient, "f"), includeStakedAssets, getStakedBalanceForChain),
|
|
231
|
+
], "f");
|
|
74
232
|
this.setIntervalLength(interval);
|
|
75
233
|
this.messagingSystem.subscribe('AccountsController:selectedEvmAccountChange', (newAddress, prevAddress) => {
|
|
76
234
|
if (newAddress !== prevAddress) {
|
|
@@ -140,6 +298,8 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
|
|
|
140
298
|
*/
|
|
141
299
|
async refresh(networkClientIds) {
|
|
142
300
|
const selectedAccount = this.messagingSystem.call('AccountsController:getSelectedAccount');
|
|
301
|
+
const allAccounts = this.messagingSystem.call('AccountsController:listAccounts');
|
|
302
|
+
const { isMultiAccountBalancesEnabled } = this.messagingSystem.call('PreferencesController:getState');
|
|
143
303
|
const releaseLock = await __classPrivateFieldGet(this, _AccountTrackerController_refreshMutex, "f").acquire();
|
|
144
304
|
try {
|
|
145
305
|
const chainIds = networkClientIds.map((networkClientId) => {
|
|
@@ -147,83 +307,74 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
|
|
|
147
307
|
return chainId;
|
|
148
308
|
});
|
|
149
309
|
this.syncAccounts(chainIds);
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
});
|
|
178
|
-
}
|
|
310
|
+
// Use balance fetchers with fallback strategy
|
|
311
|
+
const aggregated = [];
|
|
312
|
+
let remainingChains = [...chainIds];
|
|
313
|
+
// Try each fetcher in order, removing successfully processed chains
|
|
314
|
+
for (const fetcher of __classPrivateFieldGet(this, _AccountTrackerController_balanceFetchers, "f")) {
|
|
315
|
+
const supportedChains = remainingChains.filter((c) => fetcher.supports(c));
|
|
316
|
+
if (!supportedChains.length) {
|
|
317
|
+
continue;
|
|
179
318
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
},
|
|
319
|
+
try {
|
|
320
|
+
const balances = await fetcher.fetch({
|
|
321
|
+
chainIds: supportedChains,
|
|
322
|
+
queryAllAccounts: isMultiAccountBalancesEnabled,
|
|
323
|
+
selectedAccount: (0, controller_utils_1.toChecksumHexAddress)(selectedAccount.address),
|
|
324
|
+
allAccounts,
|
|
199
325
|
});
|
|
326
|
+
if (balances && balances.length > 0) {
|
|
327
|
+
aggregated.push(...balances);
|
|
328
|
+
// Remove chains that were successfully processed
|
|
329
|
+
const processedChains = new Set(balances.map((b) => b.chainId));
|
|
330
|
+
remainingChains = remainingChains.filter((chain) => !processedChains.has(chain));
|
|
331
|
+
}
|
|
200
332
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
});
|
|
211
|
-
// Wait for all networkClientId updates to settle in parallel
|
|
212
|
-
const allResults = await Promise.allSettled(updatePromises);
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
|
|
335
|
+
// Continue to next fetcher (fallback)
|
|
336
|
+
}
|
|
337
|
+
// If all chains have been processed, break early
|
|
338
|
+
if (remainingChains.length === 0) {
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
213
342
|
// Build a _copy_ of the current state and track whether anything changed
|
|
214
343
|
const nextAccountsByChainId = (0, lodash_1.cloneDeep)(this.state.accountsByChainId);
|
|
215
344
|
let hasChanges = false;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
345
|
+
// Process the aggregated balance results
|
|
346
|
+
const stakedBalancesByChainAndAddress = {};
|
|
347
|
+
aggregated.forEach(({ success, value, account, token, chainId }) => {
|
|
348
|
+
if (success && value !== undefined) {
|
|
349
|
+
const hexValue = `0x${value.toString(16)}`;
|
|
350
|
+
if (token === ZERO_ADDRESS) {
|
|
351
|
+
// Native balance
|
|
352
|
+
if (nextAccountsByChainId[chainId][account].balance !== hexValue) {
|
|
353
|
+
nextAccountsByChainId[chainId][account].balance = hexValue;
|
|
354
|
+
hasChanges = true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Staked balance (from staking contract address)
|
|
359
|
+
if (!stakedBalancesByChainAndAddress[chainId]) {
|
|
360
|
+
stakedBalancesByChainAndAddress[chainId] = {};
|
|
361
|
+
}
|
|
362
|
+
stakedBalancesByChainAndAddress[chainId][account] = hexValue;
|
|
223
363
|
}
|
|
224
364
|
}
|
|
225
365
|
});
|
|
226
|
-
//
|
|
366
|
+
// Apply staked balances
|
|
367
|
+
Object.entries(stakedBalancesByChainAndAddress).forEach(([chainId, balancesByAddress]) => {
|
|
368
|
+
Object.entries(balancesByAddress).forEach(([address, stakedBalance]) => {
|
|
369
|
+
if (nextAccountsByChainId[chainId][address].stakedBalance !==
|
|
370
|
+
stakedBalance) {
|
|
371
|
+
nextAccountsByChainId[chainId][address].stakedBalance =
|
|
372
|
+
stakedBalance;
|
|
373
|
+
hasChanges = true;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
// Only update state if something changed
|
|
227
378
|
if (hasChanges) {
|
|
228
379
|
this.update((state) => {
|
|
229
380
|
state.accountsByChainId = nextAccountsByChainId;
|
|
@@ -318,7 +469,7 @@ class AccountTrackerController extends (0, polling_controller_1.StaticIntervalPo
|
|
|
318
469
|
}
|
|
319
470
|
}
|
|
320
471
|
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) {
|
|
472
|
+
_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
473
|
const selectedNetworkClientId = networkClientId ??
|
|
323
474
|
this.messagingSystem.call('NetworkController:getState')
|
|
324
475
|
.selectedNetworkClientId;
|
|
@@ -332,19 +483,6 @@ _AccountTrackerController_refreshMutex = new WeakMap(), _AccountTrackerControlle
|
|
|
332
483
|
}, _AccountTrackerController_getNetworkClientIds = function _AccountTrackerController_getNetworkClientIds() {
|
|
333
484
|
const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
|
|
334
485
|
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
486
|
}, _AccountTrackerController_registerMessageHandlers = function _AccountTrackerController_registerMessageHandlers() {
|
|
349
487
|
this.messagingSystem.registerActionHandler(`${controllerName}:updateNativeBalances`, this.updateNativeBalances.bind(this));
|
|
350
488
|
this.messagingSystem.registerActionHandler(`${controllerName}:updateStakedBalances`, this.updateStakedBalances.bind(this));
|