Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions packages/accounts-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add support for `SnapKeyring` v2 accounts ([#8513](https://github.com/MetaMask/core/pull/8513))

### Changed

- Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373))
Expand Down
4 changes: 2 additions & 2 deletions packages/accounts-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"build:docs": "typedoc",
"changelog:update": "../../scripts/update-changelog.sh @metamask/accounts-controller",
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/accounts-controller",
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --check",
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --generate",
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --check",
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --generate",
"since-latest-release": "../../scripts/since-latest-release.sh",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
Expand Down
291 changes: 291 additions & 0 deletions packages/accounts-controller/src/AccountsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
InfuraNetworkType,
toChecksumHexAddress,
} from '@metamask/controller-utils';
import { SnapKeyring as SnapKeyringV2 } from '@metamask/eth-snap-keyring/v2';
import type {
AccountAssetListUpdatedEventPayload,
AccountBalancesUpdatedEventPayload,
Expand All @@ -16,6 +17,7 @@ import {
EthScope,
KeyringAccountEntropyTypeOption,
} from '@metamask/keyring-api';
import { KeyringType } from '@metamask/keyring-api/v2';
import {
KeyringControllerState,
KeyringTypes,
Expand Down Expand Up @@ -1068,6 +1070,182 @@ describe('AccountsController', () => {
]);
});

it('add Snap v2 accounts', () => {
const mockSnapV2Address = '0xabcdef1234567890abcdef1234567890abcdef12';
const mockSnapV2SnapId = 'mock-snap-v2-id';
const mockSnapV2AccountId = 'mock-snap-v2-account-id';

const mockSnapV2KeyringAccount = {
id: mockSnapV2AccountId,
address: mockSnapV2Address,
options: {},
methods: [...ETH_EOA_METHODS],
type: EthAccountType.Eoa,
scopes: [EthScope.Eoa],
};

const mockKeyringV2Instance = Object.assign(
Object.create(SnapKeyringV2.prototype),
{
snapId: mockSnapV2SnapId,
lookupByAddress: jest
.fn()
.mockReturnValue(mockSnapV2KeyringAccount),
},
);

const messenger = buildMessenger();
messenger.registerActionHandler(
'KeyringController:getKeyringsByType',
mockGetKeyringByType.mockReturnValue([mockKeyringV2Instance]),
);

const mockNewKeyringState = {
isUnlocked: true,
keyrings: [
{
type: KeyringType.Snap,
accounts: [mockSnapV2Address],
metadata: {
id: 'mock-keyring-v2-id',
name: 'mock-keyring-v2-name',
},
},
],
};

const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {},
selectedAccount: '',
},
accountIdByAddress: {},
},
messenger,
});

messenger.publish(
'KeyringController:stateChange',
mockNewKeyringState,
[],
);

const accounts = accountsController.listMultichainAccounts();

expect(accounts).toHaveLength(1);
expect(accounts[0]).toMatchObject({
id: mockSnapV2AccountId,
address: mockSnapV2Address,
metadata: {
keyring: { type: KeyringType.Snap },
snap: {
id: mockSnapV2SnapId,
name: mockSnapV2SnapId,
enabled: true,
},
importTime: expect.any(Number),
},
});
});

it('handles the event when a Snap v2 deleted the account before it was added', () => {
const mockSnapV2Address = '0xabcdef1234567890abcdef1234567890abcdef12';

const mockKeyringV2Instance = Object.assign(
Object.create(SnapKeyringV2.prototype),
{
snapId: 'mock-snap-v2-id',
lookupByAddress: jest.fn().mockReturnValue(undefined),
},
);

const messenger = buildMessenger();
messenger.registerActionHandler(
'KeyringController:getKeyringsByType',
mockGetKeyringByType.mockReturnValue([mockKeyringV2Instance]),
);

const mockNewKeyringState = {
isUnlocked: true,
keyrings: [
{
type: KeyringType.Snap,
accounts: [mockSnapV2Address],
metadata: {
id: 'mock-keyring-v2-id',
name: 'mock-keyring-v2-name',
},
},
],
};

const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {},
selectedAccount: '',
},
accountIdByAddress: {},
},
messenger,
});

messenger.publish(
'KeyringController:stateChange',
mockNewKeyringState,
[],
);

