@metamask-previews/account-tree-controller 2.0.0-preview-d74d2f9f → 2.0.0-preview-3d9bbf60

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.
@@ -9,8 +9,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  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");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _AccountTreeController_instances, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_backupAndSyncService, _AccountTreeController_rules, _AccountTreeController_trace, _AccountTreeController_backupAndSyncConfig, _AccountTreeController_accountOrderCallbacks, _AccountTreeController_initialized, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getRuleForWallet, _AccountTreeController_getComputedAccountGroupName, _AccountTreeController_getDefaultAccountGroupName, _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
- import { AccountWalletType, select } from "@metamask/account-api";
12
+ var _AccountTreeController_instances, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_backupAndSyncService, _AccountTreeController_rules, _AccountTreeController_trace, _AccountTreeController_backupAndSyncConfig, _AccountTreeController_accountOrderCallbacks, _AccountTreeController_initialized, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_getDefaultAccountWalletName, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getComputedAccountGroupName, _AccountTreeController_getDefaultAccountGroupPrefix, _AccountTreeController_getDefaultAccountGroupName, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_sortAccountsOfGroup, _AccountTreeController_insertAccount, _AccountTreeController_insertOrUpdateMultichainAccountWalletAndGroup, _AccountTreeController_getMultichainAccountWallets, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_assertAccountGroupNameIsUnique, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_handleMultichainAccountGroupCreatedOrUpdated, _AccountTreeController_handleMultichainAccountWalletStatusChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers, _AccountTreeController_createBackupAndSyncContext;
13
+ import { AccountWalletType, isBip44Account, select } from "@metamask/account-api";
14
+ import { AccountGroupType } from "@metamask/account-api";
14
15
  import { BaseController } from "@metamask/base-controller";
15
16
  import { isEvmAccountType } from "@metamask/keyring-api";
16
17
  import { assert } from "@metamask/utils";
@@ -18,7 +19,7 @@ import { formatAnalyticsEvent, traceFallback } from "./backup-and-sync/analytics
18
19
  import { BackupAndSyncService } from "./backup-and-sync/service/index.mjs";
19
20
  import { ACCOUNT_TYPE_TO_SORT_ORDER, isAccountGroupNameUnique, isAccountGroupNameUniqueFromWallet, MAX_SORT_ORDER } from "./group.mjs";
20
21
  import { projectLogger as log } from "./logger.mjs";
21
- import { EntropyRule } from "./rules/entropy.mjs";
22
+ import { getEntropyDefaultAccountGroupPrefix, getEntropyDefaultAccountWalletName } from "./rules/entropy.mjs";
22
23
  import { KeyringRule } from "./rules/keyring.mjs";
23
24
  import { SnapRule } from "./rules/snap.mjs";
24
25
  export const controllerName = 'AccountTreeController';
