From ebe42dda61eb96089c2bed26892ef3c38de2a799 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Fri, 10 Apr 2026 18:51:56 +0200 Subject: [PATCH 1/3] fix(account-tree-controller): always fire :selectedAccountGroupChange on init --- .../src/AccountTreeController.test.ts | 42 +++++++++++++++++++ .../src/AccountTreeController.ts | 21 ++++------ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/account-tree-controller/src/AccountTreeController.test.ts b/packages/account-tree-controller/src/AccountTreeController.test.ts index ae6a5e1e16d..25df8c5d446 100644 --- a/packages/account-tree-controller/src/AccountTreeController.test.ts +++ b/packages/account-tree-controller/src/AccountTreeController.test.ts @@ -4502,6 +4502,48 @@ describe('AccountTreeController', () => { ); }); + it('always emits selectedAccountGroupChange on init even if the selected group did not change', () => { + const { controller, messenger, mocks } = setup({ + accounts: [MOCK_HD_ACCOUNT_1], + keyrings: [MOCK_HD_KEYRING_1], + }); + + mocks.AccountsController.getSelectedMultichainAccount.mockImplementation( + () => MOCK_HD_ACCOUNT_1, + ); + + controller.init(); + + const defaultAccountGroupId = toMultichainAccountGroupId( + toMultichainAccountWalletId(MOCK_HD_ACCOUNT_1.options.entropy.id), + MOCK_HD_ACCOUNT_1.options.entropy.groupIndex, + ); + + expect(controller.state.selectedAccountGroup).toStrictEqual( + defaultAccountGroupId, + ); + + const selectedAccountGroupChangeListener = jest.fn(); + messenger.subscribe( + 'AccountTreeController:selectedAccountGroupChange', + selectedAccountGroupChangeListener, + ); + + // Re-init with the same accounts — the selected group still exists and has not changed. + controller.reinit(); + + expect(controller.state.selectedAccountGroup).toStrictEqual( + defaultAccountGroupId, + ); + // The event must fire even though the group did not change, so that + // subscribers that missed the first init can still react accordingly. + expect(selectedAccountGroupChangeListener).toHaveBeenCalledWith( + defaultAccountGroupId, + defaultAccountGroupId, + ); + expect(selectedAccountGroupChangeListener).toHaveBeenCalledTimes(1); + }); + it('emits selectedAccountGroupChange when setSelectedAccountGroup is called', () => { // Use different keyring types to ensure different groups const { controller, messenger } = setup({ diff --git a/packages/account-tree-controller/src/AccountTreeController.ts b/packages/account-tree-controller/src/AccountTreeController.ts index f1839d969aa..6cf4bd5f4a1 100644 --- a/packages/account-tree-controller/src/AccountTreeController.ts +++ b/packages/account-tree-controller/src/AccountTreeController.ts @@ -369,18 +369,15 @@ export class AccountTreeController extends BaseController< } }); - // We still compare the previous and new value, the previous one could have been - // an empty string and `#getDefaultSelectedAccountGroup` could also return an - // empty string too, thus, we would re-use the same value here again. In that - // case, no need to fire any event. - if (previousSelectedAccountGroup !== this.state.selectedAccountGroup) { - log(`Selected (initial) group is: [${this.state.selectedAccountGroup}]`); - this.messenger.publish( - `${controllerName}:selectedAccountGroupChange`, - this.state.selectedAccountGroup, - previousSelectedAccountGroup, - ); - } + // We always fire the current selected group after init, even if it did not change, to ensure that + // all subscribers are aware of it and can react accordingly (e.g. by fetching accounts from + // the selected group). + log(`Selected (initial) group is: [${this.state.selectedAccountGroup}]`); + this.messenger.publish( + `${controllerName}:selectedAccountGroupChange`, + this.state.selectedAccountGroup, + previousSelectedAccountGroup, + ); log('Initialized!'); this.#initialized = true; From deaf627268cbe07fdfe7812fee4de25071b2c08a Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Mon, 13 Apr 2026 13:31:23 +0200 Subject: [PATCH 2/3] chore: changelog --- packages/account-tree-controller/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/account-tree-controller/CHANGELOG.md b/packages/account-tree-controller/CHANGELOG.md index 01c09285810..48dbf59cd33 100644 --- a/packages/account-tree-controller/CHANGELOG.md +++ b/packages/account-tree-controller/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Always fire `:selectedAccountGroupChange` during `init` call ([#8427](https://github.com/MetaMask/core/pull/8427)) + - Many controllers were actually relying on this behavior when this used to be dynamic, so we re-introduce this behavior. - Remove dynamic identifiers (wallet IDs, group IDs) from backup and sync thrown error messages to improve Sentry error grouping ([#8349](https://github.com/MetaMask/core/pull/8349)) - Bump `@metamask/accounts-controller` from `^37.1.1` to `^37.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) - Bump `@metamask/keyring-controller` from `^25.1.1` to `^25.2.0` ([#8363](https://github.com/MetaMask/core/pull/8363)) From 0689a7b085ee43043aef474bc62537096799b305 Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Tue, 14 Apr 2026 15:39:31 +0200 Subject: [PATCH 3/3] chore: comment --- packages/account-tree-controller/src/AccountTreeController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account-tree-controller/src/AccountTreeController.ts b/packages/account-tree-controller/src/AccountTreeController.ts index 8012db4fccf..676df296fa5 100644 --- a/packages/account-tree-controller/src/AccountTreeController.ts +++ b/packages/account-tree-controller/src/AccountTreeController.ts @@ -420,7 +420,7 @@ export class AccountTreeController extends BaseController< // We always fire the current selected group after init, even if it did not change, to ensure that // all subscribers are aware of it and can react accordingly (e.g. by fetching accounts from - // the selected group). + // the selected group on startup). log(`Selected (initial) group is: [${this.state.selectedAccountGroup}]`); this.messenger.publish( `${controllerName}:selectedAccountGroupChange`,