Skip to content

Commit 9791790

Browse files
authoredJan 27, 2025··
feat(accounts-controller): re-publish Snap keyring events (#5190)
## 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
1 parent 69b50e9 commit 9791790

File tree

8 files changed

+262
-77
lines changed

8 files changed

+262
-77
lines changed
 

‎packages/accounts-controller/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
"dependencies": {
5050
"@ethereumjs/util": "^8.1.0",
5151
"@metamask/base-controller": "^7.1.1",
52-
"@metamask/eth-snap-keyring": "^8.1.1",
53-
"@metamask/keyring-api": "^14.0.0",
54-
"@metamask/keyring-internal-api": "^2.0.1",
52+
"@metamask/eth-snap-keyring": "^9.0.0",
53+
"@metamask/keyring-api": "^15.0.0",
54+
"@metamask/keyring-internal-api": "^3.0.0",
5555
"@metamask/snaps-sdk": "^6.7.0",
5656
"@metamask/snaps-utils": "^8.3.0",
5757
"@metamask/utils": "^11.0.1",

‎packages/accounts-controller/src/AccountsController.test.ts

+122-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import { ControllerMessenger } from '@metamask/base-controller';
2+
import type {
3+
AccountAssetListUpdatedEventPayload,
4+
AccountBalancesUpdatedEventPayload,
5+
AccountTransactionsUpdatedEventPayload,
6+
} from '@metamask/keyring-api';
27
import {
38
BtcAccountType,
49
EthAccountType,
@@ -134,7 +139,7 @@ const mockAccount4: InternalAccount = {
134139
};
135140

136141
class MockNormalAccountUUID {
137-
#accountIds: Record<string, string> = {};
142+
readonly #accountIds: Record<string, string> = {};
138143

139144
constructor(accounts: InternalAccount[]) {
140145
for (const account of accounts) {
@@ -299,6 +304,9 @@ function buildAccountsControllerMessenger(messenger = buildMessenger()) {
299304
allowedEvents: [
300305
'SnapController:stateChange',
301306
'KeyringController:stateChange',
307+
'SnapKeyring:accountAssetListUpdated',
308+
'SnapKeyring:accountBalancesUpdated',
309+
'SnapKeyring:accountTransactionsUpdated',
302310
],
303311
allowedActions: [
304312
'KeyringController:getAccounts',
@@ -1393,6 +1401,119 @@ describe('AccountsController', () => {
13931401
);
13941402
});
13951403

1404+
describe('onSnapKeyringEvents', () => {
1405+
const setupTest = () => {
1406+
const account = createExpectedInternalAccount({
1407+
id: 'mock-id',
1408+
name: 'Bitcoin Account',
1409+
address: 'tb1q4q7h8wuplrpmkxqvv6rrrq7qyhhjsj5uqcsxqu',
1410+
keyringType: KeyringTypes.snap,
1411+
snapId: 'mock-snap',
1412+
type: BtcAccountType.P2wpkh,
1413+
});
1414+
1415+
const messenger = buildMessenger();
1416+
const { accountsController } = setupAccountsController({
1417+
initialState: {
1418+
internalAccounts: {
1419+
accounts: {
1420+
[account.id]: account,
1421+
},
1422+
selectedAccount: account.id,
1423+
},
1424+
},
1425+
messenger,
1426+
});
1427+
1428+
return { messenger, account, accountsController };
1429+
};
1430+
1431+
it('re-publishes keyring events: SnapKeyring:accountBalancesUpdated', () => {
1432+
const { account, messenger } = setupTest();
1433+
1434+
const payload: AccountBalancesUpdatedEventPayload = {
1435+
balances: {
1436+
[account.id]: {
1437+
'bip122:000000000019d6689c085ae165831e93/slip44:0': {
1438+
amount: '0.1',
1439+
unit: 'BTC',
1440+
},
1441+
},
1442+
},
1443+
};
1444+
1445+
const mockRePublishedCallback = jest.fn();
1446+
messenger.subscribe(
1447+
'AccountsController:accountBalancesUpdated',
1448+
mockRePublishedCallback,
1449+
);
1450+
messenger.publish('SnapKeyring:accountBalancesUpdated', payload);
1451+
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
1452+
});
1453+
1454+
it('re-publishes keyring events: SnapKeyring:accountAssetListUpdated', () => {
1455+
const { account, messenger } = setupTest();
1456+
1457+
const payload: AccountAssetListUpdatedEventPayload = {
1458+
assets: {
1459+
[account.id]: {
1460+
added: ['bip122:000000000019d6689c085ae165831e93/slip44:0'],
1461+
removed: ['bip122:000000000933ea01ad0ee984209779ba/slip44:0'],
1462+
},
1463+
},
1464+
};
1465+
1466+
const mockRePublishedCallback = jest.fn();
1467+
messenger.subscribe(
1468+
'AccountsController:accountAssetListUpdated',
1469+
mockRePublishedCallback,
1470+
);
1471+
messenger.publish('SnapKeyring:accountAssetListUpdated', payload);
1472+
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
1473+
});
1474+
1475+
it('re-publishes keyring events: SnapKeyring:accountTransactionsUpdated', () => {
1476+
const { account, messenger } = setupTest();
1477+
1478+
const payload: AccountTransactionsUpdatedEventPayload = {
1479+
transactions: {
1480+
[account.id]: [
1481+
{
1482+
id: 'f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6',
1483+
timestamp: null,
1484+
chain: 'bip122:000000000019d6689c085ae165831e93',
1485+
status: 'submitted',
1486+
type: 'receive',
1487+
account: account.id,
1488+
from: [],
1489+
to: [],
1490+
fees: [
1491+
{
1492+
type: 'base',
1493+
asset: {
1494+
fungible: true,
1495+
type: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
1496+
unit: 'BTC',
1497+
amount: '0.0001',
1498+
},
1499+
},
1500+
],
1501+
events: [],
1502+
},
1503+
],
1504+
},
1505+
};
1506+
1507+
const mockRePublishedCallback = jest.fn();
1508+
messenger.subscribe(
1509+
'AccountsController:accountTransactionsUpdated',
1510+
mockRePublishedCallback,
1511+
);
1512+
messenger.publish('SnapKeyring:accountTransactionsUpdated', payload);
1513+
expect(mockRePublishedCallback).toHaveBeenCalledWith(payload);
1514+
});
1515+
});
1516+
13961517
describe('updateAccounts', () => {
13971518
const mockAddress1 = '0x123';
13981519
const mockAddress2 = '0x456';

‎packages/accounts-controller/src/AccountsController.ts

+75-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import type {
22
ControllerGetStateAction,
33
ControllerStateChangeEvent,
4+
ExtractEventPayload,
45
RestrictedControllerMessenger,
56
} from '@metamask/base-controller';
67
import { BaseController } from '@metamask/base-controller';
8+
import type {
9+
SnapKeyringAccountAssetListUpdatedEvent,
10+
SnapKeyringAccountBalancesUpdatedEvent,
11+
SnapKeyringAccountTransactionsUpdatedEvent,
12+
} from '@metamask/eth-snap-keyring';
713
import { SnapKeyring } from '@metamask/eth-snap-keyring';
814
import {
915
EthAccountType,
@@ -161,15 +167,38 @@ export type AccountsControllerAccountRenamedEvent = {
161167
payload: [InternalAccount];
162168
};
163169

164-
export type AllowedEvents = SnapStateChange | KeyringControllerStateChangeEvent;
170+
export type AccountsControllerAccountBalancesUpdatesEvent = {
171+
type: `${typeof controllerName}:accountBalancesUpdated`;
172+
payload: SnapKeyringAccountBalancesUpdatedEvent['payload'];
173+
};
174+
175+
export type AccountsControllerAccountTransactionsUpdatedEvent = {
176+
type: `${typeof controllerName}:accountTransactionsUpdated`;
177+
payload: SnapKeyringAccountTransactionsUpdatedEvent['payload'];
178+
};
179+
180+
export type AccountsControllerAccountAssetListUpdatedEvent = {
181+
type: `${typeof controllerName}:accountAssetListUpdated`;
182+
payload: SnapKeyringAccountAssetListUpdatedEvent['payload'];
183+
};
184+
185+
export type AllowedEvents =
186+
| SnapStateChange
187+
| KeyringControllerStateChangeEvent
188+
| SnapKeyringAccountAssetListUpdatedEvent
189+
| SnapKeyringAccountBalancesUpdatedEvent
190+
| SnapKeyringAccountTransactionsUpdatedEvent;
165191

166192
export type AccountsControllerEvents =
167193
| AccountsControllerChangeEvent
168194
| AccountsControllerSelectedAccountChangeEvent
169195
| AccountsControllerSelectedEvmAccountChangeEvent
170196
| AccountsControllerAccountAddedEvent
171197
| AccountsControllerAccountRemovedEvent
172-
| AccountsControllerAccountRenamedEvent;
198+
| AccountsControllerAccountRenamedEvent
199+
| AccountsControllerAccountBalancesUpdatesEvent
200+
| AccountsControllerAccountTransactionsUpdatedEvent
201+
| AccountsControllerAccountAssetListUpdatedEvent;
173202

174203
export type AccountsControllerMessenger = RestrictedControllerMessenger<
175204
typeof controllerName,
@@ -261,6 +290,33 @@ export class AccountsController extends BaseController<
261290
(keyringState) => this.#handleOnKeyringStateChange(keyringState),
262291
);
263292

293+
this.messagingSystem.subscribe(
294+
'SnapKeyring:accountAssetListUpdated',
295+
(snapAccountEvent) =>
296+
this.#handleOnSnapKeyringAccountEvent(
297+
'AccountsController:accountAssetListUpdated',
298+
snapAccountEvent,
299+
),
300+
);
301+
302+
this.messagingSystem.subscribe(
303+
'SnapKeyring:accountBalancesUpdated',
304+
(snapAccountEvent) =>
305+
this.#handleOnSnapKeyringAccountEvent(
306+
'AccountsController:accountBalancesUpdated',
307+
snapAccountEvent,
308+
),
309+
);
310+
311+
this.messagingSystem.subscribe(
312+
'SnapKeyring:accountTransactionsUpdated',
313+
(snapAccountEvent) =>
314+
this.#handleOnSnapKeyringAccountEvent(
315+
'AccountsController:accountTransactionsUpdated',
316+
snapAccountEvent,
317+
),
318+
);
319+
264320
this.#registerMessageHandlers();
265321
}
266322

@@ -677,6 +733,23 @@ export class AccountsController extends BaseController<
677733
return internalAccounts;
678734
}
679735

736+
/**
737+
* Re-publish an account event.
738+
*
739+
* @param event - The event type. This is a unique identifier for this event.
740+
* @param payload - The event payload. The type of the parameters for each event handler must
741+
* match the type of this payload.
742+
* @template EventType - A Snap keyring event type.
743+
*/
744+
#handleOnSnapKeyringAccountEvent<
745+
EventType extends AccountsControllerEvents['type'],
746+
>(
747+
event: EventType,
748+
...payload: ExtractEventPayload<AccountsControllerEvents, EventType>
749+
): void {
750+
this.messagingSystem.publish(event, ...payload);
751+
}
752+
680753
/**
681754
* Handles changes in the keyring state, specifically when new accounts are added or removed.
682755
*

‎packages/assets-controllers/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@
8080
"@metamask/approval-controller": "^7.1.2",
8181
"@metamask/auto-changelog": "^3.4.4",
8282
"@metamask/ethjs-provider-http": "^0.3.0",
83-
"@metamask/keyring-api": "^14.0.0",
83+
"@metamask/keyring-api": "^15.0.0",
8484
"@metamask/keyring-controller": "^19.0.4",
85-
"@metamask/keyring-internal-api": "^2.0.1",
86-
"@metamask/keyring-snap-client": "^3.0.0",
85+
"@metamask/keyring-internal-api": "^3.0.0",
86+
"@metamask/keyring-snap-client": "^3.0.1",
8787
"@metamask/network-controller": "^22.1.1",
8888
"@metamask/preferences-controller": "^15.0.1",
8989
"@metamask/providers": "^18.1.1",

‎packages/keyring-controller/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
"@metamask/eth-hd-keyring": "^7.0.4",
5555
"@metamask/eth-sig-util": "^8.0.0",
5656
"@metamask/eth-simple-keyring": "^6.0.5",
57-
"@metamask/keyring-api": "^14.0.0",
58-
"@metamask/keyring-internal-api": "^2.0.1",
57+
"@metamask/keyring-api": "^15.0.0",
58+
"@metamask/keyring-internal-api": "^3.0.0",
5959
"@metamask/message-manager": "^12.0.0",
6060
"@metamask/utils": "^11.0.1",
6161
"async-mutex": "^0.5.0",

‎packages/multichain-transactions-controller/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
},
4949
"dependencies": {
5050
"@metamask/base-controller": "^7.1.1",
51-
"@metamask/keyring-api": "^14.0.0",
52-
"@metamask/keyring-internal-api": "^2.0.1",
53-
"@metamask/keyring-snap-client": "^3.0.0",
51+
"@metamask/keyring-api": "^15.0.0",
52+
"@metamask/keyring-internal-api": "^3.0.0",
53+
"@metamask/keyring-snap-client": "^3.0.1",
5454
"@metamask/polling-controller": "^12.0.2",
5555
"@metamask/snaps-controllers": "^9.10.0",
5656
"@metamask/snaps-sdk": "^6.7.0",

‎packages/profile-sync-controller/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
},
102102
"dependencies": {
103103
"@metamask/base-controller": "^7.1.1",
104-
"@metamask/keyring-api": "^14.0.0",
104+
"@metamask/keyring-api": "^15.0.0",
105105
"@metamask/keyring-controller": "^19.0.4",
106106
"@metamask/network-controller": "^22.1.1",
107107
"@metamask/snaps-sdk": "^6.7.0",
@@ -117,7 +117,7 @@
117117
"@lavamoat/preinstall-always-fail": "^2.1.0",
118118
"@metamask/accounts-controller": "^21.0.2",
119119
"@metamask/auto-changelog": "^3.4.4",
120-
"@metamask/keyring-internal-api": "^2.0.1",
120+
"@metamask/keyring-internal-api": "^3.0.0",
121121
"@metamask/providers": "^18.1.1",
122122
"@metamask/snaps-controllers": "^9.10.0",
123123
"@types/jest": "^27.4.1",

0 commit comments

Comments
 (0)
Please sign in to comment.