@@ -114,7 +115,7 @@ export class AccountTreeController extends BaseController {
114
115
  // Rules to apply to construct the wallets tree.
115
116
  __classPrivateFieldSet(this, _AccountTreeController_rules, [
116
117
  // 1. We group by entropy-source
117
- new EntropyRule(this.messenger),
118
+ // NOTE: This is now done by consuming the `MultichainAccountService` wallets and groups.
118
119
  // 2. We group by Snap ID
119
120
  new SnapRule(this.messenger),
120
121
  // 3. We group by wallet type (this rule cannot fail and will group all non-matching accounts)
@@ -145,6 +146,12 @@ export class AccountTreeController extends BaseController {
145
146
  this.messenger.subscribe('UserStorageController:stateChange', (userStorageControllerState) => {
146
147
  __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").handleUserStorageStateChange(userStorageControllerState);
147
148
  });
149
+ this.messenger.subscribe('MultichainAccountService:multichainAccountGroupCreated', (group) => {
150
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_handleMultichainAccountGroupCreatedOrUpdated).call(this, group);
151
+ });
152
+ this.messenger.subscribe('MultichainAccountService:multichainAccountGroupUpdated', (group) => {
153
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_handleMultichainAccountGroupCreatedOrUpdated).call(this, group);
154
+ });
148
155
  this.messenger.subscribe('MultichainAccountService:walletStatusChange', (walletId, status) => {
149
156
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_handleMultichainAccountWalletStatusChange).call(this, walletId, status);
150
157
  });
@@ -164,6 +171,7 @@ export class AccountTreeController extends BaseController {
164
171
  return;
165
172
  }
166
173
  log('Initializing...');
174
+ // For now, we always re-compute all wallets, we do not re-use the existing state.
167
175
  const wallets = {};
168
176
  // Clear mappings for fresh rebuild.
169
177
  __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").clear();
@@ -171,6 +179,12 @@ export class AccountTreeController extends BaseController {
171
179
  // Keep the current selected group to check if it's still part of the tree
172
180
  // after rebuilding it.
173
181
  const previousSelectedAccountGroup = this.state.accountTree.selectedAccountGroup;
182
+ // Insert all multichain account wallet/groups.
183
+ for (const wallet of __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getMultichainAccountWallets).call(this)) {
184
+ for (const group of wallet.getMultichainAccountGroups()) {
185
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insertOrUpdateMultichainAccountWalletAndGroup).call(this, wallets, wallet, group);
186
+ }
187
+ }
174
188
  // There's no guarantee that accounts would be sorted by their import time
175
189
  // with `listMultichainAccounts`. We have to sort them here before constructing
176
190
  // the tree.
@@ -183,9 +197,14 @@ export class AccountTreeController extends BaseController {
183
197
  // won't be enough and we would have to use group properties instead (like group
184
198
  // index or maybe introduce a `importTime` at group level).
185
199
  const accounts = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_listAccounts).call(this).sort((a, b) => a.metadata.importTime - b.metadata.importTime);
186
- // For now, we always re-compute all wallets, we do not re-use the existing state.
200
+ // Insert all other kind of accounts (private keys, HW, other Snap accounts, etc.).
187
201
  for (const account of accounts) {
188
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insert).call(this, wallets, account);
202
+ // BIP-44 accounts are owned byt the `MultichainAccountService`, so we have to skip them
203
+ // here, since we've already added wallets/groups for each of them.
204
+ if (isBip44Account(account)) {
205
+ continue;
206
+ }
207
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insertAccount).call(this, wallets, account);
189
208
  }
190
209
  // Once we have the account tree, we can apply persisted metadata (names + UI states).
191
210
  let previousSelectedAccountGroupStillExists = false;
@@ -556,12 +575,19 @@ export class AccountTreeController extends BaseController {
556
575
  return __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").performFullSyncAtLeastOnce();
557
576
  }
558
577
  }
559
- _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_accountOrderCallbacks = new WeakMap(), _AccountTreeController_initialized = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
578
+ _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_accountOrderCallbacks = new WeakMap(), _AccountTreeController_initialized = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getSnapRule = function _AccountTreeController_getSnapRule() {
560
579
  return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[0];
561
- }, _AccountTreeController_getSnapRule = function _AccountTreeController_getSnapRule() {
562
- return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[1];
563
580
  }, _AccountTreeController_getKeyringRule = function _AccountTreeController_getKeyringRule() {
564
- return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[2];
581
+ return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[1];
582
+ }, _AccountTreeController_getDefaultAccountWalletName = function _AccountTreeController_getDefaultAccountWalletName(wallet) {
583
+ switch (wallet.type) {
584
+ case AccountWalletType.Entropy:
585
+ return getEntropyDefaultAccountWalletName(this.messenger, wallet);
586
+ case AccountWalletType.Snap:
587
+ return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).getDefaultAccountWalletName(wallet);
588
+ default:
589
+ return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountWalletName(wallet);
590
+ }
565
591
  }, _AccountTreeController_applyAccountWalletMetadata = function _AccountTreeController_applyAccountWalletMetadata(state, walletId) {
566
592
  const wallet = state.accountTree.wallets[walletId];
567
593
  const persistedMetadata = state.accountWalletsMetadata[walletId];
@@ -571,29 +597,9 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
571
597
  }
