Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d47974c
migrate `AccountsController` to use `registerMethodActionHandlers`
GuillaumeRx Feb 17, 2026
30c0ddd
migrate `MultichainAccountService` to use `registerMethodActionHandlers`
GuillaumeRx Feb 17, 2026
973c84f
migrate `AccountTreeController`, `AuthenticationController` and `User…
GuillaumeRx Feb 18, 2026
6145994
fix action name
GuillaumeRx Feb 18, 2026
19538b2
use `generate-method-action-types` in `account-tree-controller`
GuillaumeRx Feb 19, 2026
b1a94d3
use `generate-method-action-types` in `accounts-controller`
GuillaumeRx Feb 19, 2026
2f75dca
use `generate-method-action-types` in `multichain-account-service`
GuillaumeRx Feb 19, 2026
e0c14b7
use `generate-method-action-types` in `profile-sync-controller`
GuillaumeRx Feb 19, 2026
bb9f0fd
revert typo
GuillaumeRx Feb 19, 2026
d746685
fix import
GuillaumeRx Feb 19, 2026
1c14840
fix action name usage
GuillaumeRx Feb 19, 2026
beadfa9
fix lint and exports
GuillaumeRx Feb 20, 2026
7596219
use type only imports
GuillaumeRx Feb 20, 2026
8601993
fix `account-tree-controller` type imports
GuillaumeRx Feb 20, 2026
a44156c
fix lint, rename actions in other controllers
GuillaumeRx Feb 20, 2026
f4be972
fill coverage in `AccountsController`
GuillaumeRx Feb 20, 2026
82db2cb
update CHANGELOGs
GuillaumeRx Feb 20, 2026
8d63c02
fix `eslint-suppressions.json` lint
GuillaumeRx Feb 20, 2026
dda2768
fix `claims-controller` changelog
GuillaumeRx Feb 20, 2026
f6875a0
fix `multichain-account-service` tests
GuillaumeRx Feb 20, 2026
1de02f4
remove direct call to method in test
GuillaumeRx Feb 20, 2026
0731bd4
Address requested changes
GuillaumeRx Feb 23, 2026
f05f301
fix `multichain-account-service` CHANGELOG
GuillaumeRx Feb 23, 2026
6a0f46a
fix CHANGELOGs and package.json after rebase
GuillaumeRx Feb 24, 2026
73143ae
fix action types after rebase
GuillaumeRx Feb 24, 2026
603df23
re-add comment
GuillaumeRx Feb 24, 2026
5705479
fix CHANGELOG
GuillaumeRx Feb 24, 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
17 changes: 17 additions & 0 deletions packages/account-tree-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### 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.

### Changed

- 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]

### Changed
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",
"since-latest-release": "../../scripts/since-latest-release.sh",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
Expand Down Expand Up @@ -71,6 +72,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