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