refactor(assets-controllers): migrate getBalancesInSingleCall to use multicall3
Explanation
Current State
The getBalancesInSingleCall function in AssetsContractController was previously using a direct contract call to the single-call balance checker contract (SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID). This approach had limited chain coverage as the contract was only deployed on a subset of networks.
Solution
This PR refactors getBalancesInSingleCall to use the getTokenBalancesForMultipleAddresses utility function, which leverages Multicall3's aggregate3 method. This provides:
- Better chain coverage: Multicall3 is deployed on significantly more networks (30+ chains vs the limited set in the old contract)
- Unified multicall infrastructure: Reuses existing multicall utilities instead of maintaining separate balance checking logic
- Consistent behavior: Maintains the same external API while improving internal implementation
Key Changes
-
AssetsContractController.ts
- Replaced direct contract interaction with
getTokenBalancesForMultipleAddresses - Added explicit check for Multicall3 support via
MULTICALL_CONTRACT_BY_CHAINIDbefore attempting to fetch balances - Returns empty object for unsupported chains (maintaining backward compatibility)
- Updated balance result mapping to correctly transform from
tokenAddress -> userAddress -> BNtotokenAddress -> BN
- Replaced direct contract interaction with
-
multicall.ts
- Exported
MULTICALL_CONTRACT_BY_CHAINIDconstant to enable external chain support checks
- Exported
-
Test Updates
- Replaced problematic
Web3Provider.prototype.callmocks (which caused stack overflow errors) with direct mocking ofgetTokenBalancesForMultipleAddresses - Added proper test cleanup with
afterEachhooks to restore mocks and clean network mocks usingnock.cleanAll() - Fixed test in
AssetsContractControllerWithNetworkClientId.test.tsto correctly reflect that Sepolia now has Multicall3 support - Updated assertions to work with
BNobjects instead ofBigNumber
- Replaced problematic
Non-Obvious Changes
- The test "should not have balance in a single call after switching to network without token detection support" was updated because Sepolia now supports Multicall3, so both networks return balances (previously only mainnet did)
- Added
afterEachcleanup hooks to prevent test pollution from persistent mocks
References
Checklist
- [x] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
- [ ] I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
- [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
@metamaskbot publish-preview
Preview builds have been published. See these instructions for more information about preview builds.
Expand for full list of packages and versions.
{
"@metamask-previews/account-tree-controller": "3.0.0-preview-ac25cee",
"@metamask-previews/accounts-controller": "34.0.0-preview-ac25cee",
"@metamask-previews/address-book-controller": "7.0.0-preview-ac25cee",
"@metamask-previews/analytics-controller": "0.0.0-preview-ac25cee",
"@metamask-previews/announcement-controller": "8.0.0-preview-ac25cee",
"@metamask-previews/app-metadata-controller": "2.0.0-preview-ac25cee",
"@metamask-previews/approval-controller": "8.0.0-preview-ac25cee",
"@metamask-previews/assets-controllers": "88.0.0-preview-ac25cee",
"@metamask-previews/base-controller": "9.0.0-preview-ac25cee",
"@metamask-previews/bridge-controller": "60.0.0-preview-ac25cee",
"@metamask-previews/bridge-status-controller": "60.0.0-preview-ac25cee",
"@metamask-previews/build-utils": "3.0.4-preview-ac25cee",
"@metamask-previews/chain-agnostic-permission": "1.2.2-preview-ac25cee",
"@metamask-previews/claims-controller": "0.2.0-preview-ac25cee",
"@metamask-previews/composable-controller": "12.0.0-preview-ac25cee",
"@metamask-previews/controller-utils": "11.15.0-preview-ac25cee",
"@metamask-previews/core-backend": "4.0.0-preview-ac25cee",
"@metamask-previews/delegation-controller": "1.0.0-preview-ac25cee",
"@metamask-previews/earn-controller": "10.0.0-preview-ac25cee",
"@metamask-previews/eip-5792-middleware": "2.0.0-preview-ac25cee",
"@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-ac25cee",
"@metamask-previews/eip1193-permission-middleware": "1.0.2-preview-ac25cee",
"@metamask-previews/ens-controller": "18.0.0-preview-ac25cee",
"@metamask-previews/error-reporting-service": "3.0.0-preview-ac25cee",
"@metamask-previews/eth-block-tracker": "14.0.0-preview-ac25cee",
"@metamask-previews/eth-json-rpc-middleware": "21.0.0-preview-ac25cee",
"@metamask-previews/eth-json-rpc-provider": "5.0.1-preview-ac25cee",
"@metamask-previews/foundryup": "1.0.1-preview-ac25cee",
"@metamask-previews/gas-fee-controller": "25.0.0-preview-ac25cee",
"@metamask-previews/gator-permissions-controller": "0.4.0-preview-ac25cee",
"@metamask-previews/json-rpc-engine": "10.1.1-preview-ac25cee",
"@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-ac25cee",
"@metamask-previews/keyring-controller": "24.0.0-preview-ac25cee",
"@metamask-previews/logging-controller": "7.0.0-preview-ac25cee",
"@metamask-previews/message-manager": "14.0.0-preview-ac25cee",
"@metamask-previews/messenger": "0.3.0-preview-ac25cee",
"@metamask-previews/multichain-account-service": "3.0.0-preview-ac25cee",
"@metamask-previews/multichain-api-middleware": "1.2.4-preview-ac25cee",
"@metamask-previews/multichain-network-controller": "2.0.0-preview-ac25cee",
"@metamask-previews/multichain-transactions-controller": "6.0.0-preview-ac25cee",
"@metamask-previews/name-controller": "9.0.0-preview-ac25cee",
"@metamask-previews/network-controller": "25.0.0-preview-ac25cee",
"@metamask-previews/network-enablement-controller": "3.1.0-preview-ac25cee",
"@metamask-previews/notification-services-controller": "19.0.0-preview-ac25cee",
"@metamask-previews/permission-controller": "12.1.0-preview-ac25cee",
"@metamask-previews/permission-log-controller": "5.0.0-preview-ac25cee",
"@metamask-previews/phishing-controller": "15.0.0-preview-ac25cee",
"@metamask-previews/polling-controller": "15.0.0-preview-ac25cee",
"@metamask-previews/preferences-controller": "21.0.0-preview-ac25cee",
"@metamask-previews/profile-sync-controller": "26.0.0-preview-ac25cee",
"@metamask-previews/rate-limit-controller": "7.0.0-preview-ac25cee",
"@metamask-previews/remote-feature-flag-controller": "2.0.0-preview-ac25cee",
"@metamask-previews/sample-controllers": "3.0.0-preview-ac25cee",
"@metamask-previews/seedless-onboarding-controller": "6.1.0-preview-ac25cee",
"@metamask-previews/selected-network-controller": "25.0.0-preview-ac25cee",
"@metamask-previews/shield-controller": "2.0.0-preview-ac25cee",
"@metamask-previews/signature-controller": "36.0.0-preview-ac25cee",
"@metamask-previews/subscription-controller": "4.0.0-preview-ac25cee",
"@metamask-previews/token-search-discovery-controller": "4.0.0-preview-ac25cee",
"@metamask-previews/transaction-controller": "61.1.0-preview-ac25cee",
"@metamask-previews/transaction-pay-controller": "4.0.0-preview-ac25cee",
"@metamask-previews/user-operation-controller": "40.0.0-preview-ac25cee"
}