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