feat: jwt auth accounts api + handle pending requests
Explanation
What is the current state and why does it need to change?
Currently, the multi-chain accounts API calls in TokenDetectionController and TokenBalancesController are made without authentication. This limits the ability to provide user-specific data and secure API endpoints that require authenticated requests. Additionally, there is no timeout protection for these API calls, which means a hanging request could block token detection indefinitely without falling back to RPC methods.
What is the solution and how does it work?
This PR adds optional JWT token authentication and timeout protection to the accounts API calls:
-
API Layer Changes (
multi-chain-accounts.ts):- Added optional
jwtTokenparameter tofetchMultiChainBalancesandfetchMultiChainBalancesV4 - When a JWT token is provided, it's included in the
Authorization: Bearer <token>header - The token is optional to maintain backward compatibility
- Added optional
-
Controller Integration:
-
TokenDetectionController:
- Fetches JWT token from
AuthenticationController:getBearerTokenand passes it tofetchMultiChainBalanceswhen detecting tokens via Accounts API - Implements 30-second timeout protection (
ACCOUNTS_API_TIMEOUT_MS) usingPromise.race()to prevent hanging API calls - Automatically falls back to RPC detection if API call times out or fails
- Logs timeout/failure events for debugging
- Fetches JWT token from
-
TokenBalancesController: Fetches JWT token and passes it through the balance fetcher chain to
fetchMultiChainBalancesV4
-
TokenDetectionController:
-
Balance Fetcher Updates (
api-balance-fetcher.ts):- Updated
AccountsApiBalanceFetcherto accept and pass JWT token through the fetch chain - Token flows from
updateBalances→fetch→#fetchBalances→ API calls
- Updated
-
Timeout Protection:
- Added
ACCOUNTS_API_TIMEOUT_MS = 30000constant (30 seconds) - Wrapped API calls in
#attemptAccountAPIDetectionwith timeout logic - Uses
Promise.race()between the API call and a timeout promise - On timeout or error, returns
{ result: 'failed' }to trigger RPC fallback - Includes comprehensive error logging with chain IDs and error details
- Added
Key Design Decisions
- Optional Parameter: The JWT token is optional throughout the call chain, ensuring backward compatibility for environments where authentication is not available or required
- Graceful Degradation: If no token is provided, API calls proceed without authentication, allowing the system to work in both authenticated and unauthenticated scenarios
- Timeout Protection: 30-second timeout ensures token detection never gets stuck indefinitely, with automatic fallback to reliable RPC methods
- No Breaking Changes: Existing callers continue to work without modification
- Resilient Fallback: The timeout mechanism integrates seamlessly with existing error handling, treating timeouts the same as API failures
References
Checklist
- [x] I've updated the test suite for new or updated code as appropriate
- Added tests in
TokenDetectionController.test.tsto verify JWT token is passed correctly - Added test in
TokenDetectionController.test.tsto verify 30-second timeout triggers RPC fallback - Added tests in
TokenBalancesController.test.tsto verify JWT token flows through balance fetcher - Added tests in
multi-chain-accounts.test.tsto verify Authorization header is set correctly - Added tests for both scenarios: with and without JWT token
- Verified timeout behavior using fake timers (sinon) and the
advanceTimehelper
- Added tests in
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
- Updated JSDoc comments for
fetchMultiChainBalancesandfetchMultiChainBalancesV4 - Updated JSDoc for controller methods that now handle JWT tokens
- Added inline comments explaining timeout logic and fallback mechanism
- Updated JSDoc comments for
- [ ] I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
- Note: No breaking changes - all JWT token parameters are optional and timeout is an internal improvement
- [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
- Note: Not required - no breaking changes introduced
[!NOTE] Adds optional JWT authentication to Accounts API calls and 30s timeout + RPC fallback, and replaces single-call token balances with Multicall3; plumbs JWT through controllers with tests and deps updates.
- Accounts API Authentication
fetchMultiChainBalances/fetchMultiChainBalancesV4accept optionaljwtToken; sent asAuthorization: Bearer <token>.TokenDetectionControllerandTokenBalancesControllerfetch JWT viaAuthenticationController:getBearerTokenand pass it through fetchers.- Resilience & Fallbacks
TokenDetectionController: 30s timeout guard for Accounts API; on timeout/error, logs and falls back to RPC detection.- Balance Fetching Improvements
AssetsContractController.getBalancesInSingleCallnow usesmulticall.getTokenBalancesForMultipleAddresses(Multicall3) for broad network support; returns non‑zero balances only.- Tests
- Add coverage for JWT header propagation, timeout-to-RPC fallback, and JWT flow through
AccountsApiBalanceFetcher.- Deps/Config
- Add
@metamask/profile-sync-controlleras (dev/peer) dependency for auth token access.- Multicall map: add Sonic chain.
- Changelog
- Documents JWT auth, timeout fallback, and related changes.
Written by Cursor Bugbot for commit 92708ad3fd93edeb822d20af41da50782259b3cd. This will update automatically on new commits. Configure here.
@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": "2.0.0-preview-d41a7f35",
"@metamask-previews/accounts-controller": "34.0.0-preview-d41a7f35",
"@metamask-previews/address-book-controller": "7.0.0-preview-d41a7f35",
"@metamask-previews/analytics-controller": "0.0.0-preview-d41a7f35",
"@metamask-previews/announcement-controller": "8.0.0-preview-d41a7f35",
"@metamask-previews/app-metadata-controller": "2.0.0-preview-d41a7f35",
"@metamask-previews/approval-controller": "8.0.0-preview-d41a7f35",
"@metamask-previews/assets-controllers": "87.1.0-preview-d41a7f35",
"@metamask-previews/base-controller": "9.0.0-preview-d41a7f35",
"@metamask-previews/bridge-controller": "59.0.0-preview-d41a7f35",
"@metamask-previews/bridge-status-controller": "59.0.0-preview-d41a7f35",
"@metamask-previews/build-utils": "3.0.4-preview-d41a7f35",
"@metamask-previews/chain-agnostic-permission": "1.2.2-preview-d41a7f35",
"@metamask-previews/composable-controller": "12.0.0-preview-d41a7f35",
"@metamask-previews/controller-utils": "11.15.0-preview-d41a7f35",
"@metamask-previews/core-backend": "4.0.0-preview-d41a7f35",
"@metamask-previews/delegation-controller": "1.0.0-preview-d41a7f35",
"@metamask-previews/earn-controller": "9.0.0-preview-d41a7f35",
"@metamask-previews/eip-5792-middleware": "2.0.0-preview-d41a7f35",
"@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-d41a7f35",
"@metamask-previews/eip1193-permission-middleware": "1.0.2-preview-d41a7f35",
"@metamask-previews/ens-controller": "18.0.0-preview-d41a7f35",
"@metamask-previews/error-reporting-service": "3.0.0-preview-d41a7f35",
"@metamask-previews/eth-block-tracker": "14.0.0-preview-d41a7f35",
"@metamask-previews/eth-json-rpc-middleware": "21.0.0-preview-d41a7f35",
"@metamask-previews/eth-json-rpc-provider": "5.0.1-preview-d41a7f35",
"@metamask-previews/foundryup": "1.0.1-preview-d41a7f35",
"@metamask-previews/gas-fee-controller": "25.0.0-preview-d41a7f35",
"@metamask-previews/gator-permissions-controller": "0.4.0-preview-d41a7f35",
"@metamask-previews/json-rpc-engine": "10.1.1-preview-d41a7f35",
"@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-d41a7f35",
"@metamask-previews/keyring-controller": "24.0.0-preview-d41a7f35",
"@metamask-previews/logging-controller": "7.0.0-preview-d41a7f35",
"@metamask-previews/message-manager": "14.0.0-preview-d41a7f35",
"@metamask-previews/messenger": "0.3.0-preview-d41a7f35",
"@metamask-previews/multichain-account-service": "2.1.0-preview-d41a7f35",
"@metamask-previews/multichain-api-middleware": "1.2.4-preview-d41a7f35",
"@metamask-previews/multichain-network-controller": "2.0.0-preview-d41a7f35",
"@metamask-previews/multichain-transactions-controller": "6.0.0-preview-d41a7f35",
"@metamask-previews/name-controller": "9.0.0-preview-d41a7f35",
"@metamask-previews/network-controller": "25.0.0-preview-d41a7f35",
"@metamask-previews/network-enablement-controller": "3.1.0-preview-d41a7f35",
"@metamask-previews/notification-services-controller": "19.0.0-preview-d41a7f35",
"@metamask-previews/permission-controller": "12.1.0-preview-d41a7f35",
"@metamask-previews/permission-log-controller": "5.0.0-preview-d41a7f35",
"@metamask-previews/phishing-controller": "15.0.0-preview-d41a7f35",
"@metamask-previews/polling-controller": "15.0.0-preview-d41a7f35",
"@metamask-previews/preferences-controller": "21.0.0-preview-d41a7f35",
"@metamask-previews/profile-sync-controller": "26.0.0-preview-d41a7f35",
"@metamask-previews/rate-limit-controller": "7.0.0-preview-d41a7f35",
"@metamask-previews/remote-feature-flag-controller": "2.0.0-preview-d41a7f35",
"@metamask-previews/sample-controllers": "3.0.0-preview-d41a7f35",
"@metamask-previews/seedless-onboarding-controller": "6.1.0-preview-d41a7f35",
"@metamask-previews/selected-network-controller": "25.0.0-preview-d41a7f35",
"@metamask-previews/shield-controller": "2.0.0-preview-d41a7f35",
"@metamask-previews/signature-controller": "36.0.0-preview-d41a7f35",
"@metamask-previews/subscription-controller": "3.3.0-preview-d41a7f35",
"@metamask-previews/token-search-discovery-controller": "4.0.0-preview-d41a7f35",
"@metamask-previews/transaction-controller": "61.1.0-preview-d41a7f35",
"@metamask-previews/transaction-pay-controller": "3.1.0-preview-d41a7f35",
"@metamask-previews/user-operation-controller": "40.0.0-preview-d41a7f35"
}
@metamaskbot publish-preview
No dependency changes detected. Learn more about Socket for GitHub.
👍 No dependency changes detected in pull request
@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-12e7e334",
"@metamask-previews/accounts-controller": "34.0.0-preview-12e7e334",
"@metamask-previews/address-book-controller": "7.0.0-preview-12e7e334",
"@metamask-previews/analytics-controller": "0.0.0-preview-12e7e334",
"@metamask-previews/announcement-controller": "8.0.0-preview-12e7e334",
"@metamask-previews/app-metadata-controller": "2.0.0-preview-12e7e334",
"@metamask-previews/approval-controller": "8.0.0-preview-12e7e334",
"@metamask-previews/assets-controllers": "88.0.0-preview-12e7e334",
"@metamask-previews/base-controller": "9.0.0-preview-12e7e334",
"@metamask-previews/bridge-controller": "60.0.0-preview-12e7e334",
"@metamask-previews/bridge-status-controller": "60.0.0-preview-12e7e334",
"@metamask-previews/build-utils": "3.0.4-preview-12e7e334",
"@metamask-previews/chain-agnostic-permission": "1.2.2-preview-12e7e334",
"@metamask-previews/claims-controller": "0.1.0-preview-12e7e334",
"@metamask-previews/composable-controller": "12.0.0-preview-12e7e334",
"@metamask-previews/controller-utils": "11.15.0-preview-12e7e334",
"@metamask-previews/core-backend": "4.0.0-preview-12e7e334",
"@metamask-previews/delegation-controller": "1.0.0-preview-12e7e334",
"@metamask-previews/earn-controller": "10.0.0-preview-12e7e334",
"@metamask-previews/eip-5792-middleware": "2.0.0-preview-12e7e334",
"@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-12e7e334",
"@metamask-previews/eip1193-permission-middleware": "1.0.2-preview-12e7e334",
"@metamask-previews/ens-controller": "18.0.0-preview-12e7e334",
"@metamask-previews/error-reporting-service": "3.0.0-preview-12e7e334",
"@metamask-previews/eth-block-tracker": "14.0.0-preview-12e7e334",
"@metamask-previews/eth-json-rpc-middleware": "21.0.0-preview-12e7e334",
"@metamask-previews/eth-json-rpc-provider": "5.0.1-preview-12e7e334",
"@metamask-previews/foundryup": "1.0.1-preview-12e7e334",
"@metamask-previews/gas-fee-controller": "25.0.0-preview-12e7e334",
"@metamask-previews/gator-permissions-controller": "0.4.0-preview-12e7e334",
"@metamask-previews/json-rpc-engine": "10.1.1-preview-12e7e334",
"@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-12e7e334",
"@metamask-previews/keyring-controller": "24.0.0-preview-12e7e334",
"@metamask-previews/logging-controller": "7.0.0-preview-12e7e334",
"@metamask-previews/message-manager": "14.0.0-preview-12e7e334",
"@metamask-previews/messenger": "0.3.0-preview-12e7e334",
"@metamask-previews/multichain-account-service": "3.0.0-preview-12e7e334",
"@metamask-previews/multichain-api-middleware": "1.2.4-preview-12e7e334",
"@metamask-previews/multichain-network-controller": "2.0.0-preview-12e7e334",
"@metamask-previews/multichain-transactions-controller": "6.0.0-preview-12e7e334",
"@metamask-previews/name-controller": "9.0.0-preview-12e7e334",
"@metamask-previews/network-controller": "25.0.0-preview-12e7e334",
"@metamask-previews/network-enablement-controller": "3.1.0-preview-12e7e334",
"@metamask-previews/notification-services-controller": "19.0.0-preview-12e7e334",
"@metamask-previews/permission-controller": "12.1.0-preview-12e7e334",
"@metamask-previews/permission-log-controller": "5.0.0-preview-12e7e334",
"@metamask-previews/phishing-controller": "15.0.0-preview-12e7e334",
"@metamask-previews/polling-controller": "15.0.0-preview-12e7e334",
"@metamask-previews/preferences-controller": "21.0.0-preview-12e7e334",
"@metamask-previews/profile-sync-controller": "26.0.0-preview-12e7e334",
"@metamask-previews/rate-limit-controller": "7.0.0-preview-12e7e334",
"@metamask-previews/remote-feature-flag-controller": "2.0.0-preview-12e7e334",
"@metamask-previews/sample-controllers": "3.0.0-preview-12e7e334",
"@metamask-previews/seedless-onboarding-controller": "6.1.0-preview-12e7e334",
"@metamask-previews/selected-network-controller": "25.0.0-preview-12e7e334",
"@metamask-previews/shield-controller": "2.0.0-preview-12e7e334",
"@metamask-previews/signature-controller": "36.0.0-preview-12e7e334",
"@metamask-previews/subscription-controller": "3.3.0-preview-12e7e334",
"@metamask-previews/token-search-discovery-controller": "4.0.0-preview-12e7e334",
"@metamask-previews/transaction-controller": "61.1.0-preview-12e7e334",
"@metamask-previews/transaction-pay-controller": "4.0.0-preview-12e7e334",
"@metamask-previews/user-operation-controller": "40.0.0-preview-12e7e334"
}