Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
1 change: 1 addition & 0 deletions packages/account-tree-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add support for `AccountsController:accountRenamed` event handling for legacy account syncing compatibility ([#6251](https://github.com/MetaMask/core/pull/6251))
- Add `AccountTreeGroup.{get,select}` selectors ([#6248](https://github.com/MetaMask/core/pull/6248))
- Add persistence support for user customizations ([#6221](https://github.com/MetaMask/core/pull/6221))
- New `accountGroupsMetadata` (of new type `AccountTreeGroupPersistedMetadata`) and `accountWalletsMetadata` (of new type `AccountTreeWalletPersistedMetadata`) state properties to persist custom names, pinning, and hiding states.
Expand Down
168 changes: 168 additions & 0 deletions packages/account-tree-controller/src/AccountTreeController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ function getAccountTreeControllerMessenger(
name: 'AccountTreeController',
allowedEvents: [
'AccountsController:accountAdded',
'AccountsController:accountRenamed',
'AccountsController:accountRemoved',
'AccountsController:selectedAccountChange',
],
Expand Down Expand Up @@ -1017,6 +1018,173 @@ describe('AccountTreeController', () => {
});
});

describe('on AccountsController:accountRenamed', () => {
it('renames a group in the tree if the renamed internal account is of EVM type, the group name is default and the internal account name is not default', () => {
const { controller, messenger } = setup({
accounts: [MOCK_HD_ACCOUNT_1],
keyrings: [MOCK_HD_KEYRING_1],
});
controller.init();

const newName = 'New Account Name';
messenger.publish('AccountsController:accountRenamed', {
...MOCK_HD_ACCOUNT_1,
metadata: {
...MOCK_HD_ACCOUNT_1.metadata,
name: newName,
},
});

const walletId = toMultichainAccountWalletId(
MOCK_HD_KEYRING_1.metadata.id,
);
const group = toMultichainAccountGroupId(
walletId,
MOCK_HD_ACCOUNT_1.options.entropy.groupIndex,
);

expect(
controller.state.accountTree.wallets[walletId]?.groups[group],
).toBeDefined();
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].metadata
.name,
).toBe(newName);
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].accounts,
).toContain(MOCK_HD_ACCOUNT_1.id);
expect(
controller.state.accountTree.wallets[walletId]?.metadata.name,
).toBe('Wallet 1');
});

it('does not rename a group in the tree if the renamed internal account is of EVM type, but the group name is not default', () => {
const { controller, messenger } = setup({
accounts: [MOCK_HD_ACCOUNT_1],
keyrings: [MOCK_HD_KEYRING_1],
});
controller.init();
const newName = 'New Account Name';
const customGroupName = 'Old Group Name';
const groupId = toMultichainAccountGroupId(
toMultichainAccountWalletId(MOCK_HD_KEYRING_1.metadata.id),
MOCK_HD_ACCOUNT_1.options.entropy.groupIndex,
);
controller.setAccountGroupName(
groupId,
customGroupName, // Set a non-default group name
);

messenger.publish('AccountsController:accountRenamed', {
...MOCK_HD_ACCOUNT_1,
metadata: {
...MOCK_HD_ACCOUNT_1.metadata,
name: newName,
},
});

const walletId = toMultichainAccountWalletId(
MOCK_HD_KEYRING_1.metadata.id,
);
const group = toMultichainAccountGroupId(
walletId,
MOCK_HD_ACCOUNT_1.options.entropy.groupIndex,
);

expect(
controller.state.accountTree.wallets[walletId]?.groups[group],
).toBeDefined();
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].metadata
.name,
).toBe(customGroupName); // Should not change
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].accounts,
).toContain(MOCK_HD_ACCOUNT_1.id);
expect(
controller.state.accountTree.wallets[walletId]?.metadata.name,
).toBe('Wallet 1'); // Should not change
});

it('does not rename a group in the tree if the renamed internal account is of EVM type, the group name is default and the internal account name is also default', () => {
const { controller, messenger } = setup({
accounts: [MOCK_HD_ACCOUNT_1],
keyrings: [MOCK_HD_KEYRING_1],
});
controller.init();

messenger.publish('AccountsController:accountRenamed', {
...MOCK_HD_ACCOUNT_1,
metadata: {
...MOCK_HD_ACCOUNT_1.metadata,
name: MOCK_HD_ACCOUNT_2.metadata.name, // Default name
},
});

const walletId = toMultichainAccountWalletId(
MOCK_HD_KEYRING_1.metadata.id,
);
const group = toMultichainAccountGroupId(
walletId,
MOCK_HD_ACCOUNT_1.options.entropy.groupIndex,
);

expect(
controller.state.accountTree.wallets[walletId]?.groups[group],
).toBeDefined();
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].metadata
.name,
).toBe(MOCK_HD_ACCOUNT_1.metadata.name); // Should not change
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].accounts,
).toContain(MOCK_HD_ACCOUNT_1.id);
expect(
controller.state.accountTree.wallets[walletId]?.metadata.name,
).toBe('Wallet 1'); // Should not change
});

