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