expect(accountsController.listMultichainAccounts()).toStrictEqual([]);
});

it('handles when no SnapKeyringV2 instance matches for a Snap v2 keyring type', () => {
const mockSnapV2Address = '0xabcdef1234567890abcdef1234567890abcdef12';

const messenger = buildMessenger();
messenger.registerActionHandler(
'KeyringController:getKeyringsByType',
mockGetKeyringByType.mockReturnValue([
// Plain object — does NOT pass instanceof SnapKeyringV2
{ lookupByAddress: jest.fn() },
]),
);

const mockNewKeyringState = {
isUnlocked: true,
keyrings: [
{
type: KeyringType.Snap,
accounts: [mockSnapV2Address],
metadata: {
id: 'mock-keyring-v2-id',
name: 'mock-keyring-v2-name',
},
},
],
};

const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {},
selectedAccount: '',
},
accountIdByAddress: {},
},
messenger,
});

messenger.publish(
'KeyringController:stateChange',
mockNewKeyringState,
[],
);

expect(accountsController.listMultichainAccounts()).toStrictEqual([]);
});

it('increment the default account number when adding an account', async () => {
const messenger = buildMessenger();

Expand Down Expand Up @@ -3295,6 +3473,119 @@ describe('AccountsController', () => {
expect(mockGetKeyringByType).toHaveBeenCalledTimes(1);
});

it('update accounts with Snap v2 accounts', async () => {
const mockSnapV2Address = '0xabcdef1234567890abcdef1234567890abcdef12';
const mockSnapV2SnapId = 'mock-snap-v2-id';
const mockSnapV2AccountId = 'mock-snap-v2-account-id';

const mockSnapV2KeyringAccount = {
id: mockSnapV2AccountId,
address: mockSnapV2Address,
options: {},
methods: [...ETH_EOA_METHODS],
type: EthAccountType.Eoa,
scopes: [EthScope.Eoa],
};

const mockKeyringV2Instance = Object.assign(
Object.create(SnapKeyringV2.prototype),
{
snapId: mockSnapV2SnapId,
lookupByAddress: jest.fn().mockReturnValue(mockSnapV2KeyringAccount),
},
);

const messenger = buildMessenger();
messenger.registerActionHandler(
'KeyringController:getState',
mockGetState.mockReturnValue({
keyrings: [
{
type: KeyringType.Snap,
accounts: [mockSnapV2Address],
metadata: {
id: 'mock-keyring-v2-id',
name: 'mock-keyring-v2-name',
},
},
],
}),
);
messenger.registerActionHandler(
'KeyringController:getKeyringsByType',
mockGetKeyringByType.mockReturnValue([mockKeyringV2Instance]),
);

const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {},
selectedAccount: '',
},
accountIdByAddress: {},
},
messenger,
});

await accountsController.updateAccounts();

const accounts = accountsController.listMultichainAccounts();
expect(accounts).toHaveLength(1);
expect(accounts[0]).toMatchObject({
id: mockSnapV2AccountId,
address: mockSnapV2Address,
metadata: {
name: 'Snap Account 1',
keyring: { type: KeyringType.Snap },
snap: {
id: mockSnapV2SnapId,
name: mockSnapV2SnapId,
enabled: true,
},
},
});
});

it('skips Snap v2 account if no SnapKeyringV2 instance is found', async () => {
const mockSnapV2Address = '0xabcdef1234567890abcdef1234567890abcdef12';

const messenger = buildMessenger();
messenger.registerActionHandler(
'KeyringController:getState',
mockGetState.mockReturnValue({
keyrings: [
{
type: KeyringType.Snap,
accounts: [mockSnapV2Address],
metadata: {
id: 'mock-keyring-v2-id',
name: 'mock-keyring-v2-name',
},
},
],
}),
);
messenger.registerActionHandler(
'KeyringController:getKeyringsByType',
mockGetKeyringByType.mockReturnValue([]),
);

const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {},
selectedAccount: '',
},
accountIdByAddress: {},
},
messenger,
});

await accountsController.updateAccounts();

expect(accountsController.listMultichainAccounts()).toStrictEqual([]);
});

it.todo(
'does not re-fire a accountChanged event if the account is still the same',
);
Expand Down
Loading
Loading