572
598
  else if (!wallet.metadata.name) {
573
599
  // Generate default name if none exists
574
- if (wallet.type === AccountWalletType.Entropy) {
575
- wallet.metadata.name =
576
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this).getDefaultAccountWalletName(wallet);
577
- }
578
- else if (wallet.type === AccountWalletType.Snap) {
579
- wallet.metadata.name =
580
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).getDefaultAccountWalletName(wallet);
581
- }
582
- else {
583
- wallet.metadata.name =
584
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountWalletName(wallet);
585
- }
600
+ wallet.metadata.name = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getDefaultAccountWalletName).call(this, wallet);
586
601
  log(`[${wallet.id}] Set default name to: "${wallet.metadata.name}"`);
587
602
  }
588
- }, _AccountTreeController_getRuleForWallet = function _AccountTreeController_getRuleForWallet(wallet) {
589
- switch (wallet.type) {
590
- case AccountWalletType.Entropy:
591
- return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this);
592
- case AccountWalletType.Snap:
593
- return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this);
594
- default:
595
- return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this);
596
- }
597
603
  }, _AccountTreeController_getComputedAccountGroupName = function _AccountTreeController_getComputedAccountGroupName(wallet, group) {
598
604
  let proposedName = ''; // Empty means there's no computed name for this group.
599
605
  for (const id of group.accounts) {
@@ -618,11 +624,18 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
618
624
  proposedName = this.resolveNameConflict(wallet, group.id, proposedName);
619
625
  }
620
626
  return proposedName;
627
+ }, _AccountTreeController_getDefaultAccountGroupPrefix = function _AccountTreeController_getDefaultAccountGroupPrefix(wallet) {
628
+ switch (wallet.type) {
629
+ case AccountWalletType.Entropy:
630
+ return getEntropyDefaultAccountGroupPrefix();
631
+ case AccountWalletType.Snap:
632
+ return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).getDefaultAccountGroupPrefix(wallet);
633
+ default:
634
+ return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountGroupPrefix(wallet);
635
+ }
621
636
  }, _AccountTreeController_getDefaultAccountGroupName = function _AccountTreeController_getDefaultAccountGroupName(state, wallet, group, nextNaturalNameIndex) {
622
- // Get the appropriate rule for this wallet type
623
- const rule = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getRuleForWallet).call(this, wallet);
624
637
  // Get the prefix for groups of this wallet
625
- const namePrefix = rule.getDefaultAccountGroupPrefix(wallet);
638
+ const namePrefix = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getDefaultAccountGroupPrefix).call(this, wallet);
626
639
  // Parse the highest account index being used (similar to accounts-controller)
627
640
  let highestNameIndex = 0;