it('does not rename an account in the tree if the renamed internal account is not of EVM type', () => {
const { controller, messenger } = setup({
accounts: [MOCK_HD_ACCOUNT_1],
keyrings: [MOCK_HD_KEYRING_1],
});
controller.init();

const newName = 'New Account Name';
messenger.publish('AccountsController:accountRenamed', {
...MOCK_HD_ACCOUNT_1,
type: SolAccountType.DataAccount, // Not an EVM account type
metadata: {
...MOCK_HD_ACCOUNT_1.metadata,
name: newName,
},
});

const walletId = toMultichainAccountWalletId(
MOCK_HD_KEYRING_1.metadata.id,
);
const group = toMultichainAccountGroupId(
walletId,
MOCK_HD_ACCOUNT_1.options.entropy.groupIndex,
);

expect(
controller.state.accountTree.wallets[walletId]?.groups[group],
).toBeDefined();
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].metadata
.name,
).toBe(MOCK_HD_ACCOUNT_1.metadata.name);
expect(
controller.state.accountTree.wallets[walletId]?.groups[group].accounts,
).toContain(MOCK_HD_ACCOUNT_1.id);
expect(
controller.state.accountTree.wallets[walletId]?.metadata.name,
).toBe('Wallet 1');
});
});

describe('getAccountWallet/getAccountWalletOrThrow', () => {
it('gets a wallet using its ID', () => {
const { controller } = setup({
Expand Down
40 changes: 39 additions & 1 deletion packages/account-tree-controller/src/AccountTreeController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';
import { AccountWalletType } from '@metamask/account-api';
import type { AccountId } from '@metamask/accounts-controller';
import { type AccountId } from '@metamask/accounts-controller';
import type { StateMetadata } from '@metamask/base-controller';
import { BaseController } from '@metamask/base-controller';
import { isEvmAccountType } from '@metamask/keyring-api';
Expand Down Expand Up @@ -138,6 +138,13 @@ export class AccountTreeController extends BaseController<
},
);

this.messagingSystem.subscribe(
'AccountsController:accountRenamed',
(account) => {
this.#handleAccountRenamed(account);
},
);

this.#registerMessageHandlers();
}

Expand Down Expand Up @@ -316,6 +323,37 @@ export class AccountTreeController extends BaseController<
}
}

#handleAccountRenamed(account: InternalAccount) {
if (!isEvmAccountType(account.type)) {
return;
}

const context = this.#accountIdToContext.get(account.id);

if (context) {
const { walletId, groupId } = context;

const wallet = this.state.accountTree.wallets[walletId];
if (wallet) {
const group = wallet.groups[groupId];
if (group) {
const isDefaultNameRegex = /^Account ([0-9]+)$/u;

const isAccountNameDefault = isDefaultNameRegex.test(
account.metadata.name,
);
const isGroupNameDefault = isDefaultNameRegex.test(
group.metadata.name,
);

if (isGroupNameDefault && !isAccountNameDefault) {
this.setAccountGroupName(groupId, account.metadata.name);
}
}
}
}
}

/**
* Helper method to prune a group if it holds no accounts and additionally
* prune the wallet if it holds no groups. This action should take place
Expand Down
2 changes: 2 additions & 0 deletions packages/account-tree-controller/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';
import type {
AccountsControllerAccountAddedEvent,
AccountsControllerAccountRenamedEvent,
AccountsControllerAccountRemovedEvent,
AccountsControllerGetAccountAction,
AccountsControllerGetSelectedAccountAction,
Expand Down Expand Up @@ -95,6 +96,7 @@ export type AccountTreeControllerStateChangeEvent = ControllerStateChangeEvent<

export type AllowedEvents =
| AccountsControllerAccountAddedEvent
| AccountsControllerAccountRenamedEvent
| AccountsControllerAccountRemovedEvent
| AccountsControllerSelectedAccountChangeEvent;

Expand Down
Loading