Skip to content

Commit 3fe4986

Browse files
committed
feat: add KeyringController:accountAdded event + use keyring events in AccountsController
1 parent 8a42225 commit 3fe4986

File tree

2 files changed

+161
-201
lines changed

2 files changed

+161
-201
lines changed

packages/accounts-controller/src/AccountsController.ts

Lines changed: 56 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import {
1919
} from '@metamask/keyring-api';
2020
import { KeyringTypes } from '@metamask/keyring-controller';
2121
import type {
22-
KeyringControllerState,
2322
KeyringControllerGetKeyringsByTypeAction,
2423
KeyringControllerStateChangeEvent,
24+
KeyringControllerAccountAddedEvent,
25+
KeyringControllerAccountRemovedEvent,
2526
KeyringControllerGetStateAction,
2627
KeyringObject,
2728
} from '@metamask/keyring-controller';
@@ -202,6 +203,8 @@ export type AccountsControllerAccountAssetListUpdatedEvent = {
202203
export type AllowedEvents =
203204
| SnapStateChange
204205
| KeyringControllerStateChangeEvent
206+
| KeyringControllerAccountRemovedEvent
207+
| KeyringControllerAccountAddedEvent
205208
| SnapKeyringAccountAssetListUpdatedEvent
206209
| SnapKeyringAccountBalancesUpdatedEvent
207210
| SnapKeyringAccountTransactionsUpdatedEvent
@@ -506,7 +509,10 @@ export class AccountsController extends BaseController<
506509
);
507510
}
508511

509-
#assertAccountCanBeRenamed(account: InternalAccount, accountName: string) {
512+
#assertAccountCanBeRenamed(
513+
account: InternalAccount,
514+
accountName: string,
515+
): void {
510516
if (
511517
this.listMultichainAccounts().find(
512518
(internalAccount) =>
@@ -748,168 +754,59 @@ export class AccountsController extends BaseController<
748754
this.messenger.publish(event, ...payload);
749755
}
750756

751-
/**
752-
* Handles changes in the keyring state, specifically when new accounts are added or removed.
753-
*
754-
* @param keyringState - The new state of the keyring controller.
755-
* @param keyringState.isUnlocked - True if the keyrings are unlocked, false otherwise.
756-
* @param keyringState.keyrings - List of all keyrings.
757-
*/
758-
#handleOnKeyringStateChange({
759-
isUnlocked,
760-
keyrings,
761-
}: KeyringControllerState): void {
762-
// TODO: Change when accountAdded event is added to the keyring controller.
763-
764-
// We check for keyrings length to be greater than 0 because the extension client may try execute
765-
// submit password twice and clear the keyring state.
766-
// https://github.com/MetaMask/KeyringController/blob/2d73a4deed8d013913f6ef0c9f5c0bb7c614f7d3/src/KeyringController.ts#L910
767-
if (!isUnlocked || keyrings.length === 0) {
768-
return;
769-
}
770-
771-
// State patches.
772-
const generatePatch = () => {
773-
return {
774-
previous: {} as Record<string, InternalAccount>,
775-
added: [] as {
776-
address: string;
777-
keyring: KeyringObject;
778-
}[],
779-
updated: [] as InternalAccount[],
780-
removed: [] as InternalAccount[],
781-
};
782-
};
783-
const patches = {
784-
snap: generatePatch(),
785-
normal: generatePatch(),
786-
};
787-
788-
// Gets the patch object based on the keyring type (since Snap accounts and other accounts
789-
// are handled differently).
790-
const patchOf = (type: string) => {
791-
if (isSnapKeyringType(type)) {
792-
return patches.snap;
793-
}
794-
return patches.normal;
795-
};
796-
797-
// Create a map (with lower-cased addresses) of all existing accounts.
798-
for (const account of this.listMultichainAccounts()) {
799-
const address = account.address.toLowerCase();
800-
const patch = patchOf(account.metadata.keyring.type);
757+
#handleOnKeyringAccountAdded(address: string, keyring: KeyringObject) {
758+
let account: InternalAccount | undefined;
801759

802-
patch.previous[address] = account;
803-
}
760+
this.#update((state) => {
761+
const { internalAccounts } = state;
804762

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

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

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

826-
// Keep track of those address to check for removed accounts later.
827-
addresses.add(address);
781+
internalAccounts.accounts[account.id] = {
782+
...account,
783+
metadata: {
784+
...account.metadata,
785+
name,
786+
importTime: Date.now(),
787+
lastSelected,
788+
},
789+
};
828790
}
829-
}
791+
});
830792

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

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

848-
this.#update(
849-
(state) => {
803+
if (account) {
804+
this.#update((state) => {
850805
const { internalAccounts } = state;
851806

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

915812
/**
@@ -1215,8 +1112,13 @@ export class AccountsController extends BaseController<
12151112
this.#handleOnSnapStateChange(snapStateState),
12161113
);
12171114

1218-
this.messenger.subscribe('KeyringController:stateChange', (keyringState) =>
1219-
this.#handleOnKeyringStateChange(keyringState),
1115+
this.messenger.subscribe('KeyringController:accountRemoved', (address) =>
1116+
this.#handleOnKeyringAccountRemoved(address),
1117+
);
1118+
1119+
this.messenger.subscribe(
1120+
'KeyringController:accountAdded',
1121+
(address, keyring) => this.#handleOnKeyringAccountAdded(address, keyring),
12201122
);
12211123

12221124
this.messenger.subscribe(

0 commit comments

Comments
 (0)