Skip to content

Commit ce12485

Browse files
Cal-Lgantunesrtommasinimcmireccharly
authored
feat: Add MultichainNetworkController to handle both EVM and non-EVM network and account switching (#5215)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR updates both the MultichainNetworkController and AccountsController to handle network switching as well as account switching. The logic handles the following logic: - Switching accounts on AccountsController will notify MultichainNetworkController to update if the account belongs to evm vs non-evm network (MultichainNetworkController subscribes to AccountsController event) - Switching between networks on MultichainNetworkController will notify AccountsController to update accounts based on which network the account belongs to (AccountsController subscribes to MultichainNetworkController event) ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Fixes https://github.com/MetaMask/accounts-planning/issues/804 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> ### `@metamask/accounts-controller` - **BREAKING**: - Added `MultichainNetworkController:networkDidChange` to allowed events. This is used to subscribe to the `setActiveNetwork` event from the `MultichainNetworkController` and is responsible for updating selected account based on network changes (both EVM and non-EVM). ### `@metamask/multichain-network-controller` - **ADDED**: - Allowed actions - `NetworkControllerGetStateAction` | `NetworkControllerSetActiveNetworkAction`. The `MultichainNetworkController` acts as a proxy for the `NetworkController` and will update it based on EVM network changes. - Allowed events - `AccountsControllerSelectedAccountChangeEvent` to allowed events. This is used to subscribe to the `selectedAccountChange` event from the `AccountsController` and is responsible for updating active network based on account changes (both EVM and non-EVM). ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes --------- Co-authored-by: gantunesr <[email protected]> Co-authored-by: tommasini <[email protected]> Co-authored-by: Elliot Winkler <[email protected]> Co-authored-by: Charly Chevalier <[email protected]>
1 parent 8acf5d7 commit ce12485

30 files changed

+1688
-139
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Each package in this repository has its own README where you can find installati
3939
- [`@metamask/logging-controller`](packages/logging-controller)
4040
- [`@metamask/message-manager`](packages/message-manager)
4141
- [`@metamask/multichain`](packages/multichain)
42+
- [`@metamask/multichain-network-controller`](packages/multichain-network-controller)
4243
- [`@metamask/multichain-transactions-controller`](packages/multichain-transactions-controller)
4344
- [`@metamask/name-controller`](packages/name-controller)
4445
- [`@metamask/network-controller`](packages/network-controller)
@@ -85,6 +86,7 @@ linkStyle default opacity:0.5
8586
logging_controller(["@metamask/logging-controller"]);
8687
message_manager(["@metamask/message-manager"]);
8788
multichain(["@metamask/multichain"]);
89+
multichain_network_controller(["@metamask/multichain-network-controller"]);
8890
multichain_transactions_controller(["@metamask/multichain-transactions-controller"]);
8991
name_controller(["@metamask/name-controller"]);
9092
network_controller(["@metamask/network-controller"]);
@@ -105,6 +107,7 @@ linkStyle default opacity:0.5
105107
user_operation_controller(["@metamask/user-operation-controller"]);
106108
accounts_controller --> base_controller;
107109
accounts_controller --> keyring_controller;
110+
accounts_controller --> network_controller;
108111
address_book_controller --> base_controller;
109112
address_book_controller --> controller_utils;
110113
announcement_controller --> base_controller;
@@ -116,10 +119,15 @@ linkStyle default opacity:0.5
116119
assets_controllers --> approval_controller;
117120
assets_controllers --> keyring_controller;
118121
assets_controllers --> network_controller;
122+
assets_controllers --> permission_controller;
119123
assets_controllers --> preferences_controller;
120124
base_controller --> json_rpc_engine;
121125
composable_controller --> base_controller;
122126
composable_controller --> json_rpc_engine;
127+
earn_controller --> base_controller;
128+
earn_controller --> controller_utils;
129+
earn_controller --> accounts_controller;
130+
earn_controller --> network_controller;
123131
ens_controller --> base_controller;
124132
ens_controller --> controller_utils;
125133
ens_controller --> network_controller;
@@ -136,8 +144,15 @@ linkStyle default opacity:0.5
136144
message_manager --> base_controller;
137145
message_manager --> controller_utils;
138146
multichain --> controller_utils;
147+
multichain --> json_rpc_engine;
139148
multichain --> network_controller;
140149
multichain --> permission_controller;
150+
multichain_network_controller --> base_controller;
151+
multichain_network_controller --> keyring_controller;
152+
multichain_transactions_controller --> base_controller;
153+
multichain_transactions_controller --> polling_controller;
154+
multichain_transactions_controller --> accounts_controller;
155+
multichain_transactions_controller --> keyring_controller;
141156
name_controller --> base_controller;
142157
name_controller --> controller_utils;
143158
network_controller --> base_controller;
@@ -184,6 +199,7 @@ linkStyle default opacity:0.5
184199
signature_controller --> keyring_controller;
185200
signature_controller --> logging_controller;
186201
signature_controller --> network_controller;
202+
token_search_discovery_controller --> base_controller;
187203
transaction_controller --> base_controller;
188204
transaction_controller --> controller_utils;
189205
transaction_controller --> accounts_controller;

eslint-warning-thresholds.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
"packages/accounts-controller/src/AccountsController.test.ts": {
1717
"import-x/namespace": 1
1818
},
19-
"packages/accounts-controller/src/utils.ts": {
20-
"jsdoc/tag-lines": 3
21-
},
2219
"packages/address-book-controller/src/AddressBookController.ts": {
2320
"jsdoc/check-tag-names": 13
2421
},

packages/accounts-controller/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"devDependencies": {
6464
"@metamask/auto-changelog": "^3.4.4",
6565
"@metamask/keyring-controller": "^19.1.0",
66+
"@metamask/network-controller": "^22.2.1",
6667
"@metamask/providers": "^18.1.1",
6768
"@metamask/snaps-controllers": "^9.19.0",
6869
"@types/jest": "^27.4.1",
@@ -75,7 +76,8 @@
7576
"webextension-polyfill": "^0.12.0"
7677
},
7778
"peerDependencies": {
78-
"@metamask/keyring-controller": "^19.0.0",
79+
"@metamask/keyring-controller": "^19.1.0",
80+
"@metamask/network-controller": "^22.2.1",
7981
"@metamask/providers": "^18.1.0",
8082
"@metamask/snaps-controllers": "^9.19.0",
8183
"webextension-polyfill": "^0.10.0 || ^0.11.0 || ^0.12.0"

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

Lines changed: 97 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Messenger } from '@metamask/base-controller';
2+
import { InfuraNetworkType } from '@metamask/controller-utils';
23
import type {
34
AccountAssetListUpdatedEventPayload,
45
AccountBalancesUpdatedEventPayload,
@@ -17,6 +18,7 @@ import type {
1718
InternalAccount,
1819
InternalAccountType,
1920
} from '@metamask/keyring-internal-api';
21+
import type { NetworkClientId } from '@metamask/network-controller';
2022
import type { SnapControllerState } from '@metamask/snaps-controllers';
2123
import { SnapStatus } from '@metamask/snaps-utils';
2224
import type { CaipChainId } from '@metamask/utils';
@@ -307,6 +309,7 @@ function buildAccountsControllerMessenger(messenger = buildMessenger()) {
307309
'SnapKeyring:accountAssetListUpdated',
308310
'SnapKeyring:accountBalancesUpdated',
309311
'SnapKeyring:accountTransactionsUpdated',
312+
'MultichainNetworkController:networkDidChange',
310313
],
311314
allowedActions: [
312315
'KeyringController:getAccounts',
@@ -339,6 +342,7 @@ function setupAccountsController({
339342
AccountsControllerActions | AllowedActions,
340343
AccountsControllerEvents | AllowedEvents
341344
>;
345+
triggerMultichainNetworkChange: (id: NetworkClientId | CaipChainId) => void;
342346
} {
343347
const accountsControllerMessenger =
344348
buildAccountsControllerMessenger(messenger);
@@ -347,10 +351,37 @@ function setupAccountsController({
347351
messenger: accountsControllerMessenger,
348352
state: { ...defaultState, ...initialState },
349353
});
350-
return { accountsController, messenger };
354+
355+
const triggerMultichainNetworkChange = (id: NetworkClientId | CaipChainId) =>
356+
messenger.publish('MultichainNetworkController:networkDidChange', id);
357+
358+
return { accountsController, messenger, triggerMultichainNetworkChange };
351359
}
352360

353361
describe('AccountsController', () => {
362+
const mockBtcAccount = createExpectedInternalAccount({
363+
id: 'mock-non-evm',
364+
name: 'non-evm',
365+
address: 'bc1qzqc2aqlw8nwa0a05ehjkk7dgt8308ac7kzw9a6',
366+
keyringType: KeyringTypes.snap,
367+
type: BtcAccountType.P2wpkh,
368+
});
369+
370+
const mockOlderEvmAccount = createExpectedInternalAccount({
371+
id: 'mock-id-1',
372+
name: 'mock account 1',
373+
address: 'mock-address-1',
374+
keyringType: KeyringTypes.hd,
375+
lastSelected: 11111,
376+
});
377+
const mockNewerEvmAccount = createExpectedInternalAccount({
378+
id: 'mock-id-2',
379+
name: 'mock account 2',
380+
address: 'mock-address-2',
381+
keyringType: KeyringTypes.hd,
382+
lastSelected: 22222,
383+
});
384+
354385
describe('onSnapStateChange', () => {
355386
it('be used enable an account if the Snap is enabled and not blocked', async () => {
356387
const messenger = buildMessenger();
@@ -1514,6 +1545,59 @@ describe('AccountsController', () => {
15141545
});
15151546
});
15161547

1548+
describe('handle MultichainNetworkController:networkDidChange event', () => {
1549+
it('should update selected account to non-EVM account when switching to non-EVM network', () => {
1550+
const messenger = buildMessenger();
1551+
const { accountsController, triggerMultichainNetworkChange } =
1552+
setupAccountsController({
1553+
initialState: {
1554+
internalAccounts: {
1555+
accounts: {
1556+
[mockOlderEvmAccount.id]: mockOlderEvmAccount,
1557+
[mockNewerEvmAccount.id]: mockNewerEvmAccount,
1558+
[mockBtcAccount.id]: mockBtcAccount,
1559+
},
1560+
selectedAccount: mockNewerEvmAccount.id,
1561+
},
1562+
},
1563+
messenger,
1564+
});
1565+
1566+
// Triggered from network switch to Bitcoin mainnet
1567+
triggerMultichainNetworkChange(BtcScope.Mainnet);
1568+
1569+
// BTC account is now selected
1570+
expect(accountsController.state.internalAccounts.selectedAccount).toBe(
1571+
mockBtcAccount.id,
1572+
);
1573+
});
1574+
1575+
it('should update selected account to EVM account when switching to EVM network', () => {
1576+
const messenger = buildMessenger();
1577+
const { accountsController, triggerMultichainNetworkChange } =
1578+
setupAccountsController({
1579+
initialState: {
1580+
internalAccounts: {
1581+
accounts: {
1582+
[mockOlderEvmAccount.id]: mockOlderEvmAccount,
1583+
[mockBtcAccount.id]: mockBtcAccount,
1584+
},
1585+
selectedAccount: mockBtcAccount.id,
1586+
},
1587+
},
1588+
messenger,
1589+
});
1590+
1591+
// Triggered from network switch to Bitcoin mainnet
1592+
triggerMultichainNetworkChange(InfuraNetworkType.mainnet);
1593+
1594+
// ETH mainnet account is now selected
1595+
expect(accountsController.state.internalAccounts.selectedAccount).toBe(
1596+
mockOlderEvmAccount.id,
1597+
);
1598+
});
1599+
});
1600+
15171601
describe('updateAccounts', () => {
15181602
const mockAddress1 = '0x123';
15191603
const mockAddress2 = '0x456';
@@ -2145,29 +2229,6 @@ describe('AccountsController', () => {
21452229
});
21462230

21472231
describe('getSelectedAccount', () => {
2148-
const mockNonEvmAccount = createExpectedInternalAccount({
2149-
id: 'mock-non-evm',
2150-
name: 'non-evm',
2151-
address: 'bc1qzqc2aqlw8nwa0a05ehjkk7dgt8308ac7kzw9a6',
2152-
keyringType: KeyringTypes.snap,
2153-
type: BtcAccountType.P2wpkh,
2154-
});
2155-
2156-
const mockOlderEvmAccount = createExpectedInternalAccount({
2157-
id: 'mock-id-1',
2158-
name: 'mock account 1',
2159-
address: 'mock-address-1',
2160-
keyringType: KeyringTypes.hd,
2161-
lastSelected: 11111,
2162-
});
2163-
const mockNewerEvmAccount = createExpectedInternalAccount({
2164-
id: 'mock-id-2',
2165-
name: 'mock account 2',
2166-
address: 'mock-address-2',
2167-
keyringType: KeyringTypes.hd,
2168-
lastSelected: 22222,
2169-
});
2170-
21712232
it.each([
21722233
{
21732234
lastSelectedAccount: mockNewerEvmAccount,
@@ -2178,7 +2239,7 @@ describe('AccountsController', () => {
21782239
expected: mockOlderEvmAccount,
21792240
},
21802241
{
2181-
lastSelectedAccount: mockNonEvmAccount,
2242+
lastSelectedAccount: mockBtcAccount,
21822243
expected: mockNewerEvmAccount,
21832244
},
21842245
])(
@@ -2190,7 +2251,7 @@ describe('AccountsController', () => {
21902251
accounts: {
21912252
[mockOlderEvmAccount.id]: mockOlderEvmAccount,
21922253
[mockNewerEvmAccount.id]: mockNewerEvmAccount,
2193-
[mockNonEvmAccount.id]: mockNonEvmAccount,
2254+
[mockBtcAccount.id]: mockBtcAccount,
21942255
},
21952256
selectedAccount: lastSelectedAccount.id,
21962257
},
@@ -2206,9 +2267,9 @@ describe('AccountsController', () => {
22062267
initialState: {
22072268
internalAccounts: {
22082269
accounts: {
2209-
[mockNonEvmAccount.id]: mockNonEvmAccount,
2270+
[mockBtcAccount.id]: mockBtcAccount,
22102271
},
2211-
selectedAccount: mockNonEvmAccount.id,
2272+
selectedAccount: mockBtcAccount.id,
22122273
},
22132274
},
22142275
});
@@ -2235,29 +2296,6 @@ describe('AccountsController', () => {
22352296
});
22362297

22372298
describe('getSelectedMultichainAccount', () => {
2238-
const mockNonEvmAccount = createExpectedInternalAccount({
2239-
id: 'mock-non-evm',
2240-
name: 'non-evm',
2241-
address: 'bc1qzqc2aqlw8nwa0a05ehjkk7dgt8308ac7kzw9a6',
2242-
keyringType: KeyringTypes.snap,
2243-
type: BtcAccountType.P2wpkh,
2244-
});
2245-
2246-
const mockOlderEvmAccount = createExpectedInternalAccount({
2247-
id: 'mock-id-1',
2248-
name: 'mock account 1',
2249-
address: 'mock-address-1',
2250-
keyringType: KeyringTypes.hd,
2251-
lastSelected: 11111,
2252-
});
2253-
const mockNewerEvmAccount = createExpectedInternalAccount({
2254-
id: 'mock-id-2',
2255-
name: 'mock account 2',
2256-
address: 'mock-address-2',
2257-
keyringType: KeyringTypes.hd,
2258-
lastSelected: 22222,
2259-
});
2260-
22612299
it.each([
22622300
{
22632301
chainId: undefined,
@@ -2266,18 +2304,18 @@ describe('AccountsController', () => {
22662304
},
22672305
{
22682306
chainId: undefined,
2269-
selectedAccount: mockNonEvmAccount,
2270-
expected: mockNonEvmAccount,
2307+
selectedAccount: mockBtcAccount,
2308+
expected: mockBtcAccount,
22712309
},
22722310
{
22732311
chainId: 'eip155:1',
2274-
selectedAccount: mockNonEvmAccount,
2312+
selectedAccount: mockBtcAccount,
22752313
expected: mockNewerEvmAccount,
22762314
},
22772315
{
22782316
chainId: 'bip122:000000000019d6689c085ae165831e93',
2279-
selectedAccount: mockNonEvmAccount,
2280-
expected: mockNonEvmAccount,
2317+
selectedAccount: mockBtcAccount,
2318+
expected: mockBtcAccount,
22812319
},
22822320
])(
22832321
"chainId $chainId with selectedAccount '$selectedAccount.id' should return $expected.id",
@@ -2288,7 +2326,7 @@ describe('AccountsController', () => {
22882326
accounts: {
22892327
[mockOlderEvmAccount.id]: mockOlderEvmAccount,
22902328
[mockNewerEvmAccount.id]: mockNewerEvmAccount,
2291-
[mockNonEvmAccount.id]: mockNonEvmAccount,
2329+
[mockBtcAccount.id]: mockBtcAccount,
22922330
},
22932331
selectedAccount: selectedAccount.id,
22942332
},
@@ -2313,9 +2351,9 @@ describe('AccountsController', () => {
23132351
accounts: {
23142352
[mockOlderEvmAccount.id]: mockOlderEvmAccount,
23152353
[mockNewerEvmAccount.id]: mockNewerEvmAccount,
2316-
[mockNonEvmAccount.id]: mockNonEvmAccount,
2354+
[mockBtcAccount.id]: mockBtcAccount,
23172355
},
2318-
selectedAccount: mockNonEvmAccount.id,
2356+
selectedAccount: mockBtcAccount.id,
23192357
},
23202358
},
23212359
});

0 commit comments

Comments
 (0)