Skip to content
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
aa318fe
feat: have group align method print all provider errors
hmalik88 Sep 14, 2025
17e855f
Merge branch 'main' into hm/bip-44-perf-and-devx-improvements
hmalik88 Sep 15, 2025
924a679
Merge branch 'main' into hm/bip-44-perf-and-devx-improvements
hmalik88 Sep 17, 2025
be8b2f7
Merge branch 'main' into hm/bip-44-perf-and-devx-improvements
hmalik88 Sep 17, 2025
b49f7b4
feat: construct service state at the top level and pass state slices …
hmalik88 Sep 18, 2025
a261e1e
Merge branch 'main' into hm/bip-44-perf-and-devx-improvements
hmalik88 Sep 18, 2025
6104131
chore: readd code removed by mistake
hmalik88 Sep 18, 2025
c45f612
feat: add getAccounts method to AccountsController
hmalik88 Sep 18, 2025
acf36e0
chore: update types to include getAccounts action
hmalik88 Sep 18, 2025
ce3cef4
refactor: derive account ID and use that to do a getAccounts call ins…
hmalik88 Sep 18, 2025
891fa50
feat: finish refactor
hmalik88 Sep 18, 2025
83060d6
Merge remote-tracking branch 'origin/main' into hm/bip-44-perf-and-de…
hmalik88 Sep 18, 2025
01096bf
chore: add JSDoc for wallet init
hmalik88 Sep 18, 2025
4c2e582
chore: remove accountId to context mapping since with the removal of …
hmalik88 Sep 19, 2025
43972f8
Merge remote-tracking branch 'origin/main' into hm/bip-44-perf-and-de…
hmalik88 Sep 19, 2025
7cec854
feat: start to add logic that will let createMultichainAccountWallet …
hmalik88 Sep 22, 2025
f05e6da
feat: remove need for mnemonicAsBytes
hmalik88 Sep 22, 2025
e325c77
Merge branch 'main' into hm/bip-44-perf-and-devx-improvements
hmalik88 Sep 26, 2025
6c5b86e
feat: update creatMultichainAccountWallet method to cover all entry p…
hmalik88 Sep 26, 2025
7d805ef
chore: update JSDoc
hmalik88 Sep 26, 2025
6b6e776
chore: update log messages
hmalik88 Sep 26, 2025
33e5e34
chore: add more JSDocs
hmalik88 Sep 26, 2025
f4d79c0
test: update multichain service tests
hmalik88 Sep 26, 2025
350aa93
fix: address type errors, remove try/catch block to not swallow error…
hmalik88 Sep 26, 2025
ce322e0
fix: lint fixes
hmalik88 Sep 26, 2025
31a6d71
chore: remove commented test
hmalik88 Sep 26, 2025
4c485a6
refactor: make changes to messenger and providers to make the compati…
hmalik88 Sep 26, 2025
c82de17
refactor: simplify init and properly filter for rejected already alig…
hmalik88 Sep 26, 2025
7d6e76a
fix: properly initialize group state
hmalik88 Sep 26, 2025
2d55dbd
test: update multichain account group tests
hmalik88 Sep 26, 2025
b8a87ff
test: update multichain account wallet tests
hmalik88 Sep 26, 2025
92d1c3a
test: update provider tests so that they are compatible with the new …
hmalik88 Sep 26, 2025
8dedd1a
Merge remote-tracking branch 'origin/main' into hm/bip-44-perf-and-de…
hmalik88 Sep 26, 2025
11cc131
test: update btc and trx tests to account for new logic
hmalik88 Sep 26, 2025
d28a324
chore: update changelogs
hmalik88 Sep 27, 2025
f308e9f
fix: lint fixes
hmalik88 Sep 27, 2025
8bc1237
test: add test for getAccounts
hmalik88 Sep 27, 2025
19cfdf3
feat: register getAccounts message handler
hmalik88 Sep 27, 2025
82fece8
chore: update JSDoc
hmalik88 Sep 27, 2025
c812a82
refactor: use Object.entries in wallet init
hmalik88 Sep 27, 2025
91aa52e
refactor: simplified error logic and include provider names in error …
hmalik88 Sep 27, 2025
4b19ed5
test: have account group creation test check actual failure message
hmalik88 Sep 27, 2025
5bcaa53
Merge remote-tracking branch 'origin/main' into hm/bip-44-perf-and-de…
hmalik88 Oct 9, 2025
c12435a
test: update tests
hmalik88 Oct 9, 2025
e9ab58d
chore: remove old comment
hmalik88 Oct 9, 2025
521a71b
chore: update changelog
hmalik88 Oct 9, 2025
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/accounts-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 a `getAccounts` method (and its associated action) that is the plural version of `getAccount` ([#6654](https://github.com/MetaMask/core/pull/6708))
- This method is added to primarily be consumed in the `MultichainAccountService`.

### Changed

- Bump `@metamask/utils` from `^11.4.2` to `^11.8.1` ([#6588](https://github.com/MetaMask/core/pull/6588), [#6708](https://github.com/MetaMask/core/pull/6708))
Expand Down
20 changes: 20 additions & 0 deletions packages/accounts-controller/src/AccountsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { SnapStatus } from '@metamask/snaps-utils';
import type { CaipChainId } from '@metamask/utils';
import type { V4Options } from 'uuid';
import * as uuid from 'uuid';

Check warning on line 23 in packages/accounts-controller/src/AccountsController.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

No exported names found in module 'uuid'

import type {
AccountsControllerActions,
Expand Down Expand Up @@ -2777,6 +2777,26 @@
});
});

describe('getAccounts', () => {
it('returns a list of accounts based on the given account IDs', () => {
const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {
[mockAccount.id]: mockAccount,
[mockAccount2.id]: mockAccount2,
},
selectedAccount: mockAccount.id,
},
},
});

const result = accountsController.getAccounts([mockAccount.id]);

expect(result).toStrictEqual([mockAccount]);
});
});

