Skip to content
Draft
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
5 changes: 5 additions & 0 deletions packages/assets-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add `AccountsApiDataSourceConfig.useBalanceV6` feature flag getter (`() => boolean`, default `() => false`) that switches the Accounts API balances endpoint from v5 to v6; the flag is read per fetch so it can be toggled at runtime to revert v6 -> v5 without re-instantiating the data source. Only `category: 'token'` rows from the v6 response are consumed (DeFi positions are ignored) to preserve parity with v5
- Add `includeTokens` option to `AssetsController.getAssets` — a list of custom ERC-20 CAIP-19 asset IDs to include in the fetch on top of the accounts' stored custom assets. These are passed to the Accounts API v6 balances endpoint as `includeAssetIds` (to confirm detection) and are fetched via RPC as custom assets

### Changed

- Bump `@metamask/transaction-controller` from `^68.2.0` to `^68.2.1` ([#9337](https://github.com/MetaMask/core/pull/9337))
Expand Down
52 changes: 52 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 @@ -124,6 +125,7 @@ type WithControllerOptions = {
controllerOptions?: Partial<{
trace: TraceCallback;
priceDataSourceConfig: PriceDataSourceConfig;
accountsApiDataSourceConfig: AccountsApiDataSourceConfig;
isEnabled: () => boolean;
}>;
};
Expand Down Expand Up @@ -758,6 +760,56 @@ describe('AssetsController', () => {
});
});

it('passes includeTokens to the Accounts API v6 endpoint as includeAssetIds', async () => {
const fetchV6MultiAccountBalances = jest.fn().mockResolvedValue({
accounts: [],
unprocessedNetworks: [],
unprocessedIncludeAssetIds: [],
});

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

await withController(
{
queryApiClient,
controllerOptions: {
accountsApiDataSourceConfig: { useBalanceV6: () => true },
},
},
async ({ controller }) => {
// Let the data source initialize its active chains.
await flushPromises();

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

expect(fetchV6MultiAccountBalances).toHaveBeenCalledWith(
expect.arrayContaining([
`eip155:1:0x1234567890123456789012345678901234567890`,
]),
{ includeAssetIds: [MOCK_ASSET_ID] },
expect.anything(),
);
},
);
});

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
23 changes: 19 additions & 4 deletions packages/assets-controller/src/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,14 @@ export class AssetsController extends BaseController<
assetsForPriceUpdate?: Caip19AssetId[];
/** When set to `'merge'`, fetch result is merged with existing state instead of replacing. Use for partial fetches (e.g. newly added chains). */
updateMode?: AssetsUpdateMode;
/**
* Additional custom ERC-20 tokens to include in this fetch, on top of the
* accounts' stored custom assets. These are passed to the Accounts API v6
* balances endpoint as `includeAssetIds` (to confirm detection) and are
* fetched via RPC as custom assets. Ignored when v6 is disabled for the
* API path, but still fetched via RPC.
*/
includeTokens?: Caip19AssetId[];
},
): Promise<Record<AccountId, Record<Caip19AssetId, Asset>>> {
const chainIds = options?.chainIds ?? [...this.#enabledChains];
Expand All @@ -1601,12 +1609,19 @@ export class AssetsController extends BaseController<
return this.#getAssetsFromState(accounts, chainIds, assetTypes);
}

// Collect custom assets for all requested accounts
const customAssets: Caip19AssetId[] = [];
// Collect custom assets for all requested accounts, plus any explicit
// includeTokens passed by the caller. Deduplicated so the same token is not
// sent twice to the Accounts API / RPC.
const customAssetsSet = new Set<Caip19AssetId>();
for (const account of accounts) {
const accountCustomAssets = this.getCustomAssets(account.id);
customAssets.push(...accountCustomAssets);
for (const accountCustomAsset of this.getCustomAssets(account.id)) {
customAssetsSet.add(accountCustomAsset);
}
}
for (const includeToken of options?.includeTokens ?? []) {
customAssetsSet.add(includeToken);
}
const customAssets: Caip19AssetId[] = [...customAssetsSet];

if (options?.forceUpdate) {
const startTime = performance.now();
Expand Down
Loading
Loading