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

## [Unreleased]

### Changed

- **BREAKING:** Now requires `SnapAccountService:account{AssetList,Balances,Transactions}Updated` events to be registered on the messenger.

### Fixed

- Re-publish `SnapAccountService:account{AssetList,Balances,Transactions}Updated` events as `AccountsController:account{AssetList,Balances,Transactions}Updated` events.

## [38.1.1]

### Changed
Expand Down
91 changes: 91 additions & 0 deletions packages/accounts-controller/src/AccountsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ function buildAccountsControllerMessenger(
'SnapKeyring:accountAssetListUpdated',
'SnapKeyring:accountBalancesUpdated',
'SnapKeyring:accountTransactionsUpdated',
'SnapAccountService:accountAssetListUpdated',
'SnapAccountService:accountBalancesUpdated',
'SnapAccountService:accountTransactionsUpdated',
'MultichainNetworkController:networkDidChange',
],
});
Expand Down Expand Up @@ -2167,6 +2170,29 @@ describe('AccountsController', () => {
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapAccountService:accountBalancesUpdated', () => {
const { account, messenger } = setupTest();

const payload: AccountBalancesUpdatedEventPayload = {
balances: {
[account.id]: {
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
amount: '0.1',
unit: 'BTC',
},
},
},
};

const mockRePublishedCallback = jest.fn();
messenger.subscribe(
'AccountsController:accountBalancesUpdated',
mockRePublishedCallback,
);
messenger.publish('SnapAccountService:accountBalancesUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapKeyring:accountAssetListUpdated', () => {
const { account, messenger } = setupTest();

Expand All @@ -2188,6 +2214,27 @@ describe('AccountsController', () => {
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapAccountService:accountAssetListUpdated', () => {
const { account, messenger } = setupTest();

const payload: AccountAssetListUpdatedEventPayload = {
assets: {
[account.id]: {
added: ['bip122:000000000019d6689c085ae165831e93/slip44:0'],
removed: ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'],
},
},
};

const mockRePublishedCallback = jest.fn();
messenger.subscribe(
'AccountsController:accountAssetListUpdated',
mockRePublishedCallback,
);
messenger.publish('SnapAccountService:accountAssetListUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapKeyring:accountTransactionsUpdated', () => {
const { account, messenger } = setupTest();

Expand Down Expand Up @@ -2228,6 +2275,50 @@ describe('AccountsController', () => {
messenger.publish('SnapKeyring:accountTransactionsUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapAccountService:accountTransactionsUpdated', () => {
const { account, messenger } = setupTest();

const payload: AccountTransactionsUpdatedEventPayload = {
transactions: {
[account.id]: [
{
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
timestamp: null,
chain: 'bip122:000000000019d6689c085ae165831e93',
status: 'submitted',
type: 'receive',
account: account.id,
from: [],
to: [],
fees: [
{
type: 'base',
asset: {
fungible: true,
type: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
unit: 'BTC',
amount: '0.0001',
},
},
],
events: [],
},
],
},
};

const mockRePublishedCallback = jest.fn();
messenger.subscribe(
'AccountsController:accountTransactionsUpdated',
mockRePublishedCallback,
);
messenger.publish(
'SnapAccountService:accountTransactionsUpdated',
payload,
);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});
});

describe('handle MultichainNetworkController:networkDidChange event', () => {
Expand Down
45 changes: 45 additions & 0 deletions packages/accounts-controller/src/AccountsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,21 @@ export type AccountsControllerAccountAssetListUpdatedEvent = {
payload: SnapKeyringAccountAssetListUpdatedEvent['payload'];
};

export type SnapAccountServiceAccountBalancesUpdatedEvent = {
type: `SnapAccountService:accountBalancesUpdated`;
payload: SnapKeyringAccountBalancesUpdatedEvent['payload'];
};

export type SnapAccountServiceAccountTransactionsUpdatedEvent = {
type: `SnapAccountService:accountTransactionsUpdated`;
payload: SnapKeyringAccountTransactionsUpdatedEvent['payload'];
};

export type SnapAccountServiceAccountAssetListUpdatedEvent = {
type: `SnapAccountService:accountAssetListUpdated`;
payload: SnapKeyringAccountAssetListUpdatedEvent['payload'];
};

/**
* @deprecated This type is deprecated and will be removed in a future version.
* Use `AccountTreeController`, `MultichainAccountService`, or the Keyring API v2 instead.
Expand All @@ -215,6 +230,9 @@ export type AllowedEvents =
| SnapKeyringAccountAssetListUpdatedEvent
| SnapKeyringAccountBalancesUpdatedEvent
| SnapKeyringAccountTransactionsUpdatedEvent
| SnapAccountServiceAccountAssetListUpdatedEvent
| SnapAccountServiceAccountBalancesUpdatedEvent
| SnapAccountServiceAccountTransactionsUpdatedEvent
| MultichainNetworkControllerNetworkDidChangeEvent;

/**
Expand Down Expand Up @@ -1287,6 +1305,15 @@ export class AccountsController extends BaseController<
),
);

this.messenger.subscribe(
'SnapAccountService:accountAssetListUpdated',
(snapAccountEvent) =>
this.#handleOnSnapKeyringAccountEvent(
'AccountsController:accountAssetListUpdated',
snapAccountEvent,
),
);

this.messenger.subscribe(
'SnapKeyring:accountBalancesUpdated',
(snapAccountEvent) =>
Expand All @@ -1296,6 +1323,15 @@ export class AccountsController extends BaseController<
),
);

this.messenger.subscribe(
'SnapAccountService:accountBalancesUpdated',
(snapAccountEvent) =>
this.#handleOnSnapKeyringAccountEvent(
'AccountsController:accountBalancesUpdated',
snapAccountEvent,
),
);

this.messenger.subscribe(
'SnapKeyring:accountTransactionsUpdated',
(snapAccountEvent) =>
Expand All @@ -1305,6 +1341,15 @@ export class AccountsController extends BaseController<
),
);

this.messenger.subscribe(
'SnapAccountService:accountTransactionsUpdated',
(snapAccountEvent) =>
this.#handleOnSnapKeyringAccountEvent(
'AccountsController:accountTransactionsUpdated',
snapAccountEvent,
),
);

// Handle account change when multichain network is changed
this.messenger.subscribe(
'MultichainNetworkController:networkDidChange',
Expand Down
3 changes: 3 additions & 0 deletions packages/accounts-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export type {
AccountsControllerAccountBalancesUpdatesEvent,
AccountsControllerAccountTransactionsUpdatedEvent,
AccountsControllerAccountAssetListUpdatedEvent,
SnapAccountServiceAccountBalancesUpdatedEvent,
SnapAccountServiceAccountTransactionsUpdatedEvent,
SnapAccountServiceAccountAssetListUpdatedEvent,
AllowedEvents,
AccountsControllerEvents,
AccountsControllerMessenger,
Expand Down
5 changes: 5 additions & 0 deletions packages/snap-account-service/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 `SnapAccountService:account{AssetList,Balances,Transactions}Updated` events.

### Changed

- Faster `:getLegacySnapKeyring` ([#8865](https://github.com/MetaMask/core/pull/8865))
Expand All @@ -15,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Re-publish account-data update events from `:handleKeyringSnapMessage` without requiring the legacy Snap keyring.
- Prevent double-lock in `:handleKeyringSnapMessage` for some events/methods ([#8860](https://github.com/MetaMask/core/pull/8860))
- The service messenger now requires the `KeyringController:withKeyringUnsafe` action.
- We now check if the keyring is available before delegating those messages.
Expand Down
111 changes: 111 additions & 0 deletions packages/snap-account-service/src/SnapAccountService.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { AccountGroupId } from '@metamask/account-api';
import type { SnapKeyring, SnapMessage } from '@metamask/eth-snap-keyring';
import type {
AccountAssetListUpdatedEventPayload,
AccountBalancesUpdatedEventPayload,
AccountTransactionsUpdatedEventPayload,
} from '@metamask/keyring-api';
import { KeyringEvent } from '@metamask/keyring-api';
import {
KeyringControllerError,
Expand Down Expand Up @@ -663,6 +668,7 @@ describe('SnapAccountService', () => {
method: KeyringEvent.AccountUpdated,
params: {},
} as unknown as SnapMessage;
const MOCK_ACCOUNT_ID = '00000000-0000-4000-8000-000000000001';

it('forwards the call to the legacy Snap keyring and returns its result', async () => {
const { service, mocks } = setup();
Expand Down Expand Up @@ -728,6 +734,111 @@ describe('SnapAccountService', () => {
expect(mocks.KeyringController.withController).not.toHaveBeenCalled();
});

describe('when the message is an account data update event', () => {
const accountBalancesUpdatedPayload: AccountBalancesUpdatedEventPayload =
{
balances: {
[MOCK_ACCOUNT_ID]: {
'eip155:1/slip44:60': {
amount: '1',
unit: 'ETH',
},
},
},
};
const accountAssetListUpdatedPayload: AccountAssetListUpdatedEventPayload =
{
assets: {
[MOCK_ACCOUNT_ID]: {
added: ['eip155:1/slip44:60'],
removed: [],
},
},
};
const accountTransactionsUpdatedPayload: AccountTransactionsUpdatedEventPayload =
{
transactions: {
[MOCK_ACCOUNT_ID]: [],
},
};

it.each([
[
KeyringEvent.AccountBalancesUpdated,
'SnapAccountService:accountBalancesUpdated',
accountBalancesUpdatedPayload,
],
[
KeyringEvent.AccountAssetListUpdated,
'SnapAccountService:accountAssetListUpdated',
accountAssetListUpdatedPayload,
],
[
KeyringEvent.AccountTransactionsUpdated,
'SnapAccountService:accountTransactionsUpdated',
accountTransactionsUpdatedPayload,
],
] as const)(
'publishes %s without requiring the legacy Snap keyring',
async (method, event, payload) => {
const { service, rootMessenger, mocks } = setup();
const handleKeyringSnapMessage = jest
.fn()
.mockResolvedValue({ ok: true });
mockLegacySnapKeyring(mocks, { handleKeyringSnapMessage });
const listener = jest.fn();
rootMessenger.subscribe(event, listener);

const result = await service.handleKeyringSnapMessage(MOCK_SNAP_ID, {
method,
params: payload,
});

expect(result).toBeNull();
expect(listener).toHaveBeenCalledWith(payload);
expect(handleKeyringSnapMessage).not.toHaveBeenCalled();
expect(
mocks.KeyringController.withKeyringUnsafe,
).not.toHaveBeenCalled();
expect(mocks.KeyringController.withController).not.toHaveBeenCalled();
},
);
});

describe('when the message is a request resolution event', () => {
it.each([
[
KeyringEvent.RequestApproved,
{ id: '00000000-0000-0000-0000-000000000002', result: true },
],
[
KeyringEvent.RequestRejected,
{ id: '00000000-0000-0000-0000-000000000002' },
],
] as const)(
'delegates %s to the legacy Snap keyring',
async (method, params) => {
const { service, mocks } = setup();
const message = { method, params };
const handleKeyringSnapMessage = jest
.fn()
.mockResolvedValue({ ok: true });
mockLegacySnapKeyring(mocks, { handleKeyringSnapMessage });

const result = await service.handleKeyringSnapMessage(
MOCK_SNAP_ID,
message,
);

expect(handleKeyringSnapMessage).toHaveBeenCalledWith(
MOCK_SNAP_ID,
message,
);
expect(result).toStrictEqual({ ok: true });
},
);
});

it('propagates non-KeyringNotFound errors from withKeyringUnsafe', async () => {
const { service, mocks } = setup();
mocks.KeyringController.withKeyringUnsafe.mockImplementation(async () => {
Expand Down
Loading
Loading