628
641
  for (const { id: otherGroupId } of Object.values(wallet.groups)) {
@@ -748,10 +761,15 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
748
761
  if (!__classPrivateFieldGet(this, _AccountTreeController_initialized, "f")) {
749
762
  return;
750
763
  }
764
+ if (isBip44Account(account)) {
765
+ // We're skipping BIP-44 accounts since we rely on the `MultichainAccountService` to do
766
+ // the grouping of wallets/groups directly.
767
+ return;
768
+ }
751
769
  // Check if this account is already known by the tree to avoid double-insertion.
752
770
  if (!__classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").has(account.id)) {
753
771
  this.update((state) => {
754
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insert).call(this, state.accountTree.wallets, account);
772
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insertAccount).call(this, state.accountTree.wallets, account);
755
773
  const context = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(account.id);
756
774
  if (context) {
757
775
  const { walletId, groupId } = context;
@@ -774,33 +792,40 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
774
792
  const context = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(accountId);
775
793
  if (context) {
776
794
  const { walletId, groupId } = context;
777
- const previousSelectedAccountGroup = this.state.accountTree.selectedAccountGroup;
778
- let selectedAccountGroupChanged = false;
779
- this.update((state) => {
780
- const accounts = state.accountTree.wallets[walletId]?.groups[groupId]?.accounts;
781
- if (accounts) {
782
- const index = accounts.indexOf(accountId);
783
- if (index !== -1) {
795
+ // If it's in the context, then `group` should be defined.
796
+ const group = this.state.accountTree.wallets[walletId]?.groups[groupId];
797
+ assert(group, 'Account group associated with a context cannot be undefined');
798
+ // We're only considering non-BIP-44 accounts for single account events.
799
+ if (group.type !== AccountGroupType.MultichainAccount) {
800
+ const index = group.accounts.indexOf(accountId);
801
+ if (index !== -1) {
802
+ let selectedAccountGroupChanged = false;
803
+ const previousSelectedAccountGroup = this.state.accountTree.selectedAccountGroup;
804
+ this.update((state) => {
805
+ const accounts = state.accountTree.wallets[walletId]?.groups[groupId].accounts;
806
+ // Effectively remove account from the group and state.
784
807
  accounts.splice(index, 1);
785
- // Check if we need to update selectedAccountGroup after removal
786
- if (state.accountTree.selectedAccountGroup === groupId &&
787
- accounts.length === 0) {
788
- // The currently selected group is now empty, find a new group to select
789
- const newSelectedAccountGroup = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getDefaultAccountGroupId).call(this, state.accountTree.wallets);
790
- state.accountTree.selectedAccountGroup = newSelectedAccountGroup;
791
- selectedAccountGroupChanged =
792
- newSelectedAccountGroup !== previousSelectedAccountGroup;
808
+ // If there's no more account, we have to prune the group and maybe select
809
+ // a new account group too.
810
+ if (accounts.length === 0) {
811
+ // Check if we need to update selectedAccountGroup after removal
812
+ if (state.accountTree.selectedAccountGroup === groupId) {
813
+ // The currently selected group is now empty, find a new group to select
814
+ const newSelectedAccountGroup = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getDefaultAccountGroupId).call(this, state.accountTree.wallets);
815
+ state.accountTree.selectedAccountGroup =
816
+ newSelectedAccountGroup;
817
+ selectedAccountGroupChanged =
818
+ newSelectedAccountGroup !== previousSelectedAccountGroup;
819
+ }
820
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_pruneEmptyGroupAndWallet).call(this, state, walletId, groupId);
793
821
  }
794
- }
795
- if (accounts.length === 0) {
796
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_pruneEmptyGroupAndWallet).call(this, state, walletId, groupId);
822
+ });
823
+ this.messenger.publish(`${controllerName}:accountTreeChange`, this.state.accountTree);
824
+ // Emit selectedAccountGroupChange event if the selected group changed
825
+ if (selectedAccountGroupChanged) {
826
+ this.messenger.publish(`${controllerName}:selectedAccountGroupChange`, this.state.accountTree.selectedAccountGroup, previousSelectedAccountGroup);
797
827
  }
798
828
  }
799
- });
800
- this.messenger.publish(`${controllerName}:accountTreeChange`, this.state.accountTree);
801
- // Emit selectedAccountGroupChange event if the selected group changed
802
- if (selectedAccountGroupChanged) {
803
- this.messenger.publish(`${controllerName}:selectedAccountGroupChange`, this.state.accountTree.selectedAccountGroup, previousSelectedAccountGroup);
804
829
  }
805
830
  // Clear reverse-mapping for that account.
806
831
  __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").delete(accountId);
@@ -817,9 +842,23 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
817
842
  delete state.accountWalletsMetadata[walletId];
818
843
  }
819
844
  return state;
820
- }, _AccountTreeController_insert = function _AccountTreeController_insert(wallets, account) {
821
- const result = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this).match(account) ??
822
- __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).match(account) ??
845
+ }, _AccountTreeController_sortAccountsOfGroup = function _AccountTreeController_sortAccountsOfGroup(group) {
846
+ group.accounts.sort(
847
+ /* istanbul ignore next: Comparator branch execution (a===id vs b===id)
848
+ * and return attribution vary across engines; final ordering is covered
849
+ * by behavior tests. Ignoring the entire comparator avoids flaky line
850
+ * coverage without reducing scenario coverage.
851
+ */
852
+ (a, b) => {
853
+ const aSortOrder = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(a)?.sortOrder;
854
+ const bSortOrder = __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(b)?.sortOrder;
855
+ return (aSortOrder ?? MAX_SORT_ORDER) - (bSortOrder ?? MAX_SORT_ORDER);
856
+ });
857
+ }, _AccountTreeController_insertAccount = function _AccountTreeController_insertAccount(wallets, account) {
858
+ // Those are owned by the `MultichainAccountService`, so they should be already handled
859
+ // by `#insertOrUpdateMultichainAccountWalletAndGroup` method.
860
+ assert(!isBip44Account(account), 'BIP-44 accounts cannot be inserted explicitly');
861
+ const result = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).match(account) ??
823
862
  __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).match(account); // This one cannot fail.
