From 5bc115051a0ad410282bb5c7582222ba4bfd5710 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Wed, 3 Jul 2024 21:12:06 +0800 Subject: [PATCH] feat: add `accountAdded` and `accountRemoved` events (#4496) ## Explanation This PR adds the following events to the `AccountsController`, `accountAdded` and `accountRemoved`. ## References ## Changelog ### `@metamask/accounts-controller` - **ADDED**: Adds `accountAdded` and `accountRemoved` events. ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --------- Co-authored-by: Charly Chevalier --- .../src/AccountsController.test.ts | 88 +++++++++++++++++++ .../src/AccountsController.ts | 32 ++++++- packages/accounts-controller/src/index.ts | 2 + 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/packages/accounts-controller/src/AccountsController.test.ts b/packages/accounts-controller/src/AccountsController.test.ts index 8a48aee9aa9..c1cfb6e5a15 100644 --- a/packages/accounts-controller/src/AccountsController.test.ts +++ b/packages/accounts-controller/src/AccountsController.test.ts @@ -919,6 +919,50 @@ describe('AccountsController', () => { ]); expect(accountsController.getSelectedAccount().id).toBe(mockAccount.id); }); + + it('publishes accountAdded event', async () => { + const messenger = buildMessenger(); + const messengerSpy = jest.spyOn(messenger, 'publish'); + mockUUID + .mockReturnValueOnce(mockAccount.id) // call to check if its a new account + .mockReturnValueOnce(mockAccount2.id) // call to check if its a new account + .mockReturnValueOnce(mockAccount2.id); // call to add account + + setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: KeyringTypes.hd, + accounts: [mockAccount.address, mockAccount2.address], + }, + ], + }; + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + // First call is 'KeyringController:stateChange' + expect(messengerSpy).toHaveBeenNthCalledWith( + 2, + 'AccountsController:accountAdded', + setLastSelectedAsAny(mockAccount2), + ); + }); }); describe('deleting account', () => { @@ -1155,6 +1199,50 @@ describe('AccountsController', () => { currentTime, ); }); + + it('publishes accountRemoved event', async () => { + const messenger = buildMessenger(); + const messengerSpy = jest.spyOn(messenger, 'publish'); + mockUUID + .mockReturnValueOnce(mockAccount.id) // call to check if its a new account + .mockReturnValueOnce(mockAccount2.id) // call to check if its a new account + .mockReturnValueOnce(mockAccount2.id); // call to add account + + setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount3.id]: mockAccount3, + }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: KeyringTypes.hd, + accounts: [mockAccount.address, mockAccount2.address], + }, + ], + }; + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + // First call is 'KeyringController:stateChange' + expect(messengerSpy).toHaveBeenNthCalledWith( + 2, + 'AccountsController:accountRemoved', + mockAccount3.id, + ); + }); }); it('handle keyring reinitialization', async () => { diff --git a/packages/accounts-controller/src/AccountsController.ts b/packages/accounts-controller/src/AccountsController.ts index 34444da995c..dde5f31a113 100644 --- a/packages/accounts-controller/src/AccountsController.ts +++ b/packages/accounts-controller/src/AccountsController.ts @@ -42,9 +42,11 @@ import { const controllerName = 'AccountsController'; +export type AccountId = string; + export type AccountsControllerState = { internalAccounts: { - accounts: Record; + accounts: Record; selectedAccount: string; // id of the selected account }; }; @@ -137,12 +139,24 @@ export type AccountsControllerSelectedEvmAccountChangeEvent = { payload: [InternalAccount]; }; +export type AccountsControllerAccountAddedEvent = { + type: `${typeof controllerName}:accountAdded`; + payload: [InternalAccount]; +}; + +export type AccountsControllerAccountRemovedEvent = { + type: `${typeof controllerName}:accountRemoved`; + payload: [AccountId]; +}; + export type AllowedEvents = SnapStateChange | KeyringControllerStateChangeEvent; export type AccountsControllerEvents = | AccountsControllerChangeEvent | AccountsControllerSelectedAccountChangeEvent - | AccountsControllerSelectedEvmAccountChangeEvent; + | AccountsControllerSelectedEvmAccountChangeEvent + | AccountsControllerAccountAddedEvent + | AccountsControllerAccountRemovedEvent; export type AccountsControllerMessenger = RestrictedControllerMessenger< typeof controllerName, @@ -951,7 +965,7 @@ export class AccountsController extends BaseController< Object.values(accountsState), ); - accountsState[newAccount.id] = { + const newAccountWithUpdatedMetadata = { ...newAccount, metadata: { ...newAccount.metadata, @@ -960,6 +974,12 @@ export class AccountsController extends BaseController< lastSelected: isFirstAccount ? this.#getLastSelectedIndex() : 0, }, }; + accountsState[newAccount.id] = newAccountWithUpdatedMetadata; + + this.messagingSystem.publish( + 'AccountsController:accountAdded', + newAccountWithUpdatedMetadata, + ); return accountsState; } @@ -988,6 +1008,12 @@ export class AccountsController extends BaseController< accountId: string, ): AccountsControllerState['internalAccounts']['accounts'] { delete accountsState[accountId]; + + this.messagingSystem.publish( + 'AccountsController:accountRemoved', + accountId, + ); + return accountsState; } diff --git a/packages/accounts-controller/src/index.ts b/packages/accounts-controller/src/index.ts index c07b5b03f26..188caf3fedf 100644 --- a/packages/accounts-controller/src/index.ts +++ b/packages/accounts-controller/src/index.ts @@ -12,6 +12,8 @@ export type { AccountsControllerChangeEvent, AccountsControllerSelectedAccountChangeEvent, AccountsControllerSelectedEvmAccountChangeEvent, + AccountsControllerAccountAddedEvent, + AccountsControllerAccountRemovedEvent, AccountsControllerEvents, AccountsControllerMessenger, } from './AccountsController';