@metamask-previews/assets-controllers 73.1.0-preview-2fd8e984 → 73.1.0-preview-c3b78c5

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 CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Implement network filtering for aggregated balance selectors across mobile and extension ([#6285](https://github.com/MetaMask/core/pull/6285))
13
+ - Add aggregated portfolio change calculator in core with period support (1d/7d/30d) and unit tests; exposes `calculateAggregatedChangeForAllWallets` for mobile/extension selectors ([#6285](https://github.com/MetaMask/core/pull/6285))
14
+
10
15
  ### Changed
11
16
 
12
17
  - Bump `@metamask/base-controller` from `^8.0.1` to `^8.1.0` ([#6284](https://github.com/MetaMask/core/pull/6284))
@@ -0,0 +1,451 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateAggregatedChangeForGroup = exports.calculateAggregatedChangeForAllWallets = exports.calculateBalanceForAllWallets = void 0;
4
+ const keyring_api_1 = require("@metamask/keyring-api");
5
+ const utils_1 = require("@metamask/utils");
6
+ const isChainEnabledByMap = (map, id) => {
7
+ if (!map) {
8
+ return true;
9
+ }
10
+ const isHex = typeof id === 'string' && id.startsWith('0x');
11
+ if (isHex) {
12
+ const evm = map[String(utils_1.KnownCaipNamespace.Eip155)];
13
+ return Boolean(evm?.[id]);
14
+ }
15
+ const { namespace } = (0, utils_1.parseCaipChainId)(id);
16
+ return Boolean(map[namespace]?.[id]);
17
+ };
18
+ const getInternalAccountsForGroup = (accountTreeState, accountsState, groupId) => {
19
+ const walletId = groupId.split('/')[0];
20
+ const wallet = accountTreeState.accountTree.wallets[walletId];
21
+ if (!wallet) {
22
+ return [];
23
+ }
24
+ const group = wallet.groups[groupId];
25
+ if (!group) {
26
+ return [];
27
+ }
28
+ return group.accounts
29
+ .map((accountId) => accountsState.internalAccounts.accounts[accountId])
30
+ .filter(Boolean);
31
+ };
32
+ const isNonNaNNumber = (value) => typeof value === 'number' && !Number.isNaN(value);
33
+ /**
34
+ * Calculate balances for all wallets and groups.
35
+ * Pure function – accepts controller states and returns aggregated totals.
36
+ *
37
+ * @param accountTreeState - AccountTreeController state
38
+ * @param accountsState - AccountsController state
39
+ * @param tokenBalancesState - TokenBalancesController state
40
+ * @param tokenRatesState - TokenRatesController state
41
+ * @param multichainRatesState - MultichainAssetsRatesController state
42
+ * @param multichainBalancesState - MultichainBalancesController state
43
+ * @param tokensState - TokensController state
44
+ * @param currencyRateState - CurrencyRateController state
45
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace
46
+ * @returns Aggregated balances for all wallets
47
+ */
48
+ function calculateBalanceForAllWallets(accountTreeState, accountsState, tokenBalancesState, tokenRatesState, multichainRatesState, multichainBalancesState, tokensState, currencyRateState, enabledNetworkMap) {
49
+ const walletBalances = {};
50
+ let totalBalanceInUserCurrency = 0;
51
+ const isEvmChainEnabled = (chainId) => isChainEnabledByMap(enabledNetworkMap, chainId);
52
+ const isAssetChainEnabled = (assetId) => {
53
+ const { chainId } = (0, utils_1.parseCaipAssetType)(assetId);
54
+ return isChainEnabledByMap(enabledNetworkMap, chainId);
55
+ };
56
+ const walletIds = Object.keys(accountTreeState.accountTree.wallets);
57
+ for (const walletId of walletIds) {
58
+ const wallet = accountTreeState.accountTree.wallets[walletId];
59
+ if (!wallet) {
60
+ continue;
61
+ }
62
+ const groupBalances = {};
63
+ let walletTotalBalance = 0;
64
+ const groups = Object.keys(wallet.groups || {});
65
+ for (const groupId of groups) {
66
+ const accounts = getInternalAccountsForGroup(accountTreeState, accountsState, groupId);
67
+ if (accounts.length === 0) {
68
+ groupBalances[groupId] = {
69
+ walletId,
70
+ groupId,
71
+ totalBalanceInUserCurrency: 0,
72
+ userCurrency: currencyRateState.currentCurrency,
73
+ };
74
+ continue;
75
+ }
76
+ let groupTotalBalance = 0;
77
+ for (const account of accounts) {
78
+ const isEvmAccount = (0, keyring_api_1.isEvmAccountType)(account.type);
79
+ if (isEvmAccount) {
80
+ const accountBalances = tokenBalancesState.tokenBalances[account.address];
81
+ if (accountBalances) {
82
+ for (const [chainId, chainBalances] of Object.entries(accountBalances)) {
83
+ if (!isEvmChainEnabled(chainId)) {
84
+ continue;
85
+ }
86
+ for (const [tokenAddress, balance] of Object.entries(chainBalances)) {
87
+ const chainTokens = tokensState.allTokens[chainId];
88
+ const accountTokens = chainTokens?.[account.address];
89
+ const token = accountTokens?.find((t) => t.address === tokenAddress);
90
+ if (!token) {
91
+ continue;
92
+ }
93
+ const decimals = typeof token.decimals === 'number' &&
94
+ !Number.isNaN(token.decimals)
95
+ ? token.decimals
96
+ : 18;
97
+ const balanceInSmallestUnit = parseInt(balance, 16);
98
+ if (Number.isNaN(balanceInSmallestUnit)) {
99
+ continue;
100
+ }
101
+ const balanceInTokenUnits = balanceInSmallestUnit / Math.pow(10, decimals);
102
+ const chainMarketData = tokenRatesState.marketData[chainId];
103
+ const tokenMarketData = chainMarketData?.[tokenAddress];
104
+ if (tokenMarketData?.price) {
105
+ const nativeCurrency = tokenMarketData.currency;
106
+ const nativeToUserRate = currencyRateState.currencyRates[nativeCurrency]
107
+ ?.conversionRate;
108
+ if (nativeToUserRate) {
109
+ const tokenPriceInUserCurrency = tokenMarketData.price * nativeToUserRate;
110
+ const balanceInUserCurrency = balanceInTokenUnits * tokenPriceInUserCurrency;
111
+ groupTotalBalance += balanceInUserCurrency;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ else {
119
+ const accountBalances = multichainBalancesState.balances[account.id];
120
+ if (accountBalances) {
121
+ for (const [assetId, balanceData] of Object.entries(accountBalances)) {
122
+ if (!isAssetChainEnabled(assetId)) {
123
+ continue;
124
+ }
125
+ const balanceAmount = parseFloat(balanceData.amount);
126
+ if (Number.isNaN(balanceAmount)) {
127
+ continue;
128
+ }
129
+ const conversionRate = multichainRatesState.conversionRates[assetId];
130
+ if (conversionRate) {
131
+ const conversionRateValue = parseFloat(conversionRate.rate);
132
+ if (Number.isNaN(conversionRateValue)) {
133
+ continue;
134
+ }
135
+ const balanceInUserCurrency = balanceAmount * conversionRateValue;
136
+ groupTotalBalance += balanceInUserCurrency;
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ groupBalances[groupId] = {
143
+ walletId,
144
+ groupId,
145
+ totalBalanceInUserCurrency: groupTotalBalance,
146
+ userCurrency: currencyRateState.currentCurrency,
147
+ };
148
+ walletTotalBalance += groupTotalBalance;
149
+ }
150
+ walletBalances[walletId] = {
151
+ walletId,
152
+ groups: groupBalances,
153
+ totalBalanceInUserCurrency: walletTotalBalance,
154
+ userCurrency: currencyRateState.currentCurrency,
155
+ };
156
+ totalBalanceInUserCurrency += walletTotalBalance;
157
+ }
158
+ return {
159
+ wallets: walletBalances,
160
+ totalBalanceInUserCurrency,
161
+ userCurrency: currencyRateState.currentCurrency,
162
+ };
163
+ }
164
+ exports.calculateBalanceForAllWallets = calculateBalanceForAllWallets;
165
+ /**
166
+ * Calculate aggregated portfolio value change for a given period (1d, 7d, 30d).
167
+ * Logic mirrors extension/mobile historical aggregation:
168
+ * - For each asset with available percent change for the requested period, compute current value in user currency.
169
+ * - Reconstruct previous value by dividing current by (1 + percent/100).
170
+ * - Sum across all assets, then compute amount change and percent change.
171
+ *
172
+ * @param accountTreeState - AccountTreeController state.
173
+ * @param accountsState - AccountsController state.
174
+ * @param tokenBalancesState - TokenBalancesController state.
175
+ * @param tokenRatesState - TokenRatesController state.
176
+ * @param multichainRatesState - MultichainAssetsRatesController state.
177
+ * @param multichainBalancesState - MultichainBalancesController state.
178
+ * @param tokensState - TokensController state.
179
+ * @param currencyRateState - CurrencyRateController state.
180
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace.
181
+ * @param period - Period to compute change for ('1d' | '7d' | '30d').
182
+ * @returns Aggregated change details for the requested period.
183
+ */
184
+ function calculateAggregatedChangeForAllWallets(accountTreeState, accountsState, tokenBalancesState, tokenRatesState, multichainRatesState, multichainBalancesState, tokensState, currencyRateState, enabledNetworkMap, period) {
185
+ let currentTotal = 0;
186
+ let previousTotal = 0;
187
+ const isEvmChainEnabled = (chainId) => isChainEnabledByMap(enabledNetworkMap, chainId);
188
+ const isAssetChainEnabled = (assetId) => {
189
+ const { chainId } = (0, utils_1.parseCaipAssetType)(assetId);
190
+ return isChainEnabledByMap(enabledNetworkMap, chainId);
191
+ };
192
+ const evmPercentField = {
193
+ '1d': 'pricePercentChange1d',
194
+ '7d': 'pricePercentChange7d',
195
+ '30d': 'pricePercentChange30d',
196
+ };
197
+ const nonEvmPercentKey = {
198
+ '1d': 'P1D',
199
+ '7d': 'P7D',
200
+ '30d': 'P30D',
201
+ };
202
+ const walletIds = Object.keys(accountTreeState.accountTree.wallets);
203
+ for (const walletId of walletIds) {
204
+ const wallet = accountTreeState.accountTree.wallets[walletId];
205
+ if (!wallet) {
206
+ continue;
207
+ }
208
+ const groups = Object.keys(wallet.groups || {});
209
+ for (const groupId of groups) {
210
+ const accounts = getInternalAccountsForGroup(accountTreeState, accountsState, groupId);
211
+ for (const account of accounts) {
212
+ const isEvmAccount = (0, keyring_api_1.isEvmAccountType)(account.type);
213
+ if (isEvmAccount) {
214
+ const accountBalances = tokenBalancesState.tokenBalances[account.address];
215
+ if (!accountBalances) {
216
+ continue;
217
+ }
218
+ for (const [chainId, chainBalances] of Object.entries(accountBalances)) {
219
+ if (!isEvmChainEnabled(chainId)) {
220
+ continue;
221
+ }
222
+ const chainMarketData = tokenRatesState.marketData[chainId];
223
+ for (const [tokenAddress, balance] of Object.entries(chainBalances)) {
224
+ const chainTokens = tokensState.allTokens[chainId];
225
+ const accountTokens = chainTokens?.[account.address];
226
+ const token = accountTokens?.find((t) => t.address === tokenAddress);
227
+ if (!token) {
228
+ continue;
229
+ }
230
+ const decimals = typeof token.decimals === 'number' &&
231
+ !Number.isNaN(token.decimals)
232
+ ? token.decimals
233
+ : 18;
234
+ const balanceInSmallestUnit = parseInt(balance, 16);
235
+ if (Number.isNaN(balanceInSmallestUnit)) {
236
+ continue;
237
+ }
238
+ const balanceInTokenUnits = balanceInSmallestUnit / Math.pow(10, decimals);
239
+ const tokenMarketData = chainMarketData?.[tokenAddress];
240
+ const price = tokenMarketData?.price;
241
+ const percentRaw = tokenMarketData?.[evmPercentField[period]];
242
+ if (typeof price !== 'number' || Number.isNaN(price)) {
243
+ continue;
244
+ }
245
+ const nativeCurrency = tokenMarketData?.currency;
246
+ const nativeToUserRate = nativeCurrency &&
247
+ currencyRateState.currencyRates[nativeCurrency]
248
+ ? currencyRateState.currencyRates[nativeCurrency]
249
+ ?.conversionRate
250
+ : undefined;
251
+ if (typeof nativeToUserRate !== 'number' ||
252
+ Number.isNaN(nativeToUserRate)) {
253
+ continue;
254
+ }
255
+ if (!isNonNaNNumber(percentRaw)) {
256
+ continue;
257
+ }
258
+ const priceInUserCurrency = price * nativeToUserRate;
259
+ const currentValue = balanceInTokenUnits * priceInUserCurrency;
260
+ const denom = Number((1 + percentRaw / 100).toFixed(8));
261
+ if (denom === 0) {
262
+ continue;
263
+ }
264
+ const previousValue = currentValue / denom;
265
+ currentTotal += currentValue;
266
+ previousTotal += previousValue;
267
+ }
268
+ }
269
+ }
270
+ else {
271
+ const accountBalances = multichainBalancesState.balances[account.id];
272
+ if (!accountBalances) {
273
+ continue;
274
+ }
275
+ for (const [assetId, balanceData] of Object.entries(accountBalances)) {
276
+ if (!isAssetChainEnabled(assetId)) {
277
+ continue;
278
+ }
279
+ const balanceAmount = parseFloat(balanceData.amount);
280
+ if (Number.isNaN(balanceAmount)) {
281
+ continue;
282
+ }
283
+ const conversionRate = multichainRatesState.conversionRates[assetId];
284
+ const rateStr = conversionRate?.rate;
285
+ const percentObj = conversionRate?.marketData?.pricePercentChange;
286
+ const percentRaw = percentObj?.[nonEvmPercentKey[period]];
287
+ const rate = typeof rateStr === 'string' ? parseFloat(rateStr) : undefined;
288
+ if (!isNonNaNNumber(rate) || !isNonNaNNumber(percentRaw)) {
289
+ continue;
290
+ }
291
+ const currentValue = balanceAmount * rate;
292
+ const denom = Number((1 + percentRaw / 100).toFixed(8));
293
+ if (denom === 0) {
294
+ continue;
295
+ }
296
+ const previousValue = currentValue / denom;
297
+ currentTotal += currentValue;
298
+ previousTotal += previousValue;
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ const amountChange = currentTotal - previousTotal;
305
+ const percentChange = previousTotal !== 0 ? (amountChange / previousTotal) * 100 : 0;
306
+ return {
307
+ period,
308
+ currentTotalInUserCurrency: Number(currentTotal.toFixed(8)),
309
+ previousTotalInUserCurrency: Number(previousTotal.toFixed(8)),
310
+ amountChangeInUserCurrency: Number(amountChange.toFixed(8)),
311
+ percentChange: Number(percentChange.toFixed(8)),
312
+ userCurrency: currencyRateState.currentCurrency,
313
+ };
314
+ }
315
+ exports.calculateAggregatedChangeForAllWallets = calculateAggregatedChangeForAllWallets;
316
+ /**
317
+ * Calculate aggregated portfolio value change for a specific account group and period.
318
+ * Mirrors the logic of calculateAggregatedChangeForAllWallets but scoped to one group.
319
+ *
320
+ * @param accountTreeState - AccountTreeController state.
321
+ * @param accountsState - AccountsController state.
322
+ * @param tokenBalancesState - TokenBalancesController state.
323
+ * @param tokenRatesState - TokenRatesController state.
324
+ * @param multichainRatesState - MultichainAssetsRatesController state.
325
+ * @param multichainBalancesState - MultichainBalancesController state.
326
+ * @param tokensState - TokensController state.
327
+ * @param currencyRateState - CurrencyRateController state.
328
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace.
329
+ * @param groupId - The account group ID to compute change for.
330
+ * @param period - Period to compute change for ('1d' | '7d' | '30d').
331
+ * @returns Aggregated change details for the requested group and period.
332
+ */
333
+ function calculateAggregatedChangeForGroup(accountTreeState, accountsState, tokenBalancesState, tokenRatesState, multichainRatesState, multichainBalancesState, tokensState, currencyRateState, enabledNetworkMap, groupId, period) {
334
+ let currentTotal = 0;
335
+ let previousTotal = 0;
336
+ const isEvmChainEnabled = (chainId) => isChainEnabledByMap(enabledNetworkMap, chainId);
337
+ const isAssetChainEnabled = (assetId) => {
338
+ const { chainId } = (0, utils_1.parseCaipAssetType)(assetId);
339
+ return isChainEnabledByMap(enabledNetworkMap, chainId);
340
+ };
341
+ const evmPercentField = {
342
+ '1d': 'pricePercentChange1d',
343
+ '7d': 'pricePercentChange7d',
344
+ '30d': 'pricePercentChange30d',
345
+ };
346
+ const nonEvmPercentKey = {
347
+ '1d': 'P1D',
348
+ '7d': 'P7D',
349
+ '30d': 'P30D',
350
+ };
351
+ const accounts = getInternalAccountsForGroup(accountTreeState, accountsState, groupId);
352
+ for (const account of accounts) {
353
+ const isEvmAccount = (0, keyring_api_1.isEvmAccountType)(account.type);
354
+ if (isEvmAccount) {
355
+ const accountBalances = tokenBalancesState.tokenBalances[account.address];
356
+ if (!accountBalances) {
357
+ continue;
358
+ }
359
+ for (const [chainId, chainBalances] of Object.entries(accountBalances)) {
360
+ if (!isEvmChainEnabled(chainId)) {
361
+ continue;
362
+ }
363
+ const chainMarketData = tokenRatesState.marketData[chainId];
364
+ for (const [tokenAddress, balance] of Object.entries(chainBalances)) {
365
+ const chainTokens = tokensState.allTokens[chainId];
366
+ const accountTokens = chainTokens?.[account.address];
367
+ const token = accountTokens?.find((t) => t.address === tokenAddress);
368
+ if (!token) {
369
+ continue;
370
+ }
371
+ const decimals = typeof token.decimals === 'number' && !Number.isNaN(token.decimals)
372
+ ? token.decimals
373
+ : 18;
374
+ const balanceInSmallestUnit = parseInt(balance, 16);
375
+ if (Number.isNaN(balanceInSmallestUnit)) {
376
+ continue;
377
+ }
378
+ const balanceInTokenUnits = balanceInSmallestUnit / Math.pow(10, decimals);
379
+ const tokenMarketData = chainMarketData?.[tokenAddress];
380
+ const price = tokenMarketData?.price;
381
+ const percentRaw = tokenMarketData?.[evmPercentField[period]];
382
+ if (!isNonNaNNumber(price)) {
383
+ continue;
384
+ }
385
+ const nativeCurrency = tokenMarketData?.currency;
386
+ const nativeToUserRate = nativeCurrency && currencyRateState.currencyRates[nativeCurrency]
387
+ ? currencyRateState.currencyRates[nativeCurrency]?.conversionRate
388
+ : undefined;
389
+ if (!isNonNaNNumber(nativeToUserRate)) {
390
+ continue;
391
+ }
392
+ if (!isNonNaNNumber(percentRaw)) {
393
+ continue;
394
+ }
395
+ const priceInUserCurrency = price * nativeToUserRate;
396
+ const currentValue = balanceInTokenUnits * priceInUserCurrency;
397
+ const denom = Number((1 + percentRaw / 100).toFixed(8));
398
+ if (denom === 0) {
399
+ continue;
400
+ }
401
+ const previousValue = currentValue / denom;
402
+ currentTotal += currentValue;
403
+ previousTotal += previousValue;
404
+ }
405
+ }
406
+ }
407
+ else {
408
+ const accountBalances = multichainBalancesState.balances[account.id];
409
+ if (!accountBalances) {
410
+ continue;
411
+ }
412
+ for (const [assetId, balanceData] of Object.entries(accountBalances)) {
413
+ if (!isAssetChainEnabled(assetId)) {
414
+ continue;
415
+ }
416
+ const balanceAmount = parseFloat(balanceData.amount);
417
+ if (Number.isNaN(balanceAmount)) {
418
+ continue;
419
+ }
420
+ const conversionRate = multichainRatesState.conversionRates[assetId];
421
+ const rateStr = conversionRate?.rate;
422
+ const percentObj = conversionRate?.marketData?.pricePercentChange;
423
+ const percentRaw = percentObj?.[nonEvmPercentKey[period]];
424
+ const rate = typeof rateStr === 'string' ? parseFloat(rateStr) : undefined;
425
+ if (!isNonNaNNumber(rate) || !isNonNaNNumber(percentRaw)) {
426
+ continue;
427
+ }
428
+ const currentValue = balanceAmount * rate;
429
+ const denom = Number((1 + percentRaw / 100).toFixed(8));
430
+ if (denom === 0) {
431
+ continue;
432
+ }
433
+ const previousValue = currentValue / denom;
434
+ currentTotal += currentValue;
435
+ previousTotal += previousValue;
436
+ }
437
+ }
438
+ }
439
+ const amountChange = currentTotal - previousTotal;
440
+ const percentChange = previousTotal !== 0 ? (amountChange / previousTotal) * 100 : 0;
441
+ return {
442
+ period,
443
+ currentTotalInUserCurrency: Number(currentTotal.toFixed(8)),
444
+ previousTotalInUserCurrency: Number(previousTotal.toFixed(8)),
445
+ amountChangeInUserCurrency: Number(amountChange.toFixed(8)),
446
+ percentChange: Number(percentChange.toFixed(8)),
447
+ userCurrency: currencyRateState.currentCurrency,
448
+ };
449
+ }
450
+ exports.calculateAggregatedChangeForGroup = calculateAggregatedChangeForGroup;
451
+ //# sourceMappingURL=balances.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balances.cjs","sourceRoot":"","sources":["../src/balances.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerState } from '@metamask/account-tree-controller';\nimport type { AccountWalletObject } from '@metamask/account-tree-controller';\nimport type { AccountGroupObject } from '@metamask/account-tree-controller';\nimport type { AccountsControllerState } from '@metamask/accounts-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { EntropySourceId } from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Hex } from '@metamask/utils';\nimport type { CaipAssetType, CaipChainId } from '@metamask/utils';\nimport {\n KnownCaipNamespace,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\n\nimport type { CurrencyRateState } from './CurrencyRateController';\nimport type { MultichainAssetsRatesControllerState } from './MultichainAssetsRatesController';\nimport type { MultichainBalancesControllerState } from './MultichainBalancesController';\nimport type { TokenBalancesControllerState } from './TokenBalancesController';\nimport type { TokenRatesControllerState } from './TokenRatesController';\nimport type { TokensControllerState } from './TokensController';\n\nexport type AccountGroupBalance = {\n walletId: string;\n groupId: string;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type WalletBalance = {\n walletId: string;\n groups: Record<string, AccountGroupBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type AllWalletsBalance = {\n wallets: Record<string, WalletBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type PortfolioChangePeriod = '1d' | '7d' | '30d';\n\nexport type AggregatedChangeForAllWallets = {\n period: PortfolioChangePeriod;\n currentTotalInUserCurrency: number;\n previousTotalInUserCurrency: number;\n amountChangeInUserCurrency: number;\n percentChange: number;\n userCurrency: string;\n};\n\nconst isChainEnabledByMap = (\n map: Record<string, Record<string, boolean>> | undefined,\n id: Hex | CaipChainId,\n): boolean => {\n if (!map) {\n return true;\n }\n const isHex = typeof id === 'string' && id.startsWith('0x');\n if (isHex) {\n const evm = map[String(KnownCaipNamespace.Eip155)];\n return Boolean(evm?.[id as Hex]);\n }\n const { namespace } = parseCaipChainId(id as CaipChainId);\n return Boolean(map[namespace]?.[id as CaipChainId]);\n};\n\nconst getInternalAccountsForGroup = (\n accountTreeState: AccountTreeControllerState,\n accountsState: AccountsControllerState,\n groupId: string,\n) => {\n const walletId = groupId.split('/')[0] as EntropySourceId;\n const wallet = (\n accountTreeState.accountTree.wallets as Record<string, AccountWalletObject>\n )[walletId];\n if (!wallet) {\n return [] as InternalAccount[];\n }\n const group = (wallet.groups as Record<string, AccountGroupObject>)[groupId];\n if (!group) {\n return [] as InternalAccount[];\n }\n return group.accounts\n .map(\n (accountId: string) => accountsState.internalAccounts.accounts[accountId],\n )\n .filter(Boolean) as InternalAccount[];\n};\n\nconst isNonNaNNumber = (value: unknown): value is number =>\n typeof value === 'number' && !Number.isNaN(value);\n\n/**\n * Calculate balances for all wallets and groups.\n * Pure function – accepts controller states and returns aggregated totals.\n *\n * @param accountTreeState - AccountTreeController state\n * @param accountsState - AccountsController state\n * @param tokenBalancesState - TokenBalancesController state\n * @param tokenRatesState - TokenRatesController state\n * @param multichainRatesState - MultichainAssetsRatesController state\n * @param multichainBalancesState - MultichainBalancesController state\n * @param tokensState - TokensController state\n * @param currencyRateState - CurrencyRateController state\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace\n * @returns Aggregated balances for all wallets\n */\nexport function calculateBalanceForAllWallets(\n accountTreeState: AccountTreeControllerState,\n accountsState: AccountsControllerState,\n tokenBalancesState: TokenBalancesControllerState,\n tokenRatesState: TokenRatesControllerState,\n multichainRatesState: MultichainAssetsRatesControllerState,\n multichainBalancesState: MultichainBalancesControllerState,\n tokensState: TokensControllerState,\n currencyRateState: CurrencyRateState,\n enabledNetworkMap: Record<string, Record<string, boolean>> | undefined,\n): AllWalletsBalance {\n const walletBalances: Record<string, WalletBalance> = {};\n let totalBalanceInUserCurrency = 0;\n\n const isEvmChainEnabled = (chainId: Hex): boolean =>\n isChainEnabledByMap(enabledNetworkMap, chainId);\n\n const isAssetChainEnabled = (assetId: CaipAssetType): boolean => {\n const { chainId } = parseCaipAssetType(assetId);\n return isChainEnabledByMap(enabledNetworkMap, chainId);\n };\n\n const walletIds = Object.keys(\n accountTreeState.accountTree.wallets,\n ) as string[];\n\n for (const walletId of walletIds) {\n const wallet = (\n accountTreeState.accountTree.wallets as Record<\n string,\n AccountWalletObject\n >\n )[walletId];\n if (!wallet) {\n continue;\n }\n\n const groupBalances: Record<string, AccountGroupBalance> = {};\n let walletTotalBalance = 0;\n\n const groups = Object.keys(wallet.groups || {}) as string[];\n for (const groupId of groups) {\n const accounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsState,\n groupId,\n );\n\n if (accounts.length === 0) {\n groupBalances[groupId] = {\n walletId,\n groupId,\n totalBalanceInUserCurrency: 0,\n userCurrency: currencyRateState.currentCurrency,\n };\n continue;\n }\n\n let groupTotalBalance = 0;\n\n for (const account of accounts) {\n const isEvmAccount = isEvmAccountType(account.type);\n if (isEvmAccount) {\n const accountBalances =\n tokenBalancesState.tokenBalances[account.address as Hex];\n if (accountBalances) {\n for (const [chainId, chainBalances] of Object.entries(\n accountBalances,\n )) {\n if (!isEvmChainEnabled(chainId as Hex)) {\n continue;\n }\n for (const [tokenAddress, balance] of Object.entries(\n chainBalances,\n )) {\n const chainTokens = tokensState.allTokens[chainId as Hex];\n const accountTokens = chainTokens?.[account.address];\n const token = accountTokens?.find(\n (t) => t.address === tokenAddress,\n );\n if (!token) {\n continue;\n }\n const decimals =\n typeof token.decimals === 'number' &&\n !Number.isNaN(token.decimals)\n ? token.decimals\n : 18;\n const balanceInSmallestUnit = parseInt(balance as string, 16);\n if (Number.isNaN(balanceInSmallestUnit)) {\n continue;\n }\n const balanceInTokenUnits =\n balanceInSmallestUnit / Math.pow(10, decimals);\n const chainMarketData =\n tokenRatesState.marketData[chainId as Hex];\n const tokenMarketData = chainMarketData?.[tokenAddress as Hex];\n if (tokenMarketData?.price) {\n const nativeCurrency = tokenMarketData.currency;\n const nativeToUserRate =\n currencyRateState.currencyRates[nativeCurrency]\n ?.conversionRate;\n if (nativeToUserRate) {\n const tokenPriceInUserCurrency =\n tokenMarketData.price * nativeToUserRate;\n const balanceInUserCurrency =\n balanceInTokenUnits * tokenPriceInUserCurrency;\n groupTotalBalance += balanceInUserCurrency;\n }\n }\n }\n }\n }\n } else {\n const accountBalances = multichainBalancesState.balances[account.id];\n if (accountBalances) {\n for (const [assetId, balanceData] of Object.entries(\n accountBalances,\n )) {\n if (!isAssetChainEnabled(assetId as CaipAssetType)) {\n continue;\n }\n const balanceAmount = parseFloat(balanceData.amount);\n if (Number.isNaN(balanceAmount)) {\n continue;\n }\n const conversionRate =\n multichainRatesState.conversionRates[assetId as CaipAssetType];\n if (conversionRate) {\n const conversionRateValue = parseFloat(conversionRate.rate);\n if (Number.isNaN(conversionRateValue)) {\n continue;\n }\n const balanceInUserCurrency =\n balanceAmount * conversionRateValue;\n groupTotalBalance += balanceInUserCurrency;\n }\n }\n }\n }\n }\n\n groupBalances[groupId] = {\n walletId,\n groupId,\n totalBalanceInUserCurrency: groupTotalBalance,\n userCurrency: currencyRateState.currentCurrency,\n };\n walletTotalBalance += groupTotalBalance;\n }\n\n walletBalances[walletId] = {\n walletId,\n groups: groupBalances,\n totalBalanceInUserCurrency: walletTotalBalance,\n userCurrency: currencyRateState.currentCurrency,\n };\n totalBalanceInUserCurrency += walletTotalBalance;\n }\n\n return {\n wallets: walletBalances,\n totalBalanceInUserCurrency,\n userCurrency: currencyRateState.currentCurrency,\n };\n}\n\n/**\n * Calculate aggregated portfolio value change for a given period (1d, 7d, 30d).\n * Logic mirrors extension/mobile historical aggregation:\n * - For each asset with available percent change for the requested period, compute current value in user currency.\n * - Reconstruct previous value by dividing current by (1 + percent/100).\n * - Sum across all assets, then compute amount change and percent change.\n *\n * @param accountTreeState - AccountTreeController state.\n * @param accountsState - AccountsController state.\n * @param tokenBalancesState - TokenBalancesController state.\n * @param tokenRatesState - TokenRatesController state.\n * @param multichainRatesState - MultichainAssetsRatesController state.\n * @param multichainBalancesState - MultichainBalancesController state.\n * @param tokensState - TokensController state.\n * @param currencyRateState - CurrencyRateController state.\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param period - Period to compute change for ('1d' | '7d' | '30d').\n * @returns Aggregated change details for the requested period.\n */\nexport function calculateAggregatedChangeForAllWallets(\n accountTreeState: AccountTreeControllerState,\n accountsState: AccountsControllerState,\n tokenBalancesState: TokenBalancesControllerState,\n tokenRatesState: TokenRatesControllerState,\n multichainRatesState: MultichainAssetsRatesControllerState,\n multichainBalancesState: MultichainBalancesControllerState,\n tokensState: TokensControllerState,\n currencyRateState: CurrencyRateState,\n enabledNetworkMap: Record<string, Record<string, boolean>> | undefined,\n period: PortfolioChangePeriod,\n): AggregatedChangeForAllWallets {\n let currentTotal = 0;\n let previousTotal = 0;\n\n const isEvmChainEnabled = (chainId: Hex): boolean =>\n isChainEnabledByMap(enabledNetworkMap, chainId);\n\n const isAssetChainEnabled = (assetId: CaipAssetType): boolean => {\n const { chainId } = parseCaipAssetType(assetId);\n return isChainEnabledByMap(enabledNetworkMap, chainId);\n };\n\n const evmPercentField: Record<\n PortfolioChangePeriod,\n keyof ReturnType<() => never> | string\n > = {\n '1d': 'pricePercentChange1d',\n '7d': 'pricePercentChange7d',\n '30d': 'pricePercentChange30d',\n };\n\n const nonEvmPercentKey: Record<PortfolioChangePeriod, string> = {\n '1d': 'P1D',\n '7d': 'P7D',\n '30d': 'P30D',\n };\n\n const walletIds = Object.keys(\n accountTreeState.accountTree.wallets,\n ) as string[];\n\n for (const walletId of walletIds) {\n const wallet = (\n accountTreeState.accountTree.wallets as Record<\n string,\n AccountWalletObject\n >\n )[walletId];\n if (!wallet) {\n continue;\n }\n\n const groups = Object.keys(wallet.groups || {}) as string[];\n for (const groupId of groups) {\n const accounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsState,\n groupId,\n );\n\n for (const account of accounts) {\n const isEvmAccount = isEvmAccountType(account.type);\n\n if (isEvmAccount) {\n const accountBalances =\n tokenBalancesState.tokenBalances[account.address as Hex];\n if (!accountBalances) {\n continue;\n }\n\n for (const [chainId, chainBalances] of Object.entries(\n accountBalances,\n )) {\n if (!isEvmChainEnabled(chainId as Hex)) {\n continue;\n }\n const chainMarketData = tokenRatesState.marketData[chainId as Hex];\n\n for (const [tokenAddress, balance] of Object.entries(\n chainBalances,\n )) {\n const chainTokens = tokensState.allTokens[chainId as Hex];\n const accountTokens = chainTokens?.[account.address];\n const token = accountTokens?.find(\n (t) => t.address === tokenAddress,\n );\n if (!token) {\n continue;\n }\n\n const decimals =\n typeof token.decimals === 'number' &&\n !Number.isNaN(token.decimals)\n ? token.decimals\n : 18;\n\n const balanceInSmallestUnit = parseInt(balance as string, 16);\n if (Number.isNaN(balanceInSmallestUnit)) {\n continue;\n }\n\n const balanceInTokenUnits =\n balanceInSmallestUnit / Math.pow(10, decimals);\n\n const tokenMarketData = chainMarketData?.[tokenAddress as Hex];\n const price = tokenMarketData?.price as number | undefined;\n const percentRaw = (\n tokenMarketData as unknown as Record<string, unknown>\n )?.[evmPercentField[period] as string] as number | undefined;\n\n if (typeof price !== 'number' || Number.isNaN(price)) {\n continue;\n }\n\n const nativeCurrency = (\n tokenMarketData as unknown as { currency?: string }\n )?.currency;\n const nativeToUserRate =\n nativeCurrency &&\n currencyRateState.currencyRates[nativeCurrency]\n ? currencyRateState.currencyRates[nativeCurrency]\n ?.conversionRate\n : undefined;\n\n if (\n typeof nativeToUserRate !== 'number' ||\n Number.isNaN(nativeToUserRate)\n ) {\n continue;\n }\n\n if (!isNonNaNNumber(percentRaw)) {\n continue;\n }\n\n const priceInUserCurrency = price * nativeToUserRate;\n const currentValue = balanceInTokenUnits * priceInUserCurrency;\n const denom = Number((1 + percentRaw / 100).toFixed(8));\n if (denom === 0) {\n continue;\n }\n const previousValue = currentValue / denom;\n currentTotal += currentValue;\n previousTotal += previousValue;\n }\n }\n } else {\n const accountBalances = multichainBalancesState.balances[account.id];\n if (!accountBalances) {\n continue;\n }\n\n for (const [assetId, balanceData] of Object.entries(\n accountBalances,\n )) {\n if (!isAssetChainEnabled(assetId as CaipAssetType)) {\n continue;\n }\n\n const balanceAmount = parseFloat(balanceData.amount);\n if (Number.isNaN(balanceAmount)) {\n continue;\n }\n\n const conversionRate =\n multichainRatesState.conversionRates[assetId as CaipAssetType];\n const rateStr = conversionRate?.rate as string | undefined;\n const percentObj = (\n conversionRate as unknown as {\n marketData?: { pricePercentChange?: Record<string, number> };\n }\n )?.marketData?.pricePercentChange;\n const percentRaw = percentObj?.[nonEvmPercentKey[period]];\n\n const rate =\n typeof rateStr === 'string' ? parseFloat(rateStr) : undefined;\n if (!isNonNaNNumber(rate) || !isNonNaNNumber(percentRaw)) {\n continue;\n }\n\n const currentValue = balanceAmount * rate;\n const denom = Number((1 + percentRaw / 100).toFixed(8));\n if (denom === 0) {\n continue;\n }\n const previousValue = currentValue / denom;\n currentTotal += currentValue;\n previousTotal += previousValue;\n }\n }\n }\n }\n }\n\n const amountChange = currentTotal - previousTotal;\n const percentChange =\n previousTotal !== 0 ? (amountChange / previousTotal) * 100 : 0;\n\n return {\n period,\n currentTotalInUserCurrency: Number(currentTotal.toFixed(8)),\n previousTotalInUserCurrency: Number(previousTotal.toFixed(8)),\n amountChangeInUserCurrency: Number(amountChange.toFixed(8)),\n percentChange: Number(percentChange.toFixed(8)),\n userCurrency: currencyRateState.currentCurrency,\n };\n}\n\n/**\n * Calculate aggregated portfolio value change for a specific account group and period.\n * Mirrors the logic of calculateAggregatedChangeForAllWallets but scoped to one group.\n *\n * @param accountTreeState - AccountTreeController state.\n * @param accountsState - AccountsController state.\n * @param tokenBalancesState - TokenBalancesController state.\n * @param tokenRatesState - TokenRatesController state.\n * @param multichainRatesState - MultichainAssetsRatesController state.\n * @param multichainBalancesState - MultichainBalancesController state.\n * @param tokensState - TokensController state.\n * @param currencyRateState - CurrencyRateController state.\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param groupId - The account group ID to compute change for.\n * @param period - Period to compute change for ('1d' | '7d' | '30d').\n * @returns Aggregated change details for the requested group and period.\n */\nexport function calculateAggregatedChangeForGroup(\n accountTreeState: AccountTreeControllerState,\n accountsState: AccountsControllerState,\n tokenBalancesState: TokenBalancesControllerState,\n tokenRatesState: TokenRatesControllerState,\n multichainRatesState: MultichainAssetsRatesControllerState,\n multichainBalancesState: MultichainBalancesControllerState,\n tokensState: TokensControllerState,\n currencyRateState: CurrencyRateState,\n enabledNetworkMap: Record<string, Record<string, boolean>> | undefined,\n groupId: string,\n period: PortfolioChangePeriod,\n): AggregatedChangeForAllWallets {\n let currentTotal = 0;\n let previousTotal = 0;\n\n const isEvmChainEnabled = (chainId: Hex): boolean =>\n isChainEnabledByMap(enabledNetworkMap, chainId);\n\n const isAssetChainEnabled = (assetId: CaipAssetType): boolean => {\n const { chainId } = parseCaipAssetType(assetId);\n return isChainEnabledByMap(enabledNetworkMap, chainId);\n };\n\n const evmPercentField: Record<PortfolioChangePeriod, string> = {\n '1d': 'pricePercentChange1d',\n '7d': 'pricePercentChange7d',\n '30d': 'pricePercentChange30d',\n };\n\n const nonEvmPercentKey: Record<PortfolioChangePeriod, string> = {\n '1d': 'P1D',\n '7d': 'P7D',\n '30d': 'P30D',\n };\n\n const accounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsState,\n groupId,\n );\n\n for (const account of accounts) {\n const isEvmAccount = isEvmAccountType(account.type);\n\n if (isEvmAccount) {\n const accountBalances =\n tokenBalancesState.tokenBalances[account.address as Hex];\n if (!accountBalances) {\n continue;\n }\n\n for (const [chainId, chainBalances] of Object.entries(accountBalances)) {\n if (!isEvmChainEnabled(chainId as Hex)) {\n continue;\n }\n const chainMarketData = tokenRatesState.marketData[chainId as Hex];\n\n for (const [tokenAddress, balance] of Object.entries(chainBalances)) {\n const chainTokens = tokensState.allTokens[chainId as Hex];\n const accountTokens = chainTokens?.[account.address];\n const token = accountTokens?.find((t) => t.address === tokenAddress);\n if (!token) {\n continue;\n }\n\n const decimals =\n typeof token.decimals === 'number' && !Number.isNaN(token.decimals)\n ? token.decimals\n : 18;\n\n const balanceInSmallestUnit = parseInt(balance as string, 16);\n if (Number.isNaN(balanceInSmallestUnit)) {\n continue;\n }\n\n const balanceInTokenUnits =\n balanceInSmallestUnit / Math.pow(10, decimals);\n\n const tokenMarketData = chainMarketData?.[tokenAddress as Hex];\n const price = tokenMarketData?.price as number | undefined;\n const percentRaw = (\n tokenMarketData as unknown as Record<string, unknown>\n )?.[evmPercentField[period] as string] as number | undefined;\n\n if (!isNonNaNNumber(price)) {\n continue;\n }\n\n const nativeCurrency = (\n tokenMarketData as unknown as { currency?: string }\n )?.currency;\n const nativeToUserRate =\n nativeCurrency && currencyRateState.currencyRates[nativeCurrency]\n ? currencyRateState.currencyRates[nativeCurrency]?.conversionRate\n : undefined;\n\n if (!isNonNaNNumber(nativeToUserRate)) {\n continue;\n }\n\n if (!isNonNaNNumber(percentRaw)) {\n continue;\n }\n\n const priceInUserCurrency = price * nativeToUserRate;\n const currentValue = balanceInTokenUnits * priceInUserCurrency;\n const denom = Number((1 + percentRaw / 100).toFixed(8));\n if (denom === 0) {\n continue;\n }\n const previousValue = currentValue / denom;\n currentTotal += currentValue;\n previousTotal += previousValue;\n }\n }\n } else {\n const accountBalances = multichainBalancesState.balances[account.id];\n if (!accountBalances) {\n continue;\n }\n\n for (const [assetId, balanceData] of Object.entries(accountBalances)) {\n if (!isAssetChainEnabled(assetId as CaipAssetType)) {\n continue;\n }\n\n const balanceAmount = parseFloat(balanceData.amount);\n if (Number.isNaN(balanceAmount)) {\n continue;\n }\n\n const conversionRate =\n multichainRatesState.conversionRates[assetId as CaipAssetType];\n const rateStr = conversionRate?.rate as string | undefined;\n const percentObj = (\n conversionRate as unknown as {\n marketData?: { pricePercentChange?: Record<string, number> };\n }\n )?.marketData?.pricePercentChange;\n const percentRaw = percentObj?.[nonEvmPercentKey[period]];\n\n const rate =\n typeof rateStr === 'string' ? parseFloat(rateStr) : undefined;\n if (!isNonNaNNumber(rate) || !isNonNaNNumber(percentRaw)) {\n continue;\n }\n\n const currentValue = balanceAmount * rate;\n const denom = Number((1 + percentRaw / 100).toFixed(8));\n if (denom === 0) {\n continue;\n }\n const previousValue = currentValue / denom;\n currentTotal += currentValue;\n previousTotal += previousValue;\n }\n }\n }\n\n const amountChange = currentTotal - previousTotal;\n const percentChange =\n previousTotal !== 0 ? (amountChange / previousTotal) * 100 : 0;\n\n return {\n period,\n currentTotalInUserCurrency: Number(currentTotal.toFixed(8)),\n previousTotalInUserCurrency: Number(previousTotal.toFixed(8)),\n amountChangeInUserCurrency: Number(amountChange.toFixed(8)),\n percentChange: Number(percentChange.toFixed(8)),\n userCurrency: currencyRateState.currentCurrency,\n };\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import type { AccountTreeControllerState } from "@metamask/account-tree-controller";
2
+ import type { AccountsControllerState } from "@metamask/accounts-controller";
3
+ import type { CurrencyRateState } from "./CurrencyRateController.cjs";
4
+ import type { MultichainAssetsRatesControllerState } from "./MultichainAssetsRatesController/index.cjs";
5
+ import type { MultichainBalancesControllerState } from "./MultichainBalancesController/index.cjs";
6
+ import type { TokenBalancesControllerState } from "./TokenBalancesController.cjs";
7
+ import type { TokenRatesControllerState } from "./TokenRatesController.cjs";
8
+ import type { TokensControllerState } from "./TokensController.cjs";
9
+ export type AccountGroupBalance = {
10
+ walletId: string;
11
+ groupId: string;
12
+ totalBalanceInUserCurrency: number;
13
+ userCurrency: string;
14
+ };
15
+ export type WalletBalance = {
16
+ walletId: string;
17
+ groups: Record<string, AccountGroupBalance>;
18
+ totalBalanceInUserCurrency: number;
19
+ userCurrency: string;
20
+ };
21
+ export type AllWalletsBalance = {
22
+ wallets: Record<string, WalletBalance>;
23
+ totalBalanceInUserCurrency: number;
24
+ userCurrency: string;
25
+ };
26
+ export type PortfolioChangePeriod = '1d' | '7d' | '30d';
27
+ export type AggregatedChangeForAllWallets = {
28
+ period: PortfolioChangePeriod;
29
+ currentTotalInUserCurrency: number;
30
+ previousTotalInUserCurrency: number;
31
+ amountChangeInUserCurrency: number;
32
+ percentChange: number;
33
+ userCurrency: string;
34
+ };
35
+ /**
36
+ * Calculate balances for all wallets and groups.
37
+ * Pure function – accepts controller states and returns aggregated totals.
38
+ *
39
+ * @param accountTreeState - AccountTreeController state
40
+ * @param accountsState - AccountsController state
41
+ * @param tokenBalancesState - TokenBalancesController state
42
+ * @param tokenRatesState - TokenRatesController state
43
+ * @param multichainRatesState - MultichainAssetsRatesController state
44
+ * @param multichainBalancesState - MultichainBalancesController state
45
+ * @param tokensState - TokensController state
46
+ * @param currencyRateState - CurrencyRateController state
47
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace
48
+ * @returns Aggregated balances for all wallets
49
+ */
50
+ export declare function calculateBalanceForAllWallets(accountTreeState: AccountTreeControllerState, accountsState: AccountsControllerState, tokenBalancesState: TokenBalancesControllerState, tokenRatesState: TokenRatesControllerState, multichainRatesState: MultichainAssetsRatesControllerState, multichainBalancesState: MultichainBalancesControllerState, tokensState: TokensControllerState, currencyRateState: CurrencyRateState, enabledNetworkMap: Record<string, Record<string, boolean>> | undefined): AllWalletsBalance;
51
+ /**
52
+ * Calculate aggregated portfolio value change for a given period (1d, 7d, 30d).
53
+ * Logic mirrors extension/mobile historical aggregation:
54
+ * - For each asset with available percent change for the requested period, compute current value in user currency.
55
+ * - Reconstruct previous value by dividing current by (1 + percent/100).
56
+ * - Sum across all assets, then compute amount change and percent change.
57
+ *
58
+ * @param accountTreeState - AccountTreeController state.
59
+ * @param accountsState - AccountsController state.
60
+ * @param tokenBalancesState - TokenBalancesController state.
61
+ * @param tokenRatesState - TokenRatesController state.
62
+ * @param multichainRatesState - MultichainAssetsRatesController state.
63
+ * @param multichainBalancesState - MultichainBalancesController state.
64
+ * @param tokensState - TokensController state.
65
+ * @param currencyRateState - CurrencyRateController state.
66
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace.
67
+ * @param period - Period to compute change for ('1d' | '7d' | '30d').
68
+ * @returns Aggregated change details for the requested period.
69
+ */
70
+ export declare function calculateAggregatedChangeForAllWallets(accountTreeState: AccountTreeControllerState, accountsState: AccountsControllerState, tokenBalancesState: TokenBalancesControllerState, tokenRatesState: TokenRatesControllerState, multichainRatesState: MultichainAssetsRatesControllerState, multichainBalancesState: MultichainBalancesControllerState, tokensState: TokensControllerState, currencyRateState: CurrencyRateState, enabledNetworkMap: Record<string, Record<string, boolean>> | undefined, period: PortfolioChangePeriod): AggregatedChangeForAllWallets;
71
+ /**
72
+ * Calculate aggregated portfolio value change for a specific account group and period.
73
+ * Mirrors the logic of calculateAggregatedChangeForAllWallets but scoped to one group.
74
+ *
75
+ * @param accountTreeState - AccountTreeController state.
76
+ * @param accountsState - AccountsController state.
77
+ * @param tokenBalancesState - TokenBalancesController state.
78
+ * @param tokenRatesState - TokenRatesController state.
79
+ * @param multichainRatesState - MultichainAssetsRatesController state.
80
+ * @param multichainBalancesState - MultichainBalancesController state.
81
+ * @param tokensState - TokensController state.
82
+ * @param currencyRateState - CurrencyRateController state.
83
+ * @param enabledNetworkMap - Map of enabled networks keyed by namespace.
84
+ * @param groupId - The account group ID to compute change for.
85
+ * @param period - Period to compute change for ('1d' | '7d' | '30d').
86
+ * @returns Aggregated change details for the requested group and period.
87
+ */
88
+ export declare function calculateAggregatedChangeForGroup(accountTreeState: AccountTreeControllerState, accountsState: AccountsControllerState, tokenBalancesState: TokenBalancesControllerState, tokenRatesState: TokenRatesControllerState, multichainRatesState: MultichainAssetsRatesControllerState, multichainBalancesState: MultichainBalancesControllerState, tokensState: TokensControllerState, currencyRateState: CurrencyRateState, enabledNetworkMap: Record<string, Record<string, boolean>> | undefined, groupId: string, period: PortfolioChangePeriod): AggregatedChangeForAllWallets;
89
+ //# sourceMappingURL=balances.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balances.d.cts","sourceRoot":"","sources":["../src/balances.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,0CAA0C;AAGpF,OAAO,KAAK,EAAE,uBAAuB,EAAE,sCAAsC;AAY7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,qCAAiC;AAClE,OAAO,KAAK,EAAE,oCAAoC,EAAE,oDAA0C;AAC9F,OAAO,KAAK,EAAE,iCAAiC,EAAE,iDAAuC;AACxF,OAAO,KAAK,EAAE,4BAA4B,EAAE,sCAAkC;AAC9E,OAAO,KAAK,EAAE,yBAAyB,EAAE,mCAA+B;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,+BAA2B;AAEhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5C,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;AAExD,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,qBAAqB,CAAC;IAC9B,0BAA0B,EAAE,MAAM,CAAC;IACnC,2BAA2B,EAAE,MAAM,CAAC;IACpC,0BAA0B,EAAE,MAAM,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AA4CF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,6BAA6B,CAC3C,gBAAgB,EAAE,0BAA0B,EAC5C,aAAa,EAAE,uBAAuB,EACtC,kBAAkB,EAAE,4BAA4B,EAChD,eAAe,EAAE,yBAAyB,EAC1C,oBAAoB,EAAE,oCAAoC,EAC1D,uBAAuB,EAAE,iCAAiC,EAC1D,WAAW,EAAE,qBAAqB,EAClC,iBAAiB,EAAE,iBAAiB,EACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GACrE,iBAAiB,CA2JnB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sCAAsC,CACpD,gBAAgB,EAAE,0BAA0B,EAC5C,aAAa,EAAE,uBAAuB,EACtC,kBAAkB,EAAE,4BAA4B,EAChD,eAAe,EAAE,yBAAyB,EAC1C,oBAAoB,EAAE,oCAAoC,EAC1D,uBAAuB,EAAE,iCAAiC,EAC1D,WAAW,EAAE,qBAAqB,EAClC,iBAAiB,EAAE,iBAAiB,EACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EACtE,MAAM,EAAE,qBAAqB,GAC5B,6BAA6B,CAoM/B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iCAAiC,CAC/C,gBAAgB,EAAE,0BAA0B,EAC5C,aAAa,EAAE,uBAAuB,EACtC,kBAAkB,EAAE,4BAA4B,EAChD,eAAe,EAAE,yBAAyB,EAC1C,oBAAoB,EAAE,oCAAoC,EAC1D,uBAAuB,EAAE,iCAAiC,EAC1D,WAAW,EAAE,qBAAqB,EAClC,iBAAiB,EAAE,iBAAiB,EACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,EACtE,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,6BAA6B,CAgK/B"}