describe('getSelectedAccount', () => {
it.each([
{
Expand Down
21 changes: 21 additions & 0 deletions packages/accounts-controller/src/AccountsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export type AccountsControllerGetAccountAction = {
handler: AccountsController['getAccount'];
};

export type AccountsControllerGetAccountsAction = {
type: `${typeof controllerName}:getAccounts`;
handler: AccountsController['getAccounts'];
};

export type AccountsControllerUpdateAccountMetadataAction = {
type: `${typeof controllerName}:updateAccountMetadata`;
handler: AccountsController['updateAccountMetadata'];
Expand All @@ -145,6 +150,7 @@ export type AccountsControllerActions =
| AccountsControllerGetSelectedAccountAction
| AccountsControllerGetNextAvailableAccountNameAction
| AccountsControllerGetAccountAction
| AccountsControllerGetAccountsAction
| AccountsControllerGetSelectedMultichainAccountAction
| AccountsControllerUpdateAccountMetadataAction;

Expand Down Expand Up @@ -303,6 +309,16 @@ export class AccountsController extends BaseController<
return this.state.internalAccounts.accounts[accountId];
}

/**
* Returns the internal account objects for the given account IDs, if they exist.
*
* @param accountIds - The IDs of the accounts to retrieve.
* @returns The internal account objects, or undefined if the account(s) do not exist.
*/
getAccounts(accountIds: string[]): (InternalAccount | undefined)[] {
return accountIds.map((accountId) => this.getAccount(accountId));
}

/**
* Returns an array of all evm internal accounts.
*
Expand Down Expand Up @@ -1305,6 +1321,11 @@ export class AccountsController extends BaseController<
this.getAccount.bind(this),
);

this.messagingSystem.registerActionHandler(
`AccountsController:getAccounts`,
this.getAccounts.bind(this),
);

this.messagingSystem.registerActionHandler(
`AccountsController:updateAccountMetadata`,
this.updateAccountMetadata.bind(this),
Expand Down
1 change: 1 addition & 0 deletions packages/accounts-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type {
AccountsControllerGetAccountByAddressAction,
AccountsControllerGetNextAvailableAccountNameAction,
AccountsControllerGetAccountAction,
AccountsControllerGetAccountsAction,
AccountsControllerUpdateAccountMetadataAction,
AllowedActions,
AccountsControllerActions,
Expand Down
5 changes: 5 additions & 0 deletions packages/keyring-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 actions for `createNewVaultAndKeychain` and `createNewVaultAndRestore` ([#6654](https://github.com/MetaMask/core/pull/6708))
- These actions are meant to to be consumed by the `MultichainAccountService` in its `createMultichainAccountWallet` method.

### Changed

- Bump `@metamask/utils` from `^11.4.2` to `^11.8.1` ([#6588](https://github.com/MetaMask/core/pull/6588), [#6708](https://github.com/MetaMask/core/pull/6708))
Expand Down
24 changes: 23 additions & 1 deletion packages/keyring-controller/src/KeyringController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ export type KeyringControllerWithKeyringAction = {
handler: KeyringController['withKeyring'];
};

export type KeyringControllerCreateNewVaultAndKeychainAction = {
type: `${typeof name}:createNewVaultAndKeychain`;
handler: KeyringController['createNewVaultAndKeychain'];
};

export type KeyringControllerCreateNewVaultAndRestoreAction = {
type: `${typeof name}:createNewVaultAndRestore`;
handler: KeyringController['createNewVaultAndRestore'];
};

export type KeyringControllerAddNewKeyringAction = {
type: `${typeof name}:addNewKeyring`;
handler: KeyringController['addNewKeyring'];
Expand Down Expand Up @@ -226,7 +236,9 @@ export type KeyringControllerActions =
| KeyringControllerSignUserOperationAction
| KeyringControllerAddNewAccountAction
| KeyringControllerWithKeyringAction
| KeyringControllerAddNewKeyringAction;
| KeyringControllerAddNewKeyringAction
| KeyringControllerCreateNewVaultAndKeychainAction
| KeyringControllerCreateNewVaultAndRestoreAction;

export type KeyringControllerEvents =
| KeyringControllerStateChangeEvent
Expand Down Expand Up @@ -1780,6 +1792,16 @@ export class KeyringController extends BaseController<
`${name}:addNewKeyring`,
this.addNewKeyring.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:createNewVaultAndKeychain`,
this.createNewVaultAndKeychain.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:createNewVaultAndRestore`,
this.createNewVaultAndRestore.bind(this),
);
}

/**
Expand Down
18 changes: 18 additions & 0 deletions packages/multichain-account-service/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add `{Btc/Trx}AccountProvider` account providers ([#6662](https://github.com/MetaMask/core/pull/6662))
- **BREAKING** A performance refactor was made around all the classes in this package ([#6654](https://github.com/MetaMask/core/pull/6708))
- Add logic in the `createMultichainAccountWallet` method in `MultichainAccountService` so that it can handle all entry points: importing an SRP, recovering a vault and creating a new vault.
- Add a `getAccountIds` method which returns all the account ids pertaining to a group.
- Add an `addAccounts` method on the `BaseBip44AccountProvider` class which keeps track of all the account IDs that pertain to it.

### Changed

- Bump `@metamask/utils` from `^11.8.0` to `^11.8.1` ([#6708](https://github.com/MetaMask/core/pull/6708))
- **BREAKING** A performance refactor was made around all the classes in this package ([#6654](https://github.com/MetaMask/core/pull/6708))
- The `MultichainAccountService` is refactored to construct a top level service state for its `init` function, this state is passed down to the `MultichainAccountWallet` and `MultichainAccountGroup` classes in slices for them to construct their internal states.
- Additional state is generated at the entry points where it needs to be updated i.e. `createMultichainAccountGroup`, `discoverAccounts` and `alignAccounts`.
- We no longer prevent group creation if some providers' `createAccounts` calls fail during group creation, only if they all fail.
- The `getAccounts` method in the `BaseBip44AccountProvider` class no longer relies on fetching the entire list of internal accounts from the `AccountsController`, instead it gets the specific accounts that it stores in its internal accounts list.
- The `EvmAccountProvider` no longer fetches from the `AccountController` to get an account for its ID, we deterministically get the associated account ID through `getUUIDFromAddressOfNormalAccount`.
- The `EvmAccountProvider` now uses the `getAccount` method from the `AccountsController` when fetching an account after account creation as it is more efficient.

### Removed

- **BREAKING** A performance refactor was made around all the classes in this package ([#6654](https://github.com/MetaMask/core/pull/6708))
- Remove `#handleOnAccountAdded` and `#handleOnAccountRemoved` methods in `MultichainAccountService` due to internal state being updated within the service.
- Remove `getAccountContext` (and associated map) in the `MultichainAccountService` as the service no longer uses that method.
- Remove the `sync` method in favor of the sole `init` method for both `MultichainAccountWallet` and `MultichainAccountGroup`.

## [1.2.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import type { Messenger } from '@metamask/base-controller';
import { EthScope, SolScope } from '@metamask/keyring-api';
import type { InternalAccount } from '@metamask/keyring-internal-api';

import { MultichainAccountGroup } from './MultichainAccountGroup';
import {
type GroupState,
MultichainAccountGroup,
} from './MultichainAccountGroup';
import { MultichainAccountWallet } from './MultichainAccountWallet';
import type { BaseBip44AccountProvider } from './providers';
import type { MockAccountProvider } from './tests';
import {
MOCK_SNAP_ACCOUNT_2,
Expand Down Expand Up @@ -54,23 +58,40 @@ function setup({
group: MultichainAccountGroup<Bip44Account<InternalAccount>>;
providers: MockAccountProvider[];
} {
const providers = accounts.map((providerAccounts) => {
return setupNamedAccountProvider({ accounts: providerAccounts });
const providers = accounts.map((providerAccounts, idx) => {
return setupNamedAccountProvider({
name: `Provider ${idx + 1}`,
accounts: providerAccounts,
});
});

const wallet = new MultichainAccountWallet<Bip44Account<InternalAccount>>({
entropySource: MOCK_WALLET_1_ENTROPY_SOURCE,
messenger: getMultichainAccountServiceMessenger(messenger),
providers,
providers: providers as unknown as BaseBip44AccountProvider[],
});

const group = new MultichainAccountGroup({
wallet,
groupIndex,
providers,
providers: providers as unknown as BaseBip44AccountProvider[],
messenger: getMultichainAccountServiceMessenger(messenger),
});

// Initialize group state from provided accounts so that constructor tests
// observe accounts immediately
const groupState = providers.reduce<GroupState>((state, provider, idx) => {
const ids = accounts[idx]
.filter((a) => 'options' in a && a.options?.entropy)
.map((a) => a.id);
if (ids.length > 0) {
state[provider.getName()] = ids;
}
return state;
}, {});

group.init(groupState);

return { wallet, group, providers };
}

Expand All @@ -95,6 +116,10 @@ describe('MultichainAccount', () => {
expect(group.type).toBe(AccountGroupType.MultichainAccount);
expect(group.groupIndex).toBe(groupIndex);
expect(group.wallet).toStrictEqual(wallet);
expect(group.hasAccounts()).toBe(true);
expect(group.getAccountIds()).toStrictEqual(
expectedAccounts.map((a) => a.id),
);
expect(group.getAccounts()).toHaveLength(expectedAccounts.length);
expect(group.getAccounts()).toStrictEqual(expectedAccounts);
});
Expand Down Expand Up @@ -177,6 +202,10 @@ describe('MultichainAccount', () => {
],
});

providers[1].createAccounts.mockResolvedValueOnce([
MOCK_WALLET_1_SOL_ACCOUNT,
]);

await group.alignAccounts();

expect(providers[0].createAccounts).not.toHaveBeenCalled();
Expand All @@ -199,7 +228,7 @@ describe('MultichainAccount', () => {
expect(providers[1].createAccounts).not.toHaveBeenCalled();
});

it('warns if provider alignment fails', async () => {
it('warns if alignment fails for a single provider', async () => {
const groupIndex = 0;
const { group, providers, wallet } = setup({
groupIndex,
Expand All @@ -219,7 +248,39 @@ describe('MultichainAccount', () => {
groupIndex,
});
expect(consoleSpy).toHaveBeenCalledWith(
`Failed to fully align multichain account group for entropy ID: ${wallet.entropySource} and group index: ${groupIndex}, some accounts might be missing`,
`Failed to fully align multichain account group for entropy ID: ${wallet.entropySource} and group index: ${groupIndex}, some accounts might be missing. Provider threw the following error:\n- Provider 2: Unable to create accounts`,
);
});

it('warns if alignment fails for multiple providers', async () => {
const groupIndex = 0;
const { group, providers, wallet } = setup({
groupIndex,
accounts: [[MOCK_WALLET_1_EVM_ACCOUNT], [], []],
});

const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
providers[1].createAccounts.mockRejectedValueOnce(
new Error('Unable to create accounts'),
);

providers[2].createAccounts.mockRejectedValueOnce(
new Error('Unable to create accounts'),
);

await group.alignAccounts();

expect(providers[0].createAccounts).not.toHaveBeenCalled();
expect(providers[1].createAccounts).toHaveBeenCalledWith({
entropySource: wallet.entropySource,
groupIndex,
});
expect(providers[2].createAccounts).toHaveBeenCalledWith({
entropySource: wallet.entropySource,
groupIndex,
});
expect(consoleSpy).toHaveBeenCalledWith(
`Failed to fully align multichain account group for entropy ID: ${wallet.entropySource} and group index: ${groupIndex}, some accounts might be missing. Providers threw the following errors:\n- Provider 2: Unable to create accounts\n- Provider 3: Unable to create accounts`,
);
});
});
Expand Down
Loading
Loading