@metamask/assets-controllers 73.1.0 → 73.2.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/dist/AccountTrackerController.cjs +51 -1
  3. package/dist/AccountTrackerController.cjs.map +1 -1
  4. package/dist/AccountTrackerController.d.cts +40 -1
  5. package/dist/AccountTrackerController.d.cts.map +1 -1
  6. package/dist/AccountTrackerController.d.mts +40 -1
  7. package/dist/AccountTrackerController.d.mts.map +1 -1
  8. package/dist/AccountTrackerController.mjs +51 -1
  9. package/dist/AccountTrackerController.mjs.map +1 -1
  10. package/dist/TokenBalancesController.cjs +278 -319
  11. package/dist/TokenBalancesController.cjs.map +1 -1
  12. package/dist/TokenBalancesController.d.cts +51 -93
  13. package/dist/TokenBalancesController.d.cts.map +1 -1
  14. package/dist/TokenBalancesController.d.mts +51 -93
  15. package/dist/TokenBalancesController.d.mts.map +1 -1
  16. package/dist/TokenBalancesController.mjs +277 -317
  17. package/dist/TokenBalancesController.mjs.map +1 -1
  18. package/dist/assetsUtil.cjs +13 -1
  19. package/dist/assetsUtil.cjs.map +1 -1
  20. package/dist/assetsUtil.d.cts +8 -0
  21. package/dist/assetsUtil.d.cts.map +1 -1
  22. package/dist/assetsUtil.d.mts +8 -0
  23. package/dist/assetsUtil.d.mts.map +1 -1
  24. package/dist/assetsUtil.mjs +12 -1
  25. package/dist/assetsUtil.mjs.map +1 -1
  26. package/dist/balances.cjs +447 -0
  27. package/dist/balances.cjs.map +1 -0
  28. package/dist/balances.d.cts +88 -0
  29. package/dist/balances.d.cts.map +1 -0
  30. package/dist/balances.d.mts +88 -0
  31. package/dist/balances.d.mts.map +1 -0
  32. package/dist/balances.mjs +441 -0
  33. package/dist/balances.mjs.map +1 -0
  34. package/dist/constants.cjs +13 -1
  35. package/dist/constants.cjs.map +1 -1
  36. package/dist/constants.d.cts +1 -0
  37. package/dist/constants.d.cts.map +1 -1
  38. package/dist/constants.d.mts +1 -0
  39. package/dist/constants.d.mts.map +1 -1
  40. package/dist/constants.mjs +12 -0
  41. package/dist/constants.mjs.map +1 -1
  42. package/dist/index.cjs +6 -3
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +6 -4
  45. package/dist/index.d.cts.map +1 -1
  46. package/dist/index.d.mts +6 -4
  47. package/dist/index.d.mts.map +1 -1
  48. package/dist/index.mjs +2 -1
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +247 -0
  51. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +1 -0
  52. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts +30 -0
  53. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +1 -0
  54. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts +30 -0
  55. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +1 -0
  56. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +247 -0
  57. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +1 -0
  58. package/dist/multi-chain-accounts-service/multi-chain-accounts.cjs +35 -1
  59. package/dist/multi-chain-accounts-service/multi-chain-accounts.cjs.map +1 -1
  60. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.cts +16 -0
  61. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.cts.map +1 -1
  62. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.mts +16 -0
  63. package/dist/multi-chain-accounts-service/multi-chain-accounts.d.mts.map +1 -1
  64. package/dist/multi-chain-accounts-service/multi-chain-accounts.mjs +33 -0
  65. package/dist/multi-chain-accounts-service/multi-chain-accounts.mjs.map +1 -1
  66. package/dist/multi-chain-accounts-service/types.cjs.map +1 -1
  67. package/dist/multi-chain-accounts-service/types.d.cts +8 -0
  68. package/dist/multi-chain-accounts-service/types.d.cts.map +1 -1
  69. package/dist/multi-chain-accounts-service/types.d.mts +8 -0
  70. package/dist/multi-chain-accounts-service/types.d.mts.map +1 -1
  71. package/dist/multi-chain-accounts-service/types.mjs.map +1 -1
  72. package/dist/multicall.cjs +457 -1
  73. package/dist/multicall.cjs.map +1 -1
  74. package/dist/multicall.d.cts +51 -0
  75. package/dist/multicall.d.cts.map +1 -1
  76. package/dist/multicall.d.mts +51 -0
  77. package/dist/multicall.d.mts.map +1 -1
  78. package/dist/multicall.mjs +457 -0
  79. package/dist/multicall.mjs.map +1 -1
  80. package/dist/rpc-service/rpc-balance-fetcher.cjs +184 -0
  81. package/dist/rpc-service/rpc-balance-fetcher.cjs.map +1 -0
  82. package/dist/rpc-service/rpc-balance-fetcher.d.cts +34 -0
  83. package/dist/rpc-service/rpc-balance-fetcher.d.cts.map +1 -0
  84. package/dist/rpc-service/rpc-balance-fetcher.d.mts +34 -0
  85. package/dist/rpc-service/rpc-balance-fetcher.d.mts.map +1 -0
  86. package/dist/rpc-service/rpc-balance-fetcher.mjs +184 -0
  87. package/dist/rpc-service/rpc-balance-fetcher.mjs.map +1 -0
  88. package/package.json +9 -9
  89. package/dist/selectors/balanceSelectors.cjs +0 -328
  90. package/dist/selectors/balanceSelectors.cjs.map +0 -1
  91. package/dist/selectors/balanceSelectors.d.cts +0 -1676
  92. package/dist/selectors/balanceSelectors.d.cts.map +0 -1
  93. package/dist/selectors/balanceSelectors.d.mts +0 -1676
  94. package/dist/selectors/balanceSelectors.d.mts.map +0 -1
  95. package/dist/selectors/balanceSelectors.mjs +0 -321
  96. package/dist/selectors/balanceSelectors.mjs.map +0 -1
