@metamask-previews/account-tree-controller 1.0.0-preview-8ccfcb37 → 1.3.0-preview-a5441fae

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
@@ -9,11 +9,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
- - Add new group naming for non-HD keyring accounts ([#6679](https://github.com/MetaMask/core/pull/6679))
12
+ - Bump `@metamask/utils` from `^11.8.0` to `^11.8.1` ([#6708](https://github.com/MetaMask/core/pull/6708))
13
+
14
+ ## [1.3.0]
15
+
16
+ ### Changed
17
+
18
+ - Add more internal logs ([#6730](https://github.com/MetaMask/core/pull/6730))
19
+
20
+ ### Fixed
21
+
22
+ - Preverve import time for account groups ([#6727](https://github.com/MetaMask/core/pull/6727))
23
+ - We now wait sort accounts by their `importTime` before re-building the tree.
24
+ - Prevent `:account{Added,Removed}` to be used if `init` has not been called yet ([#6717](https://github.com/MetaMask/core/pull/6717))
25
+ - We now wait for `init` to have been called at least once. Clients will need to ensure internal accounts are fully ready before calling `init`.
26
+ - This should also enforce account group ordering, since all accounts will be ready to consume right away.
27
+
28
+ ## [1.2.0]
29
+
30
+ ### Added
31
+
32
+ - Add `reinit` method ([#6709](https://github.com/MetaMask/core/pull/6709))
33
+ - This method can be used if we change the entire list of accounts of the `AccountsController` and want to re-initilize the tree with it.
34
+
35
+ ### Changed
36
+
37
+ - Implicitly call `init` before mutating the tree ([#6709](https://github.com/MetaMask/core/pull/6709))
38
+ - This ensure the tree is always using existing accounts before inserting/removing any new accounts if `init` has not been called yet.
39
+
40
+ ### Fixed
41
+
42
+ - Fix use of unknown `group.metadata.name` when checking for group name uniqueness ([#6706](https://github.com/MetaMask/core/pull/6706))
43
+ - Added logic that prevents an account within a group from being out of order ([#6683](https://github.com/MetaMask/core/pull/6683))
44
+
45
+ ## [1.1.0]
46
+
47
+ ### Changed
48
+
49
+ - Set the `setAccountGroupName`'s option `autoHandleConflict` to `true` for all backup & sync operations ([#6697](https://github.com/MetaMask/core/pull/6697))
50
+ - Add new group naming for non-HD keyring accounts ([#6679](https://github.com/MetaMask/core/pull/6679)), ([#6696](https://github.com/MetaMask/core/pull/6696))
13
51
  - Hardware-wallet account groups are now named: "Ledger|Trezor|QR|Lattice|OneKey Account N".
14
52
  - Private key account groups are now named: "Imported Account N".
15
53
  - Snap account groups are now named: "Snap Account N".
16
- - Account group names now use natural indexing as a fallback ([#6677](https://github.com/MetaMask/core/pull/6677)), ([#6679](https://github.com/MetaMask/core/pull/6679))
54
+ - Account group names now use natural indexing as a fallback ([#6677](https://github.com/MetaMask/core/pull/6677)), ([#6679](https://github.com/MetaMask/core/pull/6679)), ([#6696](https://github.com/MetaMask/core/pull/6696))
17
55
  - If a user names his accounts without any indexes, we would just use the number of accounts to compute the next available index.
18
56
 
19
57
  ### Fixed
@@ -303,7 +341,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
303
341
  - Initial release ([#5847](https://github.com/MetaMask/core/pull/5847))
304
342
  - Grouping accounts into 3 main categories: Entropy source, Snap ID, keyring types.
305
343
 
306
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@1.0.0...HEAD
344
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@1.3.0...HEAD
345
+ [1.3.0]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@1.2.0...@metamask/account-tree-controller@1.3.0
346
+ [1.2.0]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@1.1.0...@metamask/account-tree-controller@1.2.0
347
+ [1.1.0]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@1.0.0...@metamask/account-tree-controller@1.1.0
307
348
  [1.0.0]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@0.18.1...@metamask/account-tree-controller@1.0.0
308
349
  [0.18.1]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@0.18.0...@metamask/account-tree-controller@0.18.1
309
350
  [0.18.0]: https://github.com/MetaMask/core/compare/@metamask/account-tree-controller@0.17.0...@metamask/account-tree-controller@0.18.0
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _AccountTreeController_instances, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_backupAndSyncService, _AccountTreeController_rules, _AccountTreeController_trace, _AccountTreeController_backupAndSyncConfig, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getRuleForWallet, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_assertAccountGroupNameIsUnique, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_handleMultichainAccountWalletStatusChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers, _AccountTreeController_createBackupAndSyncContext;
13
+ var _AccountTreeController_instances, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_backupAndSyncService, _AccountTreeController_rules, _AccountTreeController_trace, _AccountTreeController_backupAndSyncConfig, _AccountTreeController_initialized, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getRuleForWallet, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_assertAccountGroupNameIsUnique, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleMultichainAccountWalletStatusChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers, _AccountTreeController_createBackupAndSyncContext;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.AccountTreeController = exports.getDefaultAccountTreeControllerState = exports.controllerName = void 0;
16
16
  const account_api_1 = require("@metamask/account-api");
@@ -20,6 +20,7 @@ const utils_1 = require("@metamask/utils");
20
20
  const analytics_1 = require("./backup-and-sync/analytics/index.cjs");
21
21
  const service_1 = require("./backup-and-sync/service/index.cjs");
22
22
  const group_1 = require("./group.cjs");
23
+ const logger_1 = require("./logger.cjs");
23
24
  const entropy_1 = require("./rules/entropy.cjs");
24
25
  const keyring_1 = require("./rules/keyring.cjs");
25
26
  const snap_1 = require("./rules/snap.cjs");
@@ -103,6 +104,9 @@ class AccountTreeController extends base_controller_1.BaseController {
103
104
  _AccountTreeController_rules.set(this, void 0);
104
105
  _AccountTreeController_trace.set(this, void 0);
105
106
  _AccountTreeController_backupAndSyncConfig.set(this, void 0);
107
+ _AccountTreeController_initialized.set(this, void 0);
108
+ // This will be set to true upon the first `init` call.
109
+ __classPrivateFieldSet(this, _AccountTreeController_initialized, false, "f");
106
110
  // Reverse map to allow fast node access from an account ID.
107
111
  __classPrivateFieldSet(this, _AccountTreeController_accountIdToContext, new Map(), "f");
108
112
  // Reverse map to allow fast wallet node access from a group ID.
@@ -133,9 +137,6 @@ class AccountTreeController extends base_controller_1.BaseController {
133
137
  this.messagingSystem.subscribe('AccountsController:accountRemoved', (accountId) => {
134
138
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_handleAccountRemoved).call(this, accountId);
135
139
  });
136
- this.messagingSystem.subscribe('AccountsController:selectedAccountChange', (account) => {
137
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_handleSelectedAccountChange).call(this, account);
138
- });
139
140
  this.messagingSystem.subscribe('UserStorageController:stateChange', (userStorageControllerState) => {
140
141
  __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").handleUserStorageStateChange(userStorageControllerState);
141
142
  });
@@ -152,6 +153,12 @@ class AccountTreeController extends base_controller_1.BaseController {
152
153
  * state with it.
153
154
  */
154
155
  init() {
156
+ if (__classPrivateFieldGet(this, _AccountTreeController_initialized, "f")) {
157
+ // We prevent re-initilializing the state multiple times. Though, we can use
158
+ // `reinit` to re-init everything from scratch.
159
+ return;
160
+ }
161
+ (0, logger_1.projectLogger)('Initializing...');
155
162
  const wallets = {};
156
163
  // Clear mappings for fresh rebuild.
157
164
  __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").clear();
@@ -159,8 +166,20 @@ class AccountTreeController extends base_controller_1.BaseController {
159
166
  // Keep the current selected group to check if it's still part of the tree
160
167
  // after rebuilding it.
161
168
  const previousSelectedAccountGroup = this.state.accountTree.selectedAccountGroup;
169
+ // There's no guarantee that accounts would be sorted by their import time
170
+ // with `listMultichainAccounts`. We have to sort them here before constructing
171
+ // the tree.
172
+ //
173
+ // Because of the alignment mecanism, some accounts from the same group might not
174
+ // have been imported at the same time, but at least of them should have been
175
+ // imported at the right time, thus, inserting the group at the proper place too.
176
+ //
177
+ // Lastly, if one day we allow to have "gaps" in between groups, then this `sort`
178
+ // won't be enough and we would have to use group properties instead (like group
179
+ // index or maybe introduce a `importTime` at group level).
180
+ const accounts = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_listAccounts).call(this).sort((a, b) => a.metadata.importTime - b.metadata.importTime);
162
181
  // For now, we always re-compute all wallets, we do not re-use the existing state.
163
- for (const account of __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_listAccounts).call(this)) {
182
+ for (const account of accounts) {
164
183
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insert).call(this, wallets, account);
165
184
  }
166
185
  // Once we have the account tree, we can apply persisted metadata (names + UI states).
@@ -203,8 +222,22 @@ class AccountTreeController extends base_controller_1.BaseController {
203
222
  // case, no need to fire any event.
204
223
  if (previousSelectedAccountGroup !==
205
224
  this.state.accountTree.selectedAccountGroup) {
225
+ (0, logger_1.projectLogger)(`Selected (initial) group is: [${this.state.accountTree.selectedAccountGroup}]`);
206
226
  this.messagingSystem.publish(`${exports.controllerName}:selectedAccountGroupChange`, this.state.accountTree.selectedAccountGroup, previousSelectedAccountGroup);
207
227
  }
228
+ (0, logger_1.projectLogger)('Initialized!');
229
+ __classPrivateFieldSet(this, _AccountTreeController_initialized, true, "f");
230
+ }
231
+ /**
232
+ * Re-initialize the controller's state.
233
+ *
234
+ * This is done in one single (atomic) `update` block to avoid having a temporary
235
+ * cleared state. Use this when you need to force a full re-init even if already initialized.
236
+ */
237
+ reinit() {
238
+ (0, logger_1.projectLogger)('Re-initializing...');
239
+ __classPrivateFieldSet(this, _AccountTreeController_initialized, false, "f");
240
+ this.init();
208
241
  }
209
242
  /**
210
243
  * Gets the account wallet object from its ID.
@@ -302,6 +335,7 @@ class AccountTreeController extends base_controller_1.BaseController {
302
335
  this.update((state) => {
303
336
  state.accountTree.selectedAccountGroup = groupId;
304
337
  });
338
+ (0, logger_1.projectLogger)(`Selected group is now: [${this.state.accountTree.selectedAccountGroup}]`);
305
339
  this.messagingSystem.publish(`${exports.controllerName}:selectedAccountGroupChange`, groupId, previousSelectedAccountGroup);
306
340
  // Update AccountsController - this will trigger selectedAccountChange event,
307
341
  // but our handler is idempotent so it won't cause infinite loop
@@ -351,6 +385,7 @@ class AccountTreeController extends base_controller_1.BaseController {
351
385
  // Validate that the name is unique
352
386
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_assertAccountGroupNameIsUnique).call(this, groupId, finalName);
353
387
  }
388
+ (0, logger_1.projectLogger)(`[${groupId}] Set new name to: "${finalName}" (auto handle conflict: ${autoHandleConflict})`);
354
389
  this.update((state) => {
355
390
  /* istanbul ignore next */
356
391
  if (!state.accountGroupsMetadata[groupId]) {
@@ -470,12 +505,15 @@ class AccountTreeController extends base_controller_1.BaseController {
470
505
  * Also clears the backup and sync service state.
471
506
  */
472
507
  clearState() {
508
+ (0, logger_1.projectLogger)('Clearing state');
473
509
  this.update(() => {
474
510
  return {
475
511
  ...getDefaultAccountTreeControllerState(),
476
512
  };
477
513
  });
478
514
  __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").clearState();
515
+ // So we know we have to call `init` again.
516
+ __classPrivateFieldSet(this, _AccountTreeController_initialized, false, "f");
479
517
  }
480
518
  /**
481
519
  * Bi-directionally syncs the account tree with user storage.
@@ -508,7 +546,7 @@ class AccountTreeController extends base_controller_1.BaseController {
508
546
  }
509
547
  }
510
548
  exports.AccountTreeController = AccountTreeController;
511
- _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_backupAndSyncService = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_trace = new WeakMap(), _AccountTreeController_backupAndSyncConfig = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
549
+ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_backupAndSyncService = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_trace = new WeakMap(), _AccountTreeController_backupAndSyncConfig = new WeakMap(), _AccountTreeController_initialized = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
512
550
  return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[0];
513
551
  }, _AccountTreeController_getSnapRule = function _AccountTreeController_getSnapRule() {
514
552
  return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[1];
@@ -535,6 +573,7 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
535
573
  wallet.metadata.name =
536
574
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountWalletName(wallet);
537
575
  }
576
+ (0, logger_1.projectLogger)(`[${wallet.id}] Set default name to: "${wallet.metadata.name}"`);
538
577
  }
539
578
  }, _AccountTreeController_getRuleForWallet = function _AccountTreeController_getRuleForWallet(wallet) {
540
579
  switch (wallet.type) {
@@ -546,7 +585,7 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
546
585
  return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this);
547
586
  }
548
587
  }, _AccountTreeController_applyAccountGroupMetadata = function _AccountTreeController_applyAccountGroupMetadata(state, walletId, groupId, nextNaturalNameIndex) {
549
- var _a, _b;
588
+ var _a;
550
589
  const wallet = state.accountTree.wallets[walletId];
551
590
  const group = wallet.groups[groupId];
552
591
  const persistedGroupMetadata = state.accountGroupsMetadata[group.id];
@@ -564,13 +603,17 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
564
603
  // TODO: Implement computed names in a future iteration
565
604
  // Parse the highest account index being used (similar to accounts-controller)
566
605
  let highestNameIndex = 0;
567
- for (const existingGroup of Object.values(wallet.groups)) {
606
+ for (const { id: otherGroupId } of Object.values(wallet.groups)) {
568
607
  // Skip the current group being processed
569
- if (existingGroup.id === group.id) {
608
+ if (otherGroupId === groupId) {
570
609
  continue;
571
610
  }
611
+ // We always get the name from the persisted map, since `init` will clear the
612
+ // `state.accountTree.wallets`, thus, given empty `group.metadata.name`.
613
+ // NOTE: If the other group has not been named yet, we just use an empty name.
614
+ const otherGroupName = state.accountGroupsMetadata[otherGroupId]?.name?.value ?? '';
572
615
  // Parse the existing group name to extract the numeric index
573
- const nameMatch = existingGroup.metadata.name.match(/Account (\d+)$/u);
616
+ const nameMatch = otherGroupName.match(/account\s+(\d+)$/iu);
574
617
  if (nameMatch) {
575
618
  const nameIndex = parseInt(nameMatch[1], 10);
576
619
  if (nameIndex > highestNameIndex) {
@@ -610,9 +653,10 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
610
653
  } while (proposedNameExists);
611
654
  state.accountTree.wallets[walletId].groups[groupId].metadata.name =
612
655
  proposedName;
656
+ (0, logger_1.projectLogger)(`[${group.id}] Set default name to: "${group.metadata.name}"`);
613
657
  // Persist the generated name to ensure consistency
614
- (_a = state.accountGroupsMetadata)[_b = group.id] ?? (_a[_b] = {});
615
- state.accountGroupsMetadata[group.id].name = {
658
+ (_a = state.accountGroupsMetadata)[groupId] ?? (_a[groupId] = {});
659
+ state.accountGroupsMetadata[groupId].name = {
616
660
  value: proposedName,
617
661
  // The `lastUpdatedAt` field is used for backup and sync, when comparing local names
618
662
  // with backed up names. In this case, the generated name should never take precedence
@@ -628,20 +672,35 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
628
672
  group.metadata.hidden = persistedGroupMetadata.hidden.value;
629
673
  }
630
674
  }, _AccountTreeController_handleAccountAdded = function _AccountTreeController_handleAccountAdded(account) {
631
- this.update((state) => {
632
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insert).call(this, state.accountTree.wallets, account);
633
- const context = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(account.id);
634
- if (context) {
635
- const { walletId, groupId } = context;
636
- const wallet = state.accountTree.wallets[walletId];
637
- if (wallet) {
638
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountWalletMetadata).call(this, state, walletId);
639
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountGroupMetadata).call(this, state, walletId, groupId);
675
+ // We wait for the first `init` to be called to actually build up the tree and
676
+ // mutate it. We expect the caller to first update the `AccountsController` state
677
+ // to force the migration of accounts, and then call `init`.
678
+ if (!__classPrivateFieldGet(this, _AccountTreeController_initialized, "f")) {
679
+ return;
680
+ }
681
+ // Check if this account is already known by the tree to avoid double-insertion.
682
+ if (!__classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").has(account.id)) {
683
+ this.update((state) => {
684
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insert).call(this, state.accountTree.wallets, account);
685
+ const context = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(account.id);
686
+ if (context) {
687
+ const { walletId, groupId } = context;
688
+ const wallet = state.accountTree.wallets[walletId];
689
+ if (wallet) {
690
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountWalletMetadata).call(this, state, walletId);
691
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountGroupMetadata).call(this, state, walletId, groupId);
692
+ }
640
693
  }
641
- }
642
- });
643
- this.messagingSystem.publish(`${exports.controllerName}:accountTreeChange`, this.state.accountTree);
694
+ });
695
+ this.messagingSystem.publish(`${exports.controllerName}:accountTreeChange`, this.state.accountTree);
696
+ }
644
697
  }, _AccountTreeController_handleAccountRemoved = function _AccountTreeController_handleAccountRemoved(accountId) {
698
+ // We wait for the first `init` to be called to actually build up the tree and
699
+ // mutate it. We expect the caller to first update the `AccountsController` state
700
+ // to force the migration of accounts, and then call `init`.
701
+ if (!__classPrivateFieldGet(this, _AccountTreeController_initialized, "f")) {
702
+ return;
703
+ }
645
704
  const context = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(accountId);
646
705
  if (context) {
647
706
  const { walletId, groupId } = context;
@@ -694,6 +753,7 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
694
753
  const walletId = result.wallet.id;
695
754
  let wallet = wallets[walletId];
696
755
  if (!wallet) {
756
+ (0, logger_1.projectLogger)(`[${walletId}] Added as new wallet`);
697
757
  wallets[walletId] = {
698
758
  ...result.wallet,
699
759
  status: 'ready',
@@ -713,11 +773,14 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
713
773
  }
714
774
  const groupId = result.group.id;
715
775
  let group = wallet.groups[groupId];
776
+ const { type, id } = account;
777
+ const sortOrder = group_1.ACCOUNT_TYPE_TO_SORT_ORDER[type];
716
778
  if (!group) {
779
+ (0, logger_1.projectLogger)(`[${walletId}] Add new group: [${groupId}]`);
717
780
  wallet.groups[groupId] = {
718
781
  ...result.group,
719
782
  // Type-wise, we are guaranteed to always have at least 1 account.
720
- accounts: [account.id],
783
+ accounts: [id],
721
784
  metadata: {
722
785
  name: '',
723
786
  ...{ pinned: false, hidden: false },
@@ -735,12 +798,29 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
735
798
  }
736
799
  }
737
800
  else {
738
- group.accounts.push(account.id);
801
+ group.accounts.push(id);
802
+ // We need to do this at every insertion because race conditions can happen
803
+ // during the account creation process where one provider completes before the other.
804
+ // The discovery process in the service can also lead to some accounts being created "out of order".
805
+ const { accounts } = group;
806
+ accounts.sort(
807
+ /* istanbul ignore next: Comparator branch execution (a===id vs b===id)
808
+ * and return attribution vary across engines; final ordering is covered
809
+ * by behavior tests. Ignoring the entire comparator avoids flaky line
810
+ * coverage without reducing scenario coverage.
811
+ */
812
+ (a, b) => {
813
+ const aSortOrder = a === id ? sortOrder : __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(a)?.sortOrder;
814
+ const bSortOrder = b === id ? sortOrder : __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(b)?.sortOrder;
815
+ return ((aSortOrder ?? group_1.MAX_SORT_ORDER) - (bSortOrder ?? group_1.MAX_SORT_ORDER));
816
+ });
739
817
  }
818
+ (0, logger_1.projectLogger)(`[${groupId}] Add new account: { id: "${account.id}", type: "${account.type}", address: "${account.address}"`);
740
819
  // Update the reverse mapping for this account.
741
820
  __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").set(account.id, {
742
821
  walletId: wallet.id,
743
822
  groupId: group.id,
823
+ sortOrder,
744
824
  });
745
825
  }, _AccountTreeController_listAccounts = function _AccountTreeController_listAccounts() {
746
826
  return this.messagingSystem.call('AccountsController:listMultichainAccounts');
@@ -769,23 +849,6 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
769
849
  }
770
850
  // Default to the default group in case of errors.
771
851
  return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getDefaultAccountGroupId).call(this, wallets);
772
- }, _AccountTreeController_handleSelectedAccountChange = function _AccountTreeController_handleSelectedAccountChange(account) {
773
- const accountMapping = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(account.id);
774
- if (!accountMapping) {
775
- // Account not in tree yet, might be during initialization
776
- return;
777
- }
778
- const { groupId } = accountMapping;
779
- const previousSelectedAccountGroup = this.state.accountTree.selectedAccountGroup;
780
- // Idempotent check - if the same group is already selected, do nothing
781
- if (previousSelectedAccountGroup === groupId) {
782
- return;
783
- }
784
- // Update selectedAccountGroup to match the selected account
785
- this.update((state) => {
786
- state.accountTree.selectedAccountGroup = groupId;
787
- });
788
- this.messagingSystem.publish(`${exports.controllerName}:selectedAccountGroupChange`, groupId, previousSelectedAccountGroup);
789
852
  }, _AccountTreeController_handleMultichainAccountWalletStatusChange = function _AccountTreeController_handleMultichainAccountWalletStatusChange(walletId, walletStatus) {
790
853
  this.update((state) => {
791
854
  const wallet = state.accountTree.wallets[walletId];