824
863
  // Update controller's state.
825
864
  const walletId = result.wallet.id;
@@ -838,62 +877,119 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
838
877
  // the union tag `result.wallet.type`.
839
878
  };
840
879
  wallet = wallets[walletId];
841
- // Trigger atomic sync for new wallet (only for entropy wallets)
842
- if (wallet.type === AccountWalletType.Entropy) {
843
- __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").enqueueSingleWalletSync(walletId);
844
- }
845
880
  }
846
881
  const groupId = result.group.id;
847
- let group = wallet.groups[groupId];
848
- const { type, id } = account;
849
- const sortOrder = ACCOUNT_TYPE_TO_SORT_ORDER[type];
882
+ assert(result.group.type === AccountGroupType.SingleAccount, `Account insertion should always result in a "${AccountGroupType.SingleAccount}" group type`);
883
+ assert(!wallet.groups[groupId], 'Single account insertion cannot re-use existing group');
884
+ log(`[${walletId}] Add new group: [${groupId}]`);
885
+ const group = {
886
+ ...result.group,
887
+ // Type-wise, we are guaranteed to always have at least 1 account.
888
+ accounts: [account.id],
889
+ metadata: {
890
+ name: '',
891
+ ...{ pinned: false, hidden: false },
892
+ ...result.group.metadata, // Allow rules to override defaults
893
+ },
894
+ // We do need to type-cast since we're not narrowing `result` with
895
+ // the union tag `result.group.type`.
896
+ };
897
+ wallet.groups[groupId] = group;
898
+ // Map group ID to its containing wallet ID for efficient direct access
899
+ __classPrivateFieldGet(this, _AccountTreeController_groupIdToWalletId, "f").set(groupId, walletId);
900
+ log(`[${groupId}] Add new account: { id: "${account.id}", type: "${account.type}", address: "${account.address}"`);
901
+ // Update the reverse mapping for this account.
902
+ __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").set(account.id, {
903
+ walletId: wallet.id,
904
+ groupId: group.id,
905
+ sortOrder: ACCOUNT_TYPE_TO_SORT_ORDER[account.type],
906
+ });
907
+ // We need to do this at every insertion because race conditions can happen
908
+ // during the account creation process where one provider completes before the other.
909
+ // The discovery process in the service can also lead to some accounts being created "out of order".
910
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_sortAccountsOfGroup).call(this, group);
911
+ }, _AccountTreeController_insertOrUpdateMultichainAccountWalletAndGroup = function _AccountTreeController_insertOrUpdateMultichainAccountWalletAndGroup(wallets, multichainAccountWallet, multichainAccountGroup) {
912
+ const walletId = multichainAccountWallet.id;
913
+ const groupId = multichainAccountGroup.id;
914
+ let wallet = null;
915
+ let group = null;
916
+ // Check type, mainly to infer proper wallet object type.
917
+ const walletObject = wallets[walletId];
918
+ if (walletObject && walletObject.type === AccountWalletType.Entropy) {
919
+ wallet = walletObject;
920
+ group = wallet.groups[groupId];
921
+ }
922
+ // We always re-use the account list from the group (if accounts get
923
+ // added/removed/re-ordered within the group).
924
+ const accounts = multichainAccountGroup.getAccounts();
925
+ const accountIds = accounts
926
+ // For now, we need this type-cast because `getAccounts` do not have the same
927
+ // type-constraint (uses string[] instead of [string; ...string])
928
+ .map((account) => account.id);
929
+ // Create the group object first, to inject it in the wallet in case this wallet is
930
+ // not part of the tree yet.
850
931
  if (!group) {
851
- log(`[${walletId}] Add new group: [${groupId}]`);
852
- wallet.groups[groupId] = {
853
- ...result.group,
854
- // Type-wise, we are guaranteed to always have at least 1 account.
855
- accounts: [id],
932
+ group = {
933
+ id: multichainAccountGroup.id,
934
+ type: multichainAccountGroup.type,
935
+ accounts: accountIds,
856
936
  metadata: {
937
+ entropy: {
938
+ groupIndex: multichainAccountGroup.groupIndex,
939
+ },
857
940
  name: '',
858
- ...{ pinned: false, hidden: false },
859
- ...result.group.metadata, // Allow rules to override defaults
941
+ pinned: false,
942
+ hidden: false,
860
943
  },
861
- // We do need to type-cast since we're not narrowing `result` with
862
- // the union tag `result.group.type`.
863
944
  };
864
- group = wallet.groups[groupId];
865
- // Map group ID to its containing wallet ID for efficient direct access
866
- __classPrivateFieldGet(this, _AccountTreeController_groupIdToWalletId, "f").set(groupId, walletId);
867
- // Trigger atomic sync for new group (only for entropy wallets)
868
- if (wallet.type === AccountWalletType.Entropy) {
869
- __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").enqueueSingleGroupSync(groupId);
870
- }
871
945
  }
872
946
  else {
873
- group.accounts.push(id);
874
- // We need to do this at every insertion because race conditions can happen
875
- // during the account creation process where one provider completes before the other.
876
- // The discovery process in the service can also lead to some accounts being created "out of order".
877
- const { accounts } = group;
878
- accounts.sort(
879
- /* istanbul ignore next: Comparator branch execution (a===id vs b===id)
880
- * and return attribution vary across engines; final ordering is covered
881
- * by behavior tests. Ignoring the entire comparator avoids flaky line
882
- * coverage without reducing scenario coverage.
883
- */
884
- (a, b) => {
885
- const aSortOrder = a === id ? sortOrder : __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(a)?.sortOrder;
886
- const bSortOrder = b === id ? sortOrder : __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").get(b)?.sortOrder;
887
- return ((aSortOrder ?? MAX_SORT_ORDER) - (bSortOrder ?? MAX_SORT_ORDER));
947
+ // We clear existing contexts for previous accounts of that group because:
948
+ // - Accounts might have been removed
949
+ // - Accounts context mapping will get updated below
950
+ group.accounts.forEach((accountId) => __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").delete(accountId));
951
+ group.accounts = accountIds;
952
+ }
953
+ if (!wallet) {
954
+ wallet = {
955
+ id: multichainAccountWallet.id,
956
+ type: multichainAccountWallet.type,
957
+ status: multichainAccountWallet.status,
958
+ groups: {
959
+ [group.id]: group,
960
+ },
961
+ metadata: {
962
+ entropy: {
963
+ id: multichainAccountWallet.entropySource,
964
+ },
965
+ name: '', // Will get updated later.
966
+ },
967
+ };
968
+ wallets[walletId] = wallet;
969
+ // Trigger atomic sync for new wallet.
970
+ __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").enqueueSingleWalletSync(walletId);
971
+ }
972
+ else {
973
+ wallet.groups[group.id] = group;
974
+ // Trigger atomic sync for new group.
975
+ __classPrivateFieldGet(this, _AccountTreeController_backupAndSyncService, "f").enqueueSingleGroupSync(groupId);
976
+ }
977
+ // Map group ID to its containing wallet ID for efficient direct access
978
+ __classPrivateFieldGet(this, _AccountTreeController_groupIdToWalletId, "f").set(groupId, walletId);
979
+ // Update the reverse mapping for all accounts account.
980
+ for (const account of accounts) {
981
+ __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").set(account.id, {
982
+ walletId: wallet.id,
983
+ groupId: group.id,
984
+ sortOrder: ACCOUNT_TYPE_TO_SORT_ORDER[account.type],
888
985
  });
889
986
  }
890
- log(`[${groupId}] Add new account: { id: "${account.id}", type: "${account.type}", address: "${account.address}"`);
891
- // Update the reverse mapping for this account.
892
- __classPrivateFieldGet(this, _AccountTreeController_accountIdToContext, "f").set(account.id, {
893
- walletId: wallet.id,
894
- groupId: group.id,
895
- sortOrder,
896
- });
987
+ // We need to do this at every insertion because race conditions can happen
988
+ // during the account creation process where one provider completes before the other.
989
+ // The discovery process in the service can also lead to some accounts being created "out of order".
990
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_sortAccountsOfGroup).call(this, group);
991
+ }, _AccountTreeController_getMultichainAccountWallets = function _AccountTreeController_getMultichainAccountWallets() {
992
+ return this.messenger.call('MultichainAccountService:getMultichainAccountWallets');
897
993
  }, _AccountTreeController_listAccounts = function _AccountTreeController_listAccounts() {
898
994
  return this.messenger.call('AccountsController:listMultichainAccounts');
899
995
  }, _AccountTreeController_assertAccountGroupExists = function _AccountTreeController_assertAccountGroupExists(groupId) {
@@ -938,6 +1034,25 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
938
1034
  state.accountTree.selectedAccountGroup = groupId;
939
1035
  });