@@ -0,0 +1,184 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ 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");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _RpcBalanceFetcher_instances, _RpcBalanceFetcher_getProvider, _RpcBalanceFetcher_getNetworkClient, _RpcBalanceFetcher_getTokensState, _RpcBalanceFetcher_getStakingContractAddress, _RpcBalanceFetcher_ensureFreshBlockData;
13
+ function $importDefault(module) {
14
+ if (module?.__esModule) {
15
+ return module.default;
16
+ }
17
+ return module;
18
+ }
19
+ import { toChecksumHexAddress } from "@metamask/controller-utils";
20
+ import $BN from "bn.js";
21
+ const BN = $importDefault($BN);
22
+ import { STAKING_CONTRACT_ADDRESS_BY_CHAINID } from "../AssetsContractController.mjs";
23
+ import { getTokenBalancesForMultipleAddresses } from "../multicall.mjs";
24
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
25
+ const checksum = (addr) => toChecksumHexAddress(addr);
26
+ export class RpcBalanceFetcher {
27
+ constructor(getProvider, getNetworkClient, getTokensState) {
28
+ _RpcBalanceFetcher_instances.add(this);
29
+ _RpcBalanceFetcher_getProvider.set(this, void 0);
30
+ _RpcBalanceFetcher_getNetworkClient.set(this, void 0);
31
+ _RpcBalanceFetcher_getTokensState.set(this, void 0);
32
+ __classPrivateFieldSet(this, _RpcBalanceFetcher_getProvider, getProvider, "f");
33
+ __classPrivateFieldSet(this, _RpcBalanceFetcher_getNetworkClient, getNetworkClient, "f");
34
+ __classPrivateFieldSet(this, _RpcBalanceFetcher_getTokensState, getTokensState, "f");
35
+ }
36
+ supports() {
37
+ return true; // fallback – supports every chain
38
+ }
39
+ async fetch({ chainIds, queryAllAccounts, selectedAccount, allAccounts, }) {
40
+ const results = [];
41
+ for (const chainId of chainIds) {
42
+ const tokensState = __classPrivateFieldGet(this, _RpcBalanceFetcher_getTokensState, "f").call(this);
43
+ const accountTokenGroups = buildAccountTokenGroupsStatic(chainId, queryAllAccounts, selectedAccount, allAccounts, tokensState.allTokens, tokensState.allDetectedTokens);
44
+ if (!accountTokenGroups.length) {
45
+ continue;
46
+ }
47
+ const provider = __classPrivateFieldGet(this, _RpcBalanceFetcher_getProvider, "f").call(this, chainId);
48
+ await __classPrivateFieldGet(this, _RpcBalanceFetcher_instances, "m", _RpcBalanceFetcher_ensureFreshBlockData).call(this, chainId);
49
+ const { tokenBalances, stakedBalances } = await getTokenBalancesForMultipleAddresses(accountTokenGroups, chainId, provider, true, // include native
50
+ true);
51
+ // Add native token entries for all addresses being processed
52
+ const allAddressesForNative = new Set();
53
+ accountTokenGroups.forEach((group) => {
54
+ allAddressesForNative.add(group.accountAddress);
55
+ });
56
+ // Ensure native token entries exist for all addresses
57
+ allAddressesForNative.forEach((address) => {
58
+ const nativeBalance = tokenBalances[ZERO_ADDRESS]?.[address] || null;
59
+ results.push({
60
+ success: true,
61
+ value: nativeBalance ? nativeBalance : new BN('0'),
62
+ account: address,
63
+ token: ZERO_ADDRESS,
64
+ chainId,
65
+ });
66
+ });
67
+ // Add other token balances
68
+ Object.entries(tokenBalances).forEach(([tokenAddr, balances]) => {
69
+ // Skip native token since we handled it explicitly above
70
+ if (tokenAddr === ZERO_ADDRESS) {
71
+ return;
72
+ }
73
+ Object.entries(balances).forEach(([acct, bn]) => {
74
+ results.push({
75
+ success: bn !== null,
76
+ value: bn,
77
+ account: acct,
78
+ token: checksum(tokenAddr),
79
+ chainId,
80
+ });
81
+ });
82
+ });
83
+ // Add staked balances for all addresses being processed
84
+ const stakingContractAddress = __classPrivateFieldGet(this, _RpcBalanceFetcher_instances, "m", _RpcBalanceFetcher_getStakingContractAddress).call(this, chainId);
85
+ if (stakingContractAddress) {
86
+ // Get all unique addresses being processed for this chain
87
+ const allAddresses = new Set();
88
+ accountTokenGroups.forEach((group) => {
89
+ allAddresses.add(group.accountAddress);
90
+ });
91
+ // Add staked balance entry for each address
92
+ const checksummedStakingAddress = checksum(stakingContractAddress);
93
+ allAddresses.forEach((address) => {
94
+ const stakedBalance = stakedBalances?.[address] || null;
95
+ results.push({
96
+ success: true,
97
+ value: stakedBalance ? stakedBalance : new BN('0'),
98
+ account: address,
99
+ token: checksummedStakingAddress,
100
+ chainId,
101
+ });
102
+ });
103
+ }
104
+ }
105
+ return results;
106
+ }
107
+ }
108
+ _RpcBalanceFetcher_getProvider = new WeakMap(), _RpcBalanceFetcher_getNetworkClient = new WeakMap(), _RpcBalanceFetcher_getTokensState = new WeakMap(), _RpcBalanceFetcher_instances = new WeakSet(), _RpcBalanceFetcher_getStakingContractAddress = function _RpcBalanceFetcher_getStakingContractAddress(chainId) {
109
+ return STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId];
110
+ }, _RpcBalanceFetcher_ensureFreshBlockData =
111
+ /**
112
+ * Ensures that the block tracker has the latest block data before performing multicall operations.
113
+ * This is a temporary fix to ensure that the block number is up to date.
114
+ *
115
+ * @param chainId - The chain id to update block data for.
116
+ */
117
+ async function _RpcBalanceFetcher_ensureFreshBlockData(chainId) {
118
+ // Force fresh block data before multicall
119
+ // TODO: This is a temporary fix to ensure that the block number is up to date.
120
+ // We should remove this once we have a better solution for this on the block tracker controller.
121
+ const networkClient = __classPrivateFieldGet(this, _RpcBalanceFetcher_getNetworkClient, "f").call(this, chainId);
122
+ await networkClient.blockTracker?.checkForLatestBlock?.();
123
+ };
124
+ /**
125
+ * Merges imported & detected tokens for the requested chain and returns a list
126
+ * of `{ accountAddress, tokenAddresses[] }` suitable for getTokenBalancesForMultipleAddresses.
127
+ *
128
+ * @param chainId - The chain ID to build account token groups for
129
+ * @param queryAllAccounts - Whether to query all accounts or just the selected one
130
+ * @param selectedAccount - The currently selected account
131
+ * @param allAccounts - All available accounts
132
+ * @param allTokens - All tokens from TokensController
133
+ * @param allDetectedTokens - All detected tokens from TokensController
134
+ * @returns Array of account/token groups for multicall
135
+ */
136
+ function buildAccountTokenGroupsStatic(chainId, queryAllAccounts, selectedAccount, allAccounts, allTokens, allDetectedTokens) {
137
+ const pairs = [];
138
+ const add = ([account, tokens]) => {
139
+ const shouldInclude = queryAllAccounts || checksum(account) === checksum(selectedAccount);
140
+ if (!shouldInclude) {
141
+ return;
142
+ }
143
+ tokens.forEach((t) => pairs.push({
144
+ accountAddress: account,
145
+ tokenAddress: checksum(t.address),
146
+ }));
147
+ };
148
+ Object.entries(allTokens[chainId] ?? {}).forEach(add);
149
+ Object.entries(allDetectedTokens[chainId] ?? {}).forEach(add);
150
+ // Always include native token for relevant accounts
151
+ if (queryAllAccounts) {
152
+ allAccounts.forEach((a) => {
153
+ pairs.push({
154
+ accountAddress: a.address,
155
+ tokenAddress: ZERO_ADDRESS,
156
+ });
157
+ });
158
+ }
159
+ else {
160
+ pairs.push({
161
+ accountAddress: selectedAccount,
162
+ tokenAddress: ZERO_ADDRESS,
163
+ });
164
+ }
165
+ if (!pairs.length) {
166
+ return [];
167
+ }
168
+ // group by account
169
+ const map = new Map();
170
+ pairs.forEach(({ accountAddress, tokenAddress }) => {
171
+ if (!map.has(accountAddress)) {
172
+ map.set(accountAddress, []);
173
+ }
174
+ const tokens = map.get(accountAddress);
175
+ if (tokens) {
176
+ tokens.push(tokenAddress);
177
+ }
178
+ });
179
+ return Array.from(map.entries()).map(([accountAddress, tokenAddresses]) => ({
180
+ accountAddress,
181
+ tokenAddresses,
182
+ }));
183
+ }
184
+ //# sourceMappingURL=rpc-balance-fetcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-balance-fetcher.mjs","sourceRoot":"","sources":["../../src/rpc-service/rpc-balance-fetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Web3Provider } from '@ethersproject/providers';\nimport { toChecksumHexAddress } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { NetworkClient } from '@metamask/network-controller';\nimport type { Hex } from '@metamask/utils';\nimport BN from 'bn.js';\n\nimport { STAKING_CONTRACT_ADDRESS_BY_CHAINID } from '../AssetsContractController';\nimport { getTokenBalancesForMultipleAddresses } from '../multicall';\nimport type { TokensControllerState } from '../TokensController';\n\nexport type ChainIdHex = Hex;\nexport type ChecksumAddress = Hex;\n\nexport type ProcessedBalance = {\n success: boolean;\n value?: BN;\n account: ChecksumAddress;\n token: ChecksumAddress;\n chainId: ChainIdHex;\n};\n\nexport type BalanceFetcher = {\n supports(chainId: ChainIdHex): boolean;\n fetch(input: {\n chainIds: ChainIdHex[];\n queryAllAccounts: boolean;\n selectedAccount: ChecksumAddress;\n allAccounts: InternalAccount[];\n }): Promise<ProcessedBalance[]>;\n};\n\nconst ZERO_ADDRESS =\n '0x0000000000000000000000000000000000000000' as ChecksumAddress;\n\nconst checksum = (addr: string): ChecksumAddress =>\n toChecksumHexAddress(addr) as ChecksumAddress;\n\nexport class RpcBalanceFetcher implements BalanceFetcher {\n readonly #getProvider: (chainId: ChainIdHex) => Web3Provider;\n\n readonly #getNetworkClient: (chainId: ChainIdHex) => NetworkClient;\n\n readonly #getTokensState: () => {\n allTokens: TokensControllerState['allTokens'];\n allDetectedTokens: TokensControllerState['allDetectedTokens'];\n };\n\n constructor(\n getProvider: (chainId: ChainIdHex) => Web3Provider,\n getNetworkClient: (chainId: ChainIdHex) => NetworkClient,\n getTokensState: () => {\n allTokens: TokensControllerState['allTokens'];\n allDetectedTokens: TokensControllerState['allDetectedTokens'];\n },\n ) {\n this.#getProvider = getProvider;\n this.#getNetworkClient = getNetworkClient;\n this.#getTokensState = getTokensState;\n }\n\n supports(): boolean {\n return true; // fallback – supports every chain\n }\n\n #getStakingContractAddress(chainId: ChainIdHex): string | undefined {\n return STAKING_CONTRACT_ADDRESS_BY_CHAINID[\n chainId as keyof typeof STAKING_CONTRACT_ADDRESS_BY_CHAINID\n ];\n }\n\n async fetch({\n chainIds,\n queryAllAccounts,\n selectedAccount,\n allAccounts,\n }: Parameters<BalanceFetcher['fetch']>[0]): Promise<ProcessedBalance[]> {\n const results: ProcessedBalance[] = [];\n\n for (const chainId of chainIds) {\n const tokensState = this.#getTokensState();\n const accountTokenGroups = buildAccountTokenGroupsStatic(\n chainId,\n queryAllAccounts,\n selectedAccount,\n allAccounts,\n tokensState.allTokens,\n tokensState.allDetectedTokens,\n );\n if (!accountTokenGroups.length) {\n continue;\n }\n\n const provider = this.#getProvider(chainId);\n await this.#ensureFreshBlockData(chainId);\n\n const { tokenBalances, stakedBalances } =\n await getTokenBalancesForMultipleAddresses(\n accountTokenGroups,\n chainId,\n provider,\n true, // include native\n true, // include staked\n );\n\n // Add native token entries for all addresses being processed\n const allAddressesForNative = new Set<string>();\n accountTokenGroups.forEach((group) => {\n allAddressesForNative.add(group.accountAddress);\n });\n\n // Ensure native token entries exist for all addresses\n allAddressesForNative.forEach((address) => {\n const nativeBalance = tokenBalances[ZERO_ADDRESS]?.[address] || null;\n results.push({\n success: true,\n value: nativeBalance ? (nativeBalance as BN) : new BN('0'),\n account: address as ChecksumAddress,\n token: ZERO_ADDRESS,\n chainId,\n });\n });\n\n // Add other token balances\n Object.entries(tokenBalances).forEach(([tokenAddr, balances]) => {\n // Skip native token since we handled it explicitly above\n if (tokenAddr === ZERO_ADDRESS) {\n return;\n }\n Object.entries(balances).forEach(([acct, bn]) => {\n results.push({\n success: bn !== null,\n value: bn as BN,\n account: acct as ChecksumAddress,\n token: checksum(tokenAddr),\n chainId,\n });\n });\n });\n\n // Add staked balances for all addresses being processed\n const stakingContractAddress = this.#getStakingContractAddress(chainId);\n if (stakingContractAddress) {\n // Get all unique addresses being processed for this chain\n const allAddresses = new Set<string>();\n accountTokenGroups.forEach((group) => {\n allAddresses.add(group.accountAddress);\n });\n\n // Add staked balance entry for each address\n const checksummedStakingAddress = checksum(stakingContractAddress);\n allAddresses.forEach((address) => {\n const stakedBalance = stakedBalances?.[address] || null;\n results.push({\n success: true,\n value: stakedBalance ? (stakedBalance as BN) : new BN('0'),\n account: address as ChecksumAddress,\n token: checksummedStakingAddress,\n chainId,\n });\n });\n }\n }\n\n return results;\n }\n\n /**\n * Ensures that the block tracker has the latest block data before performing multicall operations.\n * This is a temporary fix to ensure that the block number is up to date.\n *\n * @param chainId - The chain id to update block data for.\n */\n async #ensureFreshBlockData(chainId: Hex): Promise<void> {\n // Force fresh block data before multicall\n // TODO: This is a temporary fix to ensure that the block number is up to date.\n // We should remove this once we have a better solution for this on the block tracker controller.\n const networkClient = this.#getNetworkClient(chainId);\n await networkClient.blockTracker?.checkForLatestBlock?.();\n }\n}\n\n/**\n * Merges imported & detected tokens for the requested chain and returns a list\n * of `{ accountAddress, tokenAddresses[] }` suitable for getTokenBalancesForMultipleAddresses.\n *\n * @param chainId - The chain ID to build account token groups for\n * @param queryAllAccounts - Whether to query all accounts or just the selected one\n * @param selectedAccount - The currently selected account\n * @param allAccounts - All available accounts\n * @param allTokens - All tokens from TokensController\n * @param allDetectedTokens - All detected tokens from TokensController\n * @returns Array of account/token groups for multicall\n */\nfunction buildAccountTokenGroupsStatic(\n chainId: ChainIdHex,\n queryAllAccounts: boolean,\n selectedAccount: ChecksumAddress,\n allAccounts: InternalAccount[],\n allTokens: TokensControllerState['allTokens'],\n allDetectedTokens: TokensControllerState['allDetectedTokens'],\n): { accountAddress: ChecksumAddress; tokenAddresses: ChecksumAddress[] }[] {\n const pairs: {\n accountAddress: ChecksumAddress;\n tokenAddress: ChecksumAddress;\n }[] = [];\n\n const add = ([account, tokens]: [string, unknown[]]) => {\n const shouldInclude =\n queryAllAccounts || checksum(account) === checksum(selectedAccount);\n if (!shouldInclude) {\n return;\n }\n (tokens as unknown[]).forEach((t: unknown) =>\n pairs.push({\n accountAddress: account as ChecksumAddress,\n tokenAddress: checksum((t as { address: string }).address),\n }),\n );\n };\n\n Object.entries(allTokens[chainId] ?? {}).forEach(\n add as (entry: [string, unknown]) => void,\n );\n Object.entries(allDetectedTokens[chainId] ?? {}).forEach(\n add as (entry: [string, unknown]) => void,\n );\n\n // Always include native token for relevant accounts\n if (queryAllAccounts) {\n allAccounts.forEach((a) => {\n pairs.push({\n accountAddress: a.address as ChecksumAddress,\n tokenAddress: ZERO_ADDRESS,\n });\n });\n } else {\n pairs.push({\n accountAddress: selectedAccount,\n tokenAddress: ZERO_ADDRESS,\n });\n }\n\n if (!pairs.length) {\n return [];\n }\n\n // group by account\n const map = new Map<ChecksumAddress, ChecksumAddress[]>();\n pairs.forEach(({ accountAddress, tokenAddress }) => {\n if (!map.has(accountAddress)) {\n map.set(accountAddress, []);\n }\n const tokens = map.get(accountAddress);\n if (tokens) {\n tokens.push(tokenAddress);\n }\n });\n\n return Array.from(map.entries()).map(([accountAddress, tokenAddresses]) => ({\n accountAddress,\n tokenAddresses,\n }));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/assets-controllers",
3
- "version": "73.1.0",
3
+ "version": "73.2.0",
4
4
  "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
