Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
271e0cd
migrate `AccountsController` to use `registerMethodActionHandlers`
GuillaumeRx Feb 17, 2026
2635b01
migrate `MultichainAccountService` to use `registerMethodActionHandlers`
GuillaumeRx Feb 17, 2026
0cc9967
migrate `AccountTreeController`, `AuthenticationController` and `User…
GuillaumeRx Feb 18, 2026
e7322e2
fix action name
GuillaumeRx Feb 18, 2026
c78f96b
use `generate-method-action-types` in `account-tree-controller`
GuillaumeRx Feb 19, 2026
000c741
use `generate-method-action-types` in `accounts-controller`
GuillaumeRx Feb 19, 2026
926052c
use `generate-method-action-types` in `multichain-account-service`
GuillaumeRx Feb 19, 2026
2c604d4
use `generate-method-action-types` in `profile-sync-controller`
GuillaumeRx Feb 19, 2026
a79fc19
revert typo
GuillaumeRx Feb 19, 2026
5418e3b
fix import
GuillaumeRx Feb 19, 2026
d6e1fe9
fix action name usage
GuillaumeRx Feb 19, 2026
efec6bf
fix lint and exports
GuillaumeRx Feb 20, 2026
47cdaed
use type only imports
GuillaumeRx Feb 20, 2026
58fc302
fix `account-tree-controller` type imports
GuillaumeRx Feb 20, 2026
7e9d46b
fix lint, rename actions in other controllers
GuillaumeRx Feb 20, 2026
47419ab
fill coverage in `AccountsController`
GuillaumeRx Feb 20, 2026
0ff1bd8
update CHANGELOGs
GuillaumeRx Feb 20, 2026
e91e209
fix `core-backend` CHANGELOG
GuillaumeRx Feb 20, 2026
a84ed9e
fix `core-backend` CHANGELOG
GuillaumeRx Feb 20, 2026
9653421
fix `eslint-suppressions.json` lint
GuillaumeRx Feb 20, 2026
80fba50
fix `claims-controller` changelog
GuillaumeRx Feb 20, 2026
6867941
fix `multichain-account-service` tests
GuillaumeRx Feb 20, 2026
7ec9ffb
remove direct call to method in test
GuillaumeRx Feb 20, 2026
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
2 changes: 1 addition & 1 deletion eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1415,7 +1415,7 @@
},
"packages/profile-sync-controller/src/controllers/user-storage/UserStorageController.ts": {
"@typescript-eslint/explicit-function-return-type": {
"count": 13
"count": 12
},
"id-length": {
"count": 1
Expand Down
5 changes: 5 additions & 0 deletions packages/account-tree-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Refactor the controller's messenger action registration ([#7976](https://github.com/MetaMask/core/pull/7976/))
- The controller's methods are now exposed to the messenger through `registerMethodActionHandlers` and `MESSENGER_EXPOSED_METHODS`.
- The action types are now generated using `generate-method-action-types`.
- Exposes the missing public methods in the messenger.
Comment on lines 10 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been keeping changelog entries focused on public changes to the API only.
Curious if instead of highlighting that we refactored the messenger action registration, we should instead call out which methods are now available through the messenger? Also should this be in "Added" since we are adding new things that consumers can use?

Suggested change
### Changed
- Refactor the controller's messenger action registration ([#7976](https://github.com/MetaMask/core/pull/7976/))
- The controller's methods are now exposed to the messenger through `registerMethodActionHandlers` and `MESSENGER_EXPOSED_METHODS`.
- The action types are now generated using `generate-method-action-types`.
- Exposes the missing public methods in the messenger.
### Added
- Expose missing public `AccountTreeController` methods through its messenger ([#7976](https://github.com/MetaMask/core/pull/7976/))
- The following actions are now available:
- `AccountTreeController:getAccountWalletObject`
- `AccountTreeController:getAccountWalletObjects`
- `AccountTreeController:getAccountGroupObject`
- `AccountTreeController:clearState`
- `AccountTreeController:syncWithUserStorage`
- `AccountTreeController:syncWithUserStorageAtLeastOnce`
- Corresponding action types (e.g. `AccountTreeControllerGetAccountWalletObjectAction`) are available as well.

- Update `UserStorageController` and `AuthenticationController` actions to their new names ([#7976](https://github.com/MetaMask/core/pull/7976))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these public-facing changes? It looks like we are using new names for the types in the union, but neither the names of the action type strings nor the signatures of the action handlers themselves have changed. So consumers should not need to adjust how they construct the AccountTreeController messenger. Is this true?

Suggested change
- Update `UserStorageController` and `AuthenticationController` actions to their new names ([#7976](https://github.com/MetaMask/core/pull/7976))

- Bump `@metamask/accounts-controller` from `^36.0.0` to `^36.0.1` ([#7996](https://github.com/MetaMask/core/pull/7996))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mention that we effectively removed resolveNameConflict?

Suggested change
- Bump `@metamask/accounts-controller` from `^36.0.0` to `^36.0.1` ([#7996](https://github.com/MetaMask/core/pull/7996))
### Removed
- **BREAKING:** Remove `resolveNameConflict` from `AccountTreeController` ([#7976](https://github.com/MetaMask/core/pull/7976))
- This method was only used internally.


## [4.1.1]
Expand Down
2 changes: 2 additions & 0 deletions packages/account-tree-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"build:docs": "typedoc",
"changelog:update": "../../scripts/update-changelog.sh @metamask/account-tree-controller",
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/account-tree-controller",
"generate-method-action-types": "tsx ../../scripts/generate-method-action-types.ts",
"publish:preview": "yarn npm publish --tag preview",
"since-latest-release": "../../scripts/since-latest-release.sh",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
Expand Down Expand Up @@ -72,6 +73,7 @@
"deepmerge": "^4.2.2",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"tsx": "^4.20.5",
"typedoc": "^0.25.13",
"typedoc-plugin-missing-exports": "^2.0.0",
"typescript": "~5.3.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* This file is auto generated by `scripts/generate-method-action-types.ts`.
* Do not edit manually.
*/

import type { AccountTreeController } from './AccountTreeController';

/**
* Gets the account wallet object from its ID.
*
* @param walletId - Account wallet ID.
* @returns The account wallet object if found, undefined otherwise.
*/
export type AccountTreeControllerGetAccountWalletObjectAction = {
type: `AccountTreeController:getAccountWalletObject`;
handler: AccountTreeController['getAccountWalletObject'];
};

/**
* Gets all account wallet objects.
*
* @returns All account wallet objects.
*/
export type AccountTreeControllerGetAccountWalletObjectsAction = {
type: `AccountTreeController:getAccountWalletObjects`;
handler: AccountTreeController['getAccountWalletObjects'];
};

/**
* Gets all underlying accounts from the currently selected account
* group.
*
* It also support account selector, which allows to filter specific
* accounts given some criterias (account type, address, scopes, etc...).
*
* @param selector - Optional account selector.
* @returns Underlying accounts for the currently selected account (filtered
* by the selector if provided).
*/
export type AccountTreeControllerGetAccountsFromSelectedAccountGroupAction = {
type: `AccountTreeController:getAccountsFromSelectedAccountGroup`;
handler: AccountTreeController['getAccountsFromSelectedAccountGroup'];
};

/**
* Gets the account group object from its ID.
*
* @param groupId - Account group ID.
* @returns The account group object if found, undefined otherwise.
*/
export type AccountTreeControllerGetAccountGroupObjectAction = {
type: `AccountTreeController:getAccountGroupObject`;
handler: AccountTreeController['getAccountGroupObject'];
};

/**
* Gets the account's context which contains its wallet ID, group ID, and sort order.
*
* @param accountId - Account ID.
* @returns The account context if found, undefined otherwise.
*/
export type AccountTreeControllerGetAccountContextAction = {
type: `AccountTreeController:getAccountContext`;
handler: AccountTreeController['getAccountContext'];
};

/**
* Gets the currently selected account group ID.
*
* @returns The selected account group ID or empty string if none selected.
*/
export type AccountTreeControllerGetSelectedAccountGroupAction = {
type: `AccountTreeController:getSelectedAccountGroup`;
handler: AccountTreeController['getSelectedAccountGroup'];
};

/**
* Sets the selected account group and updates the AccountsController selectedAccount accordingly.
*
* @param groupId - The account group ID to select.
*/
export type AccountTreeControllerSetSelectedAccountGroupAction = {
type: `AccountTreeController:setSelectedAccountGroup`;
handler: AccountTreeController['setSelectedAccountGroup'];
};

/**
* Sets a custom name for an account group.
*
* @param groupId - The account group ID.
* @param name - The custom name to set.
* @param autoHandleConflict - If true, automatically resolves name conflicts by adding a suffix. If false, throws on conflicts.
* @throws If the account group ID is not found in the current tree.
* @throws If the account group name already exists and autoHandleConflict is false.
*/
export type AccountTreeControllerSetAccountGroupNameAction = {
type: `AccountTreeController:setAccountGroupName`;
handler: AccountTreeController['setAccountGroupName'];
};

/**
* Sets a custom name for an account wallet.
*
* @param walletId - The account wallet ID.
* @param name - The custom name to set.
* @throws If the account wallet ID is not found in the current tree.
*/
export type AccountTreeControllerSetAccountWalletNameAction = {
type: `AccountTreeController:setAccountWalletName`;
handler: AccountTreeController['setAccountWalletName'];
};

/**
* Toggles the pinned state of an account group.
*
* @param groupId - The account group ID.
* @param pinned - Whether the group should be pinned.
* @throws If the account group ID is not found in the current tree.
*/
export type AccountTreeControllerSetAccountGroupPinnedAction = {
type: `AccountTreeController:setAccountGroupPinned`;
handler: AccountTreeController['setAccountGroupPinned'];
};

/**
* Toggles the hidden state of an account group.
*
* @param groupId - The account group ID.
* @param hidden - Whether the group should be hidden.
* @throws If the account group ID is not found in the current tree.
*/
export type AccountTreeControllerSetAccountGroupHiddenAction = {
type: `AccountTreeController:setAccountGroupHidden`;
handler: AccountTreeController['setAccountGroupHidden'];
};

/**
* Clears the controller state and resets to default values.
* Also clears the backup and sync service state.
*/
export type AccountTreeControllerClearStateAction = {
type: `AccountTreeController:clearState`;
handler: AccountTreeController['clearState'];
};

/**
* Bi-directionally syncs the account tree with user storage.
* This will perform a full sync, including both pulling updates
* from user storage and pushing local changes to user storage.
* This also performs legacy account syncing if needed.
*
* IMPORTANT:
* If a full sync is already in progress, it will return the ongoing promise.
*
* @returns A promise that resolves when the sync is complete.
*/
export type AccountTreeControllerSyncWithUserStorageAction = {
type: `AccountTreeController:syncWithUserStorage`;
handler: AccountTreeController['syncWithUserStorage'];
};

/**
* Bi-directionally syncs the account tree with user storage.
* This will ensure at least one full sync is ran, including both pulling updates
* from user storage and pushing local changes to user storage.
* This also performs legacy account syncing if needed.
*
* IMPORTANT:
* If the first ever full sync is already in progress, it will return the ongoing promise.
* If the first ever full sync was previously completed, it will NOT start a new sync, and will resolve immediately.
*
* @returns A promise that resolves when the first ever full sync is complete.
*/
export type AccountTreeControllerSyncWithUserStorageAtLeastOnceAction = {
type: `AccountTreeController:syncWithUserStorageAtLeastOnce`;
handler: AccountTreeController['syncWithUserStorageAtLeastOnce'];
};

/**
* Union of all AccountTreeController action types.
*/
export type AccountTreeControllerMethodActions =
| AccountTreeControllerGetAccountWalletObjectAction
| AccountTreeControllerGetAccountWalletObjectsAction
| AccountTreeControllerGetAccountsFromSelectedAccountGroupAction
| AccountTreeControllerGetAccountGroupObjectAction
| AccountTreeControllerGetAccountContextAction
| AccountTreeControllerGetSelectedAccountGroupAction
| AccountTreeControllerSetSelectedAccountGroupAction
| AccountTreeControllerSetAccountGroupNameAction
| AccountTreeControllerSetAccountWalletNameAction
| AccountTreeControllerSetAccountGroupPinnedAction
| AccountTreeControllerSetAccountGroupHiddenAction
| AccountTreeControllerClearStateAction
| AccountTreeControllerSyncWithUserStorageAction
| AccountTreeControllerSyncWithUserStorageAtLeastOnceAction;
Original file line number Diff line number Diff line change
Expand Up @@ -4575,21 +4575,18 @@ describe('AccountTreeController', () => {
});

// Test suffix resolution directly using the public method
const wallet = controller.state.accountTree.wallets[walletId];
const resolvedName = controller.resolveNameConflict(
wallet,
groupId,
'Suffix Test',
);
expect(resolvedName).toBe('Suffix Test (3)');
controller.setAccountGroupName(groupId, 'Suffix Test', true);

// Test with no conflicts: should return "Unique Name (2)"
const uniqueName = controller.resolveNameConflict(
wallet,
groupId,
'Unique Name',
);
expect(uniqueName).toBe('Unique Name (2)');
const collidingGroupObject = controller.getAccountGroupObject(groupId);

expect(collidingGroupObject?.metadata.name).toBe('Suffix Test (3)');

// Test with no conflicts: should return "Unique Name"
controller.setAccountGroupName(groupId, 'Unique Name', true);

const uniqueGroupObject = controller.getAccountGroupObject(groupId);

expect(uniqueGroupObject?.metadata.name).toBe('Unique Name');
Comment on lines +4578 to +4589
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveNameConflict is only internally used. It doesn't make sense to have it publicly only to test its behavior in unit tests. We can do that via the public methods that uses it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm this is not used in either extension and mobile.

Comment on lines +4578 to +4589
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion was very misleading by the way. If there's no conflict the method shouldn't be called therefore no suffix should be added.

});

it('throws error when group ID not found in tree', () => {
Expand Down
73 changes: 24 additions & 49 deletions packages/account-tree-controller/src/AccountTreeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ import type { AccountWalletObject, AccountWalletObjectOf } from './wallet';

export const controllerName = 'AccountTreeController';

const MESSENGER_EXPOSED_METHODS = [
'getSelectedAccountGroup',
'setSelectedAccountGroup',
'getAccountsFromSelectedAccountGroup',
'getAccountContext',
'setAccountWalletName',
'setAccountGroupName',
'setAccountGroupPinned',
'setAccountGroupHidden',
'getAccountWalletObject',
'getAccountWalletObjects',
'getAccountGroupObject',
'clearState',
'syncWithUserStorage',
'syncWithUserStorageAtLeastOnce',
] as const;

const accountTreeControllerMetadata: StateMetadata<AccountTreeControllerState> =
{
accountTree: {
Expand Down Expand Up @@ -246,7 +263,10 @@ export class AccountTreeController extends BaseController<
},
);

this.#registerMessageHandlers();
this.messenger.registerMethodActionHandlers(
this,
MESSENGER_EXPOSED_METHODS,
);
}

/**
Expand Down Expand Up @@ -507,7 +527,7 @@ export class AccountTreeController extends BaseController<
proposedName.length &&
!isAccountGroupNameUniqueFromWallet(wallet, group.id, proposedName)
) {
proposedName = this.resolveNameConflict(wallet, group.id, proposedName);
proposedName = this.#resolveNameConflict(wallet, group.id, proposedName);
}

return proposedName;
Expand Down Expand Up @@ -1324,7 +1344,7 @@ export class AccountTreeController extends BaseController<
* @param name - The desired name that has a conflict.
* @returns A unique name with suffix added if necessary.
*/
resolveNameConflict(
#resolveNameConflict(
wallet: AccountWalletObject,
groupId: AccountGroupId,
name: string,
Expand Down Expand Up @@ -1371,7 +1391,7 @@ export class AccountTreeController extends BaseController<
autoHandleConflict &&
!isAccountGroupNameUniqueFromWallet(wallet, groupId, name)
) {
finalName = this.resolveNameConflict(wallet, groupId, name);
finalName = this.#resolveNameConflict(wallet, groupId, name);
} else {
// Validate that the name is unique
this.#assertAccountGroupNameIsUnique(groupId, finalName);
Expand Down Expand Up @@ -1538,51 +1558,6 @@ export class AccountTreeController extends BaseController<
this.#initialized = false;
}

/**
* Registers message handlers for the AccountTreeController.
*/
#registerMessageHandlers(): void {
this.messenger.registerActionHandler(
`${controllerName}:getSelectedAccountGroup`,
this.getSelectedAccountGroup.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:setSelectedAccountGroup`,
this.setSelectedAccountGroup.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:getAccountsFromSelectedAccountGroup`,
this.getAccountsFromSelectedAccountGroup.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:getAccountContext`,
this.getAccountContext.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:setAccountWalletName`,
this.setAccountWalletName.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:setAccountGroupName`,
this.setAccountGroupName.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:setAccountGroupPinned`,
this.setAccountGroupPinned.bind(this),
);

this.messenger.registerActionHandler(
`${controllerName}:setAccountGroupHidden`,
this.setAccountGroupHidden.bind(this),
);
}

/**
* Bi-directionally syncs the account tree with user storage.
* This will perform a full sync, including both pulling updates
Expand Down
Loading
Loading