⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,80 @@ export const createMultichainAccountGroup = async (
}
};

/**
* Creates multiple multichain account groups in batch (from 0 to maxGroupIndex).
* This is an optimized version that creates all groups in one operation instead of
* creating them sequentially.
*
* @param context - The sync context containing controller and messenger.
* @param entropySourceId - The entropy source ID.
* @param maxGroupIndex - Maximum group index (inclusive) to create.
* @param profileId - The profile ID for analytics.
* @param analyticsAction - The analytics action to log for each created group.
* @returns Array of created group IDs.
*/
export const createMultichainAccountGroupsBatch = async (
context: BackupAndSyncContext,
entropySourceId: string,
maxGroupIndex: number,
profileId: ProfileId,
analyticsAction: BackupAndSyncAnalyticsAction,
): Promise<string[]> => {
backupAndSyncLogger(
`Creating account groups 0-${maxGroupIndex} in batch for entropy source: ${entropySourceId}`,
);

try {
// Call the batched creation method.
const groups = await context.messenger.call(
'MultichainAccountService:createMultichainAccountGroups',
{
entropySource: entropySourceId,
maxGroupIndex,
},
);

// Emit analytics event for each newly created group.
// Note: groups array contains all groups (existing + newly created).
const createdGroupIds: string[] = [];

for (const group of groups) {
// TODO: A group should not be null here, but EVM provider might fail to create some groups sometimes, which means
// we can end up having an "empty group" for some time.
if (group) {
createdGroupIds.push(group.id);

// Emit analytics event.
context.emitAnalyticsEventFn({
action: analyticsAction,
profileId,
});
}
}

backupAndSyncLogger(
`Successfully created ${groups.length} groups (indices 0-${maxGroupIndex})`,
);

return createdGroupIds;
} catch (error) {
// This can happen if the Snap Keyring is not ready yet when invoking
// `MultichainAccountService:createMultichainAccountGroups`.
// Since `MultichainAccountService:createMultichainAccountGroups` will at
// least create the EVM account and the account group before throwing, we can safely
// ignore this error and swallow it.
// Any missing Snap accounts will be added later with alignment.

backupAndSyncLogger(
`Failed to create account groups batch:`,
// istanbul ignore next
error instanceof Error ? error.message : String(error),
);

return [];
}
};

/**
* Creates local groups from user storage groups.
*
Expand All @@ -93,19 +167,14 @@ export async function createLocalGroupsFromUserStorage(

// Creating multichain account group is idempotent, so we can safely
// re-create every groups starting from 0.
for (
let groupIndex = 0;
groupIndex <= numberOfAccountGroupsToCreate;
groupIndex++
) {
await createMultichainAccountGroup(
context,
entropySourceId,
groupIndex,
profileId,
BackupAndSyncAnalyticsEvent.GroupAdded,
);
}
// Use batch creation for better performance.
await createMultichainAccountGroupsBatch(
context,
entropySourceId,
numberOfAccountGroupsToCreate,
profileId,
BackupAndSyncAnalyticsEvent.GroupAdded,
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { toMultichainAccountWalletId } from '@metamask/account-api';
import { getUUIDFromAddressOfNormalAccount } from '@metamask/accounts-controller';

import { createMultichainAccountGroup } from './group';
import { createMultichainAccountGroupsBatch } from './group';
import { backupAndSyncLogger } from '../../logger';
import { BackupAndSyncAnalyticsEvent } from '../analytics';
import type { ProfileId } from '../authentication';
Expand Down Expand Up @@ -51,16 +51,14 @@ export const performLegacyAccountSyncing = async (
if (numberOfAccountGroupsToCreate > 0) {
// Creating multichain account group is idempotent, so we can safely
// re-create every groups starting from 0.
for (let i = 0; i < numberOfAccountGroupsToCreate; i++) {
backupAndSyncLogger(`Creating account group ${i} for legacy account`);
await createMultichainAccountGroup(
context,
entropySourceId,
i,
profileId,
BackupAndSyncAnalyticsEvent.LegacyGroupAddedFromAccount,
);
}
// Use batch creation for better performance.
await createMultichainAccountGroupsBatch(
context,
entropySourceId,
numberOfAccountGroupsToCreate - 1, // maxGroupIndex is inclusive, so subtract 1
profileId,
BackupAndSyncAnalyticsEvent.LegacyGroupAddedFromAccount,
);
}

// 3. Rename account groups if needed
Expand Down
8 changes: 6 additions & 2 deletions packages/account-tree-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import type {
import type { TraceCallback } from '@metamask/controller-utils';
import type { KeyringControllerGetStateAction } from '@metamask/keyring-controller';
import type { Messenger } from '@metamask/messenger';
import type { MultichainAccountServiceCreateMultichainAccountGroupAction } from '@metamask/multichain-account-service';
import type {
MultichainAccountServiceCreateMultichainAccountGroupAction,
MultichainAccountServiceCreateMultichainAccountGroupsAction,
} from '@metamask/multichain-account-service';
import type {
AuthenticationController,
UserStorageController,
Expand Down Expand Up @@ -132,7 +135,8 @@ export type AllowedActions =
| UserStorageController.UserStorageControllerPerformSetStorage
| UserStorageController.UserStorageControllerPerformBatchSetStorage
| AuthenticationController.AuthenticationControllerGetSessionProfile
| MultichainAccountServiceCreateMultichainAccountGroupAction;
| MultichainAccountServiceCreateMultichainAccountGroupAction
| MultichainAccountServiceCreateMultichainAccountGroupsAction;

export type AccountTreeControllerActions =
| AccountTreeControllerGetStateAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
mockCreateAccountsOnce,
} from './tests';
import type { MultichainAccountServiceMessenger } from './types';
import { AccountCreationType } from './types';

function setup({
groupIndex = 0,
Expand Down Expand Up @@ -195,10 +196,12 @@ describe('MultichainAccount', () => {
await group.alignAccounts();

expect(providers[0].createAccounts).toHaveBeenCalledWith({
type: AccountCreationType.Bip44DeriveIndex,
entropySource: wallet.entropySource,
groupIndex,
});
expect(providers[1].createAccounts).toHaveBeenCalledWith({
type: AccountCreationType.Bip44DeriveIndex,
entropySource: wallet.entropySource,
groupIndex,
});
Expand All @@ -225,6 +228,7 @@ describe('MultichainAccount', () => {
await group.alignAccounts();

expect(providers[1].createAccounts).toHaveBeenCalledWith({
type: AccountCreationType.Bip44DeriveIndex,
entropySource: wallet.entropySource,
groupIndex,
});
Expand Down Expand Up @@ -252,10 +256,12 @@ describe('MultichainAccount', () => {
await group.alignAccounts();

expect(providers[1].createAccounts).toHaveBeenCalledWith({
type: AccountCreationType.Bip44DeriveIndex,
entropySource: wallet.entropySource,
groupIndex,
});
expect(providers[2].createAccounts).toHaveBeenCalledWith({
type: AccountCreationType.Bip44DeriveIndex,
entropySource: wallet.entropySource,
groupIndex,
});
Expand Down
Loading
Loading