@metamask/accounts-controller 27.0.0 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [28.0.0]
11
+
12
+ ### Added
13
+
14
+ - Add new `setAccountNameAndSelectAccount` action ([#5714](https://github.com/MetaMask/core/pull/5714))
15
+ - Add `entropySource` and `derivationPath` to EVM HD account options ([#5618](https://github.com/MetaMask/core/pull/5618))
16
+
17
+ ### Changed
18
+
19
+ - **BREAKING:** Bump `@metamask/snaps-controllers` peer dependency from `^9.19.0` to `^11.0.0` ([#5639](https://github.com/MetaMask/core/pull/5639))
20
+ - **BREAKING:** Bump `@metamask/providers` peer dependency from `^18.1.0` to `^21.0.0` ([#5639](https://github.com/MetaMask/core/pull/5639))
21
+ - Bump `@metamask/base-controller` from `^8.0.0` to `^8.0.1` ([#5722](https://github.com/MetaMask/core/pull/5722))
22
+ - Bump `@metamask/snaps-sdk` from `^6.17.1` to `^6.22.0` ([#5639](https://github.com/MetaMask/core/pull/5639))
23
+ - Bump `@metamask/snaps-utils` from `^8.10.0` to `^9.2.0` ([#5639](https://github.com/MetaMask/core/pull/5639))
24
+ - Bump `@metamask/eth-snap-keyring` from `^12.0.0` to `^12.1.1` ([#5565](https://github.com/MetaMask/core/pull/5565))
25
+ - Bump `@metamask/keyring-api` from `^17.2.0` to `^17.4.0` ([#5565](https://github.com/MetaMask/core/pull/5565))
26
+ - Bump `@metamask/keyring-internal-api` from `^6.0.0` to `^6.0.1` ([#5565](https://github.com/MetaMask/core/pull/5565))
27
+
28
+ ### Fixed
29
+
30
+ - Do not fire events during `update` blocks ([#5555](https://github.com/MetaMask/core/pull/5555))
31
+ - Prevent unnecessary state updates when updating `InternalAccount.metadata.snap` ([#5735](https://github.com/MetaMask/core/pull/5735))
32
+
10
33
  ## [27.0.0]
11
34
 
12
35
  ### Changed
@@ -505,7 +528,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
505
528
 
506
529
  - Initial release ([#1637](https://github.com/MetaMask/core/pull/1637))
507
530
 
508
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@27.0.0...HEAD
531
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@28.0.0...HEAD
532
+ [28.0.0]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@27.0.0...@metamask/accounts-controller@28.0.0
509
533
  [27.0.0]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@26.1.0...@metamask/accounts-controller@27.0.0
510
534
  [26.1.0]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@26.0.0...@metamask/accounts-controller@26.1.0
511
535
  [26.0.0]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@25.0.0...@metamask/accounts-controller@26.0.0
@@ -4,7 +4,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
4
4
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
5
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
6
  };
7
- var _AccountsController_instances, _AccountsController_generateInternalAccountForNonSnapAccount, _AccountsController_listSnapAccounts, _AccountsController_listNormalAccounts, _AccountsController_handleOnSnapKeyringAccountEvent, _AccountsController_handleOnKeyringStateChange, _AccountsController_handleOnSnapStateChange, _AccountsController_getAccountsByKeyringType, _AccountsController_getLastSelectedAccount, _AccountsController_getLastSelectedIndex, _AccountsController_handleNewAccountAdded, _AccountsController_publishAccountChangeEvent, _AccountsController_handleAccountRemoved, _AccountsController_handleOnMultichainNetworkDidChange, _AccountsController_populateExistingMetadata, _AccountsController_subscribeToMessageEvents, _AccountsController_registerMessageHandlers;
7
+ var _AccountsController_instances, _AccountsController_assertAccountCanBeRenamed, _AccountsController_generateInternalAccountForNonSnapAccount, _AccountsController_getSnapKeyring, _AccountsController_listSnapAccounts, _AccountsController_listNormalAccounts, _AccountsController_handleOnSnapKeyringAccountEvent, _AccountsController_handleOnKeyringStateChange, _AccountsController_update, _AccountsController_handleOnSnapStateChange, _AccountsController_getAccountsByKeyringType, _AccountsController_getLastSelectedAccount, _AccountsController_getLastSelectedIndex, _AccountsController_getInternalAccountFromAddressAndType, _AccountsController_handleOnMultichainNetworkDidChange, _AccountsController_populateExistingMetadata, _AccountsController_subscribeToMessageEvents, _AccountsController_registerMessageHandlers;
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.AccountsController = exports.EMPTY_ACCOUNT = void 0;
10
10
  const base_controller_1 = require("@metamask/base-controller");
@@ -126,14 +126,15 @@ class AccountsController extends base_controller_1.BaseController {
126
126
  * @returns The selected internal account.
127
127
  */
128
128
  getSelectedAccount() {
129
+ const { internalAccounts: { selectedAccount }, } = this.state;
129
130
  // Edge case where the extension is setup but the srp is not yet created
130
131
  // certain ui elements will query the selected address before any accounts are created.
131
- if (this.state.internalAccounts.selectedAccount === '') {
132
+ if (selectedAccount === '') {
132
133
  return exports.EMPTY_ACCOUNT;
133
134
  }
134
- const selectedAccount = this.getAccountExpect(this.state.internalAccounts.selectedAccount);
135
- if ((0, keyring_api_1.isEvmAccountType)(selectedAccount.type)) {
136
- return selectedAccount;
135
+ const account = this.getAccountExpect(selectedAccount);
136
+ if ((0, keyring_api_1.isEvmAccountType)(account.type)) {
137
+ return account;
137
138
  }
138
139
  const accounts = this.listAccounts();
139
140
  if (!accounts.length) {
@@ -153,13 +154,14 @@ class AccountsController extends base_controller_1.BaseController {
153
154
  * @returns The last selected account compatible with the specified chain ID or undefined.
154
155
  */
155
156
  getSelectedMultichainAccount(chainId) {
157
+ const { internalAccounts: { selectedAccount }, } = this.state;
156
158
  // Edge case where the extension is setup but the srp is not yet created
157
159
  // certain ui elements will query the selected address before any accounts are created.
158
- if (this.state.internalAccounts.selectedAccount === '') {
160
+ if (selectedAccount === '') {
159
161
  return exports.EMPTY_ACCOUNT;
160
162
  }
161
163
  if (!chainId) {
162
- return this.getAccountExpect(this.state.internalAccounts.selectedAccount);
164
+ return this.getAccountExpect(selectedAccount);
163
165
  }
164
166
  const accounts = this.listMultichainAccounts(chainId);
165
167
  return __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedAccount).call(this, accounts);
@@ -181,12 +183,11 @@ class AccountsController extends base_controller_1.BaseController {
181
183
  */
182
184
  setSelectedAccount(accountId) {
183
185
  const account = this.getAccountExpect(accountId);
184
- this.update((currentState) => {
185
- currentState.internalAccounts.accounts[account.id].metadata.lastSelected =
186
- Date.now();
187
- currentState.internalAccounts.selectedAccount = account.id;
186
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_update).call(this, (state) => {
187
+ const { internalAccounts } = state;
188
+ internalAccounts.accounts[account.id].metadata.lastSelected = Date.now();
189
+ internalAccounts.selectedAccount = account.id;
188
190
  });
189
- __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_publishAccountChangeEvent).call(this, account);
190
191
  }
191
192
  /**
192
193
  * Sets the name of the account with the given ID.
@@ -203,6 +204,37 @@ class AccountsController extends base_controller_1.BaseController {
203
204
  nameLastUpdatedAt: Date.now(),
204
205
  });
205
206
  }
207
+ /**
208
+ * Sets the name of the account with the given ID and select it.
209
+ *
210
+ * @param accountId - The ID of the account to set the name for and select.
211
+ * @param accountName - The new name for the account.
212
+ * @throws An error if an account with the same name already exists.
213
+ */
214
+ setAccountNameAndSelectAccount(accountId, accountName) {
215
+ const account = this.getAccountExpect(accountId);
216
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_assertAccountCanBeRenamed).call(this, account, accountName);
217
+ const internalAccount = {
218
+ ...account,
219
+ metadata: {
220
+ ...account.metadata,
221
+ name: accountName,
222
+ nameLastUpdatedAt: Date.now(),
223
+ lastSelected: __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this),
224
+ },
225
+ };
226
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_update).call(this, (state) => {
227
+ // FIXME: Using the state as-is cause the following error: "Type instantiation is excessively
228
+ // deep and possibly infinite.ts(2589)" (https://github.com/MetaMask/utils/issues/168)
229
+ // Using a type-cast workaround this error and is slightly better than using a @ts-expect-error
230
+ // which sometimes fail when compiling locally.
231
+ state.internalAccounts.accounts[account.id] =
232
+ internalAccount;
233
+ state.internalAccounts.selectedAccount =
234
+ account.id;
235
+ });
236
+ this.messagingSystem.publish('AccountsController:accountRenamed', internalAccount);
237
+ }
206
238
  /**
207
239
  * Updates the metadata of the account with the given ID.
208
240
  *
@@ -211,24 +243,24 @@ class AccountsController extends base_controller_1.BaseController {
211
243
  */
212
244
  updateAccountMetadata(accountId, metadata) {
213
245
  const account = this.getAccountExpect(accountId);
214
- if (metadata.name &&
215
- this.listMultichainAccounts().find((internalAccount) => internalAccount.metadata.name === metadata.name &&
216
- internalAccount.id !== accountId)) {
217
- throw new Error('Account name already exists');
246
+ if (metadata.name) {
247
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_assertAccountCanBeRenamed).call(this, account, metadata.name);
218
248
  }
219
- this.update((currentState) => {
220
- const internalAccount = {
221
- ...account,
222
- metadata: { ...account.metadata, ...metadata },
223
- };
224
- // Do not remove this comment - This error is flaky: Comment out or restore the `ts-expect-error` directive below as needed.
225
- // See: https://github.com/MetaMask/utils/issues/168
226
- // // @ts-expect-error Known issue - `Json` causes recursive error in immer `Draft`/`WritableDraft` types
227
- currentState.internalAccounts.accounts[accountId] = internalAccount;
228
- if (metadata.name) {
229
- this.messagingSystem.publish('AccountsController:accountRenamed', internalAccount);
230
- }
249
+ const internalAccount = {
250
+ ...account,
251
+ metadata: { ...account.metadata, ...metadata },
252
+ };
253
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_update).call(this, (state) => {
254
+ // FIXME: Using the state as-is cause the following error: "Type instantiation is excessively
255
+ // deep and possibly infinite.ts(2589)" (https://github.com/MetaMask/utils/issues/168)
256
+ // Using a type-cast workaround this error and is slightly better than using a @ts-expect-error
257
+ // which sometimes fail when compiling locally.
258
+ state.internalAccounts.accounts[accountId] =
259
+ internalAccount;
231
260
  });
261
+ if (metadata.name) {
262
+ this.messagingSystem.publish('AccountsController:accountRenamed', internalAccount);
263
+ }
232
264
  }
233
265
  /**
234
266
  * Updates the internal accounts list by retrieving normal and snap accounts,
@@ -267,21 +299,8 @@ class AccountsController extends base_controller_1.BaseController {
267
299
  };
268
300
  return internalAccountMap;
269
301
  }, {});
270
- this.update((currentState) => {
271
- currentState.internalAccounts.accounts = accounts;
272
- if (!currentState.internalAccounts.accounts[currentState.internalAccounts.selectedAccount]) {
273
- const lastSelectedAccount = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedAccount).call(this, Object.values(accounts));
274
- if (lastSelectedAccount) {
275
- currentState.internalAccounts.selectedAccount =
276
- lastSelectedAccount.id;
277
- currentState.internalAccounts.accounts[lastSelectedAccount.id].metadata.lastSelected = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this);
278
- __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_publishAccountChangeEvent).call(this, lastSelectedAccount);
279
- }
280
- else {
281
- // It will be undefined if there are no accounts
282
- currentState.internalAccounts.selectedAccount = '';
283
- }
284
- }
302
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_update).call(this, (state) => {
303
+ state.internalAccounts.accounts = accounts;
285
304
  });
286
305
  }
287
306
  /**
@@ -325,7 +344,12 @@ class AccountsController extends base_controller_1.BaseController {
325
344
  }
326
345
  }
327
346
  exports.AccountsController = AccountsController;
328
- _AccountsController_instances = new WeakSet(), _AccountsController_generateInternalAccountForNonSnapAccount = function _AccountsController_generateInternalAccountForNonSnapAccount(address, type) {
347
+ _AccountsController_instances = new WeakSet(), _AccountsController_assertAccountCanBeRenamed = function _AccountsController_assertAccountCanBeRenamed(account, accountName) {
348
+ if (this.listMultichainAccounts().find((internalAccount) => internalAccount.metadata.name === accountName &&
349
+ internalAccount.id !== account.id)) {
350
+ throw new Error('Account name already exists');
351
+ }
352
+ }, _AccountsController_generateInternalAccountForNonSnapAccount = function _AccountsController_generateInternalAccountForNonSnapAccount(address, type) {
329
353
  return {
330
354
  id: (0, utils_2.getUUIDFromAddressOfNormalAccount)(address),
331
355
  address,
@@ -348,6 +372,11 @@ _AccountsController_instances = new WeakSet(), _AccountsController_generateInter
348
372
  },
349
373
  },
350
374
  };
375
+ }, _AccountsController_getSnapKeyring = function _AccountsController_getSnapKeyring() {
376
+ const [snapKeyring] = this.messagingSystem.call('KeyringController:getKeyringsByType', eth_snap_keyring_1.SnapKeyring.type);
377
+ // Snap keyring is not available until the first account is created in the keyring
378
+ // controller, so this might be undefined.
379
+ return snapKeyring;
351
380
  }, _AccountsController_listSnapAccounts =
352
381
  /**
353
382
  * Returns a list of internal accounts created using the SnapKeyring.
@@ -355,13 +384,11 @@ _AccountsController_instances = new WeakSet(), _AccountsController_generateInter
355
384
  * @returns A promise that resolves to an array of InternalAccount objects.
356
385
  */
357
386
  async function _AccountsController_listSnapAccounts() {
358
- const [snapKeyring] = this.messagingSystem.call('KeyringController:getKeyringsByType', eth_snap_keyring_1.SnapKeyring.type);
359
- // snap keyring is not available until the first account is created in the keyring controller
360
- if (!snapKeyring) {
387
+ const keyring = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getSnapKeyring).call(this);
388
+ if (!keyring) {
361
389
  return [];
362
390
  }
363
- const snapAccounts = snapKeyring.listAccounts();
364
- return snapAccounts;
391
+ return keyring.listAccounts();
365
392
  }, _AccountsController_listNormalAccounts =
366
393
  /**
367
394
  * Returns a list of normal accounts.
@@ -372,20 +399,30 @@ async function _AccountsController_listSnapAccounts() {
372
399
  */
373
400
  async function _AccountsController_listNormalAccounts() {
374
401
  const internalAccounts = [];
375
- const { keyrings } = await this.messagingSystem.call('KeyringController:getState');
376
- for (const keyring of keyrings) {
402
+ const { keyrings, keyringsMetadata } = this.messagingSystem.call('KeyringController:getState');
403
+ for (const [keyringIndex, keyring] of keyrings.entries()) {
377
404
  const keyringType = keyring.type;
378
405
  if (!(0, utils_2.isNormalKeyringType)(keyringType)) {
379
406
  // We only consider "normal accounts" here, so keep looping
380
407
  continue;
381
408
  }
382
- for (const address of keyring.accounts) {
409
+ for (const [accountIndex, address] of keyring.accounts.entries()) {
383
410
  const id = (0, utils_2.getUUIDFromAddressOfNormalAccount)(address);
411
+ let options = {};
412
+ if ((0, utils_2.isHdKeyringType)(keyring.type)) {
413
+ options = {
414
+ entropySource: keyringsMetadata[keyringIndex].id,
415
+ // NOTE: We are not using the `hdPath` from the associated keyring here and
416
+ // getting the keyring instance here feels a bit overkill.
417
+ // This will be naturally fixed once every keyring start using `KeyringAccount` and implement the keyring API.
418
+ derivationPath: (0, utils_2.getDerivationPathForIndex)(accountIndex),
419
+ };
420
+ }
384
421
  const nameLastUpdatedAt = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_populateExistingMetadata).call(this, id, 'nameLastUpdatedAt');
385
422
  internalAccounts.push({
386
423
  id,
387
424
  address,
388
- options: {},
425
+ options,
389
426
  methods: [
390
427
  keyring_api_1.EthMethod.PersonalSign,
391
428
  keyring_api_1.EthMethod.Sign,
@@ -411,122 +448,178 @@ async function _AccountsController_listNormalAccounts() {
411
448
  return internalAccounts;
412
449
  }, _AccountsController_handleOnSnapKeyringAccountEvent = function _AccountsController_handleOnSnapKeyringAccountEvent(event, ...payload) {
413
450
  this.messagingSystem.publish(event, ...payload);
414
- }, _AccountsController_handleOnKeyringStateChange = function _AccountsController_handleOnKeyringStateChange(keyringState) {
415
- // check if there are any new accounts added
416
- // TODO: change when accountAdded event is added to the keyring controller
451
+ }, _AccountsController_handleOnKeyringStateChange = function _AccountsController_handleOnKeyringStateChange({ isUnlocked, keyrings, }) {
452
+ // TODO: Change when accountAdded event is added to the keyring controller.
417
453
  // We check for keyrings length to be greater than 0 because the extension client may try execute
418
454
  // submit password twice and clear the keyring state.
419
455
  // https://github.com/MetaMask/KeyringController/blob/2d73a4deed8d013913f6ef0c9f5c0bb7c614f7d3/src/KeyringController.ts#L910
420
- if (keyringState.isUnlocked && keyringState.keyrings.length > 0) {
421
- const updatedNormalKeyringAddresses = [];
422
- const updatedSnapKeyringAddresses = [];
423
- for (const keyring of keyringState.keyrings) {
424
- if (keyring.type === keyring_controller_1.KeyringTypes.snap) {
425
- updatedSnapKeyringAddresses.push(...keyring.accounts.map((address) => {
426
- return {
427
- address,
428
- type: keyring.type,
429
- };
430
- }));
431
- }
432
- else {
433
- updatedNormalKeyringAddresses.push(...keyring.accounts.map((address) => {
434
- return {
435
- address,
436
- type: keyring.type,
437
- };
438
- }));
439
- }
456
+ if (!isUnlocked || keyrings.length === 0) {
457
+ return;
458
+ }
459
+ // State patches.
460
+ const generatePatch = () => {
461
+ return {
462
+ previous: {},
463
+ added: [],
464
+ updated: [],
465
+ removed: [],
466
+ };
467
+ };
468
+ const patches = {
469
+ snap: generatePatch(),
470
+ normal: generatePatch(),
471
+ };
472
+ // Gets the patch object based on the keyring type (since Snap accounts and other accounts
473
+ // are handled differently).
474
+ const patchOf = (type) => {
475
+ if (type === keyring_controller_1.KeyringTypes.snap) {
476
+ return patches.snap;
440
477
  }
441
- const { previousNormalInternalAccounts, previousSnapInternalAccounts } = this.listMultichainAccounts().reduce((accumulator, account) => {
442
- if (account.metadata.keyring.type === keyring_controller_1.KeyringTypes.snap) {
443
- accumulator.previousSnapInternalAccounts.push(account);
478
+ return patches.normal;
479
+ };
480
+ // Create a map (with lower-cased addresses) of all existing accounts.
481
+ for (const account of this.listMultichainAccounts()) {
482
+ const address = account.address.toLowerCase();
483
+ const patch = patchOf(account.metadata.keyring.type);
484
+ patch.previous[address] = account;
485
+ }
486
+ // Go over all keyring changes and create patches out of it.
487
+ const addresses = new Set();
488
+ for (const keyring of keyrings) {
489
+ const patch = patchOf(keyring.type);
490
+ for (const accountAddress of keyring.accounts) {
491
+ // Lower-case address to use it in the `previous` map.
492
+ const address = accountAddress.toLowerCase();
493
+ const account = patch.previous[address];
494
+ if (account) {
495
+ // If the account exists before, this might be an update.
496
+ patch.updated.push(account);
444
497
  }
445
498
  else {
446
- accumulator.previousNormalInternalAccounts.push(account);
447
- }
448
- return accumulator;
449
- }, {
450
- previousNormalInternalAccounts: [],
451
- previousSnapInternalAccounts: [],
452
- });
453
- const addedAccounts = [];
454
- const deletedAccounts = [];
455
- // snap account ids are random uuid while normal accounts
456
- // are determininistic based on the address
457
- // ^NOTE: This will be removed when normal accounts also implement internal accounts
458
- // finding all the normal accounts that were added
459
- for (const account of updatedNormalKeyringAddresses) {
460
- if (!this.state.internalAccounts.accounts[(0, utils_2.getUUIDFromAddressOfNormalAccount)(account.address)]) {
461
- addedAccounts.push(account);
499
+ // Otherwise, that's a new account.
500
+ patch.added.push({
501
+ address,
502
+ type: keyring.type,
503
+ });
462
504
  }
505
+ // Keep track of those address to check for removed accounts later.
506
+ addresses.add(address);
463
507
  }
464
- // finding all the snap accounts that were added
465
- for (const account of updatedSnapKeyringAddresses) {
466
- if (!previousSnapInternalAccounts.find((internalAccount) => internalAccount.address.toLowerCase() ===
467
- account.address.toLowerCase())) {
468
- addedAccounts.push(account);
508
+ }
509
+ // We might have accounts associated with removed keyrings, so we iterate
510
+ // over all previous known accounts and check against the keyring addresses.
511
+ for (const patch of [patches.snap, patches.normal]) {
512
+ for (const [address, account] of Object.entries(patch.previous)) {
513
+ // If a previous address is not part of the new addesses, then it got removed.
514
+ if (!addresses.has(address)) {
515
+ patch.removed.push(account);
469
516
  }
470
517
  }
471
- // finding all the normal accounts that were deleted
472
- for (const account of previousNormalInternalAccounts) {
473
- if (!updatedNormalKeyringAddresses.find(({ address }) => address.toLowerCase() === account.address.toLowerCase())) {
474
- deletedAccounts.push(account);
518
+ }
519
+ // Diff that we will use to publish events afterward.
520
+ const diff = {
521
+ removed: [],
522
+ added: [],
523
+ };
524
+ __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_update).call(this, (state) => {
525
+ const { internalAccounts } = state;
526
+ for (const patch of [patches.snap, patches.normal]) {
527
+ for (const account of patch.removed) {
528
+ delete internalAccounts.accounts[account.id];
529
+ diff.removed.push(account.id);
475
530
  }
476
- }
477
- // finding all the snap accounts that were deleted
478
- for (const account of previousSnapInternalAccounts) {
479
- if (!updatedSnapKeyringAddresses.find(({ address }) => address.toLowerCase() === account.address.toLowerCase())) {
480
- deletedAccounts.push(account);
531
+ for (const added of patch.added) {
532
+ const account = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getInternalAccountFromAddressAndType).call(this, added.address, added.type);
533
+ if (account) {
534
+ // Re-compute the list of accounts everytime, so we can make sure new names
535
+ // are also considered.
536
+ const accounts = Object.values(internalAccounts.accounts);
537
+ // Get next account name available for this given keyring.
538
+ const name = this.getNextAvailableAccountName(account.metadata.keyring.type, accounts);
539
+ // If it's the first account, we need to select it.
540
+ const lastSelected = accounts.length === 0 ? __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this) : 0;
541
+ internalAccounts.accounts[account.id] = {
542
+ ...account,
543
+ metadata: {
544
+ ...account.metadata,
545
+ name,
546
+ importTime: Date.now(),
547
+ lastSelected,
548
+ },
549
+ };
550
+ diff.added.push(internalAccounts.accounts[account.id]);
551
+ }
481
552
  }
482
553
  }
483
- this.update((currentState) => {
484
- if (deletedAccounts.length > 0) {
485
- for (const account of deletedAccounts) {
486
- currentState.internalAccounts.accounts = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_handleAccountRemoved).call(this, currentState.internalAccounts.accounts, account.id);
487
- }
554
+ });
555
+ // Now publish events
556
+ for (const id of diff.removed) {
557
+ this.messagingSystem.publish('AccountsController:accountRemoved', id);
558
+ }
559
+ for (const account of diff.added) {
560
+ this.messagingSystem.publish('AccountsController:accountAdded', account);
561
+ }
562
+ // NOTE: Since we also track "updated" accounts with our patches, we could fire a new event
563
+ // like `accountUpdated` (we would still need to check if anything really changed on the account).
564
+ }, _AccountsController_update = function _AccountsController_update(callback) {
565
+ // The currently selected account might get deleted during the update, so keep track
566
+ // of it before doing any change.
567
+ const previouslySelectedAccount = this.state.internalAccounts.selectedAccount;
568
+ this.update((state) => {
569
+ callback(state);
570
+ // If the account no longer exists (or none is selected), we need to re-select another one.
571
+ const { internalAccounts } = state;
572
+ if (!internalAccounts.accounts[previouslySelectedAccount]) {
573
+ const accounts = Object.values(internalAccounts.accounts);
574
+ // Get the lastly selected account (according to the current accounts).
575
+ const lastSelectedAccount = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedAccount).call(this, accounts);
576
+ if (lastSelectedAccount) {
577
+ internalAccounts.selectedAccount = lastSelectedAccount.id;
578
+ internalAccounts.accounts[lastSelectedAccount.id].metadata.lastSelected = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this);
488
579
  }
489
- if (addedAccounts.length > 0) {
490
- for (const account of addedAccounts) {
491
- currentState.internalAccounts.accounts =
492
- __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_handleNewAccountAdded).call(this, currentState.internalAccounts.accounts, account);
493
- }
580
+ else {
581
+ // It will be undefined if there are no accounts.
582
+ internalAccounts.selectedAccount = '';
494
583
  }
495
- // We don't use list accounts because it is not the updated state yet.
496
- const existingAccounts = Object.values(currentState.internalAccounts.accounts);
497
- // handle if the selected account was deleted
498
- if (!currentState.internalAccounts.accounts[this.state.internalAccounts.selectedAccount]) {
499
- const lastSelectedAccount = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedAccount).call(this, existingAccounts);
500
- if (lastSelectedAccount) {
501
- currentState.internalAccounts.selectedAccount =
502
- lastSelectedAccount.id;
503
- currentState.internalAccounts.accounts[lastSelectedAccount.id].metadata.lastSelected = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this);
504
- __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_publishAccountChangeEvent).call(this, lastSelectedAccount);
505
- }
506
- else {
507
- // It will be undefined if there are no accounts
508
- currentState.internalAccounts.selectedAccount = '';
509
- }
584
+ }
585
+ });
586
+ // Now, we compare the newly selected account, and we send event if different.
587
+ const { selectedAccount } = this.state.internalAccounts;
588
+ if (selectedAccount && selectedAccount !== previouslySelectedAccount) {
589
+ const account = this.getSelectedMultichainAccount();
590
+ // The account should always be defined at this point, since we have already checked for
591
+ // `selectedAccount` to be non-empty.
592
+ if (account) {
593
+ if ((0, keyring_api_1.isEvmAccountType)(account.type)) {
594
+ this.messagingSystem.publish('AccountsController:selectedEvmAccountChange', account);
510
595
  }
511
- });
596
+ this.messagingSystem.publish('AccountsController:selectedAccountChange', account);
597
+ }
512
598
  }
513
599
  }, _AccountsController_handleOnSnapStateChange = function _AccountsController_handleOnSnapStateChange(snapState) {
514
600
  // only check if snaps changed in status
515
601
  const { snaps } = snapState;
516
- const accounts = this.listMultichainAccounts().filter((account) => account.metadata.snap);
517
- this.update((currentState) => {
518
- accounts.forEach((account) => {
519
- const currentAccount = currentState.internalAccounts.accounts[account.id];
520
- if (currentAccount.metadata.snap) {
521
- const snapId = currentAccount.metadata.snap.id;
522
- const storedSnap = snaps[snapId];
523
- if (storedSnap) {
524
- currentAccount.metadata.snap.enabled =
525
- storedSnap.enabled && !storedSnap.blocked;
602
+ const accounts = [];
603
+ for (const account of this.listMultichainAccounts()) {
604
+ if (account.metadata.snap) {
605
+ const snap = snaps[account.metadata.snap.id];
606
+ const enabled = snap.enabled && !snap.blocked;
607
+ const metadata = account.metadata.snap;
608
+ if (metadata.enabled !== enabled) {
609
+ accounts.push({ id: account.id, enabled });
610
+ }
611
+ }
612
+ }
613
+ if (accounts.length > 0) {
614
+ this.update((state) => {
615
+ for (const { id, enabled } of accounts) {
616
+ const account = state.internalAccounts.accounts[id];
617
+ if (account.metadata.snap) {
618
+ account.metadata.snap.enabled = enabled;
526
619
  }
527
620
  }
528
621
  });
529
- });
622
+ }
530
623
  }, _AccountsController_getAccountsByKeyringType = function _AccountsController_getAccountsByKeyringType(keyringType, accounts) {
531
624
  return (accounts ?? this.listMultichainAccounts()).filter((internalAccount) => {
532
625
  // We do consider `hd` and `simple` keyrings to be of same type. So we check those 2 types
@@ -549,43 +642,18 @@ async function _AccountsController_listNormalAccounts() {
549
642
  // NOTE: For now we use the current date, since we know this value
550
643
  // will always be higher than any already selected account index.
551
644
  return Date.now();
552
- }, _AccountsController_handleNewAccountAdded = function _AccountsController_handleNewAccountAdded(accountsState, account) {
553
- let newAccount;
554
- if (account.type !== keyring_controller_1.KeyringTypes.snap) {
555
- newAccount = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_generateInternalAccountForNonSnapAccount).call(this, account.address, account.type);
556
- }
557
- else {
558
- const [snapKeyring] = this.messagingSystem.call('KeyringController:getKeyringsByType', eth_snap_keyring_1.SnapKeyring.type);
559
- newAccount = snapKeyring.getAccountByAddress(account.address);
560
- // The snap deleted the account before the keyring controller could add it
561
- if (!newAccount) {
562
- return accountsState;
645
+ }, _AccountsController_getInternalAccountFromAddressAndType = function _AccountsController_getInternalAccountFromAddressAndType(address, type) {
646
+ if (type === keyring_controller_1.KeyringTypes.snap) {
647
+ const keyring = __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getSnapKeyring).call(this);
648
+ // We need the Snap keyring to retrieve the account from its address.
649
+ if (!keyring) {
650
+ return undefined;
563
651
  }
652
+ // This might be undefined if the Snap deleted the account before
653
+ // reaching that point.
654
+ return keyring.getAccountByAddress(address);
564
655
  }
565
- const isFirstAccount = Object.keys(accountsState).length === 0;
566
- // Get next account name available for this given keyring
567
- const accountName = this.getNextAvailableAccountName(newAccount.metadata.keyring.type, Object.values(accountsState));
568
- const newAccountWithUpdatedMetadata = {
569
- ...newAccount,
570
- metadata: {
571
- ...newAccount.metadata,
572
- name: accountName,
573
- importTime: Date.now(),
574
- lastSelected: isFirstAccount ? __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_getLastSelectedIndex).call(this) : 0,
575
- },
576
- };
577
- accountsState[newAccount.id] = newAccountWithUpdatedMetadata;
578
- this.messagingSystem.publish('AccountsController:accountAdded', newAccountWithUpdatedMetadata);
579
- return accountsState;
580
- }, _AccountsController_publishAccountChangeEvent = function _AccountsController_publishAccountChangeEvent(account) {
581
- if ((0, keyring_api_1.isEvmAccountType)(account.type)) {
582
- this.messagingSystem.publish('AccountsController:selectedEvmAccountChange', account);
583
- }
584
- this.messagingSystem.publish('AccountsController:selectedAccountChange', account);
585
- }, _AccountsController_handleAccountRemoved = function _AccountsController_handleAccountRemoved(accountsState, accountId) {
586
- delete accountsState[accountId];
587
- this.messagingSystem.publish('AccountsController:accountRemoved', accountId);
588
- return accountsState;
656
+ return __classPrivateFieldGet(this, _AccountsController_instances, "m", _AccountsController_generateInternalAccountForNonSnapAccount).call(this, address, type);
589
657
  }, _AccountsController_handleOnMultichainNetworkDidChange = function _AccountsController_handleOnMultichainNetworkDidChange(id) {
590
658
  let accountId;
591
659
  // We only support non-EVM Caip chain IDs at the moment. Ex Solana and Bitcoin
@@ -623,6 +691,7 @@ async function _AccountsController_listNormalAccounts() {
623
691
  this.messagingSystem.registerActionHandler(`${controllerName}:listAccounts`, this.listAccounts.bind(this));
624
692
  this.messagingSystem.registerActionHandler(`${controllerName}:listMultichainAccounts`, this.listMultichainAccounts.bind(this));
625
693
  this.messagingSystem.registerActionHandler(`${controllerName}:setAccountName`, this.setAccountName.bind(this));
694
+ this.messagingSystem.registerActionHandler(`${controllerName}:setAccountNameAndSelectAccount`, this.setAccountNameAndSelectAccount.bind(this));
626
695
  this.messagingSystem.registerActionHandler(`${controllerName}:updateAccounts`, this.updateAccounts.bind(this));
627
696
  this.messagingSystem.registerActionHandler(`${controllerName}:getSelectedAccount`, this.getSelectedAccount.bind(this));
628
697
  this.messagingSystem.registerActionHandler(`${controllerName}:getSelectedMultichainAccount`, this.getSelectedMultichainAccount.bind(this));