Skip to content

Commit

Permalink
feat(accounts-controller): re-publish Snap keyring events (#5190)
Browse files Browse the repository at this point in the history
## Explanation

The `SnapKeyring` now forward some keyring events coming from a Snap. To
avoid having controllers to depend the `SnapKeyring` for those new
events, we subscribe to them from the `AccountsController` and
re-publish them from it. This way, the `AccountsController` is the sole
dependency for those events (and is already a dependency of many
controllers anyway).

## References

- https://github.com/MetaMask/accounts/pull/154/files

## Changelog

### `@metamask/accounts-controller`

- **ADDED**: Re-publish Snap keyring events

## Checklist

- [ ] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [ ] I've highlighted breaking changes using the "BREAKING" category
above as appropriate
- [ ] I've prepared draft pull requests for clients and consumer
packages to resolve any breaking changes
  • Loading branch information
ccharly authored Jan 27, 2025
1 parent 69b50e9 commit 9791790
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 77 deletions.
6 changes: 3 additions & 3 deletions packages/accounts-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
"dependencies": {
"@ethereumjs/util": "^8.1.0",
"@metamask/base-controller": "^7.1.1",
"@metamask/eth-snap-keyring": "^8.1.1",
"@metamask/keyring-api": "^14.0.0",
"@metamask/keyring-internal-api": "^2.0.1",
"@metamask/eth-snap-keyring": "^9.0.0",
"@metamask/keyring-api": "^15.0.0",
"@metamask/keyring-internal-api": "^3.0.0",
"@metamask/snaps-sdk": "^6.7.0",
"@metamask/snaps-utils": "^8.3.0",
"@metamask/utils": "^11.0.1",
Expand Down
123 changes: 122 additions & 1 deletion packages/accounts-controller/src/AccountsController.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ControllerMessenger } from '@metamask/base-controller';
import type {
AccountAssetListUpdatedEventPayload,
AccountBalancesUpdatedEventPayload,
AccountTransactionsUpdatedEventPayload,
} from '@metamask/keyring-api';
import {
BtcAccountType,
EthAccountType,
Expand Down Expand Up @@ -134,7 +139,7 @@ const mockAccount4: InternalAccount = {
};

class MockNormalAccountUUID {
#accountIds: Record<string, string> = {};
readonly #accountIds: Record<string, string> = {};

constructor(accounts: InternalAccount[]) {
for (const account of accounts) {
Expand Down Expand Up @@ -299,6 +304,9 @@ function buildAccountsControllerMessenger(messenger = buildMessenger()) {
allowedEvents: [
'SnapController:stateChange',
'KeyringController:stateChange',
'SnapKeyring:accountAssetListUpdated',
'SnapKeyring:accountBalancesUpdated',
'SnapKeyring:accountTransactionsUpdated',
],
allowedActions: [
'KeyringController:getAccounts',
Expand Down Expand Up @@ -1393,6 +1401,119 @@ describe('AccountsController', () => {
);
});

describe('onSnapKeyringEvents', () => {
const setupTest = () => {
const account = createExpectedInternalAccount({
id: 'mock-id',
name: 'Bitcoin Account',
address: 'tb1q4q7h8wuplrpmkxqvv6rrrq7qyhhjsj5uqcsxqu',
keyringType: KeyringTypes.snap,
snapId: 'mock-snap',
type: BtcAccountType.P2wpkh,
});

const messenger = buildMessenger();
const { accountsController } = setupAccountsController({
initialState: {
internalAccounts: {
accounts: {
[account.id]: account,
},
selectedAccount: account.id,
},
},
messenger,
});

return { messenger, account, accountsController };
};

it('re-publishes keyring events: SnapKeyring: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('SnapKeyring:accountBalancesUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapKeyring: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('SnapKeyring:accountAssetListUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});

it('re-publishes keyring events: SnapKeyring: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('SnapKeyring:accountTransactionsUpdated', payload);
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
});
});

describe('updateAccounts', () => {
const mockAddress1 = '0x123';
const mockAddress2 = '0x456';
Expand Down
77 changes: 75 additions & 2 deletions packages/accounts-controller/src/AccountsController.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import type {
ControllerGetStateAction,
ControllerStateChangeEvent,
ExtractEventPayload,
RestrictedControllerMessenger,
} from '@metamask/base-controller';
import { BaseController } from '@metamask/base-controller';
import type {
SnapKeyringAccountAssetListUpdatedEvent,
SnapKeyringAccountBalancesUpdatedEvent,
SnapKeyringAccountTransactionsUpdatedEvent,
} from '@metamask/eth-snap-keyring';
import { SnapKeyring } from '@metamask/eth-snap-keyring';
import {
EthAccountType,
Expand Down Expand Up @@ -161,15 +167,38 @@ export type AccountsControllerAccountRenamedEvent = {
payload: [InternalAccount];
};

export type AllowedEvents = SnapStateChange | KeyringControllerStateChangeEvent;
export type AccountsControllerAccountBalancesUpdatesEvent = {
type: `${typeof controllerName}:accountBalancesUpdated`;
payload: SnapKeyringAccountBalancesUpdatedEvent['payload'];
};

export type AccountsControllerAccountTransactionsUpdatedEvent = {
type: `${typeof controllerName}:accountTransactionsUpdated`;
payload: SnapKeyringAccountTransactionsUpdatedEvent['payload'];
};

export type AccountsControllerAccountAssetListUpdatedEvent = {
type: `${typeof controllerName}:accountAssetListUpdated`;
payload: SnapKeyringAccountAssetListUpdatedEvent['payload'];
};

export type AllowedEvents =
| SnapStateChange
| KeyringControllerStateChangeEvent
| SnapKeyringAccountAssetListUpdatedEvent
| SnapKeyringAccountBalancesUpdatedEvent
| SnapKeyringAccountTransactionsUpdatedEvent;

export type AccountsControllerEvents =
| AccountsControllerChangeEvent
| AccountsControllerSelectedAccountChangeEvent
| AccountsControllerSelectedEvmAccountChangeEvent
| AccountsControllerAccountAddedEvent
| AccountsControllerAccountRemovedEvent
| AccountsControllerAccountRenamedEvent;
| AccountsControllerAccountRenamedEvent
| AccountsControllerAccountBalancesUpdatesEvent
| AccountsControllerAccountTransactionsUpdatedEvent
| AccountsControllerAccountAssetListUpdatedEvent;

export type AccountsControllerMessenger = RestrictedControllerMessenger<
typeof controllerName,
Expand Down Expand Up @@ -261,6 +290,33 @@ export class AccountsController extends BaseController<
(keyringState) => this.#handleOnKeyringStateChange(keyringState),
);

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

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

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

this.#registerMessageHandlers();
}

Expand Down Expand Up @@ -677,6 +733,23 @@ export class AccountsController extends BaseController<
return internalAccounts;
}

/**
* Re-publish an account event.
*
* @param event - The event type. This is a unique identifier for this event.
* @param payload - The event payload. The type of the parameters for each event handler must
* match the type of this payload.
* @template EventType - A Snap keyring event type.
*/
#handleOnSnapKeyringAccountEvent<
EventType extends AccountsControllerEvents['type'],
>(
event: EventType,
...payload: ExtractEventPayload<AccountsControllerEvents, EventType>
): void {
this.messagingSystem.publish(event, ...payload);
}

/**
* Handles changes in the keyring state, specifically when new accounts are added or removed.
*
Expand Down
6 changes: 3 additions & 3 deletions packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@
"@metamask/approval-controller": "^7.1.2",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/ethjs-provider-http": "^0.3.0",
"@metamask/keyring-api": "^14.0.0",
"@metamask/keyring-api": "^15.0.0",
"@metamask/keyring-controller": "^19.0.4",
"@metamask/keyring-internal-api": "^2.0.1",
"@metamask/keyring-snap-client": "^3.0.0",
"@metamask/keyring-internal-api": "^3.0.0",
"@metamask/keyring-snap-client": "^3.0.1",
"@metamask/network-controller": "^22.1.1",
"@metamask/preferences-controller": "^15.0.1",
"@metamask/providers": "^18.1.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/keyring-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
"@metamask/eth-hd-keyring": "^7.0.4",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/eth-simple-keyring": "^6.0.5",
"@metamask/keyring-api": "^14.0.0",
"@metamask/keyring-internal-api": "^2.0.1",
"@metamask/keyring-api": "^15.0.0",
"@metamask/keyring-internal-api": "^3.0.0",
"@metamask/message-manager": "^12.0.0",
"@metamask/utils": "^11.0.1",
"async-mutex": "^0.5.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/multichain-transactions-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
},
"dependencies": {
"@metamask/base-controller": "^7.1.1",
"@metamask/keyring-api": "^14.0.0",
"@metamask/keyring-internal-api": "^2.0.1",
"@metamask/keyring-snap-client": "^3.0.0",
"@metamask/keyring-api": "^15.0.0",
"@metamask/keyring-internal-api": "^3.0.0",
"@metamask/keyring-snap-client": "^3.0.1",
"@metamask/polling-controller": "^12.0.2",
"@metamask/snaps-controllers": "^9.10.0",
"@metamask/snaps-sdk": "^6.7.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/profile-sync-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
},
"dependencies": {
"@metamask/base-controller": "^7.1.1",
"@metamask/keyring-api": "^14.0.0",
"@metamask/keyring-api": "^15.0.0",
"@metamask/keyring-controller": "^19.0.4",
"@metamask/network-controller": "^22.1.1",
"@metamask/snaps-sdk": "^6.7.0",
Expand All @@ -117,7 +117,7 @@
"@lavamoat/preinstall-always-fail": "^2.1.0",
"@metamask/accounts-controller": "^21.0.2",
"@metamask/auto-changelog": "^3.4.4",
"@metamask/keyring-internal-api": "^2.0.1",
"@metamask/keyring-internal-api": "^3.0.0",
"@metamask/providers": "^18.1.1",
"@metamask/snaps-controllers": "^9.10.0",
"@types/jest": "^27.4.1",
Expand Down
Loading

0 comments on commit 9791790

Please sign in to comment.