@metamask-previews/assets-controllers 79.0.1-preview-e8868d87 → 79.0.1-preview-5f3688c1
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 +21 -2
- package/README.md +1 -1
- package/dist/TokenBalancesController.cjs +229 -8
- package/dist/TokenBalancesController.cjs.map +1 -1
- package/dist/TokenBalancesController.d.cts +27 -3
- package/dist/TokenBalancesController.d.cts.map +1 -1
- package/dist/TokenBalancesController.d.mts +27 -3
- package/dist/TokenBalancesController.d.mts.map +1 -1
- package/dist/TokenBalancesController.mjs +227 -8
- package/dist/TokenBalancesController.mjs.map +1 -1
- package/dist/TokenDetectionController.cjs +73 -44
- package/dist/TokenDetectionController.cjs.map +1 -1
- package/dist/TokenDetectionController.d.cts +37 -9
- package/dist/TokenDetectionController.d.cts.map +1 -1
- package/dist/TokenDetectionController.d.mts +37 -9
- package/dist/TokenDetectionController.d.mts.map +1 -1
- package/dist/TokenDetectionController.mjs +73 -44
- package/dist/TokenDetectionController.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +2 -6
- package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +1 -1
- package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +2 -6
- package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +1 -1
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
###
|
|
10
|
+
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Add real-time balance updates via WebSocket integration with `AccountActivityService` to `TokenBalancesController` ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
13
|
+
- Add `@metamask/core-backend` as a dependency and peer dependency ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
14
|
+
- Controller now subscribes to `AccountActivityService:balanceUpdated` events for instant balance updates
|
|
15
|
+
- Add support for real-time balance updates for both ERC20 tokens and native tokens
|
|
16
|
+
- Add `TokenDetectionController:addDetectedTokensViaWs` action handler for adding tokens detected via WebSocket
|
|
17
|
+
- Controller now subscribes to `AccountActivityService:statusChanged` events to dynamically adjust polling intervals
|
|
18
|
+
- When WebSocket service is "up", polling interval increases to backup interval (5 minutes)
|
|
19
|
+
- When WebSocket service is "down", polling interval restores to default interval (30 seconds)
|
|
20
|
+
- Status changes are debounced (5 seconds) and jittered to prevent thundering herd
|
|
21
|
+
- Add fallback to polling when balance updates contain errors or unsupported asset types
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **BREAKING:** `TokenBalancesController` messenger must now allow `AccountActivityService:balanceUpdated` and `AccountActivityService:statusChanged` events ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
26
|
+
- **BREAKING:** `TokenBalancesController` messenger must now allow `TokenDetectionController:addDetectedTokensViaWs` action ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
27
|
+
- **BREAKING:** Change `TokenBalancesController` default polling interval to 30 seconds (was 180 seconds) ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
28
|
+
- With real-time WebSocket updates, the default interval only applies when WebSocket is disconnected
|
|
29
|
+
- When WebSocket is connected, polling automatically adjusts to 5 minutes as a backup
|
|
30
|
+
- Update `TokenBalancesController` README documentation to mention real-time balance updates via WebSocket and intelligent polling management ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
31
|
+
- `TokenDetectionController` code cleanup: remove unused private properties and ESLint disable comments ([#6784](https://github.com/MetaMask/core/pull/6784))
|
|
13
32
|
|
|
14
33
|
## [79.0.1]
|
|
15
34
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ This package features the following controllers:
|
|
|
21
21
|
- [**CurrencyRateController**](src/CurrencyRateController.ts) keeps a periodically updated value of the exchange rate from the currently selected "native" currency to another (handling testnet tokens specially).
|
|
22
22
|
- [**DeFiPositionsController**](src/DeFiPositionsController/DeFiPositionsController.ts.ts) keeps a periodically updated value of the DeFi positions for the owner EVM addresses.
|
|
23
23
|
- [**RatesController**](src/RatesController/RatesController.ts) keeps a periodically updated value for the exchange rates for different cryptocurrencies. The difference between the `RatesController` and `CurrencyRateController` is that the second one is coupled to the `NetworksController` and is EVM specific, whilst the first one can handle different blockchain currencies like BTC and SOL.
|
|
24
|
-
- [**TokenBalancesController**](src/TokenBalancesController.ts) keeps a periodically updated set of balances for the current set of ERC-20 tokens.
|
|
24
|
+
- [**TokenBalancesController**](src/TokenBalancesController.ts) keeps a periodically updated set of balances for the current set of ERC-20 tokens. It supports real-time balance updates via WebSocket and intelligent polling management. See [Real-Time Balance Updates Flow](./REAL_TIME_BALANCE_UPDATES.md) for details.
|
|
25
25
|
- [**TokenDetectionController**](src/TokenDetectionController.ts) keeps a periodically updated list of ERC-20 tokens assigned to the currently selected address.
|
|
26
26
|
- [**TokenListController**](src/TokenListController.ts) uses the MetaSwap API to keep a periodically updated list of known ERC-20 tokens along with their metadata.
|
|
27
27
|
- [**TokenRatesController**](src/TokenRatesController.ts) keeps a periodically updated list of exchange rates for known ERC-20 tokens relative to the currently selected native currency.
|
|
@@ -10,9 +10,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
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
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_requestedChainIds, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved;
|
|
13
|
+
var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_allIgnoredTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_websocketActivePollingInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_requestedChainIds, _TokenBalancesController_statusChangeDebouncer, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_isTokenTracked, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved, _TokenBalancesController_prepareBalanceUpdates, _TokenBalancesController_onAccountActivityBalanceUpdate, _TokenBalancesController_onAccountActivityStatusChanged, _TokenBalancesController_processAccumulatedStatusChanges;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.TokenBalancesController = void 0;
|
|
15
|
+
exports.TokenBalancesController = exports.parseAssetType = exports.caipChainIdToHex = void 0;
|
|
16
16
|
const providers_1 = require("@ethersproject/providers");
|
|
17
17
|
const controller_utils_1 = require("@metamask/controller-utils");
|
|
18
18
|
const polling_controller_1 = require("@metamask/polling-controller");
|
|
@@ -23,7 +23,8 @@ const AssetsContractController_1 = require("./AssetsContractController.cjs");
|
|
|
23
23
|
const api_balance_fetcher_1 = require("./multi-chain-accounts-service/api-balance-fetcher.cjs");
|
|
24
24
|
const rpc_balance_fetcher_1 = require("./rpc-service/rpc-balance-fetcher.cjs");
|
|
25
25
|
const CONTROLLER = 'TokenBalancesController';
|
|
26
|
-
const DEFAULT_INTERVAL_MS =
|
|
26
|
+
const DEFAULT_INTERVAL_MS = 30000; // 30 seconds
|
|
27
|
+
const DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS = 300000; // 5 minutes
|
|
27
28
|
const metadata = {
|
|
28
29
|
tokenBalances: {
|
|
29
30
|
includeInStateLogs: false,
|
|
@@ -38,11 +39,52 @@ const metadata = {
|
|
|
38
39
|
const draft = (base, fn) => (0, immer_1.produce)(base, fn);
|
|
39
40
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
40
41
|
const checksum = (addr) => (0, controller_utils_1.toChecksumHexAddress)(addr);
|
|
42
|
+
/**
|
|
43
|
+
* Convert CAIP chain ID or hex chain ID to hex chain ID
|
|
44
|
+
* Handles both CAIP-2 format (e.g., "eip155:1") and hex format (e.g., "0x1")
|
|
45
|
+
*
|
|
46
|
+
* @param chainId - CAIP chain ID (e.g., "eip155:1") or hex chain ID (e.g., "0x1")
|
|
47
|
+
* @returns Hex chain ID (e.g., "0x1")
|
|
48
|
+
* @throws {Error} If chainId is neither a valid CAIP-2 chain ID nor a hex string
|
|
49
|
+
*/
|
|
50
|
+
const caipChainIdToHex = (chainId) => {
|
|
51
|
+
if ((0, utils_1.isStrictHexString)(chainId)) {
|
|
52
|
+
return chainId;
|
|
53
|
+
}
|
|
54
|
+
if ((0, utils_1.isCaipChainId)(chainId)) {
|
|
55
|
+
return (0, controller_utils_1.toHex)((0, utils_1.parseCaipChainId)(chainId).reference);
|
|
56
|
+
}
|
|
57
|
+
throw new Error('caipChainIdToHex - Failed to provide CAIP-2 or Hex chainId');
|
|
58
|
+
};
|
|
59
|
+
exports.caipChainIdToHex = caipChainIdToHex;
|
|
60
|
+
/**
|
|
61
|
+
* Extract token address from asset type
|
|
62
|
+
* Returns tuple of [tokenAddress, isNativeToken] or null if invalid
|
|
63
|
+
*
|
|
64
|
+
* @param assetType - Asset type string (e.g., 'eip155:1/erc20:0x...' or 'eip155:1/slip44:60')
|
|
65
|
+
* @returns Tuple of [tokenAddress, isNativeToken] or null if invalid
|
|
66
|
+
*/
|
|
67
|
+
const parseAssetType = (assetType) => {
|
|
68
|
+
if (!(0, utils_1.isCaipAssetType)(assetType)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const parsed = (0, utils_1.parseCaipAssetType)(assetType);
|
|
72
|
+
// ERC20 token (e.g., "eip155:1/erc20:0x...")
|
|
73
|
+
if (parsed.assetNamespace === 'erc20') {
|
|
74
|
+
return [parsed.assetReference, false];
|
|
75
|
+
}
|
|
76
|
+
// Native token (e.g., "eip155:1/slip44:60")
|
|
77
|
+
if (parsed.assetNamespace === 'slip44') {
|
|
78
|
+
return [ZERO_ADDRESS, true];
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
exports.parseAssetType = parseAssetType;
|
|
41
83
|
// endregion
|
|
42
84
|
// ────────────────────────────────────────────────────────────────────────────
|
|
43
85
|
// region: Main controller
|
|
44
86
|
class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPollingController)() {
|
|
45
|
-
constructor({ messenger, interval = DEFAULT_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
|
|
87
|
+
constructor({ messenger, interval = DEFAULT_INTERVAL_MS, websocketActivePollingInterval = DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
|
|
46
88
|
super({
|
|
47
89
|
name: CONTROLLER,
|
|
48
90
|
messenger,
|
|
@@ -56,8 +98,11 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
56
98
|
_TokenBalancesController_balanceFetchers.set(this, void 0);
|
|
57
99
|
_TokenBalancesController_allTokens.set(this, {});
|
|
58
100
|
_TokenBalancesController_detectedTokens.set(this, {});
|
|
101
|
+
_TokenBalancesController_allIgnoredTokens.set(this, {});
|
|
59
102
|
/** Default polling interval for chains without specific configuration */
|
|
60
103
|
_TokenBalancesController_defaultInterval.set(this, void 0);
|
|
104
|
+
/** Polling interval when WebSocket is active and providing real-time updates */
|
|
105
|
+
_TokenBalancesController_websocketActivePollingInterval.set(this, void 0);
|
|
61
106
|
/** Per-chain polling configuration */
|
|
62
107
|
_TokenBalancesController_chainPollingConfig.set(this, void 0);
|
|
63
108
|
/** Active polling timers grouped by interval */
|
|
@@ -66,6 +111,11 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
66
111
|
_TokenBalancesController_isControllerPollingActive.set(this, false);
|
|
67
112
|
/** Store original chainIds from startPolling to preserve intent */
|
|
68
113
|
_TokenBalancesController_requestedChainIds.set(this, []);
|
|
114
|
+
/** Debouncing for rapid status changes to prevent excessive HTTP calls */
|
|
115
|
+
_TokenBalancesController_statusChangeDebouncer.set(this, {
|
|
116
|
+
timer: null,
|
|
117
|
+
pendingChanges: new Map(),
|
|
118
|
+
});
|
|
69
119
|
_TokenBalancesController_getProvider.set(this, (chainId) => {
|
|
70
120
|
const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
|
|
71
121
|
const cfg = networkConfigurationsByChainId[chainId];
|
|
@@ -167,6 +217,7 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
167
217
|
});
|
|
168
218
|
__classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
|
|
169
219
|
__classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
|
|
220
|
+
__classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, state.allIgnoredTokens, "f");
|
|
170
221
|
// Only update balances for chains that still have tokens (and only if we haven't already updated state)
|
|
171
222
|
if (changed.length && !hasChanges) {
|
|
172
223
|
this.updateBalances({ chainIds: changed }).catch((error) => {
|
|
@@ -210,10 +261,85 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
210
261
|
delete s.tokenBalances[addr];
|
|
211
262
|
});
|
|
212
263
|
});
|
|
264
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
265
|
+
// AccountActivityService event handlers
|
|
266
|
+
/**
|
|
267
|
+
* Handle real-time balance updates from AccountActivityService
|
|
268
|
+
* Processes balance updates and updates the token balance state
|
|
269
|
+
* If any balance update has an error, triggers fallback polling for the chain
|
|
270
|
+
*
|
|
271
|
+
* @param options0 - Balance update parameters
|
|
272
|
+
* @param options0.address - Account address
|
|
273
|
+
* @param options0.chain - CAIP chain identifier
|
|
274
|
+
* @param options0.updates - Array of balance updates for the account
|
|
275
|
+
*/
|
|
276
|
+
_TokenBalancesController_onAccountActivityBalanceUpdate.set(this, async ({ address, chain, updates, }) => {
|
|
277
|
+
const chainId = (0, exports.caipChainIdToHex)(chain);
|
|
278
|
+
const account = checksum(address);
|
|
279
|
+
try {
|
|
280
|
+
// Process all balance updates at once
|
|
281
|
+
const { tokenBalances, newTokens, nativeBalanceUpdates } = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_prepareBalanceUpdates).call(this, updates, account, chainId);
|
|
282
|
+
// Update state once with all token balances
|
|
283
|
+
if (tokenBalances.length > 0) {
|
|
284
|
+
this.update((state) => {
|
|
285
|
+
var _a, _b;
|
|
286
|
+
// Initialize account and chain structure
|
|
287
|
+
(_a = state.tokenBalances)[account] ?? (_a[account] = {});
|
|
288
|
+
(_b = state.tokenBalances[account])[chainId] ?? (_b[chainId] = {});
|
|
289
|
+
// Apply all token balance updates
|
|
290
|
+
for (const { tokenAddress, balance } of tokenBalances) {
|
|
291
|
+
state.tokenBalances[account][chainId][tokenAddress] = balance;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
// Update native balances in AccountTrackerController
|
|
296
|
+
if (nativeBalanceUpdates.length > 0) {
|
|
297
|
+
this.messagingSystem.call('AccountTrackerController:updateNativeBalances', nativeBalanceUpdates);
|
|
298
|
+
}
|
|
299
|
+
// Import any new tokens that were discovered (balance already updated from websocket)
|
|
300
|
+
if (newTokens.length > 0) {
|
|
301
|
+
await this.messagingSystem.call('TokenDetectionController:addDetectedTokensViaWs', {
|
|
302
|
+
tokensSlice: newTokens,
|
|
303
|
+
chainId: chainId,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.warn(`Error updating balances from AccountActivityService for chain ${chain}, account ${address}:`, error);
|
|
309
|
+
console.warn('Balance update data:', JSON.stringify(updates, null, 2));
|
|
310
|
+
// On error, trigger fallback polling
|
|
311
|
+
await this.updateBalances({ chainIds: [chainId] }).catch(() => {
|
|
312
|
+
// Silently handle polling errors
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
/**
|
|
317
|
+
* Handle status changes from AccountActivityService
|
|
318
|
+
* Uses aggressive debouncing to prevent excessive HTTP calls from rapid up/down changes
|
|
319
|
+
*
|
|
320
|
+
* @param options0 - Status change event data
|
|
321
|
+
* @param options0.chainIds - Array of chain identifiers
|
|
322
|
+
* @param options0.status - Connection status ('up' for connected, 'down' for disconnected)
|
|
323
|
+
*/
|
|
324
|
+
_TokenBalancesController_onAccountActivityStatusChanged.set(this, ({ chainIds, status, }) => {
|
|
325
|
+
// Update pending changes (latest status wins for each chain)
|
|
326
|
+
for (const chainId of chainIds) {
|
|
327
|
+
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.set(chainId, status);
|
|
328
|
+
}
|
|
329
|
+
// Clear existing timer to extend debounce window
|
|
330
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
|
|
331
|
+
clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
|
|
332
|
+
}
|
|
333
|
+
// Set new timer - only process changes after activity settles
|
|
334
|
+
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = setTimeout(() => {
|
|
335
|
+
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_processAccumulatedStatusChanges).call(this);
|
|
336
|
+
}, 5000); // 5-second debounce window
|
|
337
|
+
});
|
|
213
338
|
__classPrivateFieldSet(this, _TokenBalancesController_platform, platform ?? 'extension', "f");
|
|
214
339
|
__classPrivateFieldSet(this, _TokenBalancesController_queryAllAccounts, queryMultipleAccounts, "f");
|
|
215
340
|
__classPrivateFieldSet(this, _TokenBalancesController_accountsApiChainIds, accountsApiChainIds, "f");
|
|
216
341
|
__classPrivateFieldSet(this, _TokenBalancesController_defaultInterval, interval, "f");
|
|
342
|
+
__classPrivateFieldSet(this, _TokenBalancesController_websocketActivePollingInterval, websocketActivePollingInterval, "f");
|
|
217
343
|
__classPrivateFieldSet(this, _TokenBalancesController_chainPollingConfig, { ...chainPollingIntervals }, "f");
|
|
218
344
|
// Strategy order: API first, then RPC fallback
|
|
219
345
|
__classPrivateFieldSet(this, _TokenBalancesController_balanceFetchers, [
|
|
@@ -227,9 +353,10 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
227
353
|
], "f");
|
|
228
354
|
this.setIntervalLength(interval);
|
|
229
355
|
// initial token state & subscriptions
|
|
230
|
-
const { allTokens, allDetectedTokens } = this.messagingSystem.call('TokensController:getState');
|
|
356
|
+
const { allTokens, allDetectedTokens, allIgnoredTokens } = this.messagingSystem.call('TokensController:getState');
|
|
231
357
|
__classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
|
|
232
358
|
__classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, allDetectedTokens, "f");
|
|
359
|
+
__classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, allIgnoredTokens, "f");
|
|
233
360
|
this.messagingSystem.subscribe('TokensController:stateChange', (tokensState) => {
|
|
234
361
|
__classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
|
|
235
362
|
console.warn('Error handling token state change:', error);
|
|
@@ -240,6 +367,10 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
240
367
|
// Register action handlers for polling interval control
|
|
241
368
|
this.messagingSystem.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
|
|
242
369
|
this.messagingSystem.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
|
|
370
|
+
// Subscribe to AccountActivityService balance updates for real-time updates
|
|
371
|
+
this.messagingSystem.subscribe('AccountActivityService:balanceUpdated', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").bind(this));
|
|
372
|
+
// Subscribe to AccountActivityService status changes for dynamic polling management
|
|
373
|
+
this.messagingSystem.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
|
|
243
374
|
}
|
|
244
375
|
/**
|
|
245
376
|
* Override to support per-chain polling intervals by grouping chains by interval
|
|
@@ -394,14 +525,15 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
394
525
|
}
|
|
395
526
|
// Update with actual fetched balances only if the value has changed
|
|
396
527
|
aggregated.forEach(({ success, value, account, token, chainId }) => {
|
|
397
|
-
var _a, _b
|
|
528
|
+
var _a, _b;
|
|
398
529
|
if (success && value !== undefined) {
|
|
399
530
|
const newBalance = (0, controller_utils_1.toHex)(value);
|
|
400
531
|
const tokenAddress = checksum(token);
|
|
401
532
|
const currentBalance = d.tokenBalances[account]?.[chainId]?.[tokenAddress];
|
|
402
533
|
// Only update if the balance has actually changed
|
|
403
534
|
if (currentBalance !== newBalance) {
|
|
404
|
-
((
|
|
535
|
+
((_b = ((_a = d.tokenBalances)[account] ?? (_a[account] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] =
|
|
536
|
+
newBalance;
|
|
405
537
|
}
|
|
406
538
|
}
|
|
407
539
|
});
|
|
@@ -466,6 +598,11 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
466
598
|
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
|
|
467
599
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
468
600
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
601
|
+
// Clean up debouncing timer
|
|
602
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
|
|
603
|
+
clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
|
|
604
|
+
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
|
|
605
|
+
}
|
|
469
606
|
// Unregister action handlers
|
|
470
607
|
this.messagingSystem.unregisterActionHandler(`TokenBalancesController:updateChainPollingConfigs`);
|
|
471
608
|
this.messagingSystem.unregisterActionHandler(`TokenBalancesController:getChainPollingConfig`);
|
|
@@ -473,7 +610,7 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
473
610
|
}
|
|
474
611
|
}
|
|
475
612
|
exports.TokenBalancesController = TokenBalancesController;
|
|
476
|
-
_TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_queryAllAccounts = new WeakMap(), _TokenBalancesController_accountsApiChainIds = new WeakMap(), _TokenBalancesController_balanceFetchers = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_detectedTokens = new WeakMap(), _TokenBalancesController_defaultInterval = new WeakMap(), _TokenBalancesController_chainPollingConfig = new WeakMap(), _TokenBalancesController_intervalPollingTimers = new WeakMap(), _TokenBalancesController_isControllerPollingActive = new WeakMap(), _TokenBalancesController_requestedChainIds = new WeakMap(), _TokenBalancesController_getProvider = new WeakMap(), _TokenBalancesController_getNetworkClient = new WeakMap(), _TokenBalancesController_createAccountsApiFetcher = new WeakMap(), _TokenBalancesController_onTokensChanged = new WeakMap(), _TokenBalancesController_onNetworkChanged = new WeakMap(), _TokenBalancesController_onAccountRemoved = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_chainIdsWithTokens = function _TokenBalancesController_chainIdsWithTokens() {
|
|
613
|
+
_TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_queryAllAccounts = new WeakMap(), _TokenBalancesController_accountsApiChainIds = new WeakMap(), _TokenBalancesController_balanceFetchers = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_detectedTokens = new WeakMap(), _TokenBalancesController_allIgnoredTokens = new WeakMap(), _TokenBalancesController_defaultInterval = new WeakMap(), _TokenBalancesController_websocketActivePollingInterval = new WeakMap(), _TokenBalancesController_chainPollingConfig = new WeakMap(), _TokenBalancesController_intervalPollingTimers = new WeakMap(), _TokenBalancesController_isControllerPollingActive = new WeakMap(), _TokenBalancesController_requestedChainIds = new WeakMap(), _TokenBalancesController_statusChangeDebouncer = new WeakMap(), _TokenBalancesController_getProvider = new WeakMap(), _TokenBalancesController_getNetworkClient = new WeakMap(), _TokenBalancesController_createAccountsApiFetcher = new WeakMap(), _TokenBalancesController_onTokensChanged = new WeakMap(), _TokenBalancesController_onNetworkChanged = new WeakMap(), _TokenBalancesController_onAccountRemoved = new WeakMap(), _TokenBalancesController_onAccountActivityBalanceUpdate = new WeakMap(), _TokenBalancesController_onAccountActivityStatusChanged = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_chainIdsWithTokens = function _TokenBalancesController_chainIdsWithTokens() {
|
|
477
614
|
return [
|
|
478
615
|
...new Set([
|
|
479
616
|
...Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")),
|
|
@@ -528,6 +665,90 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
528
665
|
});
|
|
529
666
|
}, interval);
|
|
530
667
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").set(interval, timer);
|
|
668
|
+
}, _TokenBalancesController_isTokenTracked = function _TokenBalancesController_isTokenTracked(tokenAddress, account, chainId) {
|
|
669
|
+
const normalizedAddress = tokenAddress.toLowerCase();
|
|
670
|
+
// Check if token exists in allTokens
|
|
671
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token.address.toLowerCase() === normalizedAddress)) {
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
// Check if token exists in allIgnoredTokens
|
|
675
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_allIgnoredTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((addr) => addr.toLowerCase() === normalizedAddress)) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
return false;
|
|
679
|
+
}, _TokenBalancesController_prepareBalanceUpdates = function _TokenBalancesController_prepareBalanceUpdates(updates, account, chainId) {
|
|
680
|
+
const tokenBalances = [];
|
|
681
|
+
const newTokens = [];
|
|
682
|
+
const nativeBalanceUpdates = [];
|
|
683
|
+
for (const update of updates) {
|
|
684
|
+
const { asset, postBalance } = update;
|
|
685
|
+
// Throw if balance update has an error
|
|
686
|
+
if (postBalance.error) {
|
|
687
|
+
throw new Error('Balance update has error');
|
|
688
|
+
}
|
|
689
|
+
// Parse token address from asset type
|
|
690
|
+
const parsed = (0, exports.parseAssetType)(asset.type);
|
|
691
|
+
if (!parsed) {
|
|
692
|
+
throw new Error('Failed to parse asset type');
|
|
693
|
+
}
|
|
694
|
+
const [tokenAddress, isNativeToken] = parsed;
|
|
695
|
+
// Validate token address
|
|
696
|
+
if (!(0, utils_1.isStrictHexString)(tokenAddress) ||
|
|
697
|
+
!(0, controller_utils_1.isValidHexAddress)(tokenAddress)) {
|
|
698
|
+
throw new Error('Invalid token address');
|
|
699
|
+
}
|
|
700
|
+
const checksumTokenAddress = checksum(tokenAddress);
|
|
701
|
+
const isTracked = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_isTokenTracked).call(this, checksumTokenAddress, account, chainId);
|
|
702
|
+
// postBalance.amount is in hex format (raw units)
|
|
703
|
+
const balanceHex = postBalance.amount;
|
|
704
|
+
// Add token balance (tracked tokens, ignored tokens, and native tokens all get balance updates)
|
|
705
|
+
tokenBalances.push({
|
|
706
|
+
tokenAddress: checksumTokenAddress,
|
|
707
|
+
balance: balanceHex,
|
|
708
|
+
});
|
|
709
|
+
// Add native balance update if this is a native token
|
|
710
|
+
if (isNativeToken) {
|
|
711
|
+
nativeBalanceUpdates.push({
|
|
712
|
+
address: account,
|
|
713
|
+
chainId,
|
|
714
|
+
balance: balanceHex,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
// Handle untracked ERC20 tokens - queue for import
|
|
718
|
+
if (!isNativeToken && !isTracked) {
|
|
719
|
+
newTokens.push(checksumTokenAddress);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return { tokenBalances, newTokens, nativeBalanceUpdates };
|
|
723
|
+
}, _TokenBalancesController_processAccumulatedStatusChanges = function _TokenBalancesController_processAccumulatedStatusChanges() {
|
|
724
|
+
const changes = Array.from(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.entries());
|
|
725
|
+
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.clear();
|
|
726
|
+
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
|
|
727
|
+
if (changes.length === 0) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// Calculate final polling configurations
|
|
731
|
+
const chainConfigs = {};
|
|
732
|
+
for (const [chainId, status] of changes) {
|
|
733
|
+
// Convert CAIP format (eip155:1) to hex format (0x1)
|
|
734
|
+
// chainId is always in CAIP format from AccountActivityService
|
|
735
|
+
const hexChainId = (0, exports.caipChainIdToHex)(chainId);
|
|
736
|
+
if (status === 'down') {
|
|
737
|
+
// Chain is down - use default polling since no real-time updates available
|
|
738
|
+
chainConfigs[hexChainId] = { interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f") };
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// Chain is up - use longer intervals since WebSocket provides real-time updates
|
|
742
|
+
chainConfigs[hexChainId] = {
|
|
743
|
+
interval: __classPrivateFieldGet(this, _TokenBalancesController_websocketActivePollingInterval, "f"),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// Add jitter to prevent synchronized requests across instances
|
|
748
|
+
const jitterDelay = Math.random() * __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"); // 0 to default interval
|
|
749
|
+
setTimeout(() => {
|
|
750
|
+
this.updateChainPollingConfigs(chainConfigs, { immediateUpdate: true });
|
|
751
|
+
}, jitterDelay);
|
|
531
752
|
};
|
|
532
753
|
exports.default = TokenBalancesController;
|
|
533
754
|
//# sourceMappingURL=TokenBalancesController.cjs.map
|