Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **BREAKING:** Add optional JWT token authentication to multi-chain accounts API calls ([#7165](https://github.com/MetaMask/core/pull/7165))
- `fetchMultiChainBalances` and `fetchMultiChainBalancesV4` now accept an optional `jwtToken` parameter
- `TokenDetectionController` fetches and passes JWT token from `AuthenticationController` when using Accounts API
- `TokenBalancesController` fetches and passes JWT token through balance fetcher chain
- JWT token is included in `Authorization: Bearer <token>` header when provided
- Backward compatible: token parameter is optional and APIs work without authentication

## [91.0.0]

### Changed
Expand Down
2 changes: 2 additions & 0 deletions packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"@metamask/permission-controller": "^12.1.1",
"@metamask/phishing-controller": "^16.1.0",
"@metamask/preferences-controller": "^22.0.0",
"@metamask/profile-sync-controller": "^27.0.0",
"@metamask/providers": "^22.1.0",
"@metamask/snaps-controllers": "^14.0.1",
"@metamask/transaction-controller": "^62.0.0",
Expand Down Expand Up @@ -124,6 +125,7 @@
"@metamask/permission-controller": "^12.0.0",
"@metamask/phishing-controller": "^16.0.0",
"@metamask/preferences-controller": "^22.0.0",
"@metamask/profile-sync-controller": "^27.0.0",
"@metamask/providers": "^22.0.0",
"@metamask/snaps-controllers": "^14.0.0",
"@metamask/transaction-controller": "^62.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const setupController = ({
'AccountTrackerController:getState',
'AccountTrackerController:updateNativeBalances',
'AccountTrackerController:updateStakedBalances',
'AuthenticationController:getBearerToken',
],
events: [
'NetworkController:stateChange',
Expand Down
14 changes: 13 additions & 1 deletion packages/assets-controllers/src/TokenBalancesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import {
BNToHex,
isValidHexAddress,
safelyExecuteWithTimeout,
toChecksumHexAddress,
toHex,
} from '@metamask/controller-utils';
Expand All @@ -32,6 +33,7 @@ import type {
PreferencesControllerGetStateAction,
PreferencesControllerStateChangeEvent,
} from '@metamask/preferences-controller';
import type { AuthenticationController } from '@metamask/profile-sync-controller';
import type { Hex } from '@metamask/utils';
import {
isCaipAssetType,
Expand Down Expand Up @@ -130,7 +132,8 @@ export type AllowedActions =
| AccountsControllerListAccountsAction
| AccountTrackerControllerGetStateAction
| AccountTrackerUpdateNativeBalancesAction
| AccountTrackerUpdateStakedBalancesAction;
| AccountTrackerUpdateStakedBalancesAction
| AuthenticationController.AuthenticationControllerGetBearerToken;

export type AllowedEvents =
| TokensControllerStateChangeEvent
Expand Down Expand Up @@ -640,6 +643,14 @@ export class TokenBalancesController extends StaticIntervalPollingController<{
);
const allAccounts = this.messenger.call('AccountsController:listAccounts');

const jwtToken = await safelyExecuteWithTimeout<string | undefined>(
() => {
return this.messenger.call('AuthenticationController:getBearerToken');
},
false,
5000,
);

const aggregated: ProcessedBalance[] = [];
let remainingChains = [...targetChains];

Expand All @@ -658,6 +669,7 @@ export class TokenBalancesController extends StaticIntervalPollingController<{
queryAllAccounts: queryAllAccounts ?? this.#queryAllAccounts,
selectedAccount: selected as ChecksumAddress,
allAccounts,
jwtToken,
});

if (result.balances && result.balances.length > 0) {
Expand Down
114 changes: 38 additions & 76 deletions packages/assets-controllers/src/TokenDetectionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ function buildTokenDetectionControllerMessenger(
'PreferencesController:getState',
'TokensController:addTokens',
'NetworkController:findNetworkClientIdByChainId',
'AuthenticationController:getBearerToken',
],
events: [
'AccountsController:selectedEvmAccountChange',
Expand Down Expand Up @@ -3748,15 +3749,7 @@ describe('TokenDetectionController', () => {
options: {
disabled: false,
},
},
async ({
controller,
mockTokenListGetState,
callActionSpy,
triggerTokenListStateChange,
}) => {
const tokenListState = {
...getDefaultTokenListState(),
mockTokenListState: {
tokensChainsCache: {
[chainId]: {
timestamp: 0,
Expand All @@ -3773,11 +3766,9 @@ describe('TokenDetectionController', () => {
},
},
},
};

mockTokenListGetState(tokenListState);
triggerTokenListStateChange(tokenListState);

},
},
async ({ controller, callActionSpy }) => {
await controller.addDetectedTokensViaWs({
tokensSlice: [mockTokenAddress],
chainId: chainId as Hex,
Expand Down Expand Up @@ -3813,27 +3804,16 @@ describe('TokenDetectionController', () => {
options: {
disabled: false,
},
},
async ({
controller,
mockTokenListGetState,
callActionSpy,
triggerTokenListStateChange,
}) => {
// Empty token cache - token not found
const tokenListState = {
...getDefaultTokenListState(),
mockTokenListState: {
tokensChainsCache: {
[chainId]: {
timestamp: 0,
data: {},
},
},
};

mockTokenListGetState(tokenListState);
triggerTokenListStateChange(tokenListState);

},
},
async ({ controller, callActionSpy }) => {
await controller.addDetectedTokensViaWs({
tokensSlice: [mockTokenAddress],
chainId: chainId as Hex,
Expand Down Expand Up @@ -3877,16 +3857,7 @@ describe('TokenDetectionController', () => {
getSelectedAccount: selectedAccount,
getAccount: selectedAccount,
},
},
async ({
controller,
mockTokenListGetState,
callActionSpy,
triggerTokenListStateChange,
}) => {
// Set up token list with both tokens
const tokenListState = {
...getDefaultTokenListState(),
mockTokenListState: {
tokensChainsCache: {
[chainId]: {
timestamp: 0,
Expand All @@ -3912,11 +3883,9 @@ describe('TokenDetectionController', () => {
},
},
},
};

mockTokenListGetState(tokenListState);
triggerTokenListStateChange(tokenListState);

},
},
async ({ controller, callActionSpy }) => {
// Add both tokens via websocket
await controller.addDetectedTokensViaWs({
tokensSlice: [mockTokenAddress, secondTokenAddress],
Expand Down Expand Up @@ -3965,15 +3934,7 @@ describe('TokenDetectionController', () => {
disabled: false,
trackMetaMetricsEvent: mockTrackMetricsEvent,
},
},
async ({
controller,
mockTokenListGetState,
callActionSpy,
triggerTokenListStateChange,
}) => {
const tokenListState = {
...getDefaultTokenListState(),
mockTokenListState: {
tokensChainsCache: {
[chainId]: {
timestamp: 0,
Expand All @@ -3990,11 +3951,9 @@ describe('TokenDetectionController', () => {
},
},
},
};

mockTokenListGetState(tokenListState);
triggerTokenListStateChange(tokenListState);

},
},
async ({ controller, callActionSpy }) => {
await controller.addDetectedTokensViaWs({
tokensSlice: [mockTokenAddress],
chainId: chainId as Hex,
Expand Down Expand Up @@ -4031,15 +3990,7 @@ describe('TokenDetectionController', () => {
options: {
disabled: false,
},
},
async ({
controller,
mockTokenListGetState,
callActionSpy,
triggerTokenListStateChange,
}) => {
const tokenListState = {
...getDefaultTokenListState(),
mockTokenListState: {
tokensChainsCache: {
[chainId]: {
timestamp: 0,
Expand All @@ -4056,11 +4007,9 @@ describe('TokenDetectionController', () => {
},
},
},
};

mockTokenListGetState(tokenListState);
triggerTokenListStateChange(tokenListState);

},
},
async ({ controller, callActionSpy }) => {
// Call the public method directly on the controller instance
await controller.addDetectedTokensViaWs({
tokensSlice: [mockTokenAddress],
Expand Down Expand Up @@ -4157,7 +4106,9 @@ type WithControllerOptions = {
mocks?: {
getAccount?: InternalAccount;
getSelectedAccount?: InternalAccount;
getBearerToken?: string;
};
mockTokenListState?: Partial<TokenListState>;
};

type WithControllerArgs<ReturnValue> =
Expand All @@ -4177,7 +4128,7 @@ async function withController<ReturnValue>(
...args: WithControllerArgs<ReturnValue>
): Promise<ReturnValue> {
const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]];
const { options, isKeyringUnlocked, mocks } = rest;
const { options, isKeyringUnlocked, mocks, mockTokenListState } = rest;
const messenger = buildRootMessenger();

const mockGetAccount = jest.fn<InternalAccount, []>();
Expand Down Expand Up @@ -4240,10 +4191,13 @@ async function withController<ReturnValue>(
'TokensController:getState',
mockTokensState.mockReturnValue({ ...getDefaultTokensState() }),
);
const mockTokenListState = jest.fn<TokenListState, []>();
const mockTokenListStateFunc = jest.fn<TokenListState, []>();
messenger.registerActionHandler(
'TokenListController:getState',
mockTokenListState.mockReturnValue({ ...getDefaultTokenListState() }),
mockTokenListStateFunc.mockReturnValue({
...getDefaultTokenListState(),
...mockTokenListState,
}),
);
const mockPreferencesState = jest.fn<PreferencesState, []>();
messenger.registerActionHandler(
Expand All @@ -4253,6 +4207,14 @@ async function withController<ReturnValue>(
}),
);

const mockGetBearerToken = jest.fn<Promise<string>, []>();
messenger.registerActionHandler(
'AuthenticationController:getBearerToken',
mockGetBearerToken.mockResolvedValue(
mocks?.getBearerToken ?? 'mock-jwt-token',
),
);

const mockFindNetworkClientIdByChainId = jest.fn<NetworkClientId, [Hex]>();
messenger.registerActionHandler(
'NetworkController:findNetworkClientIdByChainId',
Expand Down Expand Up @@ -4312,7 +4274,7 @@ async function withController<ReturnValue>(
mockPreferencesState.mockReturnValue(state);
},
mockTokenListGetState: (state: TokenListState) => {
mockTokenListState.mockReturnValue(state);
mockTokenListStateFunc.mockReturnValue(state);
},
mockGetNetworkClientById: (
handler: (
Expand Down
Loading
Loading