5
5
  "keywords": [
6
6
  "MetaMask",
@@ -54,9 +54,9 @@
54
54
  "@ethersproject/contracts": "^5.7.0",
55
55
  "@ethersproject/providers": "^5.7.0",
56
56
  "@metamask/abi-utils": "^2.0.3",
57
- "@metamask/base-controller": "^8.0.1",
57
+ "@metamask/base-controller": "^8.1.0",
58
58
  "@metamask/contract-metadata": "^2.4.0",
59
- "@metamask/controller-utils": "^11.11.0",
59
+ "@metamask/controller-utils": "^11.12.0",
60
60
  "@metamask/eth-query": "^4.0.0",
61
61
  "@metamask/keyring-api": "^20.0.0",
62
62
  "@metamask/metamask-eth-abis": "^3.1.1",
@@ -80,22 +80,22 @@
80
80
  "devDependencies": {
81
81
  "@babel/runtime": "^7.23.9",
82
82
  "@metamask/account-api": "^0.9.0",
83
- "@metamask/account-tree-controller": "^0.7.0",
84
- "@metamask/accounts-controller": "^32.0.1",
83
+ "@metamask/account-tree-controller": "^0.8.0",
84
+ "@metamask/accounts-controller": "^32.0.2",
85
85
  "@metamask/approval-controller": "^7.1.3",
86
86
  "@metamask/auto-changelog": "^3.4.4",
87
87
  "@metamask/ethjs-provider-http": "^0.3.0",
88
- "@metamask/keyring-controller": "^22.1.0",
88
+ "@metamask/keyring-controller": "^22.1.1",
89
89
  "@metamask/keyring-internal-api": "^8.0.0",
90
90
  "@metamask/keyring-snap-client": "^7.0.0",
91
- "@metamask/multichain-account-service": "^0.3.0",
92
- "@metamask/network-controller": "^24.0.1",
91
+ "@metamask/multichain-account-service": "^0.4.0",
92
+ "@metamask/network-controller": "^24.1.0",
93
93
  "@metamask/permission-controller": "^11.0.6",
94
94
  "@metamask/phishing-controller": "^13.1.0",
95
95
  "@metamask/preferences-controller": "^18.4.1",
96
96
  "@metamask/providers": "^22.1.0",
97
97
  "@metamask/snaps-controllers": "^14.0.1",
98
- "@metamask/transaction-controller": "^59.1.0",
98
+ "@metamask/transaction-controller": "^59.2.0",
99
99
  "@types/jest": "^27.4.1",
100
100
  "@types/lodash": "^4.14.191",
101
101
  "@types/node": "^16.18.54",
@@ -1,328 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.balanceSelectors = exports.selectBalanceForSelectedAccountGroup = exports.selectBalanceByWallet = exports.selectBalanceByAccountGroup = exports.selectBalanceForAllWallets = void 0;
4
- const keyring_api_1 = require("@metamask/keyring-api");
5
- const reselect_1 = require("reselect");
6
- /**
7
- * Individual controller state selectors using direct state access
8
- * This avoids new object creation and provides stable references
9
- * Supports both mobile (state.engine.backgroundState) and extension (state.metamask) structures
10
- */
11
- /**
12
- * Helper function to get controller state from different state structures
13
- *
14
- * @param state - The application state
15
- * @param controllerName - The name of the controller
16
- * @returns The controller state or undefined if not found
17
- */
18
- const getControllerState = (
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- state, controllerName) => {
21
- // Mobile structure: state.engine.backgroundState.ControllerName
22
- if (state?.engine?.backgroundState?.[controllerName]) {
23
- return state.engine.backgroundState[controllerName];
24
- }
25
- // Extension structure: state.metamask.ControllerName
26
- if (state?.metamask?.[controllerName]) {
27
- return state.metamask[controllerName];
28
- }
29
- // Flat structure (default assets-controllers structure)
30
- if (state?.[controllerName]) {
31
- return state[controllerName];
32
- }
33
- // Since controllers always have default states, this should never happen
34
- // but we need to return something for TypeScript
35
- return state?.[controllerName];
36
- };
37
- /**
38
- * Selector for AccountTreeController state using direct state access
39
- *
40
- * @param state - The application state
41
- * @returns AccountTreeController state
42
- */
43
- const selectAccountTreeControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'AccountTreeController'));
44
- /**
45
- * Selector for AccountsController state using direct state access
46
- *
47
- * @param state - The application state
48
- * @returns AccountsController state
49
- */
50
- const selectAccountsControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'AccountsController'));
51
- /**
52
- * Selector for TokenBalancesController state using direct state access
53
- *
54
- * @param state - The application state
55
- * @returns TokenBalancesController state
56
- */
57
- const selectTokenBalancesControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'TokenBalancesController'));
58
- /**
59
- * Selector for TokenRatesController state using direct state access
60
- *
61
- * @param state - The application state
62
- * @returns TokenRatesController state
63
- */
64
- const selectTokenRatesControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'TokenRatesController'));
65
- /**
66
- * Selector for MultichainAssetsRatesController state using direct state access
67
- *
68
- * @param state - The application state
69
- * @returns MultichainAssetsRatesController state
70
- */
71
- const selectMultichainAssetsRatesControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'MultichainAssetsRatesController'));
72
- /**
73
- * Selector for MultichainBalancesController state using direct state access
74
- *
75
- * @param state - The application state
76
- * @returns MultichainBalancesController state
77
- */
78
- const selectMultichainBalancesControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'MultichainBalancesController'));
79
- /**
80
- * Selector for TokensController state using direct state access
81
- *
82
- * @param state - The application state
83
- * @returns TokensController state
84
- */
85
- const selectTokensControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'TokensController'));
86
- /**
87
- * Selector for CurrencyRateController state using direct state access
88
- *
89
- * @param state - The application state
90
- * @returns CurrencyRateController state
91
- */
92
- const selectCurrencyRateControllerState = (0, reselect_1.createSelector)([(state) => state], (state) => getControllerState(state, 'CurrencyRateController'));
93
- /**
94
- * Helper function to get internal accounts for a specific group.
95
- * Uses AccountTreeController state to find accounts.
96
- *
97
- * @param accountTreeState - AccountTreeController state
98
- * @param accountsState - AccountsController state
99
- * @param groupId - The account group ID (format: "walletId/groupIndex")
100
- * @returns Array of internal accounts in the group
101
- */
102
- const getInternalAccountsForGroup = (accountTreeState, accountsState, groupId) => {
103
- // Extract walletId from groupId (format: "walletId/groupIndex")
104
- const walletId = groupId.split('/')[0];
105
- const wallet = accountTreeState.accountTree.wallets[walletId];
106
- if (!wallet) {
107
- return [];
108
- }
109
- const group = wallet.groups[groupId];
110
- if (!group) {
111
- return [];
112
- }
113
- // Map account IDs to actual account objects
114
- return group.accounts
115
- .map((accountId) => accountsState.internalAccounts.accounts[accountId])
116
- .filter(Boolean);
117
- };
118
- /**
119
- * Comprehensive selector that calculates all balances for all wallets and groups.
120
- * This is the single source of truth for all balance calculations.
121
- * Other selectors will derive from this to ensure proper memoization.
122
- *
123
- * @returns Aggregated balance for all wallets
124
- */
125
- const selectBalanceForAllWallets = () => (0, reselect_1.createSelector)([
126
- selectAccountTreeControllerState,
127
- selectAccountsControllerState,
128
- selectTokenBalancesControllerState,
129
- selectTokenRatesControllerState,
130
- selectMultichainAssetsRatesControllerState,
131
- selectMultichainBalancesControllerState,
132
- selectTokensControllerState,
133
- selectCurrencyRateControllerState,
134
- ], (accountTreeState, accountsState, tokenBalancesState, tokenRatesState, multichainRatesState, multichainBalancesState, tokensState, currencyRateState) => {
135
- const walletBalances = {};
136
- let totalBalanceInUserCurrency = 0;
137
- const walletIds = Object.keys(accountTreeState.accountTree.wallets);
138
- for (const walletId of walletIds) {
139
- const wallet = accountTreeState.accountTree.wallets[walletId];
140
- if (!wallet) {
141
- continue;
142
- }
143
- const groupBalances = {};
144
- let walletTotalBalance = 0;
145
- const groups = Object.keys(wallet.groups || {});
146
- for (const groupId of groups) {
147
- const accounts = getInternalAccountsForGroup(accountTreeState, accountsState, groupId);
148
- if (accounts.length === 0) {
149
- groupBalances[groupId] = {
150
- walletId,
151
- groupId,
152
- totalBalanceInUserCurrency: 0,
153
- userCurrency: currencyRateState.currentCurrency,
154
- };
155
- continue;
156
- }
157
- let groupTotalBalance = 0;
158
- // Process each account's balances
159
- for (const account of accounts) {
160
- const isEvmAccount = (0, keyring_api_1.isEvmAccountType)(account.type);
161
- if (isEvmAccount) {
162
- // Handle EVM account balances from TokenBalancesController
163
- const accountBalances = tokenBalancesState.tokenBalances[account.address];
164
- if (accountBalances) {
165
- for (const [chainId, chainBalances] of Object.entries(accountBalances)) {
166
- for (const [tokenAddress, balance] of Object.entries(chainBalances)) {
167
- // Find token in TokensController state
168
- const chainTokens = tokensState.allTokens[chainId];
169
- const accountTokens = chainTokens?.[account.address];
170
- const token = accountTokens?.find((t) => t.address === tokenAddress);
171
- if (!token) {
172
- continue;
173
- }
174
- // Use nullish coalescing to handle 0 decimals correctly
175
- // and ensure decimals is a valid number to prevent NaN propagation
176
- const decimals = typeof token.decimals === 'number' &&
177
- !Number.isNaN(token.decimals)
178
- ? token.decimals
179
- : 18;
180
- const balanceInSmallestUnit = parseInt(balance, 16);
181
- // Skip invalid balance values to prevent NaN propagation
182
- if (Number.isNaN(balanceInSmallestUnit)) {
183
- continue;
184
- }
185
- const balanceInTokenUnits = balanceInSmallestUnit / Math.pow(10, decimals);
186
- // Get token rate in native currency from TokenRatesController
187
- const chainMarketData = tokenRatesState.marketData[chainId];
188
- const tokenMarketData = chainMarketData?.[tokenAddress];
189
- if (tokenMarketData?.price) {
190
- // Convert token price to user currency using native currency conversion rate
191
- const nativeCurrency = tokenMarketData.currency;
192
- const nativeToUserRate = currencyRateState.currencyRates[nativeCurrency]
193
- ?.conversionRate;
194
- if (nativeToUserRate) {
195
- // Convert token price to user currency: tokenPrice * nativeToUserRate
196
- const tokenPriceInUserCurrency = tokenMarketData.price * nativeToUserRate;
197
- const balanceInUserCurrency = balanceInTokenUnits * tokenPriceInUserCurrency;
198
- groupTotalBalance += balanceInUserCurrency;
199
- }
200
- }
201
- }
202
- }
203
- }
204
- }
205
- else {
206
- // Handle non-EVM account balances from MultichainBalancesController
207
- const accountBalances = multichainBalancesState.balances[account.id];
208
- if (accountBalances) {
209
- for (const [assetId, balanceData] of Object.entries(accountBalances)) {
210
- const balanceAmount = parseFloat(balanceData.amount);
211
- // Skip invalid balance values to prevent NaN propagation
212
- if (Number.isNaN(balanceAmount)) {
213
- continue;
214
- }
215
- // Get conversion rate for this asset (already in user currency)
216
- const conversionRate = multichainRatesState.conversionRates[assetId];
217
- if (conversionRate) {
218
- const conversionRateValue = parseFloat(conversionRate.rate);
219
- // Skip invalid conversion rate values to prevent NaN propagation
220
- if (Number.isNaN(conversionRateValue)) {
221
- continue;
222
- }
223
- // MultichainAssetsRatesController already provides rates in user currency
224
- const balanceInUserCurrency = balanceAmount * conversionRateValue;
225
- groupTotalBalance += balanceInUserCurrency;
226
- }
227
- }
228
- }
229
- }
230
- }
231
- groupBalances[groupId] = {
232
- walletId,
233
- groupId,
234
- totalBalanceInUserCurrency: groupTotalBalance,
235
- userCurrency: currencyRateState.currentCurrency,
236
- };
237
- walletTotalBalance += groupTotalBalance;
238
- }
239
- walletBalances[walletId] = {
240
- walletId,
241
- groups: groupBalances,
242
- totalBalanceInUserCurrency: walletTotalBalance,
243
- userCurrency: currencyRateState.currentCurrency,
244
- };
245
- totalBalanceInUserCurrency += walletTotalBalance;
246
- }
247
- return {
248
- wallets: walletBalances,
249
- totalBalanceInUserCurrency,
250
- userCurrency: currencyRateState.currentCurrency,
251
- };
252
- });
253
- exports.selectBalanceForAllWallets = selectBalanceForAllWallets;
254
- /**
255
- * Selector to get aggregated balances for a specific account group.
256
- * Derives from the comprehensive selector to ensure proper memoization.
257
- *
258
- * @param groupId - The account group ID (format: "walletId/groupIndex", e.g., "entropy:entropy-source-1/0")
259
- * @returns Aggregated balance for the account group
260
- */
261
- const selectBalanceByAccountGroup = (groupId) => (0, reselect_1.createSelector)([(0, exports.selectBalanceForAllWallets)()], (allBalances) => {
262
- const walletId = groupId.split('/')[0];
263
- const wallet = allBalances.wallets[walletId];
264
- if (!wallet || !wallet.groups[groupId]) {
265
- return {
266
- walletId,
267
- groupId,
268
- totalBalanceInUserCurrency: 0,
269
- userCurrency: allBalances.userCurrency,
270
- };
271
- }
272
- return wallet.groups[groupId];
273
- });
274
- exports.selectBalanceByAccountGroup = selectBalanceByAccountGroup;
275
- /**
276
- * Selector to get aggregated balances for all account groups in a wallet.
277
- * Derives from the comprehensive selector to ensure proper memoization.
278
- *
279
- * @param walletId - The wallet ID (entropy source)
280
- * @returns Aggregated balance for all groups in the wallet
281
- */
282
- const selectBalanceByWallet = (walletId) => (0, reselect_1.createSelector)([(0, exports.selectBalanceForAllWallets)()], (allBalances) => {
283
- const wallet = allBalances.wallets[walletId];
284
- if (!wallet) {
285
- return {
286
- walletId,
287
- groups: {},
288
- totalBalanceInUserCurrency: 0,
289
- userCurrency: allBalances.userCurrency,
290
- };
291
- }
292
- return wallet;
293
- });
294
- exports.selectBalanceByWallet = selectBalanceByWallet;
295
- /**
296
- * Selector to get aggregated balances for the currently selected account group.
297
- * Derives from the comprehensive selector to ensure proper memoization.
298
- *
299
- * @returns Aggregated balance for the currently selected group
300
- */
301
- const selectBalanceForSelectedAccountGroup = () => (0, reselect_1.createSelector)([selectAccountTreeControllerState, (0, exports.selectBalanceForAllWallets)()], (accountTreeState, allBalances) => {
302
- const selectedGroupId = accountTreeState.accountTree.selectedAccountGroup;
303
- if (!selectedGroupId) {
304
- return null;
305
- }
306
- const walletId = selectedGroupId.split('/')[0];
307
- const wallet = allBalances.wallets[walletId];
308
- if (!wallet || !wallet.groups[selectedGroupId]) {
309
- return {
310
- walletId,
311
- groupId: selectedGroupId,
312
- totalBalanceInUserCurrency: 0,
313
- userCurrency: allBalances.userCurrency,
314
- };
315
- }
316
- return wallet.groups[selectedGroupId];
317
- });
318
- exports.selectBalanceForSelectedAccountGroup = selectBalanceForSelectedAccountGroup;
319
- /**
320
- * Collection of balance-related selectors for assets controllers
321
- */
322
- exports.balanceSelectors = {
323
- selectBalanceByAccountGroup: exports.selectBalanceByAccountGroup,
324
- selectBalanceByWallet: exports.selectBalanceByWallet,
325
- selectBalanceForAllWallets: exports.selectBalanceForAllWallets,
326
- selectBalanceForSelectedAccountGroup: exports.selectBalanceForSelectedAccountGroup,
327
- };
328
- //# sourceMappingURL=balanceSelectors.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"balanceSelectors.cjs","sourceRoot":"","sources":["../../src/selectors/balanceSelectors.ts"],"names":[],"mappings":";;;AAKA,uDAAyD;AAGzD,uCAA0C;AAS1C;;;;GAIG;AAEH;;;;;;GAMG;AAEH,MAAM,kBAAkB,GAAG;AACzB,8DAA8D;AAC9D,KAAU,EACV,cAAsB,EACnB,EAAE;IACL,gEAAgE;IAChE,IAAI,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,cAAc,CAAC,EAAE;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;KACrD;IAED,qDAAqD;IACrD,IAAI,KAAK,EAAE,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE;QACrC,OAAO,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;KACvC;IAED,wDAAwD;IACxD,IAAI,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE;QAC3B,OAAO,KAAK,CAAC,cAAc,CAAC,CAAC;KAC9B;IAED,yEAAyE;IACzE,iDAAiD;IACjD,OAAO,KAAK,EAAE,CAAC,cAAc,CAAM,CAAC;AACtC,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,gCAAgC,GAAG,IAAA,yBAAc,EACrD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAA8B,EAAE,CACpC,kBAAkB,CAChB,KAAK,EACL,uBAAuB,CACxB,CACJ,CAAC;AAEF;;;;;GAKG;AACH,MAAM,6BAA6B,GAAG,IAAA,yBAAc,EAClD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAA2B,EAAE,CACjC,kBAAkB,CAA0B,KAAK,EAAE,oBAAoB,CAAC,CAC3E,CAAC;AAEF;;;;;GAKG;AACH,MAAM,kCAAkC,GAAG,IAAA,yBAAc,EACvD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAAgC,EAAE,CACtC,kBAAkB,CAChB,KAAK,EACL,yBAAyB,CAC1B,CACJ,CAAC;AAEF;;;;;GAKG;AACH,MAAM,+BAA+B,GAAG,IAAA,yBAAc,EACpD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAA6B,EAAE,CACnC,kBAAkB,CAChB,KAAK,EACL,sBAAsB,CACvB,CACJ,CAAC;AAEF;;;;;GAKG;AACH,MAAM,0CAA0C,GAAG,IAAA,yBAAc,EAC/D,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAAwC,EAAE,CAC9C,kBAAkB,CAChB,KAAK,EACL,iCAAiC,CAClC,CACJ,CAAC;AAEF;;;;;GAKG;AACH,MAAM,uCAAuC,GAAG,IAAA,yBAAc,EAC5D,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAAqC,EAAE,CAC3C,kBAAkB,CAChB,KAAK,EACL,8BAA8B,CAC/B,CACJ,CAAC;AAEF;;;;;GAKG;AACH,MAAM,2BAA2B,GAAG,IAAA,yBAAc,EAChD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAAyB,EAAE,CAC/B,kBAAkB,CAAwB,KAAK,EAAE,kBAAkB,CAAC,CACvE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,iCAAiC,GAAG,IAAA,yBAAc,EACtD,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAC3B,CAAC,KAAK,EAAqB,EAAE,CAC3B,kBAAkB,CAAoB,KAAK,EAAE,wBAAwB,CAAC,CACzE,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,2BAA2B,GAAG,CAClC,gBAA4C,EAC5C,aAAsC,EACtC,OAAe,EACf,EAAE;IACF,gEAAgE;IAChE,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAoB,CAAC;IAE1D,MAAM,MAAM,GACV,gBAAgB,CAAC,WAAW,CAAC,OAC9B,CAAC,QAAQ,CAAC,CAAC;IACZ,IAAI,CAAC,MAAM,EAAE;QACX,OAAO,EAAE,CAAC;KACX;IAED,MAAM,KAAK,GAAI,MAAM,CAAC,MAA6C,CAAC,OAAO,CAAC,CAAC;IAC7E,IAAI,CAAC,KAAK,EAAE;QACV,OAAO,EAAE,CAAC;KACX;IAED,4CAA4C;IAC5C,OAAO,KAAK,CAAC,QAAQ;SAClB,GAAG,CACF,CAAC,SAAiB,EAAE,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAC1E;SACA,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC,CAAC;AAEF;;;;;;GAMG;AACI,MAAM,0BAA0B,GAAG,GAAG,EAAE,CAC7C,IAAA,yBAAc,EACZ;IACE,gCAAgC;IAChC,6BAA6B;IAC7B,kCAAkC;IAClC,+BAA+B;IAC/B,0CAA0C;IAC1C,uCAAuC;IACvC,2BAA2B;IAC3B,iCAAiC;CAClC,EACD,CACE,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,uBAAuB,EACvB,WAAW,EACX,iBAAiB,EACE,EAAE;IACrB,MAAM,cAAc,GAAkC,EAAE,CAAC;IACzD,IAAI,0BAA0B,GAAG,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAC3B,gBAAgB,CAAC,WAAW,CAAC,OAAO,CACzB,CAAC;IAEd,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE;QAChC,MAAM,MAAM,GACV,gBAAgB,CAAC,WAAW,CAAC,OAI9B,CAAC,QAAQ,CAAC,CAAC;QACZ,IAAI,CAAC,MAAM,EAAE;YACX,SAAS;SACV;QAED,MAAM,aAAa,GAAwC,EAAE,CAAC;QAC9D,IAAI,kBAAkB,GAAG,CAAC,CAAC;QAE3B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAa,CAAC;QAE5D,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE;YAC5B,MAAM,QAAQ,GAAG,2BAA2B,CAC1C,gBAAgB,EAChB,aAAa,EACb,OAAO,CACR,CAAC;YAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBACzB,aAAa,CAAC,OAAO,CAAC,GAAG;oBACvB,QAAQ;oBACR,OAAO;oBACP,0BAA0B,EAAE,CAAC;oBAC7B,YAAY,EAAE,iBAAiB,CAAC,eAAe;iBAChD,CAAC;gBACF,SAAS;aACV;YAED,IAAI,iBAAiB,GAAG,CAAC,CAAC;YAE1B,kCAAkC;YAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;gBAC9B,MAAM,YAAY,GAAG,IAAA,8BAAgB,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAEpD,IAAI,YAAY,EAAE;oBAChB,2DAA2D;oBAC3D,MAAM,eAAe,GACnB,kBAAkB,CAAC,aAAa,CAAC,OAAO,CAAC,OAAc,CAAC,CAAC;oBAC3D,IAAI,eAAe,EAAE;wBACnB,KAAK,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CACnD,eAAe,CAChB,EAAE;4BACD,KAAK,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAClD,aAAa,CACd,EAAE;gCACD,uCAAuC;gCACvC,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,OAAc,CAAC,CAAC;gCAC1D,MAAM,aAAa,GAAG,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gCACrD,MAAM,KAAK,GAAG,aAAa,EAAE,IAAI,CAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,YAAY,CAClC,CAAC;gCACF,IAAI,CAAC,KAAK,EAAE;oCACV,SAAS;iCACV;gCAED,wDAAwD;gCACxD,mEAAmE;gCACnE,MAAM,QAAQ,GACZ,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;oCAClC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;oCAC3B,CAAC,CAAC,KAAK,CAAC,QAAQ;oCAChB,CAAC,CAAC,EAAE,CAAC;gCACT,MAAM,qBAAqB,GAAG,QAAQ,CACpC,OAAiB,EACjB,EAAE,CACH,CAAC;gCAEF,yDAAyD;gCACzD,IAAI,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE;oCACvC,SAAS;iCACV;gCAED,MAAM,mBAAmB,GACvB,qBAAqB,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;gCAEjD,8DAA8D;gCAC9D,MAAM,eAAe,GACnB,eAAe,CAAC,UAAU,CAAC,OAAc,CAAC,CAAC;gCAC7C,MAAM,eAAe,GACnB,eAAe,EAAE,CAAC,YAAmB,CAAC,CAAC;gCACzC,IAAI,eAAe,EAAE,KAAK,EAAE;oCAC1B,6EAA6E;oCAC7E,MAAM,cAAc,GAAG,eAAe,CAAC,QAAQ,CAAC;oCAChD,MAAM,gBAAgB,GACpB,iBAAiB,CAAC,aAAa,CAAC,cAAc,CAAC;wCAC7C,EAAE,cAAc,CAAC;oCAErB,IAAI,gBAAgB,EAAE;wCACpB,sEAAsE;wCACtE,MAAM,wBAAwB,GAC5B,eAAe,CAAC,KAAK,GAAG,gBAAgB,CAAC;wCAC3C,MAAM,qBAAqB,GACzB,mBAAmB,GAAG,wBAAwB,CAAC;wCACjD,iBAAiB,IAAI,qBAAqB,CAAC;qCAC5C;iCACF;6BACF;yBACF;qBACF;iBACF;qBAAM;oBACL,oEAAoE;oBACpE,MAAM,eAAe,GACnB,uBAAuB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBAC/C,IAAI,eAAe,EAAE;wBACnB,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CACjD,eAAe,CAChB,EAAE;4BACD,MAAM,aAAa,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;4BAErD,yDAAyD;4BACzD,IAAI,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE;gCAC/B,SAAS;6BACV;4BAED,gEAAgE;4BAChE,MAAM,cAAc,GAClB,oBAAoB,CAAC,eAAe,CAClC,OAAwB,CACzB,CAAC;4BACJ,IAAI,cAAc,EAAE;gCAClB,MAAM,mBAAmB,GAAG,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;gCAE5D,iEAAiE;gCACjE,IAAI,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE;oCACrC,SAAS;iCACV;gCAED,0EAA0E;gCAC1E,MAAM,qBAAqB,GACzB,aAAa,GAAG,mBAAmB,CAAC;gCACtC,iBAAiB,IAAI,qBAAqB,CAAC;6BAC5C;yBACF;qBACF;iBACF;aACF;YAED,aAAa,CAAC,OAAO,CAAC,GAAG;gBACvB,QAAQ;gBACR,OAAO;gBACP,0BAA0B,EAAE,iBAAiB;gBAC7C,YAAY,EAAE,iBAAiB,CAAC,eAAe;aAChD,CAAC;YACF,kBAAkB,IAAI,iBAAiB,CAAC;SACzC;QAED,cAAc,CAAC,QAAQ,CAAC,GAAG;YACzB,QAAQ;YACR,MAAM,EAAE,aAAa;YACrB,0BAA0B,EAAE,kBAAkB;YAC9C,YAAY,EAAE,iBAAiB,CAAC,eAAe;SAChD,CAAC;QACF,0BAA0B,IAAI,kBAAkB,CAAC;KAClD;IAED,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,0BAA0B;QAC1B,YAAY,EAAE,iBAAiB,CAAC,eAAe;KAChD,CAAC;AACJ,CAAC,CACF,CAAC;AAnMS,QAAA,0BAA0B,8BAmMnC;AA+BJ;;;;;;GAMG;AACI,MAAM,2BAA2B,GAAG,CAAC,OAAe,EAAE,EAAE,CAC7D,IAAA,yBAAc,EACZ,CAAC,IAAA,kCAA0B,GAAE,CAAC,EAC9B,CAAC,WAAW,EAAuB,EAAE;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAoB,CAAC;IAC1D,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;QACtC,OAAO;YACL,QAAQ;YACR,OAAO;YACP,0BAA0B,EAAE,CAAC;YAC7B,YAAY,EAAE,WAAW,CAAC,YAAY;SACvC,CAAC;KACH;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC,CACF,CAAC;AAlBS,QAAA,2BAA2B,+BAkBpC;AAEJ;;;;;;GAMG;AACI,MAAM,qBAAqB,GAAG,CAAC,QAAyB,EAAE,EAAE,CACjE,IAAA,yBAAc,EACZ,CAAC,IAAA,kCAA0B,GAAE,CAAC,EAC9B,CAAC,WAAW,EAAiB,EAAE;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,CAAC,MAAM,EAAE;QACX,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,EAAE;YACV,0BAA0B,EAAE,CAAC;YAC7B,YAAY,EAAE,WAAW,CAAC,YAAY;SACvC,CAAC;KACH;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CACF,CAAC;AAjBS,QAAA,qBAAqB,yBAiB9B;AAEJ;;;;;GAKG;AACI,MAAM,oCAAoC,GAAG,GAAG,EAAE,CACvD,IAAA,yBAAc,EACZ,CAAC,gCAAgC,EAAE,IAAA,kCAA0B,GAAE,CAAC,EAChE,CAAC,gBAAgB,EAAE,WAAW,EAA8B,EAAE;IAC5D,MAAM,eAAe,GAAG,gBAAgB,CAAC,WAAW,CAAC,oBAAoB,CAAC;IAE1E,IAAI,CAAC,eAAe,EAAE;QACpB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAoB,CAAC;IAClE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE;QAC9C,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,eAAe;YACxB,0BAA0B,EAAE,CAAC;YAC7B,YAAY,EAAE,WAAW,CAAC,YAAY;SACvC,CAAC;KACH;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AACxC,CAAC,CACF,CAAC;AAxBS,QAAA,oCAAoC,wCAwB7C;AAEJ;;GAEG;AACU,QAAA,gBAAgB,GAAG;IAC9B,2BAA2B,EAA3B,mCAA2B;IAC3B,qBAAqB,EAArB,6BAAqB;IACrB,0BAA0B,EAA1B,kCAA0B;IAC1B,oCAAoC,EAApC,4CAAoC;CACrC,CAAC","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 type { EntropySourceId } from '@metamask/keyring-api';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { Hex } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\nimport { createSelector } from 'reselect';\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\n/**\n * Individual controller state selectors using direct state access\n * This avoids new object creation and provides stable references\n * Supports both mobile (state.engine.backgroundState) and extension (state.metamask) structures\n */\n\n/**\n * Helper function to get controller state from different state structures\n *\n * @param state - The application state\n * @param controllerName - The name of the controller\n * @returns The controller state or undefined if not found\n */\n\nconst getControllerState = <T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n state: any,\n controllerName: string,\n): T => {\n // Mobile structure: state.engine.backgroundState.ControllerName\n if (state?.engine?.backgroundState?.[controllerName]) {\n return state.engine.backgroundState[controllerName];\n }\n\n // Extension structure: state.metamask.ControllerName\n if (state?.metamask?.[controllerName]) {\n return state.metamask[controllerName];\n }\n\n // Flat structure (default assets-controllers structure)\n if (state?.[controllerName]) {\n return state[controllerName];\n }\n\n // Since controllers always have default states, this should never happen\n // but we need to return something for TypeScript\n return state?.[controllerName] as T;\n};\n\n/**\n * Selector for AccountTreeController state using direct state access\n *\n * @param state - The application state\n * @returns AccountTreeController state\n */\nconst selectAccountTreeControllerState = createSelector(\n [(state: unknown) => state],\n (state): AccountTreeControllerState =>\n getControllerState<AccountTreeControllerState>(\n state,\n 'AccountTreeController',\n ),\n);\n\n/**\n * Selector for AccountsController state using direct state access\n *\n * @param state - The application state\n * @returns AccountsController state\n */\nconst selectAccountsControllerState = createSelector(\n [(state: unknown) => state],\n (state): AccountsControllerState =>\n getControllerState<AccountsControllerState>(state, 'AccountsController'),\n);\n\n/**\n * Selector for TokenBalancesController state using direct state access\n *\n * @param state - The application state\n * @returns TokenBalancesController state\n */\nconst selectTokenBalancesControllerState = createSelector(\n [(state: unknown) => state],\n (state): TokenBalancesControllerState =>\n getControllerState<TokenBalancesControllerState>(\n state,\n 'TokenBalancesController',\n ),\n);\n\n/**\n * Selector for TokenRatesController state using direct state access\n *\n * @param state - The application state\n * @returns TokenRatesController state\n */\nconst selectTokenRatesControllerState = createSelector(\n [(state: unknown) => state],\n (state): TokenRatesControllerState =>\n getControllerState<TokenRatesControllerState>(\n state,\n 'TokenRatesController',\n ),\n);\n\n/**\n * Selector for MultichainAssetsRatesController state using direct state access\n *\n * @param state - The application state\n * @returns MultichainAssetsRatesController state\n */\nconst selectMultichainAssetsRatesControllerState = createSelector(\n [(state: unknown) => state],\n (state): MultichainAssetsRatesControllerState =>\n getControllerState<MultichainAssetsRatesControllerState>(\n state,\n 'MultichainAssetsRatesController',\n ),\n);\n\n/**\n * Selector for MultichainBalancesController state using direct state access\n *\n * @param state - The application state\n * @returns MultichainBalancesController state\n */\nconst selectMultichainBalancesControllerState = createSelector(\n [(state: unknown) => state],\n (state): MultichainBalancesControllerState =>\n getControllerState<MultichainBalancesControllerState>(\n state,\n 'MultichainBalancesController',\n ),\n);\n\n/**\n * Selector for TokensController state using direct state access\n *\n * @param state - The application state\n * @returns TokensController state\n */\nconst selectTokensControllerState = createSelector(\n [(state: unknown) => state],\n (state): TokensControllerState =>\n getControllerState<TokensControllerState>(state, 'TokensController'),\n);\n\n/**\n * Selector for CurrencyRateController state using direct state access\n *\n * @param state - The application state\n * @returns CurrencyRateController state\n */\nconst selectCurrencyRateControllerState = createSelector(\n [(state: unknown) => state],\n (state): CurrencyRateState =>\n getControllerState<CurrencyRateState>(state, 'CurrencyRateController'),\n);\n\n/**\n * Helper function to get internal accounts for a specific group.\n * Uses AccountTreeController state to find accounts.\n *\n * @param accountTreeState - AccountTreeController state\n * @param accountsState - AccountsController state\n * @param groupId - The account group ID (format: \"walletId/groupIndex\")\n * @returns Array of internal accounts in the group\n */\nconst getInternalAccountsForGroup = (\n accountTreeState: AccountTreeControllerState,\n accountsState: AccountsControllerState,\n groupId: string,\n) => {\n // Extract walletId from groupId (format: \"walletId/groupIndex\")\n const walletId = groupId.split('/')[0] as EntropySourceId;\n\n const wallet = (\n accountTreeState.accountTree.wallets as Record<string, AccountWalletObject>\n )[walletId];\n if (!wallet) {\n return [];\n }\n\n const group = (wallet.groups as Record<string, AccountGroupObject>)[groupId];\n if (!group) {\n return [];\n }\n\n // Map account IDs to actual account objects\n return group.accounts\n .map(\n (accountId: string) => accountsState.internalAccounts.accounts[accountId],\n )\n .filter(Boolean);\n};\n\n/**\n * Comprehensive selector that calculates all balances for all wallets and groups.\n * This is the single source of truth for all balance calculations.\n * Other selectors will derive from this to ensure proper memoization.\n *\n * @returns Aggregated balance for all wallets\n */\nexport const selectBalanceForAllWallets = () =>\n createSelector(\n [\n selectAccountTreeControllerState,\n selectAccountsControllerState,\n selectTokenBalancesControllerState,\n selectTokenRatesControllerState,\n selectMultichainAssetsRatesControllerState,\n selectMultichainBalancesControllerState,\n selectTokensControllerState,\n selectCurrencyRateControllerState,\n ],\n (\n accountTreeState,\n accountsState,\n tokenBalancesState,\n tokenRatesState,\n multichainRatesState,\n multichainBalancesState,\n tokensState,\n currencyRateState,\n ): AllWalletsBalance => {\n const walletBalances: Record<string, WalletBalance> = {};\n let totalBalanceInUserCurrency = 0;\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\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 // Process each account's balances\n for (const account of accounts) {\n const isEvmAccount = isEvmAccountType(account.type);\n\n if (isEvmAccount) {\n // Handle EVM account balances from TokenBalancesController\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 for (const [tokenAddress, balance] of Object.entries(\n chainBalances,\n )) {\n // Find token in TokensController state\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 // Use nullish coalescing to handle 0 decimals correctly\n // and ensure decimals is a valid number to prevent NaN propagation\n const decimals =\n typeof token.decimals === 'number' &&\n !Number.isNaN(token.decimals)\n ? token.decimals\n : 18;\n const balanceInSmallestUnit = parseInt(\n balance as string,\n 16,\n );\n\n // Skip invalid balance values to prevent NaN propagation\n if (Number.isNaN(balanceInSmallestUnit)) {\n continue;\n }\n\n const balanceInTokenUnits =\n balanceInSmallestUnit / Math.pow(10, decimals);\n\n // Get token rate in native currency from TokenRatesController\n const chainMarketData =\n tokenRatesState.marketData[chainId as Hex];\n const tokenMarketData =\n chainMarketData?.[tokenAddress as Hex];\n if (tokenMarketData?.price) {\n // Convert token price to user currency using native currency conversion rate\n const nativeCurrency = tokenMarketData.currency;\n const nativeToUserRate =\n currencyRateState.currencyRates[nativeCurrency]\n ?.conversionRate;\n\n if (nativeToUserRate) {\n // Convert token price to user currency: tokenPrice * 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 // Handle non-EVM account balances from MultichainBalancesController\n const accountBalances =\n multichainBalancesState.balances[account.id];\n if (accountBalances) {\n for (const [assetId, balanceData] of Object.entries(\n accountBalances,\n )) {\n const balanceAmount = parseFloat(balanceData.amount);\n\n // Skip invalid balance values to prevent NaN propagation\n if (Number.isNaN(balanceAmount)) {\n continue;\n }\n\n // Get conversion rate for this asset (already in user currency)\n const conversionRate =\n multichainRatesState.conversionRates[\n assetId as CaipAssetType\n ];\n if (conversionRate) {\n const conversionRateValue = parseFloat(conversionRate.rate);\n\n // Skip invalid conversion rate values to prevent NaN propagation\n if (Number.isNaN(conversionRateValue)) {\n continue;\n }\n\n // MultichainAssetsRatesController already provides rates in user currency\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/**\n * Aggregated balance for an account group\n */\nexport type AccountGroupBalance = {\n walletId: string;\n groupId: string;\n totalBalanceInUserCurrency: number; // not formatted\n userCurrency: string;\n};\n\n/**\n * Aggregated balance for a wallet (all groups)\n */\nexport type WalletBalance = {\n walletId: string;\n groups: Record<string, AccountGroupBalance>;\n totalBalanceInUserCurrency: number; // not formatted\n userCurrency: string;\n};\n\n/**\n * Aggregated balance for all wallets\n */\nexport type AllWalletsBalance = {\n wallets: Record<string, WalletBalance>;\n totalBalanceInUserCurrency: number; // not formatted\n userCurrency: string;\n};\n\n/**\n * Selector to get aggregated balances for a specific account group.\n * Derives from the comprehensive selector to ensure proper memoization.\n *\n * @param groupId - The account group ID (format: \"walletId/groupIndex\", e.g., \"entropy:entropy-source-1/0\")\n * @returns Aggregated balance for the account group\n */\nexport const selectBalanceByAccountGroup = (groupId: string) =>\n createSelector(\n [selectBalanceForAllWallets()],\n (allBalances): AccountGroupBalance => {\n const walletId = groupId.split('/')[0] as EntropySourceId;\n const wallet = allBalances.wallets[walletId];\n\n if (!wallet || !wallet.groups[groupId]) {\n return {\n walletId,\n groupId,\n totalBalanceInUserCurrency: 0,\n userCurrency: allBalances.userCurrency,\n };\n }\n\n return wallet.groups[groupId];\n },\n );\n\n/**\n * Selector to get aggregated balances for all account groups in a wallet.\n * Derives from the comprehensive selector to ensure proper memoization.\n *\n * @param walletId - The wallet ID (entropy source)\n * @returns Aggregated balance for all groups in the wallet\n */\nexport const selectBalanceByWallet = (walletId: EntropySourceId) =>\n createSelector(\n [selectBalanceForAllWallets()],\n (allBalances): WalletBalance => {\n const wallet = allBalances.wallets[walletId];\n\n if (!wallet) {\n return {\n walletId,\n groups: {},\n totalBalanceInUserCurrency: 0,\n userCurrency: allBalances.userCurrency,\n };\n }\n\n return wallet;\n },\n );\n\n/**\n * Selector to get aggregated balances for the currently selected account group.\n * Derives from the comprehensive selector to ensure proper memoization.\n *\n * @returns Aggregated balance for the currently selected group\n */\nexport const selectBalanceForSelectedAccountGroup = () =>\n createSelector(\n [selectAccountTreeControllerState, selectBalanceForAllWallets()],\n (accountTreeState, allBalances): AccountGroupBalance | null => {\n const selectedGroupId = accountTreeState.accountTree.selectedAccountGroup;\n\n if (!selectedGroupId) {\n return null;\n }\n\n const walletId = selectedGroupId.split('/')[0] as EntropySourceId;\n const wallet = allBalances.wallets[walletId];\n\n if (!wallet || !wallet.groups[selectedGroupId]) {\n return {\n walletId,\n groupId: selectedGroupId,\n totalBalanceInUserCurrency: 0,\n userCurrency: allBalances.userCurrency,\n };\n }\n\n return wallet.groups[selectedGroupId];\n },\n );\n\n/**\n * Collection of balance-related selectors for assets controllers\n */\nexport const balanceSelectors = {\n selectBalanceByAccountGroup,\n selectBalanceByWallet,\n selectBalanceForAllWallets,\n selectBalanceForSelectedAccountGroup,\n};\n"]}