Skip to content

Commit f128679

Browse files
committed
feat: add KeyringController:accountAdded event + use keyring events in AccountsController
1 parent 3402fd6 commit f128679

File tree

2 files changed

+161
-202
lines changed

2 files changed

+161
-202
lines changed

packages/accounts-controller/src/AccountsController.ts

Lines changed: 56 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ import {
1717
isEvmAccountType,
1818
KeyringAccountEntropyTypeOption,
1919
} from '@metamask/keyring-api';
20-
import type { KeyringObject } from '@metamask/keyring-controller';
20+
import type {
21+
KeyringControllerAccountAddedEvent,
22+
KeyringControllerAccountRemovedEvent,
23+
KeyringObject,
24+
} from '@metamask/keyring-controller';
2125
import {
22-
type KeyringControllerState,
2326
type KeyringControllerGetKeyringsByTypeAction,
2427
type KeyringControllerStateChangeEvent,
2528
type KeyringControllerGetStateAction,
@@ -36,7 +39,7 @@ import type {
3639
import type { SnapId } from '@metamask/snaps-sdk';
3740
import { type CaipChainId, isCaipChainId } from '@metamask/utils';
3841
import type { WritableDraft } from 'immer/dist/internal.js';
39-
import { cloneDeep } from 'lodash';
42+
import { add, cloneDeep } from 'lodash';
4043

4144
import type { MultichainNetworkControllerNetworkDidChangeEvent } from './types';
4245
import type { AccountsControllerStrictState } from './typing';
@@ -201,6 +204,8 @@ export type AccountsControllerAccountAssetListUpdatedEvent = {
201204
export type AllowedEvents =
202205
| SnapStateChange
203206
| KeyringControllerStateChangeEvent
207+
| KeyringControllerAccountRemovedEvent
208+
| KeyringControllerAccountAddedEvent
204209
| SnapKeyringAccountAssetListUpdatedEvent
205210
| SnapKeyringAccountBalancesUpdatedEvent
206211
| SnapKeyringAccountTransactionsUpdatedEvent
@@ -747,168 +752,59 @@ export class AccountsController extends BaseController<
747752
this.messenger.publish(event, ...payload);
748753
}
749754

750-
/**
751-
* Handles changes in the keyring state, specifically when new accounts are added or removed.
752-
*
753-
* @param keyringState - The new state of the keyring controller.
754-
* @param keyringState.isUnlocked - True if the keyrings are unlocked, false otherwise.
755-
* @param keyringState.keyrings - List of all keyrings.
756-
*/
757-
#handleOnKeyringStateChange({
758-
isUnlocked,
759-
keyrings,
760-
}: KeyringControllerState): void {
761-
// TODO: Change when accountAdded event is added to the keyring controller.
762-
763-
// We check for keyrings length to be greater than 0 because the extension client may try execute
764-
// submit password twice and clear the keyring state.
765-
// https://github.com/MetaMask/KeyringController/blob/2d73a4deed8d013913f6ef0c9f5c0bb7c614f7d3/src/KeyringController.ts#L910
766-
if (!isUnlocked || keyrings.length === 0) {
767-
return;
768-
}
769-
770-
// State patches.
771-
const generatePatch = () => {
772-
return {
773-
previous: {} as Record<string, InternalAccount>,
774-
added: [] as {
775-
address: string;
776-
keyring: KeyringObject;
777-
}[],
778-
updated: [] as InternalAccount[],
779-
removed: [] as InternalAccount[],
780-
};
781-
};
782-
const patches = {
783-
snap: generatePatch(),
784-
normal: generatePatch(),
785-
};
755+
#handleOnKeyringAccountAdded(address: string, keyring: KeyringObject) {
756+
let account: InternalAccount | undefined;
786757

787-
// Gets the patch object based on the keyring type (since Snap accounts and other accounts
788-
// are handled differently).
789-
const patchOf = (type: string) => {
790-
if (isSnapKeyringType(type)) {
791-
return patches.snap;
792-
}
793-
return patches.normal;
794-
};
795-
796-
// Create a map (with lower-cased addresses) of all existing accounts.
797-
for (const account of this.listMultichainAccounts()) {
798-
const address = account.address.toLowerCase();
799-
const patch = patchOf(account.metadata.keyring.type);
800-
801-
patch.previous[address] = account;
802-
}
758+
this.#update((state) => {
759+
const { internalAccounts } = state;
803760

804-
// Go over all keyring changes and create patches out of it.
805-
const addresses = new Set<string>();
806-
for (const keyring of keyrings) {
807-
const patch = patchOf(keyring.type);
761+
account = this.#getInternalAccountFromAddressAndType(address, keyring);
762+
if (account) {
763+
// Re-compute the list of accounts everytime, so we can make sure new names
764+
// are also considered.
765+
const accounts = Object.values(
766+
internalAccounts.accounts,
767+
) as InternalAccount[];
808768

809-
for (const accountAddress of keyring.accounts) {
810-
// Lower-case address to use it in the `previous` map.
811-
const address = accountAddress.toLowerCase();
812-
const account = patch.previous[address];
769+
// Get next account name available for this given keyring.
770+
const name = this.getNextAvailableAccountName(
771+
account.metadata.keyring.type,
772+
accounts,
773+
);
813774

814-
if (account) {
815-
// If the account exists before, this might be an update.
816-
patch.updated.push(account);
817-
} else {
818-
// Otherwise, that's a new account.
819-
patch.added.push({
820-
address,
821-
keyring,
822-
});
823-
}
775+
// If it's the first account, we need to select it.
776+
const lastSelected =
777+
accounts.length === 0 ? this.#getLastSelectedIndex() : 0;
824778

825-
// Keep track of those address to check for removed accounts later.
826-
addresses.add(address);
779+
internalAccounts.accounts[account.id] = {
780+
...account,
781+
metadata: {
782+
...account.metadata,
783+
name,
784+
importTime: Date.now(),
785+
lastSelected,
786+
},
787+
};
827788
}
828-
}
789+
});
829790

