@metamask/assets-controllers 73.1.0 → 73.3.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 +43 -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 +286 -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 +286 -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 +11 -11
  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
@@ -1,4 +1,13 @@
1
+ function $importDefault(module) {
2
+ if (module?.__esModule) {
3
+ return module.default;
4
+ }
5
+ return module;
6
+ }
1
7
  import { Contract } from "@ethersproject/contracts";
8
+ import $BN from "bn.js";
9
+ const BN = $importDefault($BN);
10
+ import { STAKING_CONTRACT_ADDRESS_BY_CHAINID } from "./AssetsContractController.mjs";
2
11
  import { reduceInBatchesSerially } from "./assetsUtil.mjs";
3
12
  // https://github.com/mds1/multicall/blob/main/deployments.json
4
13
  const MULTICALL_CONTRACT_BY_CHAINID = {
@@ -282,6 +291,78 @@ const multicallAbi = [
282
291
  ],
283
292
  },
284
293
  ];
294
+ // Multicall3 ABI for aggregate3 function
295
+ const multicall3Abi = [
296
+ {
297
+ name: 'aggregate3',
298
+ type: 'function',
299
+ stateMutability: 'payable',
300
+ inputs: [
301
+ {
302
+ name: 'calls',
303
+ type: 'tuple[]',
304
+ components: [
305
+ { name: 'target', type: 'address' },
306
+ { name: 'allowFailure', type: 'bool' },
307
+ { name: 'callData', type: 'bytes' },
308
+ ],
309
+ },
310
+ ],
311
+ outputs: [
312
+ {
313
+ name: 'returnData',
314
+ type: 'tuple[]',
315
+ components: [
316
+ { name: 'success', type: 'bool' },
317
+ { name: 'returnData', type: 'bytes' },
318
+ ],
319
+ },
320
+ ],
321
+ },
322
+ ];
323
+ // Constants for encoded strings and addresses
324
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
325
+ const BALANCE_OF_FUNCTION = 'balanceOf(address)';
326
+ const GET_ETH_BALANCE_FUNCTION = 'getEthBalance';
327
+ const GET_SHARES_FUNCTION = 'getShares';
328
+ const CONVERT_TO_ASSETS_FUNCTION = 'convertToAssets';
329
+ // ERC20 balanceOf ABI
330
+ const ERC20_BALANCE_OF_ABI = [
331
+ {
332
+ name: 'balanceOf',
333
+ type: 'function',
334
+ inputs: [{ name: 'account', type: 'address' }],
335
+ outputs: [{ name: '', type: 'uint256' }],
336
+ stateMutability: 'view',
337
+ },
338
+ ];
339
+ // Multicall3 getEthBalance ABI
340
+ const MULTICALL3_GET_ETH_BALANCE_ABI = [
341
+ {
342
+ name: 'getEthBalance',
343
+ type: 'function',
344
+ inputs: [{ name: 'addr', type: 'address' }],
345
+ outputs: [{ name: 'balance', type: 'uint256' }],
346
+ stateMutability: 'view',
347
+ },
348
+ ];
349
+ // Staking contract ABI with both getShares and convertToAssets
350
+ const STAKING_CONTRACT_ABI = [
351
+ {
352
+ inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
353
+ name: 'getShares',
354
+ outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
355
+ stateMutability: 'view',
356
+ type: 'function',
357
+ },
358
+ {
359
+ inputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }],
360
+ name: 'convertToAssets',
361
+ outputs: [{ internalType: 'uint256', name: 'assets', type: 'uint256' }],
362
+ stateMutability: 'view',
363
+ type: 'function',
364
+ },
365
+ ];
285
366
  const multicall = async (calls, multicallAddress, provider, maxCallsPerMulticall) => {
286
367
  const multicallContract = new Contract(multicallAddress, multicallAbi, provider);
287
368
  return await reduceInBatchesSerially({
@@ -327,6 +408,7 @@ const fallback = async (calls, maxCallsParallel) => {
327
408
  * Executes an array of contract calls. If the chain supports multicalls,
328
409
  * the calls will be executed in single RPC requests (up to maxCallsPerMulticall).
329
410
  * Otherwise the calls will be executed separately in parallel (up to maxCallsParallel).
411
+ *
330
412
  * @param calls - An array of contract calls to execute.
331
413
  * @param chainId - The hexadecimal chain id.
332
414
  * @param provider - An ethers rpc provider.
@@ -356,4 +438,379 @@ export const multicallOrFallback = async (calls, chainId, provider, maxCallsPerM
356
438
  }
357
439
  return await fallback(calls, maxCallsParallel);
358
440
  };
441
+ /**
442
+ * Execute multiple contract calls using Multicall3's aggregate3 function.
443
+ * This allows for more efficient batch calls with individual failure handling.
444
+ *
445
+ * @param calls - Array of calls to execute via aggregate3
446
+ * @param chainId - The hexadecimal chain id
447
+ * @param provider - An ethers rpc provider
448
+ * @returns Promise resolving to array of results from aggregate3
449
+ */
450
+ export const aggregate3 = async (calls, chainId, provider) => {
451
+ if (calls.length === 0) {
452
+ return [];
453
+ }
454
+ const multicall3Address = MULTICALL_CONTRACT_BY_CHAINID[chainId];
455
+ const multicall3Contract = new Contract(multicall3Address, multicall3Abi, provider);
456
+ return await multicall3Contract.callStatic.aggregate3(calls);
457
+ };
458
+ /**
459
+ * Processes and decodes balance results from aggregate3 calls
460
+ *
461
+ * @param results - Array of results from aggregate3 calls
462
+ * @param callMapping - Array mapping call indices to token and user addresses
463
+ * @param chainId - The hexadecimal chain id
464
+ * @param provider - An ethers rpc provider
465
+ * @param includeStaked - Whether to include staked balances
466
+ * @returns Map of token address to map of user address to balance
467
+ */
468
+ const processBalanceResults = (results, callMapping, chainId, provider, includeStaked) => {
469
+ const balanceMap = {};
470
+ const stakedBalanceMap = {};
471
+ // Create contract instances for decoding
472
+ const erc20Contract = new Contract(ZERO_ADDRESS, ERC20_BALANCE_OF_ABI, provider);
473
+ const multicall3Address = MULTICALL_CONTRACT_BY_CHAINID[chainId];
474
+ const multicall3Contract = new Contract(multicall3Address, MULTICALL3_GET_ETH_BALANCE_ABI, provider);
475
+ // Staking contracts are now handled separately in two-step process
476
+ results.forEach((result, index) => {
477
+ if (result.success) {
478
+ const { tokenAddress, userAddress, callType } = callMapping[index];
479
+ let balance;
480
+ if (callType === 'native') {
481
+ // For native token, decode the getEthBalance result
482
+ balance = multicall3Contract.interface.decodeFunctionResult(GET_ETH_BALANCE_FUNCTION, result.returnData)[0];
483
+ if (!balanceMap[tokenAddress]) {
484
+ balanceMap[tokenAddress] = {};
485
+ }
486
+ balanceMap[tokenAddress][userAddress] = balance;
487
+ }
488
+ else if (callType === 'staking') {
489
+ // Staking is now handled separately in two-step process
490
+ // This case should not occur anymore
491
+ console.warn('Staking callType found in main processing - this should not happen');
492
+ }
493
+ else {
494
+ // For ERC20 tokens, decode the balanceOf result
495
+ balance = erc20Contract.interface.decodeFunctionResult(BALANCE_OF_FUNCTION, result.returnData)[0];
496
+ if (!balanceMap[tokenAddress]) {
497
+ balanceMap[tokenAddress] = {};
498
+ }
499
+ balanceMap[tokenAddress][userAddress] = balance;
500
+ }
501
+ }
502
+ });
503
+ const result = { tokenBalances: balanceMap };
504
+ if (includeStaked && Object.keys(stakedBalanceMap).length > 0) {
505
+ result.stakedBalances = stakedBalanceMap;
506
+ }
507
+ return result;
508
+ };
509
+ /**
510
+ * Fallback function to get native token balances using individual eth_getBalance calls
511
+ * when Multicall3 is not supported on the chain.
512
+ *
513
+ * @param userAddresses - Array of user addresses to check balances for
514
+ * @param provider - An ethers rpc provider
515
+ * @param maxCallsParallel - Maximum number of parallel calls (default: 20)
516
+ * @returns Promise resolving to map of user address to balance
517
+ */
518
+ const getNativeBalancesFallback = async (userAddresses, provider, maxCallsParallel = 20) => {
519
+ const balanceMap = {};
520
+ await reduceInBatchesSerially({
521
+ values: userAddresses,
522
+ batchSize: maxCallsParallel,
523
+ initialResult: undefined,
524
+ eachBatch: async (_, batch) => {
525
+ const results = await Promise.allSettled(batch.map(async (userAddress) => {
526
+ const balance = await provider.getBalance(userAddress);
527
+ return {
528
+ success: true,
529
+ balance: new BN(balance.toString()),
530
+ userAddress,
531
+ };
532
+ }));
533
+ results.forEach((result) => {
534
+ if (result.status === 'fulfilled' &&
535
+ result.value.success &&
536
+ result.value.balance !== null) {
537
+ balanceMap[result.value.userAddress] = result.value.balance;
538
+ }
539
+ });
540
+ },
541
+ });
542
+ return balanceMap;
543
+ };
544
+ /**
545
+ * Fallback function to get token balances using individual calls
546
+ * when Multicall3 is not supported or when aggregate3 calls fail.
547
+ *
548
+ * @param tokenAddresses - Array of ERC20 token contract addresses
549
+ * @param userAddresses - Array of user addresses to check balances for
550
+ * @param provider - An ethers rpc provider
551
+ * @param includeNative - Whether to include native token balances (default: true)
552
+ * @param maxCallsParallel - Maximum number of parallel calls (default: 20)
553
+ * @returns Promise resolving to map of token address to map of user address to balance
554
+ */
555
+ const getTokenBalancesFallback = async (tokenAddresses, userAddresses, provider, includeNative, maxCallsParallel) => {
556
+ const balanceMap = {};
557
+ // Handle ERC20 token balances using the existing fallback function
558
+ if (tokenAddresses.length > 0) {
559
+ const erc20Calls = [];
560
+ const callMapping = [];
561
+ tokenAddresses.forEach((tokenAddress) => {
562
+ userAddresses.forEach((userAddress) => {
563
+ const contract = new Contract(tokenAddress, ERC20_BALANCE_OF_ABI, provider);
564
+ erc20Calls.push({
565
+ contract,
566
+ functionSignature: BALANCE_OF_FUNCTION,
567
+ arguments: [userAddress],
568
+ });
569
+ callMapping.push({ tokenAddress, userAddress });
570
+ });
571
+ });
572
+ const erc20Results = await fallback(erc20Calls, maxCallsParallel);
573
+ erc20Results.forEach((result, index) => {
574
+ if (result.success) {
575
+ const { tokenAddress, userAddress } = callMapping[index];
576
+ if (!balanceMap[tokenAddress]) {
577
+ balanceMap[tokenAddress] = {};
578
+ }
579
+ balanceMap[tokenAddress][userAddress] = result.value;
580
+ }
581
+ });
582
+ }
583
+ // Handle native token balances using the native fallback function
584
+ if (includeNative) {
585
+ const nativeBalances = await getNativeBalancesFallback(userAddresses, provider, maxCallsParallel);
586
+ if (Object.keys(nativeBalances).length > 0) {
587
+ balanceMap[ZERO_ADDRESS] = nativeBalances;
588
+ }
589
+ }
590
+ return balanceMap;
591
+ };
592
+ /**
593
+ * Fallback function to get staked balances using individual calls
594
+ * when Multicall3 is not supported or when aggregate3 calls fail.
595
+ *
596
+ * @param userAddresses - Array of user addresses to check staked balances for
597
+ * @param chainId - The hexadecimal chain id
598
+ * @param provider - An ethers rpc provider
599
+ * @param maxCallsParallel - Maximum number of parallel calls (default: 20)
600
+ * @returns Promise resolving to map of user address to staked balance
601
+ */
602
+ const getStakedBalancesFallback = async (userAddresses, chainId, provider, maxCallsParallel) => {
603
+ const stakedBalanceMap = {};
604
+ const stakingContractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId];
605
+ if (!stakingContractAddress) {
606
+ // No staking support for this chain
607
+ return stakedBalanceMap;
608
+ }
609
+ const stakingCalls = [];
610
+ const callMapping = [];
611
+ userAddresses.forEach((userAddress) => {
612
+ const contract = new Contract(stakingContractAddress, STAKING_CONTRACT_ABI, provider);
613
+ stakingCalls.push({
614
+ contract,
615
+ functionSignature: GET_SHARES_FUNCTION,
616
+ arguments: [userAddress],
617
+ });
618
+ callMapping.push({ userAddress });
619
+ });
620
+ const stakingResults = await fallback(stakingCalls, maxCallsParallel);
621
+ stakingResults.forEach((result, index) => {
622
+ if (result.success) {
623
+ const { userAddress } = callMapping[index];
624
+ stakedBalanceMap[userAddress] = result.value;
625
+ }
626
+ });
627
+ return stakedBalanceMap;
628
+ };
629
+ /**
630
+ * Get staked balances for multiple addresses using two-step process:
631
+ * 1. Get shares for all addresses
632
+ * 2. Convert non-zero shares to assets
633
+ *
634
+ * @param userAddresses - Array of user addresses to check
635
+ * @param chainId - Chain ID as hex string
636
+ * @param provider - Ethers provider
637
+ * @returns Promise resolving to map of user address to staked balance
638
+ */
639
+ export const getStakedBalancesForAddresses = async (userAddresses, chainId, provider) => {
640
+ const stakingContractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[chainId];
641
+ if (!stakingContractAddress) {
642
+ return {};
643
+ }
644
+ const stakingContract = new Contract(stakingContractAddress, STAKING_CONTRACT_ABI, provider);
645
+ try {
646
+ // Step 1: Get shares for all addresses
647
+ const shareCalls = userAddresses.map((userAddress) => ({
648
+ target: stakingContractAddress,
649
+ allowFailure: true,
650
+ callData: stakingContract.interface.encodeFunctionData(GET_SHARES_FUNCTION, [userAddress]),
651
+ }));
652
+ const shareResults = await aggregate3(shareCalls, chainId, provider);
653
+ // Step 2: For addresses with non-zero shares, convert to assets
654
+ const nonZeroSharesData = [];
655
+ shareResults.forEach((result, index) => {
656
+ if (result.success) {
657
+ const sharesRaw = stakingContract.interface.decodeFunctionResult(GET_SHARES_FUNCTION, result.returnData)[0];
658
+ const shares = new BN(sharesRaw.toString());
659
+ if (shares.gt(new BN(0))) {
660
+ nonZeroSharesData.push({
661
+ address: userAddresses[index],
662
+ shares,
663
+ });
664
+ }
665
+ }
666
+ });
667
+ if (nonZeroSharesData.length === 0) {
668
+ return {};
669
+ }
670
+ // Step 3: Convert shares to assets for addresses with non-zero shares
671
+ const assetCalls = nonZeroSharesData.map(({ shares }) => ({
672
+ target: stakingContractAddress,
673
+ allowFailure: true,
674
+ callData: stakingContract.interface.encodeFunctionData(CONVERT_TO_ASSETS_FUNCTION, [shares.toString()]),
675
+ }));
676
+ const assetResults = await aggregate3(assetCalls, chainId, provider);
677
+ // Step 4: Build final result mapping
678
+ const result = {};
679
+ assetResults.forEach((assetResult, index) => {
680
+ if (assetResult.success) {
681
+ const assetsRaw = stakingContract.interface.decodeFunctionResult(CONVERT_TO_ASSETS_FUNCTION, assetResult.returnData)[0];
682
+ const assets = new BN(assetsRaw.toString());
683
+ const { address } = nonZeroSharesData[index];
684
+ result[address] = assets;
685
+ }
686
+ });
687
+ return result;
688
+ }
689
+ catch (error) {
690
+ console.error('Error fetching staked balances:', error);
691
+ return {};
692
+ }
693
+ };
694
+ /**
695
+ * Get token balances (both ERC20 and native) for multiple addresses using aggregate3.
696
+ * This is more efficient than individual balanceOf calls for multiple addresses and tokens.
697
+ * Native token balances are mapped to the zero address (0x0000000000000000000000000000000000000000).
698
+ *
699
+ * @param accountTokenGroups - Array of objects containing account addresses and their associated token addresses
700
+ * @param chainId - The hexadecimal chain id
701
+ * @param provider - An ethers rpc provider
702
+ * @param includeNative - Whether to include native token balances (default: true)
703
+ * @param includeStaked - Whether to include staked balances from supported staking contracts (default: false)
704
+ * @returns Promise resolving to object containing tokenBalances map and optional stakedBalances map
705
+ */
706
+ export const getTokenBalancesForMultipleAddresses = async (accountTokenGroups, chainId, provider, includeNative, includeStaked) => {
707
+ // Return early if no groups provided
708
+ if (accountTokenGroups.length === 0 && !includeNative && !includeStaked) {
709
+ return { tokenBalances: {} };
710
+ }
711
+ // Extract unique token addresses and user addresses from groups
712
+ const uniqueTokenAddresses = Array.from(new Set(accountTokenGroups.flatMap((group) => group.tokenAddresses))).filter((tokenAddress) => tokenAddress !== ZERO_ADDRESS); // Exclude native token from ERC20 calls
713
+ const uniqueUserAddresses = Array.from(new Set(accountTokenGroups.map((group) => group.accountAddress)));
714
+ // Check if Multicall3 is supported on this chain
715
+ if (!MULTICALL_CONTRACT_BY_CHAINID[chainId]) {
716
+ // Fallback to individual balance calls when Multicall3 is not supported
717
+ const tokenBalances = await getTokenBalancesFallback(uniqueTokenAddresses, uniqueUserAddresses, provider, includeNative, 20);
718
+ const result = { tokenBalances };
719
+ // Handle staked balances fallback if requested
720
+ if (includeStaked) {
721
+ const stakedBalances = await getStakedBalancesFallback(uniqueUserAddresses, chainId, provider, 20);
722
+ if (Object.keys(stakedBalances).length > 0) {
723
+ result.stakedBalances = stakedBalances;
724
+ }
725
+ }
726
+ return result;
727
+ }
728
+ try {
729
+ // Create calls directly from pairs
730
+ const allCalls = [];
731
+ const allCallMapping = [];
732
+ // Create a temporary ERC20 contract for encoding
733
+ const tempERC20Contract = new Contract(ZERO_ADDRESS, ERC20_BALANCE_OF_ABI, provider);
734
+ // Create ERC20 balance calls for all account-token combinations
735
+ accountTokenGroups.forEach((group) => {
736
+ group.tokenAddresses
737
+ .filter((tokenAddress) => tokenAddress !== ZERO_ADDRESS)
738
+ .forEach((tokenAddress) => {
739
+ allCalls.push({
740
+ target: tokenAddress,
741
+ allowFailure: true,
742
+ callData: tempERC20Contract.interface.encodeFunctionData(BALANCE_OF_FUNCTION, [group.accountAddress]),
743
+ });
744
+ allCallMapping.push({
745
+ tokenAddress,
746
+ userAddress: group.accountAddress,
747
+ callType: 'erc20',
748
+ });
749
+ });
750
+ });
751
+ // Add native token balance calls if requested
752
+ if (includeNative) {
753
+ const multicall3Address = MULTICALL_CONTRACT_BY_CHAINID[chainId];
754
+ const multicall3TempContract = new Contract(multicall3Address, MULTICALL3_GET_ETH_BALANCE_ABI, provider);
755
+ uniqueUserAddresses.forEach((userAddress) => {
756
+ allCalls.push({
757
+ target: multicall3Address,
758
+ allowFailure: true,
759
+ callData: multicall3TempContract.interface.encodeFunctionData(GET_ETH_BALANCE_FUNCTION, [userAddress]),
760
+ });
761
+ allCallMapping.push({
762
+ tokenAddress: ZERO_ADDRESS,
763
+ userAddress,
764
+ callType: 'native',
765
+ });
766
+ });
767
+ }
768
+ // Note: Staking balances will be handled separately in two steps after token/native calls
769
+ // Execute all calls in batches
770
+ const maxCallsPerBatch = 300; // Limit calls per batch to avoid gas/size limits
771
+ const allResults = [];
772
+ await reduceInBatchesSerially({
773
+ values: allCalls,
774
+ batchSize: maxCallsPerBatch,
775
+ initialResult: undefined,
776
+ eachBatch: async (_, batch) => {
777
+ const batchResults = await aggregate3(batch, chainId, provider);
778
+ allResults.push(...batchResults);
779
+ },
780
+ });
781
+ // Handle staking balances in two steps if requested
782
+ let stakedBalances = {};
783
+ if (includeStaked) {
784
+ stakedBalances = await getStakedBalancesForAddresses(uniqueUserAddresses, chainId, provider);
785
+ }
786
+ // Process and return results
787
+ const result = processBalanceResults(allResults, allCallMapping, chainId, provider, false);
788
+ // Add staked balances to result
789
+ if (includeStaked && Object.keys(stakedBalances).length > 0) {
790
+ result.stakedBalances = stakedBalances;
791
+ }
792
+ return result;
793
+ }
794
+ catch (error) {
795
+ // Fallback only on revert
796
+ // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION
797
+ if (!error ||
798
+ typeof error !== 'object' ||
799
+ !('code' in error) ||
800
+ error.code !== 'CALL_EXCEPTION') {
801
+ throw error;
802
+ }
803
+ // Fallback to individual balance calls when aggregate3 fails
804
+ const tokenBalances = await getTokenBalancesFallback(uniqueTokenAddresses, uniqueUserAddresses, provider, includeNative, 20);
805
+ const result = { tokenBalances };
806
+ // Handle staked balances fallback if requested
807
+ if (includeStaked) {
808
+ const stakedBalances = await getStakedBalancesFallback(uniqueUserAddresses, chainId, provider, 20);
809
+ if (Object.keys(stakedBalances).length > 0) {
810
+ result.stakedBalances = stakedBalances;
811
+ }
812
+ }
813
+ return result;
814
+ }
815
+ };
359
816
  //# sourceMappingURL=multicall.mjs.map