940
1036
  this.messenger.publish(`${controllerName}:selectedAccountGroupChange`, groupId, previousSelectedAccountGroup);
1037
+ }, _AccountTreeController_handleMultichainAccountGroupCreatedOrUpdated = function _AccountTreeController_handleMultichainAccountGroupCreatedOrUpdated(multichainAccountGroup) {
1038
+ // We wait for the first `init` to be called to actually build up the tree and
1039
+ // mutate it. We expect the caller to first update the `AccountsController` state
1040
+ // to force the migration of accounts, and then call `init`.
1041
+ if (!__classPrivateFieldGet(this, _AccountTreeController_initialized, "f")) {
1042
+ return;
1043
+ }
1044
+ this.update((state) => {
1045
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_insertOrUpdateMultichainAccountWalletAndGroup).call(this, state.accountTree.wallets, multichainAccountGroup.wallet, multichainAccountGroup);
1046
+ const wallet = state.accountTree.wallets[multichainAccountGroup.wallet.id];
1047
+ if (wallet) {
1048
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountWalletMetadata).call(this, state, wallet.id);
1049
+ const group = wallet.groups[multichainAccountGroup.id];
1050
+ if (group) {
1051
+ __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_applyAccountGroupMetadata).call(this, state, wallet.id, group.id);
1052
+ }
1053
+ }
1054
+ });
1055
+ this.messenger.publish(`${controllerName}:accountTreeChange`, this.state.accountTree);
941
1056
  }, _AccountTreeController_handleMultichainAccountWalletStatusChange = function _AccountTreeController_handleMultichainAccountWalletStatusChange(walletId, walletStatus) {
942
1057
  this.update((state) => {
943
1058
  const wallet = state.accountTree.wallets[walletId];