830-
// We might have accounts associated with removed keyrings, so we iterate
831-
// over all previous known accounts and check against the keyring addresses.
832-
for (const patch of [patches.snap, patches.normal]) {
833-
for (const [address, account] of Object.entries(patch.previous)) {
834-
// If a previous address is not part of the new addesses, then it got removed.
835-
if (!addresses.has(address)) {
836-
patch.removed.push(account);
837-
}
838-
}
791+
if (account) {
792+
this.messenger.publish('AccountsController:accountAdded', account);
839793
}
794+
}
840795

841-
// Diff that we will use to publish events afterward.
842-
const diff = {
843-
removed: [] as string[],
844-
added: [] as InternalAccount[],
845-
};
796+
#handleOnKeyringAccountRemoved(address: string) {
797+
const account = this.listMultichainAccounts().find(
798+
({ address: accountAddress }) => accountAddress === address,
799+
);
846800

847-
this.#update(
848-
(state) => {
801+
if (account) {
802+
this.#update((state) => {
849803
const { internalAccounts } = state;
850804

851-
for (const patch of [patches.snap, patches.normal]) {
852-
for (const account of patch.removed) {
853-
delete internalAccounts.accounts[account.id];
854-
855-
diff.removed.push(account.id);
856-
}
857-
858-
for (const added of patch.added) {
859-
const account = this.#getInternalAccountFromAddressAndType(
860-
added.address,
861-
added.keyring,
862-
);
863-
864-
if (account) {
865-
// Re-compute the list of accounts everytime, so we can make sure new names
866-
// are also considered.
867-
const accounts = Object.values(
868-
internalAccounts.accounts,
869-
) as InternalAccount[];
870-
871-
// Get next account name available for this given keyring.
872-
const name = this.getNextAvailableAccountName(
873-
account.metadata.keyring.type,
874-
accounts,
875-
);
876-
877-
// If it's the first account, we need to select it.
878-
const lastSelected =
879-
accounts.length === 0 ? this.#getLastSelectedIndex() : 0;
880-
881-
internalAccounts.accounts[account.id] = {
882-
...account,
883-
metadata: {
884-
...account.metadata,
885-
name,
886-
importTime: Date.now(),
887-
lastSelected,
888-
},
889-
};
890-
891-
diff.added.push(internalAccounts.accounts[account.id]);
892-
}
893-
}
894-
}
895-
},
896-
// Will get executed after the update, but before re-selecting an account in case
897-
// the current one is not valid anymore.
898-
() => {
899-
// Now publish events
900-
for (const id of diff.removed) {
901-
this.messenger.publish('AccountsController:accountRemoved', id);
902-
}
903-
904-
for (const account of diff.added) {
905-
this.messenger.publish('AccountsController:accountAdded', account);
906-
}
907-
},
908-
);
909-
910-
// NOTE: Since we also track "updated" accounts with our patches, we could fire a new event
911-
// like `accountUpdated` (we would still need to check if anything really changed on the account).
805+
delete internalAccounts.accounts[account.id];
806+
});
807+
}
912808
}
913809

914810
/**
@@ -1214,8 +1110,13 @@ export class AccountsController extends BaseController<
12141110
this.#handleOnSnapStateChange(snapStateState),
12151111
);
12161112

1217-
this.messenger.subscribe('KeyringController:stateChange', (keyringState) =>
1218-
this.#handleOnKeyringStateChange(keyringState),
1113+
this.messenger.subscribe('KeyringController:accountRemoved', (address) =>
1114+
this.#handleOnKeyringAccountRemoved(address),
1115+
);
1116+
1117+
this.messenger.subscribe(
1118+
'KeyringController:accountAdded',
1119+
(address, keyring) => this.#handleOnKeyringAccountAdded(address, keyring),
12191120
);
12201121

12211122
this.messenger.subscribe(

0 commit comments

Comments
 (0)