Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ linkStyle default opacity:0.5
assets_controller --> phishing_controller;
assets_controller --> polling_controller;
assets_controller --> preferences_controller;
assets_controller --> remote_feature_flag_controller;
assets_controller --> transaction_controller;
assets_controllers --> account_tree_controller;
assets_controllers --> accounts_controller;
Expand Down
1 change: 1 addition & 0 deletions packages/assets-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add Robinhood Chain (`4663`/`0x1237`) in `MulticallClient` ([#9443](https://github.com/MetaMask/core/pull/9443))
- `AccountsApiDataSource` now selects the Accounts API balances endpoint version from the `RemoteFeatureFlagController` (`assetsAccountsApiV6` flag, read per fetch off the shared messenger, default v5) so the v6 endpoint is gated consistently across clients (extension, mobile) without each client wiring a getter. The flag is read as a JSON variation shaped `{ value: boolean }` (same shape as `backendWebSocketConnection`). Adds a required `messenger` option and `RemoteFeatureFlagController:getState` to `AccountsApiDataSourceAllowedActions`. Only `category: 'token'` rows from the v6 response are consumed (DeFi positions are ignored) to preserve parity with v5 ([#9344](https://github.com/MetaMask/core/pull/9344))

### Changed

Expand Down
1 change: 1 addition & 0 deletions packages/assets-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@metamask/phishing-controller": "^17.2.0",
"@metamask/polling-controller": "^16.0.8",
"@metamask/preferences-controller": "^23.1.0",
"@metamask/remote-feature-flag-controller": "^4.2.2",
"@metamask/snaps-controllers": "^19.0.0",
"@metamask/snaps-utils": "^12.1.2",
"@metamask/transaction-controller": "^68.3.0",
Expand Down
64 changes: 64 additions & 0 deletions packages/assets-controller/src/AssetsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
AssetsControllerMessenger,
AssetsControllerState,
} from './AssetsController';
import type { AccountsApiDataSourceConfig } from './data-sources/AccountsApiDataSource';
import type { PriceDataSourceConfig } from './data-sources/PriceDataSource';
import { PriceDataSource } from './data-sources/PriceDataSource';
import { TokenDataSource } from './data-sources/TokenDataSource';
Expand Down Expand Up @@ -121,10 +122,16 @@ type WithControllerOptions = {
* Required for tests that rely on asset tracking running (e.g. trace on unlock).
*/
clientControllerState?: { isUiOpen: boolean };
/**
* When set, registers RemoteFeatureFlagController:getState so the controller can
* read feature flags (e.g. `assetsAccountsApiV6` gating the balances endpoint).
*/
remoteFeatureFlags?: Record<string, unknown>;
/** Extra options passed to AssetsController constructor (e.g. trace). */
controllerOptions?: Partial<{
trace: TraceCallback;
priceDataSourceConfig: PriceDataSourceConfig;
accountsApiDataSourceConfig: AccountsApiDataSourceConfig;
isEnabled: () => boolean;
captureException: (error: Error) => void;
tempMigrateAssetsInfoMetadataAssets3346: () => Assets3346MigrationState;
Expand Down Expand Up @@ -156,6 +163,7 @@ async function withController<ReturnValue>(
state = {},
isBasicFunctionality = (): boolean => true,
clientControllerState,
remoteFeatureFlags,
queryApiClient = createMockQueryApiClient(),
controllerOptions = {},
},
Expand Down Expand Up @@ -230,6 +238,17 @@ async function withController<ReturnValue>(
);
}

if (remoteFeatureFlags !== undefined) {
(
messenger as {
registerActionHandler: (a: string, h: () => unknown) => void;
}
).registerActionHandler('RemoteFeatureFlagController:getState', () => ({
remoteFeatureFlags,
cacheTimestamp: 0,
}));
}

const controller = new AssetsController({
messenger: messenger as unknown as AssetsControllerMessenger,
state,
Expand Down Expand Up @@ -878,6 +897,51 @@ describe('AssetsController', () => {
});
});

// Endpoint selection from the flag is unit-tested in AccountsApiDataSource;
// this asserts the controller wires its messenger through so the
// `assetsAccountsApiV6` flag drives endpoint selection end-to-end.
it('routes to the Accounts API v6 endpoint when the assetsAccountsApiV6 remote flag is enabled', async () => {
const fetchV6MultiAccountBalances = jest.fn().mockResolvedValue({
accounts: [],
unprocessedNetworks: [],
unprocessedIncludeAssetIds: [],
});
const fetchV5MultiAccountBalances = jest.fn().mockResolvedValue({
balances: [],
unprocessedNetworks: [],
});

const queryApiClient = {
...createMockQueryApiClient(),
accounts: {
fetchV2SupportedNetworks: jest.fn().mockResolvedValue({
fullSupport: [1],
partialSupport: [],
}),
fetchV6MultiAccountBalances,
fetchV5MultiAccountBalances,
},
} as unknown as ApiPlatformClient;

await withController(
{
queryApiClient,
remoteFeatureFlags: { assetsAccountsApiV6: { value: true } },
},
async ({ controller }) => {
await flushPromises();

await controller.getAssets([createMockInternalAccount()], {
chainIds: ['eip155:1'],
forceUpdate: true,
});

expect(fetchV6MultiAccountBalances).toHaveBeenCalled();
expect(fetchV5MultiAccountBalances).not.toHaveBeenCalled();
},
);
});

describe('pipeline splitting', () => {
it('returns from getAssets before background pipelines complete', async () => {
// Spy on handleAssetsUpdate to count how many times state is written.
Expand Down
6 changes: 5 additions & 1 deletion packages/assets-controller/src/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import type {
} from '@metamask/permission-controller';
import { PhishingControllerBulkScanTokensAction } from '@metamask/phishing-controller';
import type { PreferencesControllerStateChangeEvent } from '@metamask/preferences-controller';
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
import type {
SnapControllerGetRunnableSnapsAction,
SnapControllerHandleRequestAction,
Expand Down Expand Up @@ -321,7 +322,9 @@ type AllowedActions =
// BackendWebsocketDataSource
| BackendWebSocketServiceActions
// PhishingController
| PhishingControllerBulkScanTokensAction;
| PhishingControllerBulkScanTokensAction
// AccountsApiDataSource (Accounts API v6 balances feature flag)
| RemoteFeatureFlagControllerGetStateAction;

type AccountTreeControllerStateChangedEvent = ControllerStateChangedEvent<
'AccountTreeController',
Expand Down Expand Up @@ -914,6 +917,7 @@ export class AssetsController extends BaseController<
this.#getAssetType(assetId),
});
this.#accountsApiDataSource = new AccountsApiDataSource({
messenger: this.messenger,
queryApiClient,
onActiveChainsUpdated: this.#onActiveChainsUpdated,
...accountsApiDataSourceConfig,
Expand Down
Loading