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