@metamask-previews/assets-controllers 93.1.0-preview-c92c27f1 → 93.1.0-preview-d717276a
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 +8 -10
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.cjs +14 -9
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.cjs.map +1 -1
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.cts.map +1 -1
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.mts.map +1 -1
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.mjs +14 -9
- package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.mjs.map +1 -1
- package/dist/NftDetectionController.cjs +10 -6
- package/dist/NftDetectionController.cjs.map +1 -1
- package/dist/NftDetectionController.d.cts +4 -0
- package/dist/NftDetectionController.d.cts.map +1 -1
- package/dist/NftDetectionController.d.mts +4 -0
- package/dist/NftDetectionController.d.mts.map +1 -1
- package/dist/NftDetectionController.mjs +10 -6
- package/dist/NftDetectionController.mjs.map +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.cjs +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.cjs.map +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.mjs +1 -1
- package/dist/Standards/NftStandards/ERC721/ERC721Standard.mjs.map +1 -1
- package/dist/TokenBalancesController.cjs +377 -337
- package/dist/TokenBalancesController.cjs.map +1 -1
- package/dist/TokenBalancesController.d.cts +38 -17
- package/dist/TokenBalancesController.d.cts.map +1 -1
- package/dist/TokenBalancesController.d.mts +38 -17
- package/dist/TokenBalancesController.d.mts.map +1 -1
- package/dist/TokenBalancesController.mjs +377 -337
- package/dist/TokenBalancesController.mjs.map +1 -1
- package/dist/TokenDetectionController.cjs +205 -44
- package/dist/TokenDetectionController.cjs.map +1 -1
- package/dist/TokenDetectionController.d.cts +10 -12
- package/dist/TokenDetectionController.d.cts.map +1 -1
- package/dist/TokenDetectionController.d.mts +10 -12
- package/dist/TokenDetectionController.d.mts.map +1 -1
- package/dist/TokenDetectionController.mjs +206 -45
- package/dist/TokenDetectionController.mjs.map +1 -1
- package/dist/rpc-service/rpc-balance-fetcher.cjs +0 -4
- 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 +0 -4
- package/dist/rpc-service/rpc-balance-fetcher.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
11
11
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
12
12
|
};
|
|
13
|
-
var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_allIgnoredTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_websocketActivePollingInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive,
|
|
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_normalizeAccountAddresses, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_isTokenTracked, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved, _TokenBalancesController_onAccountChanged, _TokenBalancesController_prepareBalanceUpdates, _TokenBalancesController_onAccountActivityBalanceUpdate, _TokenBalancesController_onAccountActivityStatusChanged, _TokenBalancesController_processAccumulatedStatusChanges;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.TokenBalancesController = exports.parseAssetType = exports.caipChainIdToHex = void 0;
|
|
16
16
|
const providers_1 = require("@ethersproject/providers");
|
|
@@ -33,14 +33,19 @@ const metadata = {
|
|
|
33
33
|
usedInUi: true,
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
|
+
// endregion
|
|
37
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// region: Helper utilities
|
|
36
39
|
const draft = (base, fn) => (0, immer_1.produce)(base, fn);
|
|
37
40
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
38
41
|
const checksum = (addr) => (0, controller_utils_1.toChecksumHexAddress)(addr);
|
|
39
42
|
/**
|
|
40
|
-
* Convert CAIP chain ID or hex chain ID to hex chain ID
|
|
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")
|
|
41
45
|
*
|
|
42
|
-
* @param chainId - CAIP chain ID or hex chain ID.
|
|
43
|
-
* @returns Hex chain ID.
|
|
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
|
|
44
49
|
*/
|
|
45
50
|
const caipChainIdToHex = (chainId) => {
|
|
46
51
|
if ((0, utils_1.isStrictHexString)(chainId)) {
|
|
@@ -53,27 +58,31 @@ const caipChainIdToHex = (chainId) => {
|
|
|
53
58
|
};
|
|
54
59
|
exports.caipChainIdToHex = caipChainIdToHex;
|
|
55
60
|
/**
|
|
56
|
-
* Extract token address from asset type
|
|
61
|
+
* Extract token address from asset type
|
|
62
|
+
* Returns tuple of [tokenAddress, isNativeToken] or null if invalid
|
|
57
63
|
*
|
|
58
|
-
* @param assetType - Asset type string.
|
|
59
|
-
* @returns Tuple of [tokenAddress, isNativeToken] or null if invalid
|
|
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
|
|
60
66
|
*/
|
|
61
67
|
const parseAssetType = (assetType) => {
|
|
62
68
|
if (!(0, utils_1.isCaipAssetType)(assetType)) {
|
|
63
69
|
return null;
|
|
64
70
|
}
|
|
65
71
|
const parsed = (0, utils_1.parseCaipAssetType)(assetType);
|
|
72
|
+
// ERC20 token (e.g., "eip155:1/erc20:0x...")
|
|
66
73
|
if (parsed.assetNamespace === 'erc20') {
|
|
67
74
|
return [parsed.assetReference, false];
|
|
68
75
|
}
|
|
76
|
+
// Native token (e.g., "eip155:1/slip44:60")
|
|
69
77
|
if (parsed.assetNamespace === 'slip44') {
|
|
70
78
|
return [ZERO_ADDRESS, true];
|
|
71
79
|
}
|
|
72
80
|
return null;
|
|
73
81
|
};
|
|
74
82
|
exports.parseAssetType = parseAssetType;
|
|
83
|
+
// endregion
|
|
75
84
|
// ────────────────────────────────────────────────────────────────────────────
|
|
76
|
-
// Main controller
|
|
85
|
+
// region: Main controller
|
|
77
86
|
class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPollingController)() {
|
|
78
87
|
constructor({ messenger, interval = DEFAULT_INTERVAL_MS, websocketActivePollingInterval = DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
|
|
79
88
|
super({
|
|
@@ -100,8 +109,6 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
100
109
|
_TokenBalancesController_intervalPollingTimers.set(this, new Map());
|
|
101
110
|
/** Track if controller-level polling is active */
|
|
102
111
|
_TokenBalancesController_isControllerPollingActive.set(this, false);
|
|
103
|
-
/** Track if the keyring is unlocked */
|
|
104
|
-
_TokenBalancesController_isUnlocked.set(this, false);
|
|
105
112
|
/** Store original chainIds from startPolling to preserve intent */
|
|
106
113
|
_TokenBalancesController_requestedChainIds.set(this, []);
|
|
107
114
|
/** Debouncing for rapid status changes to prevent excessive HTTP calls */
|
|
@@ -111,34 +118,52 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
111
118
|
});
|
|
112
119
|
_TokenBalancesController_getProvider.set(this, (chainId) => {
|
|
113
120
|
const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
|
|
114
|
-
const
|
|
115
|
-
const { networkClientId } =
|
|
121
|
+
const cfg = networkConfigurationsByChainId[chainId];
|
|
122
|
+
const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
|
|
116
123
|
const client = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
|
|
117
124
|
return new providers_1.Web3Provider(client.provider);
|
|
118
125
|
});
|
|
119
126
|
_TokenBalancesController_getNetworkClient.set(this, (chainId) => {
|
|
120
127
|
const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
|
|
121
|
-
const
|
|
122
|
-
const { networkClientId } =
|
|
128
|
+
const cfg = networkConfigurationsByChainId[chainId];
|
|
129
|
+
const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
|
|
123
130
|
return this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
|
|
124
131
|
});
|
|
132
|
+
/**
|
|
133
|
+
* Creates an AccountsApiBalanceFetcher that only supports chains in the accountsApiChainIds array
|
|
134
|
+
*
|
|
135
|
+
* @returns A BalanceFetcher that wraps AccountsApiBalanceFetcher with chainId filtering
|
|
136
|
+
*/
|
|
125
137
|
_TokenBalancesController_createAccountsApiFetcher.set(this, () => {
|
|
126
138
|
const originalFetcher = new api_balance_fetcher_1.AccountsApiBalanceFetcher(__classPrivateFieldGet(this, _TokenBalancesController_platform, "f"), __classPrivateFieldGet(this, _TokenBalancesController_getProvider, "f"));
|
|
127
139
|
return {
|
|
128
|
-
supports: (chainId) =>
|
|
129
|
-
|
|
140
|
+
supports: (chainId) => {
|
|
141
|
+
// Only support chains that are both:
|
|
142
|
+
// 1. In our specified accountsApiChainIds array
|
|
143
|
+
// 2. Actually supported by the AccountsApi
|
|
144
|
+
return (__classPrivateFieldGet(this, _TokenBalancesController_accountsApiChainIds, "f").call(this).includes(chainId) &&
|
|
145
|
+
originalFetcher.supports(chainId));
|
|
146
|
+
},
|
|
130
147
|
fetch: originalFetcher.fetch.bind(originalFetcher),
|
|
131
148
|
};
|
|
132
149
|
});
|
|
133
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
134
|
-
// TokensController / Network / Accounts events
|
|
135
150
|
_TokenBalancesController_onTokensChanged.set(this, async (state) => {
|
|
136
151
|
const changed = [];
|
|
137
152
|
let hasChanges = false;
|
|
153
|
+
// Get chains that have existing balances
|
|
154
|
+
const chainsWithBalances = new Set();
|
|
155
|
+
for (const address of Object.keys(this.state.tokenBalances)) {
|
|
156
|
+
const addressKey = address;
|
|
157
|
+
for (const chainId of Object.keys(this.state.tokenBalances[addressKey] || {})) {
|
|
158
|
+
chainsWithBalances.add(chainId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Only process chains that are explicitly mentioned in the incoming state change
|
|
138
162
|
const incomingChainIds = new Set([
|
|
139
163
|
...Object.keys(state.allTokens),
|
|
140
164
|
...Object.keys(state.allDetectedTokens),
|
|
141
165
|
]);
|
|
166
|
+
// Only proceed if there are actual changes to chains that have balances or are being added
|
|
142
167
|
const relevantChainIds = Array.from(incomingChainIds).filter((chainId) => {
|
|
143
168
|
const id = chainId;
|
|
144
169
|
const hasTokensNow = (state.allTokens[id] && Object.keys(state.allTokens[id]).length > 0) ||
|
|
@@ -147,16 +172,20 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
147
172
|
const hadTokensBefore = (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id] && Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
|
|
148
173
|
(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
|
|
149
174
|
Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
|
|
175
|
+
// Check if there's an actual change in token state
|
|
150
176
|
const hasTokenChange = !(0, lodash_1.isEqual)(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
|
|
151
177
|
!(0, lodash_1.isEqual)(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]);
|
|
178
|
+
// Process chains that have actual changes OR are new chains getting tokens
|
|
152
179
|
return hasTokenChange || (!hadTokensBefore && hasTokensNow);
|
|
153
180
|
});
|
|
154
|
-
if (
|
|
181
|
+
if (relevantChainIds.length === 0) {
|
|
182
|
+
// No relevant changes, just update internal state
|
|
155
183
|
__classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
|
|
156
184
|
__classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
|
|
157
185
|
return;
|
|
158
186
|
}
|
|
159
|
-
|
|
187
|
+
// Handle both cleanup and updates in a single state update
|
|
188
|
+
this.update((s) => {
|
|
160
189
|
for (const chainId of relevantChainIds) {
|
|
161
190
|
const id = chainId;
|
|
162
191
|
const hasTokensNow = (state.allTokens[id] &&
|
|
@@ -167,20 +196,20 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
167
196
|
Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
|
|
168
197
|
(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
|
|
169
198
|
Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
|
|
170
|
-
|
|
171
|
-
!(0, lodash_1.isEqual)(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id])
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
199
|
+
if (!(0, lodash_1.isEqual)(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
|
|
200
|
+
!(0, lodash_1.isEqual)(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id])) {
|
|
201
|
+
if (hasTokensNow) {
|
|
202
|
+
// Chain still has tokens - mark for async balance update
|
|
203
|
+
changed.push(id);
|
|
204
|
+
}
|
|
205
|
+
else if (hadTokensBefore) {
|
|
206
|
+
// Chain had tokens before but doesn't now - clean up balances immediately
|
|
207
|
+
for (const address of Object.keys(s.tokenBalances)) {
|
|
208
|
+
const addressKey = address;
|
|
209
|
+
if (s.tokenBalances[addressKey]?.[id]) {
|
|
210
|
+
s.tokenBalances[addressKey][id] = {};
|
|
211
|
+
hasChanges = true;
|
|
212
|
+
}
|
|
184
213
|
}
|
|
185
214
|
}
|
|
186
215
|
}
|
|
@@ -189,6 +218,7 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
189
218
|
__classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
|
|
190
219
|
__classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
|
|
191
220
|
__classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, state.allIgnoredTokens, "f");
|
|
221
|
+
// Only update balances for chains that still have tokens (and only if we haven't already updated state)
|
|
192
222
|
if (changed.length && !hasChanges) {
|
|
193
223
|
this.updateBalances({ chainIds: changed }).catch((error) => {
|
|
194
224
|
console.warn('Error updating balances after token change:', error);
|
|
@@ -196,7 +226,9 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
196
226
|
}
|
|
197
227
|
});
|
|
198
228
|
_TokenBalancesController_onNetworkChanged.set(this, (state) => {
|
|
229
|
+
// Check if any networks were removed by comparing with previous state
|
|
199
230
|
const currentNetworks = new Set(Object.keys(state.networkConfigurationsByChainId));
|
|
231
|
+
// Get all networks that currently have balances
|
|
200
232
|
const networksWithBalances = new Set();
|
|
201
233
|
for (const address of Object.keys(this.state.tokenBalances)) {
|
|
202
234
|
const addressKey = address;
|
|
@@ -204,59 +236,83 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
204
236
|
networksWithBalances.add(network);
|
|
205
237
|
}
|
|
206
238
|
}
|
|
239
|
+
// Find networks that were removed
|
|
207
240
|
const removedNetworks = Array.from(networksWithBalances).filter((network) => !currentNetworks.has(network));
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
241
|
+
if (removedNetworks.length > 0) {
|
|
242
|
+
this.update((s) => {
|
|
243
|
+
// Remove balances for all accounts on the deleted networks
|
|
244
|
+
for (const address of Object.keys(s.tokenBalances)) {
|
|
245
|
+
const addressKey = address;
|
|
246
|
+
for (const removedNetwork of removedNetworks) {
|
|
247
|
+
const networkKey = removedNetwork;
|
|
248
|
+
if (s.tokenBalances[addressKey]?.[networkKey]) {
|
|
249
|
+
delete s.tokenBalances[addressKey][networkKey];
|
|
250
|
+
}
|
|
218
251
|
}
|
|
219
252
|
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
222
255
|
});
|
|
223
256
|
_TokenBalancesController_onAccountRemoved.set(this, (addr) => {
|
|
224
257
|
if (!(0, utils_1.isStrictHexString)(addr) || !(0, controller_utils_1.isValidHexAddress)(addr)) {
|
|
225
258
|
return;
|
|
226
259
|
}
|
|
227
|
-
this.update((
|
|
228
|
-
delete
|
|
260
|
+
this.update((s) => {
|
|
261
|
+
delete s.tokenBalances[addr];
|
|
229
262
|
});
|
|
230
263
|
});
|
|
264
|
+
/**
|
|
265
|
+
* Handle account selection changes
|
|
266
|
+
* Triggers immediate balance fetch to ensure we have the latest balances
|
|
267
|
+
* since WebSocket only provides updates for changes going forward
|
|
268
|
+
*/
|
|
231
269
|
_TokenBalancesController_onAccountChanged.set(this, () => {
|
|
270
|
+
// Fetch balances for all chains with tokens when account changes
|
|
232
271
|
const chainIds = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
|
|
233
|
-
if (
|
|
234
|
-
|
|
272
|
+
if (chainIds.length > 0) {
|
|
273
|
+
this.updateBalances({ chainIds }).catch(() => {
|
|
274
|
+
// Silently handle polling errors
|
|
275
|
+
});
|
|
235
276
|
}
|
|
236
|
-
this.updateBalances({ chainIds }).catch(() => {
|
|
237
|
-
// Silently handle polling errors
|
|
238
|
-
});
|
|
239
277
|
});
|
|
278
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
// AccountActivityService event handlers
|
|
280
|
+
/**
|
|
281
|
+
* Handle real-time balance updates from AccountActivityService
|
|
282
|
+
* Processes balance updates and updates the token balance state
|
|
283
|
+
* If any balance update has an error, triggers fallback polling for the chain
|
|
284
|
+
*
|
|
285
|
+
* @param options0 - Balance update parameters
|
|
286
|
+
* @param options0.address - Account address
|
|
287
|
+
* @param options0.chain - CAIP chain identifier
|
|
288
|
+
* @param options0.updates - Array of balance updates for the account
|
|
289
|
+
*/
|
|
240
290
|
_TokenBalancesController_onAccountActivityBalanceUpdate.set(this, async ({ address, chain, updates, }) => {
|
|
241
291
|
const chainId = (0, exports.caipChainIdToHex)(chain);
|
|
242
292
|
const checksummedAccount = checksum(address);
|
|
243
293
|
try {
|
|
294
|
+
// Process all balance updates at once
|
|
244
295
|
const { tokenBalances, newTokens, nativeBalanceUpdates } = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_prepareBalanceUpdates).call(this, updates, checksummedAccount, chainId);
|
|
296
|
+
// Update state once with all token balances
|
|
245
297
|
if (tokenBalances.length > 0) {
|
|
246
298
|
this.update((state) => {
|
|
247
299
|
var _a, _b;
|
|
300
|
+
// Temporary until ADR to normalize all keys - tokenBalances state requires: account in lowercase, token in checksum
|
|
248
301
|
const lowercaseAccount = checksummedAccount.toLowerCase();
|
|
249
302
|
(_a = state.tokenBalances)[lowercaseAccount] ?? (_a[lowercaseAccount] = {});
|
|
250
303
|
(_b = state.tokenBalances[lowercaseAccount])[chainId] ?? (_b[chainId] = {});
|
|
304
|
+
// Apply all token balance updates
|
|
251
305
|
for (const { tokenAddress, balance } of tokenBalances) {
|
|
252
306
|
state.tokenBalances[lowercaseAccount][chainId][tokenAddress] =
|
|
253
307
|
balance;
|
|
254
308
|
}
|
|
255
309
|
});
|
|
256
310
|
}
|
|
311
|
+
// Update native balances in AccountTrackerController
|
|
257
312
|
if (nativeBalanceUpdates.length > 0) {
|
|
258
313
|
this.messenger.call('AccountTrackerController:updateNativeBalances', nativeBalanceUpdates);
|
|
259
314
|
}
|
|
315
|
+
// Import any new tokens that were discovered (balance already updated from websocket)
|
|
260
316
|
if (newTokens.length > 0) {
|
|
261
317
|
await this.messenger.call('TokenDetectionController:addDetectedTokensViaWs', {
|
|
262
318
|
tokensSlice: newTokens,
|
|
@@ -267,22 +323,35 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
267
323
|
catch (error) {
|
|
268
324
|
console.warn(`Error updating balances from AccountActivityService for chain ${chain}, account ${address}:`, error);
|
|
269
325
|
console.warn('Balance update data:', JSON.stringify(updates, null, 2));
|
|
326
|
+
// On error, trigger fallback polling
|
|
270
327
|
await this.updateBalances({ chainIds: [chainId] }).catch(() => {
|
|
271
328
|
// Silently handle polling errors
|
|
272
329
|
});
|
|
273
330
|
}
|
|
274
331
|
});
|
|
332
|
+
/**
|
|
333
|
+
* Handle status changes from AccountActivityService
|
|
334
|
+
* Uses aggressive debouncing to prevent excessive HTTP calls from rapid up/down changes
|
|
335
|
+
*
|
|
336
|
+
* @param options0 - Status change event data
|
|
337
|
+
* @param options0.chainIds - Array of chain identifiers
|
|
338
|
+
* @param options0.status - Connection status ('up' for connected, 'down' for disconnected)
|
|
339
|
+
*/
|
|
275
340
|
_TokenBalancesController_onAccountActivityStatusChanged.set(this, ({ chainIds, status, }) => {
|
|
341
|
+
// Update pending changes (latest status wins for each chain)
|
|
276
342
|
for (const chainId of chainIds) {
|
|
277
343
|
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.set(chainId, status);
|
|
278
344
|
}
|
|
345
|
+
// Clear existing timer to extend debounce window
|
|
279
346
|
if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
|
|
280
347
|
clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
|
|
281
348
|
}
|
|
349
|
+
// Set new timer - only process changes after activity settles
|
|
282
350
|
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = setTimeout(() => {
|
|
283
351
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_processAccumulatedStatusChanges).call(this);
|
|
284
|
-
}, 5000);
|
|
352
|
+
}, 5000); // 5-second debounce window
|
|
285
353
|
});
|
|
354
|
+
// Normalize all account addresses to lowercase in existing state
|
|
286
355
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_normalizeAccountAddresses).call(this);
|
|
287
356
|
__classPrivateFieldSet(this, _TokenBalancesController_platform, platform ?? 'extension', "f");
|
|
288
357
|
__classPrivateFieldSet(this, _TokenBalancesController_queryAllAccounts, queryMultipleAccounts, "f");
|
|
@@ -290,6 +359,7 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
290
359
|
__classPrivateFieldSet(this, _TokenBalancesController_defaultInterval, interval, "f");
|
|
291
360
|
__classPrivateFieldSet(this, _TokenBalancesController_websocketActivePollingInterval, websocketActivePollingInterval, "f");
|
|
292
361
|
__classPrivateFieldSet(this, _TokenBalancesController_chainPollingConfig, { ...chainPollingIntervals }, "f");
|
|
362
|
+
// Strategy order: API first, then RPC fallback
|
|
293
363
|
__classPrivateFieldSet(this, _TokenBalancesController_balanceFetchers, [
|
|
294
364
|
...(accountsApiChainIds().length > 0 && allowExternalServices()
|
|
295
365
|
? [__classPrivateFieldGet(this, _TokenBalancesController_createAccountsApiFetcher, "f").call(this)]
|
|
@@ -300,181 +370,303 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol
|
|
|
300
370
|
})),
|
|
301
371
|
], "f");
|
|
302
372
|
this.setIntervalLength(interval);
|
|
373
|
+
// initial token state & subscriptions
|
|
303
374
|
const { allTokens, allDetectedTokens, allIgnoredTokens } = this.messenger.call('TokensController:getState');
|
|
304
375
|
__classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
|
|
305
376
|
__classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, allDetectedTokens, "f");
|
|
306
377
|
__classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, allIgnoredTokens, "f");
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
378
|
+
this.messenger.subscribe('TokensController:stateChange', (tokensState) => {
|
|
379
|
+
__classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
|
|
380
|
+
console.warn('Error handling token state change:', error);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
this.messenger.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onNetworkChanged, "f"));
|
|
384
|
+
this.messenger.subscribe('KeyringController:accountRemoved', __classPrivateFieldGet(this, _TokenBalancesController_onAccountRemoved, "f"));
|
|
385
|
+
this.messenger.subscribe('AccountsController:selectedEvmAccountChange', __classPrivateFieldGet(this, _TokenBalancesController_onAccountChanged, "f"));
|
|
386
|
+
// Register action handlers for polling interval control
|
|
387
|
+
this.messenger.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
|
|
388
|
+
this.messenger.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
|
|
389
|
+
// Subscribe to AccountActivityService balance updates for real-time updates
|
|
390
|
+
this.messenger.subscribe('AccountActivityService:balanceUpdated', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").bind(this));
|
|
391
|
+
// Subscribe to AccountActivityService status changes for dynamic polling management
|
|
392
|
+
this.messenger.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
|
|
311
393
|
}
|
|
312
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
313
|
-
// Address + network helpers
|
|
314
394
|
/**
|
|
315
|
-
*
|
|
316
|
-
* When locked, balance updates should be skipped.
|
|
395
|
+
* Override to support per-chain polling intervals by grouping chains by interval
|
|
317
396
|
*
|
|
318
|
-
* @
|
|
397
|
+
* @param options0 - The polling options
|
|
398
|
+
* @param options0.chainIds - Chain IDs to start polling for
|
|
319
399
|
*/
|
|
320
|
-
get isActive() {
|
|
321
|
-
return __classPrivateFieldGet(this, _TokenBalancesController_isUnlocked, "f");
|
|
322
|
-
}
|
|
323
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
324
|
-
// Polling overrides
|
|
325
400
|
_startPolling({ chainIds }) {
|
|
401
|
+
// Store the original chainIds to preserve intent across config updates
|
|
326
402
|
__classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [...chainIds], "f");
|
|
327
403
|
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, true, "f");
|
|
328
404
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startIntervalGroupPolling).call(this, chainIds, true);
|
|
329
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Override to handle our custom polling approach
|
|
408
|
+
*
|
|
409
|
+
* @param tokenSetId - The token set ID to stop polling for
|
|
410
|
+
*/
|
|
330
411
|
_stopPollingByPollingTokenSetId(tokenSetId) {
|
|
412
|
+
let parsedTokenSetId;
|
|
331
413
|
let chainsToStop = [];
|
|
332
414
|
try {
|
|
333
|
-
|
|
334
|
-
chainsToStop = parsedTokenSetId.chainIds
|
|
415
|
+
parsedTokenSetId = JSON.parse(tokenSetId);
|
|
416
|
+
chainsToStop = parsedTokenSetId.chainIds || [];
|
|
335
417
|
}
|
|
336
418
|
catch (error) {
|
|
337
419
|
console.warn('Failed to parse tokenSetId, stopping all polling:', error);
|
|
338
|
-
|
|
420
|
+
// Fallback: stop all polling if we can't parse the tokenSetId
|
|
421
|
+
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
|
|
422
|
+
__classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
|
|
423
|
+
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
424
|
+
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
339
425
|
return;
|
|
340
426
|
}
|
|
427
|
+
// Compare with current chains - only stop if it matches our current session
|
|
341
428
|
const currentChainsSet = new Set(__classPrivateFieldGet(this, _TokenBalancesController_requestedChainIds, "f"));
|
|
342
429
|
const stopChainsSet = new Set(chainsToStop);
|
|
430
|
+
// Check if this stop request is for our current session
|
|
343
431
|
const isCurrentSession = currentChainsSet.size === stopChainsSet.size &&
|
|
344
432
|
[...currentChainsSet].every((chain) => stopChainsSet.has(chain));
|
|
345
433
|
if (isCurrentSession) {
|
|
346
|
-
|
|
434
|
+
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
|
|
435
|
+
__classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
|
|
436
|
+
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
437
|
+
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
347
438
|
}
|
|
348
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Get polling configuration for a chain (includes default fallback)
|
|
442
|
+
*
|
|
443
|
+
* @param chainId - The chain ID to get config for
|
|
444
|
+
* @returns The polling configuration for the chain
|
|
445
|
+
*/
|
|
349
446
|
getChainPollingConfig(chainId) {
|
|
350
447
|
return (__classPrivateFieldGet(this, _TokenBalancesController_chainPollingConfig, "f")[chainId] ?? {
|
|
351
448
|
interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"),
|
|
352
449
|
});
|
|
353
450
|
}
|
|
354
451
|
async _executePoll({ chainIds, queryAllAccounts = false, }) {
|
|
452
|
+
// This won't be called with our custom implementation, but keep for compatibility
|
|
355
453
|
await this.updateBalances({ chainIds, queryAllAccounts });
|
|
356
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Update multiple chain polling configurations at once
|
|
457
|
+
*
|
|
458
|
+
* @param configs - Object mapping chain IDs to polling configurations
|
|
459
|
+
* @param options - Optional configuration for the update behavior
|
|
460
|
+
* @param options.immediateUpdate - Whether to immediately fetch balances after updating configs (default: true)
|
|
461
|
+
*/
|
|
357
462
|
updateChainPollingConfigs(configs, options = { immediateUpdate: true }) {
|
|
358
463
|
Object.assign(__classPrivateFieldGet(this, _TokenBalancesController_chainPollingConfig, "f"), configs);
|
|
464
|
+
// If polling is currently active, restart with new interval groupings
|
|
359
465
|
if (__classPrivateFieldGet(this, _TokenBalancesController_isControllerPollingActive, "f")) {
|
|
466
|
+
// Restart polling with immediate fetch by default, unless explicitly disabled
|
|
360
467
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startIntervalGroupPolling).call(this, __classPrivateFieldGet(this, _TokenBalancesController_requestedChainIds, "f"), options.immediateUpdate);
|
|
361
468
|
}
|
|
362
469
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
async updateBalances({ chainIds, tokenAddresses, queryAllAccounts = false, } = {}) {
|
|
366
|
-
if (!this.isActive) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const targetChains = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getTargetChains).call(this, chainIds);
|
|
470
|
+
async updateBalances({ chainIds, queryAllAccounts = false, } = {}) {
|
|
471
|
+
const targetChains = chainIds ?? __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
|
|
370
472
|
if (!targetChains.length) {
|
|
371
473
|
return;
|
|
372
474
|
}
|
|
373
|
-
const {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
475
|
+
const { address: selected } = this.messenger.call('AccountsController:getSelectedAccount');
|
|
476
|
+
const allAccounts = this.messenger.call('AccountsController:listAccounts');
|
|
477
|
+
const jwtToken = await (0, controller_utils_1.safelyExecuteWithTimeout)(() => {
|
|
478
|
+
return this.messenger.call('AuthenticationController:getBearerToken');
|
|
479
|
+
}, false, 5000);
|
|
480
|
+
const aggregated = [];
|
|
481
|
+
let remainingChains = [...targetChains];
|
|
482
|
+
// Try each fetcher in order, removing successfully processed chains
|
|
483
|
+
for (const fetcher of __classPrivateFieldGet(this, _TokenBalancesController_balanceFetchers, "f")) {
|
|
484
|
+
const supportedChains = remainingChains.filter((c) => fetcher.supports(c));
|
|
485
|
+
if (!supportedChains.length) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const result = await fetcher.fetch({
|
|
490
|
+
chainIds: supportedChains,
|
|
491
|
+
queryAllAccounts: queryAllAccounts ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"),
|
|
492
|
+
selectedAccount: selected,
|
|
493
|
+
allAccounts,
|
|
494
|
+
jwtToken,
|
|
495
|
+
});
|
|
496
|
+
if (result.balances && result.balances.length > 0) {
|
|
497
|
+
aggregated.push(...result.balances);
|
|
498
|
+
// Remove chains that were successfully processed
|
|
499
|
+
const processedChains = new Set(result.balances.map((b) => b.chainId));
|
|
500
|
+
remainingChains = remainingChains.filter((chain) => !processedChains.has(chain));
|
|
501
|
+
}
|
|
502
|
+
// Add unprocessed chains back to remainingChains for next fetcher
|
|
503
|
+
if (result.unprocessedChainIds &&
|
|
504
|
+
result.unprocessedChainIds.length > 0) {
|
|
505
|
+
const currentRemainingChains = remainingChains;
|
|
506
|
+
const chainsToAdd = result.unprocessedChainIds.filter((chainId) => supportedChains.includes(chainId) &&
|
|
507
|
+
!currentRemainingChains.includes(chainId));
|
|
508
|
+
remainingChains.push(...chainsToAdd);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
|
|
513
|
+
// Continue to next fetcher (fallback)
|
|
514
|
+
}
|
|
515
|
+
// If all chains have been processed, break early
|
|
516
|
+
if (remainingChains.length === 0) {
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Determine which accounts to process based on queryAllAccounts parameter
|
|
521
|
+
const accountsToProcess = (queryAllAccounts ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"))
|
|
522
|
+
? allAccounts.map((a) => a.address)
|
|
523
|
+
: [selected];
|
|
383
524
|
const prev = this.state;
|
|
384
|
-
const next =
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
525
|
+
const next = draft(prev, (d) => {
|
|
526
|
+
var _a, _b;
|
|
527
|
+
// Initialize account and chain structures if they don't exist, but preserve existing balances
|
|
528
|
+
for (const chainId of targetChains) {
|
|
529
|
+
for (const account of accountsToProcess) {
|
|
530
|
+
// Ensure the nested structure exists without overwriting existing balances
|
|
531
|
+
(_a = d.tokenBalances)[account] ?? (_a[account] = {});
|
|
532
|
+
(_b = d.tokenBalances[account])[chainId] ?? (_b[chainId] = {});
|
|
533
|
+
// Initialize tokens from allTokens only if they don't exist yet
|
|
534
|
+
const chainTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId];
|
|
535
|
+
if (chainTokens?.[account]) {
|
|
536
|
+
Object.values(chainTokens[account]).forEach((token) => {
|
|
537
|
+
const tokenAddress = checksum(token.address);
|
|
538
|
+
// Only initialize if the token balance doesn't exist yet
|
|
539
|
+
if (!(tokenAddress in d.tokenBalances[account][chainId])) {
|
|
540
|
+
d.tokenBalances[account][chainId][tokenAddress] = '0x0';
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
// Initialize tokens from allDetectedTokens only if they don't exist yet
|
|
545
|
+
const detectedChainTokens = __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[chainId];
|
|
546
|
+
if (detectedChainTokens?.[account]) {
|
|
547
|
+
Object.values(detectedChainTokens[account]).forEach((token) => {
|
|
548
|
+
const tokenAddress = checksum(token.address);
|
|
549
|
+
// Only initialize if the token balance doesn't exist yet
|
|
550
|
+
if (!(tokenAddress in d.tokenBalances[account][chainId])) {
|
|
551
|
+
d.tokenBalances[account][chainId][tokenAddress] = '0x0';
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Update with actual fetched balances only if the value has changed
|
|
558
|
+
aggregated.forEach(({ success, value, account, token, chainId }) => {
|
|
559
|
+
var _a, _b;
|
|
560
|
+
if (success && value !== undefined) {
|
|
561
|
+
// Ensure all accounts we add/update are in lower-case
|
|
562
|
+
const lowerCaseAccount = account.toLowerCase();
|
|
563
|
+
const newBalance = (0, controller_utils_1.toHex)(value);
|
|
564
|
+
const tokenAddress = checksum(token);
|
|
565
|
+
const currentBalance = d.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress];
|
|
566
|
+
// Only update if the balance has actually changed
|
|
567
|
+
if (currentBalance !== newBalance) {
|
|
568
|
+
((_b = ((_a = d.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
});
|
|
389
572
|
});
|
|
390
573
|
if (!(0, lodash_1.isEqual)(prev, next)) {
|
|
391
574
|
this.update(() => next);
|
|
575
|
+
const nativeBalances = aggregated.filter((r) => r.success && r.token === ZERO_ADDRESS);
|
|
576
|
+
// Get current AccountTracker state to compare existing balances
|
|
392
577
|
const accountTrackerState = this.messenger.call('AccountTrackerController:getState');
|
|
393
|
-
|
|
394
|
-
if (
|
|
395
|
-
|
|
578
|
+
// Update native token balances only if they have changed
|
|
579
|
+
if (nativeBalances.length > 0) {
|
|
580
|
+
const balanceUpdates = nativeBalances
|
|
581
|
+
.map((balance) => ({
|
|
582
|
+
address: balance.account,
|
|
583
|
+
chainId: balance.chainId,
|
|
584
|
+
balance: balance.value ? (0, controller_utils_1.BNToHex)(balance.value) : '0x0',
|
|
585
|
+
}))
|
|
586
|
+
.filter((update) => {
|
|
587
|
+
const currentBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.balance;
|
|
588
|
+
// Only include if the balance has actually changed
|
|
589
|
+
return currentBalance !== update.balance;
|
|
590
|
+
});
|
|
591
|
+
if (balanceUpdates.length > 0) {
|
|
592
|
+
this.messenger.call('AccountTrackerController:updateNativeBalances', balanceUpdates);
|
|
593
|
+
}
|
|
396
594
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
595
|
+
// Filter and update staked balances in a single batch operation for better performance
|
|
596
|
+
const stakedBalances = aggregated.filter((r) => {
|
|
597
|
+
if (!r.success || r.token === ZERO_ADDRESS) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
// Check if the chainId and token address match any staking contract
|
|
601
|
+
const stakingContractAddress = AssetsContractController_1.STAKING_CONTRACT_ADDRESS_BY_CHAINID[r.chainId];
|
|
602
|
+
return (stakingContractAddress &&
|
|
603
|
+
stakingContractAddress.toLowerCase() === r.token.toLowerCase());
|
|
604
|
+
});
|
|
605
|
+
if (stakedBalances.length > 0) {
|
|
606
|
+
const stakedBalanceUpdates = stakedBalances
|
|
607
|
+
.map((balance) => ({
|
|
608
|
+
address: balance.account,
|
|
609
|
+
chainId: balance.chainId,
|
|
610
|
+
stakedBalance: balance.value ? (0, controller_utils_1.toHex)(balance.value) : '0x0',
|
|
611
|
+
}))
|
|
612
|
+
.filter((update) => {
|
|
613
|
+
const currentStakedBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.stakedBalance;
|
|
614
|
+
// Only include if the staked balance has actually changed
|
|
615
|
+
return currentStakedBalance !== update.stakedBalance;
|
|
616
|
+
});
|
|
617
|
+
if (stakedBalanceUpdates.length > 0) {
|
|
618
|
+
this.messenger.call('AccountTrackerController:updateStakedBalances', stakedBalanceUpdates);
|
|
619
|
+
}
|
|
400
620
|
}
|
|
401
621
|
}
|
|
402
|
-
await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_importUntrackedTokens).call(this, filteredAggregated);
|
|
403
622
|
}
|
|
404
623
|
resetState() {
|
|
405
624
|
this.update(() => ({ tokenBalances: {} }));
|
|
406
625
|
}
|
|
407
|
-
|
|
408
|
-
|
|
626
|
+
/**
|
|
627
|
+
* Clean up all timers and resources when controller is destroyed
|
|
628
|
+
*/
|
|
409
629
|
destroy() {
|
|
410
630
|
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
|
|
411
631
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
412
632
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
633
|
+
// Clean up debouncing timer
|
|
413
634
|
if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
|
|
414
635
|
clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
|
|
415
636
|
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
|
|
416
637
|
}
|
|
638
|
+
// Unregister action handlers
|
|
417
639
|
this.messenger.unregisterActionHandler(`TokenBalancesController:updateChainPollingConfigs`);
|
|
418
640
|
this.messenger.unregisterActionHandler(`TokenBalancesController:getChainPollingConfig`);
|
|
419
641
|
super.destroy();
|
|
420
642
|
}
|
|
421
643
|
}
|
|
422
644
|
exports.TokenBalancesController = TokenBalancesController;
|
|
423
|
-
_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(),
|
|
424
|
-
this.messenger.subscribe('TokensController:stateChange', (tokensState) => {
|
|
425
|
-
__classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
|
|
426
|
-
console.warn('Error handling token state change:', error);
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
this.messenger.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onNetworkChanged, "f"));
|
|
430
|
-
this.messenger.subscribe('KeyringController:unlock', () => {
|
|
431
|
-
__classPrivateFieldSet(this, _TokenBalancesController_isUnlocked, true, "f");
|
|
432
|
-
});
|
|
433
|
-
this.messenger.subscribe('KeyringController:lock', () => {
|
|
434
|
-
__classPrivateFieldSet(this, _TokenBalancesController_isUnlocked, false, "f");
|
|
435
|
-
});
|
|
436
|
-
this.messenger.subscribe('KeyringController:accountRemoved', __classPrivateFieldGet(this, _TokenBalancesController_onAccountRemoved, "f"));
|
|
437
|
-
this.messenger.subscribe('AccountsController:selectedEvmAccountChange', __classPrivateFieldGet(this, _TokenBalancesController_onAccountChanged, "f"));
|
|
438
|
-
this.messenger.subscribe('AccountActivityService:balanceUpdated', (event) => {
|
|
439
|
-
__classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").call(this, event).catch((error) => {
|
|
440
|
-
console.warn('Error handling balance update:', error);
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
this.messenger.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
|
|
444
|
-
this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
|
|
445
|
-
this.updateBalances({
|
|
446
|
-
chainIds: [transactionMeta.chainId],
|
|
447
|
-
}).catch(() => {
|
|
448
|
-
// Silently handle balance update errors
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
this.messenger.subscribe('TransactionController:incomingTransactionsReceived', (incomingTransactions) => {
|
|
452
|
-
this.updateBalances({
|
|
453
|
-
chainIds: incomingTransactions.map((tx) => tx.chainId),
|
|
454
|
-
}).catch(() => {
|
|
455
|
-
// Silently handle balance update errors
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
}, _TokenBalancesController_registerActions = function _TokenBalancesController_registerActions() {
|
|
459
|
-
this.messenger.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
|
|
460
|
-
this.messenger.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
|
|
461
|
-
}, _TokenBalancesController_normalizeAccountAddresses = function _TokenBalancesController_normalizeAccountAddresses() {
|
|
462
|
-
var _a;
|
|
645
|
+
_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_onAccountChanged = new WeakMap(), _TokenBalancesController_onAccountActivityBalanceUpdate = new WeakMap(), _TokenBalancesController_onAccountActivityStatusChanged = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_normalizeAccountAddresses = function _TokenBalancesController_normalizeAccountAddresses() {
|
|
463
646
|
const currentState = this.state.tokenBalances;
|
|
464
647
|
const normalizedBalances = {};
|
|
648
|
+
// Iterate through all accounts and normalize to lowercase
|
|
465
649
|
for (const address of Object.keys(currentState)) {
|
|
466
650
|
const lowercaseAddress = address.toLowerCase();
|
|
467
651
|
const accountBalances = currentState[address];
|
|
468
652
|
if (!accountBalances) {
|
|
469
653
|
continue;
|
|
470
654
|
}
|
|
471
|
-
|
|
655
|
+
// If this lowercase address doesn't exist yet, create it
|
|
656
|
+
if (!normalizedBalances[lowercaseAddress]) {
|
|
657
|
+
normalizedBalances[lowercaseAddress] = {};
|
|
658
|
+
}
|
|
659
|
+
// Merge chain data
|
|
472
660
|
for (const chainId of Object.keys(accountBalances)) {
|
|
473
661
|
const chainIdKey = chainId;
|
|
474
|
-
(
|
|
662
|
+
if (!normalizedBalances[lowercaseAddress][chainIdKey]) {
|
|
663
|
+
normalizedBalances[lowercaseAddress][chainIdKey] = {};
|
|
664
|
+
}
|
|
665
|
+
// Merge token balances (later values override earlier ones if duplicates exist)
|
|
475
666
|
Object.assign(normalizedBalances[lowercaseAddress][chainIdKey], accountBalances[chainIdKey]);
|
|
476
667
|
}
|
|
477
668
|
}
|
|
669
|
+
// Only update if there were changes
|
|
478
670
|
if (Object.keys(currentState).length !==
|
|
479
671
|
Object.keys(normalizedBalances).length ||
|
|
480
672
|
Object.keys(currentState).some((addr) => addr !== addr.toLowerCase())) {
|
|
@@ -488,15 +680,18 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
488
680
|
]),
|
|
489
681
|
];
|
|
490
682
|
}, _TokenBalancesController_startIntervalGroupPolling = function _TokenBalancesController_startIntervalGroupPolling(chainIds, immediate = true) {
|
|
683
|
+
// Stop any existing interval timers
|
|
491
684
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
492
685
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
686
|
+
// Group chains by their polling intervals
|
|
493
687
|
const intervalGroups = new Map();
|
|
494
688
|
for (const chainId of chainIds) {
|
|
495
689
|
const config = this.getChainPollingConfig(chainId);
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
intervalGroups.set(config.interval,
|
|
690
|
+
const existing = intervalGroups.get(config.interval) || [];
|
|
691
|
+
existing.push(chainId);
|
|
692
|
+
intervalGroups.set(config.interval, existing);
|
|
499
693
|
}
|
|
694
|
+
// Start separate polling loop for each interval group
|
|
500
695
|
for (const [interval, chainIdsGroup] of intervalGroups) {
|
|
501
696
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startPollingForInterval).call(this, interval, chainIdsGroup, immediate);
|
|
502
697
|
}
|
|
@@ -512,205 +707,33 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
512
707
|
console.warn(`Polling failed for chains ${chainIds.join(', ')} with interval ${interval}:`, error);
|
|
513
708
|
}
|
|
514
709
|
};
|
|
710
|
+
// Poll immediately first if requested
|
|
515
711
|
if (immediate) {
|
|
516
712
|
pollFunction().catch((error) => {
|
|
517
713
|
console.warn(`Immediate polling failed for chains ${chainIds.join(', ')}:`, error);
|
|
518
714
|
});
|
|
519
715
|
}
|
|
716
|
+
// Then start regular interval polling
|
|
520
717
|
__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_setPollingTimer).call(this, interval, chainIds, pollFunction);
|
|
521
718
|
}, _TokenBalancesController_setPollingTimer = function _TokenBalancesController_setPollingTimer(interval, chainIds, pollFunction) {
|
|
719
|
+
// Clear any existing timer for this interval first
|
|
720
|
+
const existingTimer = __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").get(interval);
|
|
721
|
+
if (existingTimer) {
|
|
722
|
+
clearInterval(existingTimer);
|
|
723
|
+
}
|
|
522
724
|
const timer = setInterval(() => {
|
|
523
725
|
pollFunction().catch((error) => {
|
|
524
726
|
console.warn(`Interval polling failed for chains ${chainIds.join(', ')}:`, error);
|
|
525
727
|
});
|
|
526
728
|
}, interval);
|
|
527
729
|
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").set(interval, timer);
|
|
528
|
-
}, _TokenBalancesController_stopAllPolling = function _TokenBalancesController_stopAllPolling() {
|
|
529
|
-
__classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
|
|
530
|
-
__classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
|
|
531
|
-
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
|
|
532
|
-
__classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
|
|
533
|
-
}, _TokenBalancesController_getTargetChains = function _TokenBalancesController_getTargetChains(chainIds) {
|
|
534
|
-
return chainIds?.length ? chainIds : __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
|
|
535
|
-
}, _TokenBalancesController_getAccountsAndJwt = async function _TokenBalancesController_getAccountsAndJwt() {
|
|
536
|
-
const { address: selected } = this.messenger.call('AccountsController:getSelectedAccount');
|
|
537
|
-
const allAccounts = this.messenger.call('AccountsController:listAccounts');
|
|
538
|
-
const jwtToken = await (0, controller_utils_1.safelyExecuteWithTimeout)(() => {
|
|
539
|
-
return this.messenger.call('AuthenticationController:getBearerToken');
|
|
540
|
-
}, false, 5000);
|
|
541
|
-
return {
|
|
542
|
-
selectedAccount: selected,
|
|
543
|
-
allAccounts,
|
|
544
|
-
jwtToken,
|
|
545
|
-
};
|
|
546
|
-
}, _TokenBalancesController_fetchAllBalances = async function _TokenBalancesController_fetchAllBalances({ targetChains, selectedAccount, allAccounts, jwtToken, queryAllAccounts, }) {
|
|
547
|
-
const aggregated = [];
|
|
548
|
-
let remainingChains = [...targetChains];
|
|
549
|
-
for (const fetcher of __classPrivateFieldGet(this, _TokenBalancesController_balanceFetchers, "f")) {
|
|
550
|
-
const supportedChains = remainingChains.filter((chain) => fetcher.supports(chain));
|
|
551
|
-
if (!supportedChains.length) {
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
try {
|
|
555
|
-
const result = await fetcher.fetch({
|
|
556
|
-
chainIds: supportedChains,
|
|
557
|
-
queryAllAccounts,
|
|
558
|
-
selectedAccount,
|
|
559
|
-
allAccounts,
|
|
560
|
-
jwtToken,
|
|
561
|
-
});
|
|
562
|
-
if (result.balances?.length) {
|
|
563
|
-
aggregated.push(...result.balances);
|
|
564
|
-
const processed = new Set(result.balances.map((b) => b.chainId));
|
|
565
|
-
remainingChains = remainingChains.filter((chain) => !processed.has(chain));
|
|
566
|
-
}
|
|
567
|
-
if (result.unprocessedChainIds?.length) {
|
|
568
|
-
const currentRemaining = [...remainingChains];
|
|
569
|
-
const chainsToAdd = result.unprocessedChainIds.filter((chainId) => supportedChains.includes(chainId) &&
|
|
570
|
-
!currentRemaining.includes(chainId));
|
|
571
|
-
remainingChains.push(...chainsToAdd);
|
|
572
|
-
this.messenger
|
|
573
|
-
.call('TokenDetectionController:detectTokens', {
|
|
574
|
-
chainIds: result.unprocessedChainIds,
|
|
575
|
-
forceRpc: true,
|
|
576
|
-
})
|
|
577
|
-
.catch(() => {
|
|
578
|
-
// Silently handle token detection errors
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
|
|
584
|
-
this.messenger
|
|
585
|
-
.call('TokenDetectionController:detectTokens', {
|
|
586
|
-
chainIds: supportedChains,
|
|
587
|
-
forceRpc: true,
|
|
588
|
-
})
|
|
589
|
-
.catch(() => {
|
|
590
|
-
// Silently handle token detection errors
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
if (!remainingChains.length) {
|
|
594
|
-
break;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return aggregated;
|
|
598
|
-
}, _TokenBalancesController_filterByTokenAddresses = function _TokenBalancesController_filterByTokenAddresses(balances, tokenAddresses) {
|
|
599
|
-
if (!tokenAddresses?.length) {
|
|
600
|
-
return balances;
|
|
601
|
-
}
|
|
602
|
-
const lowered = tokenAddresses.map((a) => a.toLowerCase());
|
|
603
|
-
return balances.filter((balance) => lowered.includes(balance.token.toLowerCase()));
|
|
604
|
-
}, _TokenBalancesController_getAccountsToProcess = function _TokenBalancesController_getAccountsToProcess(queryAllAccountsParam, allAccounts, selectedAccount) {
|
|
605
|
-
const effectiveQueryAll = queryAllAccountsParam ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f") ?? false;
|
|
606
|
-
if (!effectiveQueryAll) {
|
|
607
|
-
return [selectedAccount];
|
|
608
|
-
}
|
|
609
|
-
return allAccounts.map((account) => account.address);
|
|
610
|
-
}, _TokenBalancesController_applyTokenBalancesToState = function _TokenBalancesController_applyTokenBalancesToState({ prev, targetChains, accountsToProcess, balances, }) {
|
|
611
|
-
return draft(prev, (draftState) => {
|
|
612
|
-
var _a, _b;
|
|
613
|
-
for (const chainId of targetChains) {
|
|
614
|
-
for (const account of accountsToProcess) {
|
|
615
|
-
(_a = draftState.tokenBalances)[account] ?? (_a[account] = {});
|
|
616
|
-
(_b = draftState.tokenBalances[account])[chainId] ?? (_b[chainId] = {});
|
|
617
|
-
const chainTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId];
|
|
618
|
-
if (chainTokens?.[account]) {
|
|
619
|
-
Object.values(chainTokens[account]).forEach((token) => {
|
|
620
|
-
var _a;
|
|
621
|
-
const tokenAddress = checksum(token.address);
|
|
622
|
-
(_a = draftState.tokenBalances[account][chainId])[tokenAddress] ?? (_a[tokenAddress] = '0x0');
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
const detectedChainTokens = __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[chainId];
|
|
626
|
-
if (detectedChainTokens?.[account]) {
|
|
627
|
-
Object.values(detectedChainTokens[account]).forEach((token) => {
|
|
628
|
-
var _a;
|
|
629
|
-
const tokenAddress = checksum(token.address);
|
|
630
|
-
(_a = draftState.tokenBalances[account][chainId])[tokenAddress] ?? (_a[tokenAddress] = '0x0');
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
balances.forEach(({ success, value, account, token, chainId }) => {
|
|
636
|
-
var _a, _b;
|
|
637
|
-
if (!success || value === undefined) {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
const lowerCaseAccount = account.toLowerCase();
|
|
641
|
-
const newBalance = (0, controller_utils_1.toHex)(value);
|
|
642
|
-
const tokenAddress = checksum(token);
|
|
643
|
-
const currentBalance = draftState.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress];
|
|
644
|
-
if (currentBalance !== newBalance) {
|
|
645
|
-
((_b = ((_a = draftState.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance;
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
}, _TokenBalancesController_buildNativeBalanceUpdates = function _TokenBalancesController_buildNativeBalanceUpdates(balances, accountTrackerState) {
|
|
650
|
-
const nativeBalances = balances.filter((balance) => balance.success && balance.token === ZERO_ADDRESS);
|
|
651
|
-
if (!nativeBalances.length) {
|
|
652
|
-
return [];
|
|
653
|
-
}
|
|
654
|
-
return nativeBalances
|
|
655
|
-
.map((balance) => ({
|
|
656
|
-
address: balance.account,
|
|
657
|
-
chainId: balance.chainId,
|
|
658
|
-
balance: balance.value ? (0, controller_utils_1.BNToHex)(balance.value) : '0x0',
|
|
659
|
-
}))
|
|
660
|
-
.filter((update) => {
|
|
661
|
-
const currentBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.balance;
|
|
662
|
-
return currentBalance !== update.balance;
|
|
663
|
-
});
|
|
664
|
-
}, _TokenBalancesController_buildStakedBalanceUpdates = function _TokenBalancesController_buildStakedBalanceUpdates(balances, accountTrackerState) {
|
|
665
|
-
const stakedBalances = balances.filter((balance) => {
|
|
666
|
-
if (!balance.success || balance.token === ZERO_ADDRESS) {
|
|
667
|
-
return false;
|
|
668
|
-
}
|
|
669
|
-
const stakingContractAddress = AssetsContractController_1.STAKING_CONTRACT_ADDRESS_BY_CHAINID[balance.chainId];
|
|
670
|
-
return (stakingContractAddress &&
|
|
671
|
-
stakingContractAddress.toLowerCase() === balance.token.toLowerCase());
|
|
672
|
-
});
|
|
673
|
-
if (!stakedBalances.length) {
|
|
674
|
-
return [];
|
|
675
|
-
}
|
|
676
|
-
return stakedBalances
|
|
677
|
-
.map((balance) => ({
|
|
678
|
-
address: balance.account,
|
|
679
|
-
chainId: balance.chainId,
|
|
680
|
-
stakedBalance: balance.value ? (0, controller_utils_1.toHex)(balance.value) : '0x0',
|
|
681
|
-
}))
|
|
682
|
-
.filter((update) => {
|
|
683
|
-
const currentStakedBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.stakedBalance;
|
|
684
|
-
return currentStakedBalance !== update.stakedBalance;
|
|
685
|
-
});
|
|
686
|
-
}, _TokenBalancesController_importUntrackedTokens = async function _TokenBalancesController_importUntrackedTokens(balances) {
|
|
687
|
-
const untrackedTokensByChain = new Map();
|
|
688
|
-
for (const balance of balances) {
|
|
689
|
-
if (!balance.success || balance.token === ZERO_ADDRESS) {
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
const tokenAddress = checksum(balance.token);
|
|
693
|
-
const account = balance.account.toLowerCase();
|
|
694
|
-
if (!__classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_isTokenTracked).call(this, tokenAddress, account, balance.chainId)) {
|
|
695
|
-
const existing = untrackedTokensByChain.get(balance.chainId) ?? [];
|
|
696
|
-
if (!existing.includes(tokenAddress)) {
|
|
697
|
-
existing.push(tokenAddress);
|
|
698
|
-
untrackedTokensByChain.set(balance.chainId, existing);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
for (const [chainId, tokens] of untrackedTokensByChain) {
|
|
703
|
-
await this.messenger.call('TokenDetectionController:addDetectedTokensViaWs', {
|
|
704
|
-
tokensSlice: tokens,
|
|
705
|
-
chainId,
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
730
|
}, _TokenBalancesController_isTokenTracked = function _TokenBalancesController_isTokenTracked(tokenAddress, account, chainId) {
|
|
709
|
-
|
|
710
|
-
if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[
|
|
731
|
+
// Check if token exists in allTokens
|
|
732
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token.address === tokenAddress)) {
|
|
711
733
|
return true;
|
|
712
734
|
}
|
|
713
|
-
|
|
735
|
+
// Check if token exists in allIgnoredTokens
|
|
736
|
+
if (__classPrivateFieldGet(this, _TokenBalancesController_allIgnoredTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token === tokenAddress)) {
|
|
714
737
|
return true;
|
|
715
738
|
}
|
|
716
739
|
return false;
|
|
@@ -720,25 +743,31 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
720
743
|
const nativeBalanceUpdates = [];
|
|
721
744
|
for (const update of updates) {
|
|
722
745
|
const { asset, postBalance } = update;
|
|
746
|
+
// Throw if balance update has an error
|
|
723
747
|
if (postBalance.error) {
|
|
724
748
|
throw new Error('Balance update has error');
|
|
725
749
|
}
|
|
750
|
+
// Parse token address from asset type
|
|
726
751
|
const parsed = (0, exports.parseAssetType)(asset.type);
|
|
727
752
|
if (!parsed) {
|
|
728
753
|
throw new Error('Failed to parse asset type');
|
|
729
754
|
}
|
|
730
755
|
const [tokenAddress, isNativeToken] = parsed;
|
|
756
|
+
// Validate token address
|
|
731
757
|
if (!(0, utils_1.isStrictHexString)(tokenAddress) ||
|
|
732
758
|
!(0, controller_utils_1.isValidHexAddress)(tokenAddress)) {
|
|
733
759
|
throw new Error('Invalid token address');
|
|
734
760
|
}
|
|
735
761
|
const checksumTokenAddress = checksum(tokenAddress);
|
|
736
762
|
const isTracked = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_isTokenTracked).call(this, checksumTokenAddress, account, chainId);
|
|
763
|
+
// postBalance.amount is in hex format (raw units)
|
|
737
764
|
const balanceHex = postBalance.amount;
|
|
765
|
+
// Add token balance (tracked tokens, ignored tokens, and native tokens all get balance updates)
|
|
738
766
|
tokenBalances.push({
|
|
739
767
|
tokenAddress: checksumTokenAddress,
|
|
740
768
|
balance: balanceHex,
|
|
741
769
|
});
|
|
770
|
+
// Add native balance update if this is a native token
|
|
742
771
|
if (isNativeToken) {
|
|
743
772
|
nativeBalanceUpdates.push({
|
|
744
773
|
address: account,
|
|
@@ -746,6 +775,7 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
746
775
|
balance: balanceHex,
|
|
747
776
|
});
|
|
748
777
|
}
|
|
778
|
+
// Handle untracked ERC20 tokens - queue for import
|
|
749
779
|
if (!isNativeToken && !isTracked) {
|
|
750
780
|
newTokens.push(checksumTokenAddress);
|
|
751
781
|
}
|
|
@@ -755,18 +785,28 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
|
|
|
755
785
|
const changes = Array.from(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.entries());
|
|
756
786
|
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.clear();
|
|
757
787
|
__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
|
|
758
|
-
if (
|
|
788
|
+
if (changes.length === 0) {
|
|
759
789
|
return;
|
|
760
790
|
}
|
|
791
|
+
// Calculate final polling configurations
|
|
761
792
|
const chainConfigs = {};
|
|
762
793
|
for (const [chainId, status] of changes) {
|
|
794
|
+
// Convert CAIP format (eip155:1) to hex format (0x1)
|
|
795
|
+
// chainId is always in CAIP format from AccountActivityService
|
|
763
796
|
const hexChainId = (0, exports.caipChainIdToHex)(chainId);
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
797
|
+
if (status === 'down') {
|
|
798
|
+
// Chain is down - use default polling since no real-time updates available
|
|
799
|
+
chainConfigs[hexChainId] = { interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f") };
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
// Chain is up - use longer intervals since WebSocket provides real-time updates
|
|
803
|
+
chainConfigs[hexChainId] = {
|
|
804
|
+
interval: __classPrivateFieldGet(this, _TokenBalancesController_websocketActivePollingInterval, "f"),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
768
807
|
}
|
|
769
|
-
|
|
808
|
+
// Add jitter to prevent synchronized requests across instances
|
|
809
|
+
const jitterDelay = Math.random() * __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"); // 0 to default interval
|
|
770
810
|
setTimeout(() => {
|
|
771
811
|
this.updateChainPollingConfigs(chainConfigs, { immediateUpdate: true });
|
|
772
812
|
}, jitterDelay);
|