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