@metamask-previews/account-tree-controller 0.8.0-preview-2fd8e984 → 0.8.0-preview-343f3d69
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 +6 -0
- package/dist/AccountTreeController.cjs +49 -16
- package/dist/AccountTreeController.cjs.map +1 -1
- package/dist/AccountTreeController.d.cts +1 -1
- package/dist/AccountTreeController.d.cts.map +1 -1
- package/dist/AccountTreeController.d.mts +1 -1
- package/dist/AccountTreeController.d.mts.map +1 -1
- package/dist/AccountTreeController.mjs +49 -16
- package/dist/AccountTreeController.mjs.map +1 -1
- package/dist/rule.cjs +13 -4
- package/dist/rule.cjs.map +1 -1
- package/dist/rule.d.cts +18 -4
- package/dist/rule.d.cts.map +1 -1
- package/dist/rule.d.mts +18 -4
- package/dist/rule.d.mts.map +1 -1
- package/dist/rule.mjs +13 -4
- package/dist/rule.mjs.map +1 -1
- package/dist/rules/entropy.cjs +4 -1
- package/dist/rules/entropy.cjs.map +1 -1
- package/dist/rules/entropy.d.cts +2 -1
- package/dist/rules/entropy.d.cts.map +1 -1
- package/dist/rules/entropy.d.mts +2 -1
- package/dist/rules/entropy.d.mts.map +1 -1
- package/dist/rules/entropy.mjs +4 -1
- package/dist/rules/entropy.mjs.map +1 -1
- package/dist/rules/keyring.cjs +6 -0
- package/dist/rules/keyring.cjs.map +1 -1
- package/dist/rules/keyring.d.cts +4 -1
- package/dist/rules/keyring.d.cts.map +1 -1
- package/dist/rules/keyring.d.mts +4 -1
- package/dist/rules/keyring.d.mts.map +1 -1
- package/dist/rules/keyring.mjs +6 -0
- package/dist/rules/keyring.mjs.map +1 -1
- package/dist/rules/snap.cjs +6 -0
- package/dist/rules/snap.cjs.map +1 -1
- package/dist/rules/snap.d.cts +3 -0
- package/dist/rules/snap.d.cts.map +1 -1
- package/dist/rules/snap.d.mts +3 -0
- package/dist/rules/snap.d.mts.map +1 -1
- package/dist/rules/snap.mjs +6 -0
- package/dist/rules/snap.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
- Bump `@metamask/base-controller` from `^8.0.1` to `^8.1.0` ([#6284](https://github.com/MetaMask/core/pull/6284))
|
|
13
13
|
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Add fallback naming for account groups when rule-based naming fails ([#6246](https://github.com/MetaMask/core/pull/6246))
|
|
17
|
+
- Implements "indexes per wallet" strategy (Wallet 1 → Account 1, Account 2; Wallet 2 → Account 1, Account 2)
|
|
18
|
+
- Ensures new groups get proper sequential names within each wallet
|
|
19
|
+
|
|
14
20
|
## [0.8.0]
|
|
15
21
|
|
|
16
22
|
### Added
|
|
@@ -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_rules, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_handleAccountRenamed, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers;
|
|
13
|
+
var _AccountTreeController_instances, _AccountTreeController_serviceStartTime, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_newGroupsMap, _AccountTreeController_rules, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getRuleForWallet, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_handleAccountRenamed, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers;
|
|
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");
|
|
@@ -70,13 +70,17 @@ class AccountTreeController extends base_controller_1.BaseController {
|
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
72
|
_AccountTreeController_instances.add(this);
|
|
73
|
+
_AccountTreeController_serviceStartTime.set(this, Date.now());
|
|
73
74
|
_AccountTreeController_accountIdToContext.set(this, void 0);
|
|
74
75
|
_AccountTreeController_groupIdToWalletId.set(this, void 0);
|
|
76
|
+
_AccountTreeController_newGroupsMap.set(this, void 0);
|
|
75
77
|
_AccountTreeController_rules.set(this, void 0);
|
|
76
78
|
// Reverse map to allow fast node access from an account ID.
|
|
77
79
|
__classPrivateFieldSet(this, _AccountTreeController_accountIdToContext, new Map(), "f");
|
|
78
80
|
// Reverse map to allow fast wallet node access from a group ID.
|
|
79
81
|
__classPrivateFieldSet(this, _AccountTreeController_groupIdToWalletId, new Map(), "f");
|
|
82
|
+
// Temporary map to track which groups contain new accounts (for naming optimization)
|
|
83
|
+
__classPrivateFieldSet(this, _AccountTreeController_newGroupsMap, new WeakMap(), "f");
|
|
80
84
|
// Rules to apply to construct the wallets tree.
|
|
81
85
|
__classPrivateFieldSet(this, _AccountTreeController_rules, [
|
|
82
86
|
// 1. We group by entropy-source
|
|
@@ -334,7 +338,7 @@ class AccountTreeController extends base_controller_1.BaseController {
|
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
340
|
exports.AccountTreeController = AccountTreeController;
|
|
337
|
-
_AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
|
|
341
|
+
_AccountTreeController_serviceStartTime = new WeakMap(), _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_newGroupsMap = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
|
|
338
342
|
return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[0];
|
|
339
343
|
}, _AccountTreeController_getSnapRule = function _AccountTreeController_getSnapRule() {
|
|
340
344
|
return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[1];
|
|
@@ -361,6 +365,15 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
361
365
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountWalletName(wallet);
|
|
362
366
|
}
|
|
363
367
|
}
|
|
368
|
+
}, _AccountTreeController_getRuleForWallet = function _AccountTreeController_getRuleForWallet(wallet) {
|
|
369
|
+
switch (wallet.type) {
|
|
370
|
+
case account_api_1.AccountWalletType.Entropy:
|
|
371
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this);
|
|
372
|
+
case account_api_1.AccountWalletType.Snap:
|
|
373
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this);
|
|
374
|
+
default:
|
|
375
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this);
|
|
376
|
+
}
|
|
364
377
|
}, _AccountTreeController_applyAccountGroupMetadata = function _AccountTreeController_applyAccountGroupMetadata(wallet, group) {
|
|
365
378
|
const persistedMetadata = this.state.accountGroupsMetadata[group.id];
|
|
366
379
|
// Apply persisted name if available (including empty strings)
|
|
@@ -368,21 +381,32 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
368
381
|
group.metadata.name = persistedMetadata.name.value;
|
|
369
382
|
}
|
|
370
383
|
else if (!group.metadata.name) {
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
384
|
+
// Get the appropriate rule for this wallet type
|
|
385
|
+
const rule = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getRuleForWallet).call(this, wallet);
|
|
386
|
+
const typedWallet = wallet;
|
|
387
|
+
const typedGroup = typedWallet.groups[group.id];
|
|
388
|
+
// Calculate group index based on position within sorted group IDs
|
|
389
|
+
// We sort to ensure consistent ordering across all wallet types:
|
|
390
|
+
// - Entropy: group IDs like "entropy:abc/0", "entropy:abc/1" sort to logical order
|
|
391
|
+
// - Snap/Keyring: group IDs like "keyring:ledger/0xABC" get consistent alphabetical order
|
|
392
|
+
const sortedGroupIds = Object.keys(wallet.groups).sort();
|
|
393
|
+
let groupIndex = sortedGroupIds.indexOf(group.id);
|
|
394
|
+
// Defensive fallback: if group.id is not found in sortedGroupIds (should never happen
|
|
395
|
+
// in normal operation since we iterate over wallet.groups), use index 0 to prevent
|
|
396
|
+
// passing -1 to getDefaultAccountGroupName which would result in "Account 0"
|
|
397
|
+
/* istanbul ignore next */
|
|
398
|
+
if (groupIndex === -1) {
|
|
399
|
+
groupIndex = 0;
|
|
381
400
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
401
|
+
// For new groups, use default naming. For existing groups, try computed name first
|
|
402
|
+
const isNewGroup = __classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").get(group) || false;
|
|
403
|
+
group.metadata.name = isNewGroup
|
|
404
|
+
? rule.getDefaultAccountGroupName(groupIndex)
|
|
405
|
+
: rule.getComputedAccountGroupName(typedGroup) ||
|
|
406
|
+
rule.getDefaultAccountGroupName(groupIndex);
|
|
407
|
+
// Clear the flag after use to prevent stale state across rebuilds
|
|
408
|
+
if (isNewGroup) {
|
|
409
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").delete(group);
|
|
386
410
|
}
|
|
387
411
|
}
|
|
388
412
|
// Apply persisted UI states
|
|
@@ -471,6 +495,8 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
471
495
|
const result = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this).match(account) ??
|
|
472
496
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).match(account) ??
|
|
473
497
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).match(account); // This one cannot fail.
|
|
498
|
+
// Determine if this account is new (created after service start)
|
|
499
|
+
const isNewAccount = account.metadata.importTime > __classPrivateFieldGet(this, _AccountTreeController_serviceStartTime, "f");
|
|
474
500
|
// Update controller's state.
|
|
475
501
|
const walletId = result.wallet.id;
|
|
476
502
|
let wallet = wallets[walletId];
|
|
@@ -503,10 +529,17 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
503
529
|
// the union tag `result.group.type`.
|
|
504
530
|
};
|
|
505
531
|
group = wallet.groups[groupId];
|
|
532
|
+
// Store whether this is a new group (has new accounts) for naming logic
|
|
533
|
+
// We use a WeakMap to avoid polluting the group object with temporary data
|
|
534
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").set(group, isNewAccount);
|
|
506
535
|
// Map group ID to its containing wallet ID for efficient direct access
|
|
507
536
|
__classPrivateFieldGet(this, _AccountTreeController_groupIdToWalletId, "f").set(groupId, walletId);
|
|
508
537
|
}
|
|
509
538
|
else {
|
|
539
|
+
// If adding to existing group, update the "new" status if this account is new
|
|
540
|
+
if (isNewAccount) {
|
|
541
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").set(group, true);
|
|
542
|
+
}
|
|
510
543
|
group.accounts.push(account.id);
|
|
511
544
|
}
|
|
512
545
|
// Update the reverse mapping for this account.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountTreeController.cjs","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountGroupId,\n AccountSelector,\n AccountWalletId,\n} from '@metamask/account-api';\nimport { AccountWalletType, select } from '@metamask/account-api';\nimport { type AccountId } from '@metamask/accounts-controller';\nimport type { StateMetadata } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport type { AccountGroupObject } from './group';\nimport { EntropyRule } from './rules/entropy';\nimport { KeyringRule } from './rules/keyring';\nimport { SnapRule } from './rules/snap';\nimport type {\n AccountTreeControllerMessenger,\n AccountTreeControllerState,\n} from './types';\nimport type { AccountWalletObject } from './wallet';\n\nexport const controllerName = 'AccountTreeController';\n\nconst accountTreeControllerMetadata: StateMetadata<AccountTreeControllerState> =\n {\n accountTree: {\n persist: false, // We do re-recompute this state everytime.\n anonymous: false,\n },\n accountGroupsMetadata: {\n persist: true,\n anonymous: false,\n },\n accountWalletsMetadata: {\n persist: true,\n anonymous: false,\n },\n };\n\n/**\n * Gets default state of the `AccountTreeController`.\n *\n * @returns The default state of the `AccountTreeController`.\n */\nexport function getDefaultAccountTreeControllerState(): AccountTreeControllerState {\n return {\n accountTree: {\n wallets: {},\n selectedAccountGroup: '',\n },\n accountGroupsMetadata: {},\n accountWalletsMetadata: {},\n };\n}\n\n/**\n * Context for an account.\n */\nexport type AccountContext = {\n /**\n * Wallet ID associated to that account.\n */\n walletId: AccountWalletObject['id'];\n\n /**\n * Account group ID associated to that account.\n */\n groupId: AccountGroupObject['id'];\n};\n\nconst DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX = /^Account ([0-9]+)$/u;\n\nexport class AccountTreeController extends BaseController<\n typeof controllerName,\n AccountTreeControllerState,\n AccountTreeControllerMessenger\n> {\n readonly #accountIdToContext: Map<AccountId, AccountContext>;\n\n readonly #groupIdToWalletId: Map<AccountGroupId, AccountWalletId>;\n\n readonly #rules: [EntropyRule, SnapRule, KeyringRule];\n\n /**\n * Constructor for AccountTreeController.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger object.\n * @param options.state - Initial state to set on this controller\n */\n constructor({\n messenger,\n state,\n }: {\n messenger: AccountTreeControllerMessenger;\n state?: Partial<AccountTreeControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: accountTreeControllerMetadata,\n state: {\n ...getDefaultAccountTreeControllerState(),\n ...state,\n },\n });\n\n // Reverse map to allow fast node access from an account ID.\n this.#accountIdToContext = new Map();\n\n // Reverse map to allow fast wallet node access from a group ID.\n this.#groupIdToWalletId = new Map();\n\n // Rules to apply to construct the wallets tree.\n this.#rules = [\n // 1. We group by entropy-source\n new EntropyRule(this.messagingSystem),\n // 2. We group by Snap ID\n new SnapRule(this.messagingSystem),\n // 3. We group by wallet type (this rule cannot fail and will group all non-matching accounts)\n new KeyringRule(this.messagingSystem),\n ];\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountAdded',\n (account) => {\n this.#handleAccountAdded(account);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountRemoved',\n (accountId) => {\n this.#handleAccountRemoved(accountId);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:selectedAccountChange',\n (account) => {\n this.#handleSelectedAccountChange(account);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountRenamed',\n (account) => {\n this.#handleAccountRenamed(account);\n },\n );\n\n this.#registerMessageHandlers();\n }\n\n /**\n * Initialize the controller's state.\n *\n * It constructs the initial state of the account tree (tree nodes, nodes\n * names, metadata, etc..) and will automatically update the controller's\n * state with it.\n */\n init() {\n const wallets: AccountTreeControllerState['accountTree']['wallets'] = {};\n\n // Clear mappings for fresh rebuild\n this.#accountIdToContext.clear();\n this.#groupIdToWalletId.clear();\n\n // For now, we always re-compute all wallets, we do not re-use the existing state.\n for (const account of this.#listAccounts()) {\n this.#insert(wallets, account);\n }\n\n // Once we have the account tree, we can apply persisted metadata (names + UI states).\n for (const wallet of Object.values(wallets)) {\n this.#applyAccountWalletMetadata(wallet);\n\n for (const group of Object.values(wallet.groups)) {\n this.#applyAccountGroupMetadata(wallet, group);\n }\n }\n\n this.update((state) => {\n state.accountTree.wallets = wallets;\n\n if (state.accountTree.selectedAccountGroup === '') {\n // No group is selected yet, re-sync with the AccountsController.\n state.accountTree.selectedAccountGroup =\n this.#getDefaultSelectedAccountGroup(wallets);\n }\n });\n }\n\n /**\n * Rule for entropy-base wallets.\n *\n * @returns The rule for entropy-based wallets.\n */\n #getEntropyRule(): EntropyRule {\n return this.#rules[0];\n }\n\n /**\n * Rule for Snap-base wallets.\n *\n * @returns The rule for snap-based wallets.\n */\n #getSnapRule(): SnapRule {\n return this.#rules[1];\n }\n\n /**\n * Rule for keyring-base wallets.\n *\n * This rule acts as a fallback and never fails since all accounts\n * comes from a keyring anyway.\n *\n * @returns The fallback rule for every accounts that did not match\n * any other rules.\n */\n #getKeyringRule(): KeyringRule {\n return this.#rules[2];\n }\n\n /**\n * Applies wallet metadata updates (name) by checking the persistent state\n * first, and then fallbacks to default values (based on the wallet's\n * type).\n *\n * @param wallet Account wallet object to update.\n */\n #applyAccountWalletMetadata(wallet: AccountWalletObject) {\n const persistedMetadata = this.state.accountWalletsMetadata[wallet.id];\n\n // Apply persisted name if available (including empty strings)\n if (persistedMetadata?.name !== undefined) {\n wallet.metadata.name = persistedMetadata.name.value;\n } else if (!wallet.metadata.name) {\n // Generate default name if none exists\n if (wallet.type === AccountWalletType.Entropy) {\n wallet.metadata.name =\n this.#getEntropyRule().getDefaultAccountWalletName(wallet);\n } else if (wallet.type === AccountWalletType.Snap) {\n wallet.metadata.name =\n this.#getSnapRule().getDefaultAccountWalletName(wallet);\n } else {\n wallet.metadata.name =\n this.#getKeyringRule().getDefaultAccountWalletName(wallet);\n }\n }\n }\n\n /**\n * Applies group metadata updates (name, pinned, hidden flags) by checking\n * the persistent state first, and then fallbacks to default values (based\n * on the wallet's\n * type).\n *\n * @param wallet Account wallet object of the account group to update.\n * @param group Account group object to update.\n */\n #applyAccountGroupMetadata(\n wallet: AccountWalletObject,\n group: AccountGroupObject,\n ) {\n const persistedMetadata = this.state.accountGroupsMetadata[group.id];\n\n // Apply persisted name if available (including empty strings)\n if (persistedMetadata?.name !== undefined) {\n group.metadata.name = persistedMetadata.name.value;\n } else if (!group.metadata.name) {\n // Generate default name if none exists\n if (wallet.type === AccountWalletType.Entropy) {\n group.metadata.name = this.#getEntropyRule().getDefaultAccountGroupName(\n // Get the group from the wallet, to get the proper type inference.\n wallet.groups[group.id],\n );\n } else if (wallet.type === AccountWalletType.Snap) {\n group.metadata.name = this.#getSnapRule().getDefaultAccountGroupName(\n // Same here.\n wallet.groups[group.id],\n );\n } else {\n group.metadata.name = this.#getKeyringRule().getDefaultAccountGroupName(\n // Same here.\n wallet.groups[group.id],\n );\n }\n }\n\n // Apply persisted UI states\n if (persistedMetadata?.pinned?.value !== undefined) {\n group.metadata.pinned = persistedMetadata.pinned.value;\n }\n if (persistedMetadata?.hidden?.value !== undefined) {\n group.metadata.hidden = persistedMetadata.hidden.value;\n }\n }\n\n /**\n * Gets the account wallet object from its ID.\n *\n * @param walletId - Account wallet ID.\n * @returns The account wallet object if found, undefined otherwise.\n */\n getAccountWalletObject(\n walletId: AccountWalletId,\n ): AccountWalletObject | undefined {\n const wallet = this.state.accountTree.wallets[walletId];\n if (!wallet) {\n return undefined;\n }\n\n return wallet;\n }\n\n /**\n * Gets all account wallet objects.\n *\n * @returns All account wallet objects.\n */\n getAccountWalletObjects(): AccountWalletObject[] {\n return Object.values(this.state.accountTree.wallets);\n }\n\n /**\n * Gets all underlying accounts from the currently selected account\n * group.\n *\n * It also support account selector, which allows to filter specific\n * accounts given some criterias (account type, address, scopes, etc...).\n *\n * @param selector - Optional account selector.\n * @returns Underlying accounts for the currently selected account (filtered\n * by the selector if provided).\n */\n getAccountsFromSelectedAccountGroup(\n selector?: AccountSelector<InternalAccount>,\n ) {\n const groupId = this.getSelectedAccountGroup();\n if (!groupId) {\n return [];\n }\n\n const group = this.getAccountGroupObject(groupId);\n // We should never reach this part, so we cannot cover it either.\n /* istanbul ignore next */\n if (!group) {\n return [];\n }\n\n const accounts: InternalAccount[] = [];\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n // For now, we're filtering undefined account, but I believe\n // throwing would be more appropriate here.\n if (account) {\n accounts.push(account);\n }\n }\n\n return selector ? select(accounts, selector) : accounts;\n }\n\n /**\n * Gets the account group object from its ID.\n *\n * @param groupId - Account group ID.\n * @returns The account group object if found, undefined otherwise.\n */\n getAccountGroupObject(\n groupId: AccountGroupId,\n ): AccountGroupObject | undefined {\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (!walletId) {\n return undefined;\n }\n\n const wallet = this.getAccountWalletObject(walletId);\n return wallet?.groups[groupId];\n }\n\n /**\n * Handles \"AccountsController:accountAdded\" event to insert\n * new accounts into the tree.\n *\n * @param account - New account.\n */\n #handleAccountAdded(account: InternalAccount) {\n this.update((state) => {\n this.#insert(state.accountTree.wallets, account);\n\n const context = this.#accountIdToContext.get(account.id);\n if (context) {\n const { walletId, groupId } = context;\n\n const wallet = state.accountTree.wallets[walletId];\n if (wallet) {\n this.#applyAccountWalletMetadata(wallet);\n\n const group = wallet.groups[groupId];\n if (group) {\n this.#applyAccountGroupMetadata(wallet, group);\n }\n }\n }\n });\n }\n\n /**\n * Handles \"AccountsController:accountRemoved\" event to remove\n * given account from the tree.\n *\n * @param accountId - Removed account ID.\n */\n #handleAccountRemoved(accountId: AccountId) {\n const context = this.#accountIdToContext.get(accountId);\n\n if (context) {\n const { walletId, groupId } = context;\n\n this.update((state) => {\n const accounts =\n state.accountTree.wallets[walletId]?.groups[groupId]?.accounts;\n\n if (accounts) {\n const index = accounts.indexOf(accountId);\n if (index !== -1) {\n accounts.splice(index, 1);\n\n // Check if we need to update selectedAccountGroup after removal\n if (\n state.accountTree.selectedAccountGroup === groupId &&\n accounts.length === 0\n ) {\n // The currently selected group is now empty, find a new group to select\n state.accountTree.selectedAccountGroup =\n this.#getDefaultAccountGroupId(state.accountTree.wallets);\n }\n }\n if (accounts.length === 0) {\n this.#pruneEmptyGroupAndWallet(state, walletId, groupId);\n }\n }\n });\n\n // Clear reverse-mapping for that account.\n this.#accountIdToContext.delete(accountId);\n }\n }\n\n /**\n * Handles \"AccountsController:accountRenamed\" event to rename\n * the associated account group which contains the account being\n * renamed.\n *\n * NOTE: This is mainly useful for legacy backup & sync v1.\n *\n * @param account - Account being renamed.\n */\n #handleAccountRenamed(account: InternalAccount) {\n // We only consider HD and simple EVM accounts for the moment as they have\n // an higher priority over others when it comes to naming.\n // (Similar logic than `EntropyRule.getDefaultAccountGroupName`).\n // TODO: Rename other kind of accounts, but we need to compute their \"default name\" with custom prefixes.\n if (!isEvmAccountType(account.type)) {\n return;\n }\n\n const context = this.#accountIdToContext.get(account.id);\n\n if (context) {\n const { walletId, groupId } = context;\n\n const wallet = this.state.accountTree.wallets[walletId];\n if (wallet) {\n const group = wallet.groups[groupId];\n if (group) {\n // We both use the same naming conventions for HD and simple accounts,\n // so we can use the same regex to check if the name is a default one.\n const isAccountNameDefault =\n DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX.test(account.metadata.name);\n const isGroupNameDefault = DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX.test(\n group.metadata.name,\n );\n\n if (isGroupNameDefault && !isAccountNameDefault) {\n this.setAccountGroupName(groupId, account.metadata.name);\n }\n }\n }\n }\n }\n\n /**\n * Helper method to prune a group if it holds no accounts and additionally\n * prune the wallet if it holds no groups. This action should take place\n * after a singular account removal.\n *\n * NOTE: This method should only be used for a group that we know to be empty.\n *\n * @param state - The AccountTreeController state to prune.\n * @param walletId - The wallet ID to prune, the wallet should be the parent of the associated group that holds the removed account.\n * @param groupId - The group ID to prune, the group should be the parent of the associated account that was removed.\n * @returns The updated state.\n */\n #pruneEmptyGroupAndWallet(\n state: AccountTreeControllerState,\n walletId: AccountWalletId,\n groupId: AccountGroupId,\n ) {\n const { wallets } = state.accountTree;\n\n delete wallets[walletId].groups[groupId];\n this.#groupIdToWalletId.delete(groupId);\n\n if (Object.keys(wallets[walletId].groups).length === 0) {\n delete wallets[walletId];\n }\n return state;\n }\n\n /**\n * Insert an account inside an account tree.\n *\n * We go over multiple rules to try to \"match\" the account following\n * specific criterias. If a rule \"matches\" an account, then this\n * account get added into its proper account wallet and account group.\n *\n * @param wallets - Account tree.\n * @param account - The account to be inserted.\n */\n #insert(\n wallets: AccountTreeControllerState['accountTree']['wallets'],\n account: InternalAccount,\n ) {\n const result =\n this.#getEntropyRule().match(account) ??\n this.#getSnapRule().match(account) ??\n this.#getKeyringRule().match(account); // This one cannot fail.\n\n // Update controller's state.\n const walletId = result.wallet.id;\n let wallet = wallets[walletId];\n if (!wallet) {\n wallets[walletId] = {\n ...result.wallet,\n groups: {},\n metadata: {\n name: '', // Will get updated later.\n ...result.wallet.metadata,\n },\n // We do need to type-cast since we're not narrowing `result` with\n // the union tag `result.wallet.type`.\n } as AccountWalletObject;\n wallet = wallets[walletId];\n }\n\n const groupId = result.group.id;\n let group = wallet.groups[groupId];\n if (!group) {\n wallet.groups[groupId] = {\n ...result.group,\n // Type-wise, we are guaranteed to always have at least 1 account.\n accounts: [account.id],\n metadata: {\n name: '',\n ...{ pinned: false, hidden: false }, // Default UI states\n ...result.group.metadata, // Allow rules to override defaults\n },\n // We do need to type-cast since we're not narrowing `result` with\n // the union tag `result.group.type`.\n } as AccountGroupObject;\n group = wallet.groups[groupId];\n\n // Map group ID to its containing wallet ID for efficient direct access\n this.#groupIdToWalletId.set(groupId, walletId);\n } else {\n group.accounts.push(account.id);\n }\n\n // Update the reverse mapping for this account.\n this.#accountIdToContext.set(account.id, {\n walletId: wallet.id,\n groupId: group.id,\n });\n }\n\n /**\n * List all internal accounts.\n *\n * @returns The list of all internal accounts.\n */\n #listAccounts(): InternalAccount[] {\n return this.messagingSystem.call(\n 'AccountsController:listMultichainAccounts',\n );\n }\n\n /**\n * Asserts that a group exists in the current account tree.\n *\n * @param groupId - The account group ID to validate.\n * @throws Error if the group does not exist.\n */\n #assertAccountGroupExists(groupId: AccountGroupId): void {\n const exists = this.#groupIdToWalletId.has(groupId);\n if (!exists) {\n throw new Error(`Account group with ID \"${groupId}\" not found in tree`);\n }\n }\n\n /**\n * Asserts that a wallet exists in the current account tree.\n *\n * @param walletId - The account wallet ID to validate.\n * @throws Error if the wallet does not exist.\n */\n #assertAccountWalletExists(walletId: AccountWalletId): void {\n const exists = Boolean(this.state.accountTree.wallets[walletId]);\n if (!exists) {\n throw new Error(`Account wallet with ID \"${walletId}\" not found in tree`);\n }\n }\n\n /**\n * Gets the currently selected account group ID.\n *\n * @returns The selected account group ID or empty string if none selected.\n */\n getSelectedAccountGroup(): AccountGroupId | '' {\n return this.state.accountTree.selectedAccountGroup;\n }\n\n /**\n * Sets the selected account group and updates the AccountsController selectedAccount accordingly.\n *\n * @param groupId - The account group ID to select.\n */\n setSelectedAccountGroup(groupId: AccountGroupId): void {\n const currentSelectedGroup = this.state.accountTree.selectedAccountGroup;\n\n // Idempotent check - if the same group is already selected, do nothing\n if (currentSelectedGroup === groupId) {\n return;\n }\n\n // Find the first account in this group to select\n const accountToSelect = this.#getDefaultAccountFromAccountGroupId(groupId);\n if (!accountToSelect) {\n throw new Error(`No accounts found in group: ${groupId}`);\n }\n\n // Update our state first\n this.update((state) => {\n state.accountTree.selectedAccountGroup = groupId;\n });\n\n // Update AccountsController - this will trigger selectedAccountChange event,\n // but our handler is idempotent so it won't cause infinite loop\n this.messagingSystem.call(\n 'AccountsController:setSelectedAccount',\n accountToSelect,\n );\n }\n\n /**\n * Initializes the selectedAccountGroup based on the currently selected account from AccountsController.\n *\n * @param wallets - Wallets object to use for fallback logic\n * @returns The default selected account group ID or empty string if none selected.\n */\n #getDefaultSelectedAccountGroup(wallets: {\n [walletId: AccountWalletId]: AccountWalletObject;\n }): AccountGroupId | '' {\n const selectedAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n if (selectedAccount && selectedAccount.id) {\n const accountMapping = this.#accountIdToContext.get(selectedAccount.id);\n if (accountMapping) {\n const { groupId } = accountMapping;\n\n return groupId;\n }\n }\n\n // Default to the default group in case of errors.\n return this.#getDefaultAccountGroupId(wallets);\n }\n\n /**\n * Handles selected account change from AccountsController.\n * Updates selectedAccountGroup to match the selected account.\n *\n * @param account - The newly selected account.\n */\n #handleSelectedAccountChange(account: InternalAccount): void {\n const accountMapping = this.#accountIdToContext.get(account.id);\n if (!accountMapping) {\n // Account not in tree yet, might be during initialization\n return;\n }\n\n const { groupId } = accountMapping;\n const currentSelectedGroup = this.state.accountTree.selectedAccountGroup;\n\n // Idempotent check - if the same group is already selected, do nothing\n if (currentSelectedGroup === groupId) {\n return;\n }\n\n // Update selectedAccountGroup to match the selected account\n this.update((state) => {\n state.accountTree.selectedAccountGroup = groupId;\n });\n }\n\n /**\n * Gets account group.\n *\n * @param groupId - The account group ID.\n * @returns The account group or undefined if not found.\n */\n #getAccountGroup(groupId: AccountGroupId): AccountGroupObject | undefined {\n const found = Object.values(this.state.accountTree.wallets).find(\n (wallet) => wallet.groups[groupId] !== undefined,\n );\n\n return found?.groups[groupId];\n }\n\n /**\n * Gets the default account for specified group.\n *\n * @param groupId - The account group ID.\n * @returns The first account ID in the group, or undefined if no accounts found.\n */\n #getDefaultAccountFromAccountGroupId(\n groupId: AccountGroupId,\n ): AccountId | undefined {\n const group = this.#getAccountGroup(groupId);\n\n if (group) {\n let candidate;\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n if (!candidate) {\n candidate = id;\n }\n if (account && isEvmAccountType(account.type)) {\n // EVM accounts have a higher priority, so if we find any, we just\n // use that account!\n return account.id;\n }\n }\n\n return candidate;\n }\n\n return undefined;\n }\n\n /**\n * Gets the default group id, which is either, the first non-empty group that contains an EVM account or\n * just the first non-empty group with any accounts.\n *\n * @param wallets - The wallets object to search.\n * @returns The ID of the first non-empty group, or an empty string if no groups are found.\n */\n #getDefaultAccountGroupId(wallets: {\n [walletId: AccountWalletId]: AccountWalletObject;\n }): AccountGroupId | '' {\n let candidate: AccountGroupId | '' = '';\n\n for (const wallet of Object.values(wallets)) {\n for (const group of Object.values(wallet.groups)) {\n // We only update the candidate with the first non-empty group, but still\n // try to find a group that contains an EVM account (the `candidate` is\n // our fallback).\n if (candidate === '' && group.accounts.length > 0) {\n candidate = group.id;\n }\n\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n if (account && isEvmAccountType(account.type)) {\n // EVM accounts have a higher priority, so if we find any, we just\n // use that group!\n return group.id;\n }\n }\n }\n }\n return candidate;\n }\n\n /**\n * Sets a custom name for an account group.\n *\n * @param groupId - The account group ID.\n * @param name - The custom name to set.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupName(groupId: AccountGroupId, name: string): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].name = {\n value: name,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.name =\n name;\n }\n });\n }\n\n /**\n * Sets a custom name for an account wallet.\n *\n * @param walletId - The account wallet ID.\n * @param name - The custom name to set.\n * @throws If the account wallet ID is not found in the current tree.\n */\n setAccountWalletName(walletId: AccountWalletId, name: string): void {\n // Validate that the wallet exists in the current tree\n this.#assertAccountWalletExists(walletId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountWalletsMetadata[walletId] ??= {};\n state.accountWalletsMetadata[walletId].name = {\n value: name,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly\n state.accountTree.wallets[walletId].metadata.name = name;\n });\n }\n\n /**\n * Toggles the pinned state of an account group.\n *\n * @param groupId - The account group ID.\n * @param pinned - Whether the group should be pinned.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupPinned(groupId: AccountGroupId, pinned: boolean): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].pinned = {\n value: pinned,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.pinned =\n pinned;\n }\n });\n }\n\n /**\n * Toggles the hidden state of an account group.\n *\n * @param groupId - The account group ID.\n * @param hidden - Whether the group should be hidden.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupHidden(groupId: AccountGroupId, hidden: boolean): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].hidden = {\n value: hidden,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.hidden =\n hidden;\n }\n });\n }\n\n /**\n * Registers message handlers for the AccountTreeController.\n */\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getSelectedAccountGroup`,\n this.getSelectedAccountGroup.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:setSelectedAccountGroup`,\n this.setSelectedAccountGroup.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getAccountsFromSelectedAccountGroup`,\n this.getAccountsFromSelectedAccountGroup.bind(this),\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AccountTreeController.cjs","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountGroupId,\n AccountWalletId,\n AccountGroupType,\n AccountSelector,\n} from '@metamask/account-api';\nimport { AccountWalletType, select } from '@metamask/account-api';\nimport { type AccountId } from '@metamask/accounts-controller';\nimport type { StateMetadata } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\nimport type { AccountGroupObject } from './group';\nimport type { Rule } from './rule';\nimport { EntropyRule } from './rules/entropy';\nimport { KeyringRule } from './rules/keyring';\nimport { SnapRule } from './rules/snap';\nimport type {\n AccountTreeControllerMessenger,\n AccountTreeControllerState,\n} from './types';\nimport type { AccountWalletObject, AccountWalletObjectOf } from './wallet';\n\nexport const controllerName = 'AccountTreeController';\n\nconst accountTreeControllerMetadata: StateMetadata<AccountTreeControllerState> =\n {\n accountTree: {\n persist: false, // We do re-recompute this state everytime.\n anonymous: false,\n },\n accountGroupsMetadata: {\n persist: true,\n anonymous: false,\n },\n accountWalletsMetadata: {\n persist: true,\n anonymous: false,\n },\n };\n\n/**\n * Gets default state of the `AccountTreeController`.\n *\n * @returns The default state of the `AccountTreeController`.\n */\nexport function getDefaultAccountTreeControllerState(): AccountTreeControllerState {\n return {\n accountTree: {\n wallets: {},\n selectedAccountGroup: '',\n },\n accountGroupsMetadata: {},\n accountWalletsMetadata: {},\n };\n}\n\n/**\n * Context for an account.\n */\nexport type AccountContext = {\n /**\n * Wallet ID associated to that account.\n */\n walletId: AccountWalletObject['id'];\n\n /**\n * Account group ID associated to that account.\n */\n groupId: AccountGroupObject['id'];\n};\n\nconst DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX = /^Account ([0-9]+)$/u;\n\nexport class AccountTreeController extends BaseController<\n typeof controllerName,\n AccountTreeControllerState,\n AccountTreeControllerMessenger\n> {\n readonly #serviceStartTime = Date.now();\n\n readonly #accountIdToContext: Map<AccountId, AccountContext>;\n\n readonly #groupIdToWalletId: Map<AccountGroupId, AccountWalletId>;\n\n readonly #newGroupsMap: WeakMap<AccountGroupObject, boolean>;\n\n readonly #rules: [EntropyRule, SnapRule, KeyringRule];\n\n /**\n * Constructor for AccountTreeController.\n *\n * @param options - The controller options.\n * @param options.messenger - The messenger object.\n * @param options.state - Initial state to set on this controller\n */\n\n constructor({\n messenger,\n state,\n }: {\n messenger: AccountTreeControllerMessenger;\n state?: Partial<AccountTreeControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: accountTreeControllerMetadata,\n state: {\n ...getDefaultAccountTreeControllerState(),\n ...state,\n },\n });\n\n // Reverse map to allow fast node access from an account ID.\n this.#accountIdToContext = new Map();\n\n // Reverse map to allow fast wallet node access from a group ID.\n this.#groupIdToWalletId = new Map();\n\n // Temporary map to track which groups contain new accounts (for naming optimization)\n this.#newGroupsMap = new WeakMap();\n\n // Rules to apply to construct the wallets tree.\n this.#rules = [\n // 1. We group by entropy-source\n new EntropyRule(this.messagingSystem),\n // 2. We group by Snap ID\n new SnapRule(this.messagingSystem),\n // 3. We group by wallet type (this rule cannot fail and will group all non-matching accounts)\n new KeyringRule(this.messagingSystem),\n ];\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountAdded',\n (account) => {\n this.#handleAccountAdded(account);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountRemoved',\n (accountId) => {\n this.#handleAccountRemoved(accountId);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:selectedAccountChange',\n (account) => {\n this.#handleSelectedAccountChange(account);\n },\n );\n\n this.messagingSystem.subscribe(\n 'AccountsController:accountRenamed',\n (account) => {\n this.#handleAccountRenamed(account);\n },\n );\n\n this.#registerMessageHandlers();\n }\n\n /**\n * Initialize the controller's state.\n *\n * It constructs the initial state of the account tree (tree nodes, nodes\n * names, metadata, etc..) and will automatically update the controller's\n * state with it.\n */\n init() {\n const wallets: AccountTreeControllerState['accountTree']['wallets'] = {};\n\n // Clear mappings for fresh rebuild\n this.#accountIdToContext.clear();\n this.#groupIdToWalletId.clear();\n\n // For now, we always re-compute all wallets, we do not re-use the existing state.\n for (const account of this.#listAccounts()) {\n this.#insert(wallets, account);\n }\n\n // Once we have the account tree, we can apply persisted metadata (names + UI states).\n for (const wallet of Object.values(wallets)) {\n this.#applyAccountWalletMetadata(wallet);\n\n for (const group of Object.values(wallet.groups)) {\n this.#applyAccountGroupMetadata(wallet, group);\n }\n }\n\n this.update((state) => {\n state.accountTree.wallets = wallets;\n\n if (state.accountTree.selectedAccountGroup === '') {\n // No group is selected yet, re-sync with the AccountsController.\n state.accountTree.selectedAccountGroup =\n this.#getDefaultSelectedAccountGroup(wallets);\n }\n });\n }\n\n /**\n * Rule for entropy-base wallets.\n *\n * @returns The rule for entropy-based wallets.\n */\n #getEntropyRule(): EntropyRule {\n return this.#rules[0];\n }\n\n /**\n * Rule for Snap-base wallets.\n *\n * @returns The rule for snap-based wallets.\n */\n #getSnapRule(): SnapRule {\n return this.#rules[1];\n }\n\n /**\n * Rule for keyring-base wallets.\n *\n * This rule acts as a fallback and never fails since all accounts\n * comes from a keyring anyway.\n *\n * @returns The fallback rule for every accounts that did not match\n * any other rules.\n */\n #getKeyringRule(): KeyringRule {\n return this.#rules[2];\n }\n\n /**\n * Applies wallet metadata updates (name) by checking the persistent state\n * first, and then fallbacks to default values (based on the wallet's\n * type).\n *\n * @param wallet Account wallet object to update.\n */\n #applyAccountWalletMetadata(wallet: AccountWalletObject) {\n const persistedMetadata = this.state.accountWalletsMetadata[wallet.id];\n\n // Apply persisted name if available (including empty strings)\n if (persistedMetadata?.name !== undefined) {\n wallet.metadata.name = persistedMetadata.name.value;\n } else if (!wallet.metadata.name) {\n // Generate default name if none exists\n if (wallet.type === AccountWalletType.Entropy) {\n wallet.metadata.name =\n this.#getEntropyRule().getDefaultAccountWalletName(wallet);\n } else if (wallet.type === AccountWalletType.Snap) {\n wallet.metadata.name =\n this.#getSnapRule().getDefaultAccountWalletName(wallet);\n } else {\n wallet.metadata.name =\n this.#getKeyringRule().getDefaultAccountWalletName(wallet);\n }\n }\n }\n\n /**\n * Gets the appropriate rule instance for a given wallet type.\n *\n * @param wallet - The wallet object to get the rule for.\n * @returns The rule instance that handles the wallet's type.\n */\n #getRuleForWallet<WalletType extends AccountWalletType>(\n wallet: AccountWalletObjectOf<WalletType>,\n ): Rule<WalletType, AccountGroupType> {\n switch (wallet.type) {\n case AccountWalletType.Entropy:\n return this.#getEntropyRule() as unknown as Rule<\n WalletType,\n AccountGroupType\n >;\n case AccountWalletType.Snap:\n return this.#getSnapRule() as unknown as Rule<\n WalletType,\n AccountGroupType\n >;\n default:\n return this.#getKeyringRule() as unknown as Rule<\n WalletType,\n AccountGroupType\n >;\n }\n }\n\n /**\n * Applies group metadata updates (name, pinned, hidden flags) by checking\n * the persistent state first, and then fallbacks to default values (based\n * on the wallet's\n * type).\n *\n * @param wallet Account wallet object of the account group to update.\n * @param group Account group object to update.\n */\n #applyAccountGroupMetadata(\n wallet: AccountWalletObject,\n group: AccountGroupObject,\n ) {\n const persistedMetadata = this.state.accountGroupsMetadata[group.id];\n\n // Apply persisted name if available (including empty strings)\n if (persistedMetadata?.name !== undefined) {\n group.metadata.name = persistedMetadata.name.value;\n } else if (!group.metadata.name) {\n // Get the appropriate rule for this wallet type\n const rule = this.#getRuleForWallet(wallet);\n const typedWallet = wallet as AccountWalletObjectOf<typeof wallet.type>;\n const typedGroup = typedWallet.groups[group.id] as AccountGroupObject;\n\n // Calculate group index based on position within sorted group IDs\n // We sort to ensure consistent ordering across all wallet types:\n // - Entropy: group IDs like \"entropy:abc/0\", \"entropy:abc/1\" sort to logical order\n // - Snap/Keyring: group IDs like \"keyring:ledger/0xABC\" get consistent alphabetical order\n const sortedGroupIds = Object.keys(wallet.groups).sort();\n let groupIndex = sortedGroupIds.indexOf(group.id);\n\n // Defensive fallback: if group.id is not found in sortedGroupIds (should never happen\n // in normal operation since we iterate over wallet.groups), use index 0 to prevent\n // passing -1 to getDefaultAccountGroupName which would result in \"Account 0\"\n /* istanbul ignore next */\n if (groupIndex === -1) {\n groupIndex = 0;\n }\n\n // For new groups, use default naming. For existing groups, try computed name first\n const isNewGroup = this.#newGroupsMap.get(group) || false;\n group.metadata.name = isNewGroup\n ? rule.getDefaultAccountGroupName(groupIndex)\n : rule.getComputedAccountGroupName(typedGroup) ||\n rule.getDefaultAccountGroupName(groupIndex);\n\n // Clear the flag after use to prevent stale state across rebuilds\n if (isNewGroup) {\n this.#newGroupsMap.delete(group);\n }\n }\n\n // Apply persisted UI states\n if (persistedMetadata?.pinned?.value !== undefined) {\n group.metadata.pinned = persistedMetadata.pinned.value;\n }\n if (persistedMetadata?.hidden?.value !== undefined) {\n group.metadata.hidden = persistedMetadata.hidden.value;\n }\n }\n\n /**\n * Gets the account wallet object from its ID.\n *\n * @param walletId - Account wallet ID.\n * @returns The account wallet object if found, undefined otherwise.\n */\n getAccountWalletObject(\n walletId: AccountWalletId,\n ): AccountWalletObject | undefined {\n const wallet = this.state.accountTree.wallets[walletId];\n if (!wallet) {\n return undefined;\n }\n\n return wallet;\n }\n\n /**\n * Gets all account wallet objects.\n *\n * @returns All account wallet objects.\n */\n getAccountWalletObjects(): AccountWalletObject[] {\n return Object.values(this.state.accountTree.wallets);\n }\n\n /**\n * Gets all underlying accounts from the currently selected account\n * group.\n *\n * It also support account selector, which allows to filter specific\n * accounts given some criterias (account type, address, scopes, etc...).\n *\n * @param selector - Optional account selector.\n * @returns Underlying accounts for the currently selected account (filtered\n * by the selector if provided).\n */\n getAccountsFromSelectedAccountGroup(\n selector?: AccountSelector<InternalAccount>,\n ) {\n const groupId = this.getSelectedAccountGroup();\n if (!groupId) {\n return [];\n }\n\n const group = this.getAccountGroupObject(groupId);\n // We should never reach this part, so we cannot cover it either.\n /* istanbul ignore next */\n if (!group) {\n return [];\n }\n\n const accounts: InternalAccount[] = [];\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n // For now, we're filtering undefined account, but I believe\n // throwing would be more appropriate here.\n if (account) {\n accounts.push(account);\n }\n }\n\n return selector ? select(accounts, selector) : accounts;\n }\n\n /**\n * Gets the account group object from its ID.\n *\n * @param groupId - Account group ID.\n * @returns The account group object if found, undefined otherwise.\n */\n getAccountGroupObject(\n groupId: AccountGroupId,\n ): AccountGroupObject | undefined {\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (!walletId) {\n return undefined;\n }\n\n const wallet = this.getAccountWalletObject(walletId);\n return wallet?.groups[groupId];\n }\n\n /**\n * Handles \"AccountsController:accountAdded\" event to insert\n * new accounts into the tree.\n *\n * @param account - New account.\n */\n #handleAccountAdded(account: InternalAccount) {\n this.update((state) => {\n this.#insert(state.accountTree.wallets, account);\n\n const context = this.#accountIdToContext.get(account.id);\n if (context) {\n const { walletId, groupId } = context;\n\n const wallet = state.accountTree.wallets[walletId];\n if (wallet) {\n this.#applyAccountWalletMetadata(wallet);\n\n const group = wallet.groups[groupId];\n if (group) {\n this.#applyAccountGroupMetadata(wallet, group);\n }\n }\n }\n });\n }\n\n /**\n * Handles \"AccountsController:accountRemoved\" event to remove\n * given account from the tree.\n *\n * @param accountId - Removed account ID.\n */\n #handleAccountRemoved(accountId: AccountId) {\n const context = this.#accountIdToContext.get(accountId);\n\n if (context) {\n const { walletId, groupId } = context;\n\n this.update((state) => {\n const accounts =\n state.accountTree.wallets[walletId]?.groups[groupId]?.accounts;\n\n if (accounts) {\n const index = accounts.indexOf(accountId);\n if (index !== -1) {\n accounts.splice(index, 1);\n\n // Check if we need to update selectedAccountGroup after removal\n if (\n state.accountTree.selectedAccountGroup === groupId &&\n accounts.length === 0\n ) {\n // The currently selected group is now empty, find a new group to select\n state.accountTree.selectedAccountGroup =\n this.#getDefaultAccountGroupId(state.accountTree.wallets);\n }\n }\n if (accounts.length === 0) {\n this.#pruneEmptyGroupAndWallet(state, walletId, groupId);\n }\n }\n });\n\n // Clear reverse-mapping for that account.\n this.#accountIdToContext.delete(accountId);\n }\n }\n\n /**\n * Handles \"AccountsController:accountRenamed\" event to rename\n * the associated account group which contains the account being\n * renamed.\n *\n * NOTE: This is mainly useful for legacy backup & sync v1.\n *\n * @param account - Account being renamed.\n */\n #handleAccountRenamed(account: InternalAccount) {\n // We only consider HD and simple EVM accounts for the moment as they have\n // an higher priority over others when it comes to naming.\n // (Similar logic than `EntropyRule.getDefaultAccountGroupName`).\n // TODO: Rename other kind of accounts, but we need to compute their \"default name\" with custom prefixes.\n if (!isEvmAccountType(account.type)) {\n return;\n }\n\n const context = this.#accountIdToContext.get(account.id);\n\n if (context) {\n const { walletId, groupId } = context;\n\n const wallet = this.state.accountTree.wallets[walletId];\n if (wallet) {\n const group = wallet.groups[groupId];\n if (group) {\n // We both use the same naming conventions for HD and simple accounts,\n // so we can use the same regex to check if the name is a default one.\n const isAccountNameDefault =\n DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX.test(account.metadata.name);\n const isGroupNameDefault = DEFAULT_HD_SIMPLE_ACCOUNT_NAME_REGEX.test(\n group.metadata.name,\n );\n\n if (isGroupNameDefault && !isAccountNameDefault) {\n this.setAccountGroupName(groupId, account.metadata.name);\n }\n }\n }\n }\n }\n\n /**\n * Helper method to prune a group if it holds no accounts and additionally\n * prune the wallet if it holds no groups. This action should take place\n * after a singular account removal.\n *\n * NOTE: This method should only be used for a group that we know to be empty.\n *\n * @param state - The AccountTreeController state to prune.\n * @param walletId - The wallet ID to prune, the wallet should be the parent of the associated group that holds the removed account.\n * @param groupId - The group ID to prune, the group should be the parent of the associated account that was removed.\n * @returns The updated state.\n */\n #pruneEmptyGroupAndWallet(\n state: AccountTreeControllerState,\n walletId: AccountWalletId,\n groupId: AccountGroupId,\n ) {\n const { wallets } = state.accountTree;\n\n delete wallets[walletId].groups[groupId];\n this.#groupIdToWalletId.delete(groupId);\n\n if (Object.keys(wallets[walletId].groups).length === 0) {\n delete wallets[walletId];\n }\n return state;\n }\n\n /**\n * Insert an account inside an account tree.\n *\n * We go over multiple rules to try to \"match\" the account following\n * specific criterias. If a rule \"matches\" an account, then this\n * account get added into its proper account wallet and account group.\n *\n * @param wallets - Account tree.\n * @param account - The account to be inserted.\n */\n #insert(\n wallets: AccountTreeControllerState['accountTree']['wallets'],\n account: InternalAccount,\n ) {\n const result =\n this.#getEntropyRule().match(account) ??\n this.#getSnapRule().match(account) ??\n this.#getKeyringRule().match(account); // This one cannot fail.\n\n // Determine if this account is new (created after service start)\n const isNewAccount = account.metadata.importTime > this.#serviceStartTime;\n\n // Update controller's state.\n const walletId = result.wallet.id;\n let wallet = wallets[walletId];\n if (!wallet) {\n wallets[walletId] = {\n ...result.wallet,\n groups: {},\n metadata: {\n name: '', // Will get updated later.\n ...result.wallet.metadata,\n },\n // We do need to type-cast since we're not narrowing `result` with\n // the union tag `result.wallet.type`.\n } as AccountWalletObject;\n wallet = wallets[walletId];\n }\n\n const groupId = result.group.id;\n let group = wallet.groups[groupId];\n if (!group) {\n wallet.groups[groupId] = {\n ...result.group,\n // Type-wise, we are guaranteed to always have at least 1 account.\n accounts: [account.id],\n metadata: {\n name: '',\n ...{ pinned: false, hidden: false }, // Default UI states\n ...result.group.metadata, // Allow rules to override defaults\n },\n // We do need to type-cast since we're not narrowing `result` with\n // the union tag `result.group.type`.\n } as AccountGroupObject;\n group = wallet.groups[groupId];\n\n // Store whether this is a new group (has new accounts) for naming logic\n // We use a WeakMap to avoid polluting the group object with temporary data\n this.#newGroupsMap.set(group, isNewAccount);\n\n // Map group ID to its containing wallet ID for efficient direct access\n this.#groupIdToWalletId.set(groupId, walletId);\n } else {\n // If adding to existing group, update the \"new\" status if this account is new\n if (isNewAccount) {\n this.#newGroupsMap.set(group, true);\n }\n group.accounts.push(account.id);\n }\n\n // Update the reverse mapping for this account.\n this.#accountIdToContext.set(account.id, {\n walletId: wallet.id,\n groupId: group.id,\n });\n }\n\n /**\n * List all internal accounts.\n *\n * @returns The list of all internal accounts.\n */\n #listAccounts(): InternalAccount[] {\n return this.messagingSystem.call(\n 'AccountsController:listMultichainAccounts',\n );\n }\n\n /**\n * Asserts that a group exists in the current account tree.\n *\n * @param groupId - The account group ID to validate.\n * @throws Error if the group does not exist.\n */\n #assertAccountGroupExists(groupId: AccountGroupId): void {\n const exists = this.#groupIdToWalletId.has(groupId);\n if (!exists) {\n throw new Error(`Account group with ID \"${groupId}\" not found in tree`);\n }\n }\n\n /**\n * Asserts that a wallet exists in the current account tree.\n *\n * @param walletId - The account wallet ID to validate.\n * @throws Error if the wallet does not exist.\n */\n #assertAccountWalletExists(walletId: AccountWalletId): void {\n const exists = Boolean(this.state.accountTree.wallets[walletId]);\n if (!exists) {\n throw new Error(`Account wallet with ID \"${walletId}\" not found in tree`);\n }\n }\n\n /**\n * Gets the currently selected account group ID.\n *\n * @returns The selected account group ID or empty string if none selected.\n */\n getSelectedAccountGroup(): AccountGroupId | '' {\n return this.state.accountTree.selectedAccountGroup;\n }\n\n /**\n * Sets the selected account group and updates the AccountsController selectedAccount accordingly.\n *\n * @param groupId - The account group ID to select.\n */\n setSelectedAccountGroup(groupId: AccountGroupId): void {\n const currentSelectedGroup = this.state.accountTree.selectedAccountGroup;\n\n // Idempotent check - if the same group is already selected, do nothing\n if (currentSelectedGroup === groupId) {\n return;\n }\n\n // Find the first account in this group to select\n const accountToSelect = this.#getDefaultAccountFromAccountGroupId(groupId);\n if (!accountToSelect) {\n throw new Error(`No accounts found in group: ${groupId}`);\n }\n\n // Update our state first\n this.update((state) => {\n state.accountTree.selectedAccountGroup = groupId;\n });\n\n // Update AccountsController - this will trigger selectedAccountChange event,\n // but our handler is idempotent so it won't cause infinite loop\n this.messagingSystem.call(\n 'AccountsController:setSelectedAccount',\n accountToSelect,\n );\n }\n\n /**\n * Initializes the selectedAccountGroup based on the currently selected account from AccountsController.\n *\n * @param wallets - Wallets object to use for fallback logic\n * @returns The default selected account group ID or empty string if none selected.\n */\n #getDefaultSelectedAccountGroup(wallets: {\n [walletId: AccountWalletId]: AccountWalletObject;\n }): AccountGroupId | '' {\n const selectedAccount = this.messagingSystem.call(\n 'AccountsController:getSelectedAccount',\n );\n if (selectedAccount && selectedAccount.id) {\n const accountMapping = this.#accountIdToContext.get(selectedAccount.id);\n if (accountMapping) {\n const { groupId } = accountMapping;\n\n return groupId;\n }\n }\n\n // Default to the default group in case of errors.\n return this.#getDefaultAccountGroupId(wallets);\n }\n\n /**\n * Handles selected account change from AccountsController.\n * Updates selectedAccountGroup to match the selected account.\n *\n * @param account - The newly selected account.\n */\n #handleSelectedAccountChange(account: InternalAccount): void {\n const accountMapping = this.#accountIdToContext.get(account.id);\n if (!accountMapping) {\n // Account not in tree yet, might be during initialization\n return;\n }\n\n const { groupId } = accountMapping;\n const currentSelectedGroup = this.state.accountTree.selectedAccountGroup;\n\n // Idempotent check - if the same group is already selected, do nothing\n if (currentSelectedGroup === groupId) {\n return;\n }\n\n // Update selectedAccountGroup to match the selected account\n this.update((state) => {\n state.accountTree.selectedAccountGroup = groupId;\n });\n }\n\n /**\n * Gets account group.\n *\n * @param groupId - The account group ID.\n * @returns The account group or undefined if not found.\n */\n #getAccountGroup(groupId: AccountGroupId): AccountGroupObject | undefined {\n const found = Object.values(this.state.accountTree.wallets).find(\n (wallet) => wallet.groups[groupId] !== undefined,\n );\n\n return found?.groups[groupId];\n }\n\n /**\n * Gets the default account for specified group.\n *\n * @param groupId - The account group ID.\n * @returns The first account ID in the group, or undefined if no accounts found.\n */\n #getDefaultAccountFromAccountGroupId(\n groupId: AccountGroupId,\n ): AccountId | undefined {\n const group = this.#getAccountGroup(groupId);\n\n if (group) {\n let candidate;\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n if (!candidate) {\n candidate = id;\n }\n if (account && isEvmAccountType(account.type)) {\n // EVM accounts have a higher priority, so if we find any, we just\n // use that account!\n return account.id;\n }\n }\n\n return candidate;\n }\n\n return undefined;\n }\n\n /**\n * Gets the default group id, which is either, the first non-empty group that contains an EVM account or\n * just the first non-empty group with any accounts.\n *\n * @param wallets - The wallets object to search.\n * @returns The ID of the first non-empty group, or an empty string if no groups are found.\n */\n #getDefaultAccountGroupId(wallets: {\n [walletId: AccountWalletId]: AccountWalletObject;\n }): AccountGroupId | '' {\n let candidate: AccountGroupId | '' = '';\n\n for (const wallet of Object.values(wallets)) {\n for (const group of Object.values(wallet.groups)) {\n // We only update the candidate with the first non-empty group, but still\n // try to find a group that contains an EVM account (the `candidate` is\n // our fallback).\n if (candidate === '' && group.accounts.length > 0) {\n candidate = group.id;\n }\n\n for (const id of group.accounts) {\n const account = this.messagingSystem.call(\n 'AccountsController:getAccount',\n id,\n );\n\n if (account && isEvmAccountType(account.type)) {\n // EVM accounts have a higher priority, so if we find any, we just\n // use that group!\n return group.id;\n }\n }\n }\n }\n return candidate;\n }\n\n /**\n * Sets a custom name for an account group.\n *\n * @param groupId - The account group ID.\n * @param name - The custom name to set.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupName(groupId: AccountGroupId, name: string): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].name = {\n value: name,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.name =\n name;\n }\n });\n }\n\n /**\n * Sets a custom name for an account wallet.\n *\n * @param walletId - The account wallet ID.\n * @param name - The custom name to set.\n * @throws If the account wallet ID is not found in the current tree.\n */\n setAccountWalletName(walletId: AccountWalletId, name: string): void {\n // Validate that the wallet exists in the current tree\n this.#assertAccountWalletExists(walletId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountWalletsMetadata[walletId] ??= {};\n state.accountWalletsMetadata[walletId].name = {\n value: name,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly\n state.accountTree.wallets[walletId].metadata.name = name;\n });\n }\n\n /**\n * Toggles the pinned state of an account group.\n *\n * @param groupId - The account group ID.\n * @param pinned - Whether the group should be pinned.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupPinned(groupId: AccountGroupId, pinned: boolean): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].pinned = {\n value: pinned,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.pinned =\n pinned;\n }\n });\n }\n\n /**\n * Toggles the hidden state of an account group.\n *\n * @param groupId - The account group ID.\n * @param hidden - Whether the group should be hidden.\n * @throws If the account group ID is not found in the current tree.\n */\n setAccountGroupHidden(groupId: AccountGroupId, hidden: boolean): void {\n // Validate that the group exists in the current tree\n this.#assertAccountGroupExists(groupId);\n\n this.update((state) => {\n // Update persistent metadata\n state.accountGroupsMetadata[groupId] ??= {};\n state.accountGroupsMetadata[groupId].hidden = {\n value: hidden,\n lastUpdatedAt: Date.now(),\n };\n\n // Update tree node directly using efficient mapping\n const walletId = this.#groupIdToWalletId.get(groupId);\n if (walletId) {\n state.accountTree.wallets[walletId].groups[groupId].metadata.hidden =\n hidden;\n }\n });\n }\n\n /**\n * Registers message handlers for the AccountTreeController.\n */\n #registerMessageHandlers(): void {\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getSelectedAccountGroup`,\n this.getSelectedAccountGroup.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:setSelectedAccountGroup`,\n this.setSelectedAccountGroup.bind(this),\n );\n\n this.messagingSystem.registerActionHandler(\n `${controllerName}:getAccountsFromSelectedAccountGroup`,\n this.getAccountsFromSelectedAccountGroup.bind(this),\n );\n }\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountGroupId,
|
|
1
|
+
import type { AccountGroupId, AccountWalletId, AccountSelector } from "@metamask/account-api";
|
|
2
2
|
import { BaseController } from "@metamask/base-controller";
|
|
3
3
|
import type { InternalAccount } from "@metamask/keyring-internal-api";
|
|
4
4
|
import type { AccountGroupObject } from "./group.cjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountTreeController.d.cts","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"AccountTreeController.d.cts","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,eAAe,EAChB,8BAA8B;AAI/B,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAgB;AAKlD,OAAO,KAAK,EACV,8BAA8B,EAC9B,0BAA0B,EAC3B,oBAAgB;AACjB,OAAO,KAAK,EAAE,mBAAmB,EAAyB,qBAAiB;AAE3E,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAkBtD;;;;GAIG;AACH,wBAAgB,oCAAoC,IAAI,0BAA0B,CASjF;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,QAAQ,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AAIF,qBAAa,qBAAsB,SAAQ,cAAc,CACvD,OAAO,cAAc,EACrB,0BAA0B,EAC1B,8BAA8B,CAC/B;;IAWC;;;;;;OAMG;gBAES,EACV,SAAS,EACT,KAAK,GACN,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;KAC7C;IA6DD;;;;;;OAMG;IACH,IAAI;IAoLJ;;;;;OAKG;IACH,sBAAsB,CACpB,QAAQ,EAAE,eAAe,GACxB,mBAAmB,GAAG,SAAS;IASlC;;;;OAIG;IACH,uBAAuB,IAAI,mBAAmB,EAAE;IAIhD;;;;;;;;;;OAUG;IACH,mCAAmC,CACjC,QAAQ,CAAC,EAAE,eAAe,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA+B7C;;;;;OAKG;IACH,qBAAqB,CACnB,OAAO,EAAE,cAAc,GACtB,kBAAkB,GAAG,SAAS;IAwQjC;;;;OAIG;IACH,uBAAuB,IAAI,cAAc,GAAG,EAAE;IAI9C;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAsKtD;;;;;;OAMG;IACH,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAqBhE;;;;;;OAMG;IACH,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAiBnE;;;;;;OAMG;IACH,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAqBrE;;;;;;OAMG;IACH,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;CAwCtE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountGroupId,
|
|
1
|
+
import type { AccountGroupId, AccountWalletId, AccountSelector } from "@metamask/account-api";
|
|
2
2
|
import { BaseController } from "@metamask/base-controller";
|
|
3
3
|
import type { InternalAccount } from "@metamask/keyring-internal-api";
|
|
4
4
|
import type { AccountGroupObject } from "./group.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountTreeController.d.mts","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"AccountTreeController.d.mts","sourceRoot":"","sources":["../src/AccountTreeController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,eAAe,EAChB,8BAA8B;AAI/B,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,uCAAuC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAgB;AAKlD,OAAO,KAAK,EACV,8BAA8B,EAC9B,0BAA0B,EAC3B,oBAAgB;AACjB,OAAO,KAAK,EAAE,mBAAmB,EAAyB,qBAAiB;AAE3E,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAkBtD;;;;GAIG;AACH,wBAAgB,oCAAoC,IAAI,0BAA0B,CASjF;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,QAAQ,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;CACnC,CAAC;AAIF,qBAAa,qBAAsB,SAAQ,cAAc,CACvD,OAAO,cAAc,EACrB,0BAA0B,EAC1B,8BAA8B,CAC/B;;IAWC;;;;;;OAMG;gBAES,EACV,SAAS,EACT,KAAK,GACN,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;KAC7C;IA6DD;;;;;;OAMG;IACH,IAAI;IAoLJ;;;;;OAKG;IACH,sBAAsB,CACpB,QAAQ,EAAE,eAAe,GACxB,mBAAmB,GAAG,SAAS;IASlC;;;;OAIG;IACH,uBAAuB,IAAI,mBAAmB,EAAE;IAIhD;;;;;;;;;;OAUG;IACH,mCAAmC,CACjC,QAAQ,CAAC,EAAE,eAAe,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA+B7C;;;;;OAKG;IACH,qBAAqB,CACnB,OAAO,EAAE,cAAc,GACtB,kBAAkB,GAAG,SAAS;IAwQjC;;;;OAIG;IACH,uBAAuB,IAAI,cAAc,GAAG,EAAE;IAI9C;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAsKtD;;;;;;OAMG;IACH,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAqBhE;;;;;;OAMG;IACH,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAiBnE;;;;;;OAMG;IACH,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAqBrE;;;;;;OAMG;IACH,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;CAwCtE"}
|
|
@@ -9,7 +9,7 @@ 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_rules, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_handleAccountRenamed, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers;
|
|
12
|
+
var _AccountTreeController_instances, _AccountTreeController_serviceStartTime, _AccountTreeController_accountIdToContext, _AccountTreeController_groupIdToWalletId, _AccountTreeController_newGroupsMap, _AccountTreeController_rules, _AccountTreeController_getEntropyRule, _AccountTreeController_getSnapRule, _AccountTreeController_getKeyringRule, _AccountTreeController_applyAccountWalletMetadata, _AccountTreeController_getRuleForWallet, _AccountTreeController_applyAccountGroupMetadata, _AccountTreeController_handleAccountAdded, _AccountTreeController_handleAccountRemoved, _AccountTreeController_handleAccountRenamed, _AccountTreeController_pruneEmptyGroupAndWallet, _AccountTreeController_insert, _AccountTreeController_listAccounts, _AccountTreeController_assertAccountGroupExists, _AccountTreeController_assertAccountWalletExists, _AccountTreeController_getDefaultSelectedAccountGroup, _AccountTreeController_handleSelectedAccountChange, _AccountTreeController_getAccountGroup, _AccountTreeController_getDefaultAccountFromAccountGroupId, _AccountTreeController_getDefaultAccountGroupId, _AccountTreeController_registerMessageHandlers;
|
|
13
13
|
import { AccountWalletType, select } from "@metamask/account-api";
|
|
14
14
|
import { BaseController } from "@metamask/base-controller";
|
|
15
15
|
import { isEvmAccountType } from "@metamask/keyring-api";
|
|
@@ -66,13 +66,17 @@ export class AccountTreeController extends BaseController {
|
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
_AccountTreeController_instances.add(this);
|
|
69
|
+
_AccountTreeController_serviceStartTime.set(this, Date.now());
|
|
69
70
|
_AccountTreeController_accountIdToContext.set(this, void 0);
|
|
70
71
|
_AccountTreeController_groupIdToWalletId.set(this, void 0);
|
|
72
|
+
_AccountTreeController_newGroupsMap.set(this, void 0);
|
|
71
73
|
_AccountTreeController_rules.set(this, void 0);
|
|
72
74
|
// Reverse map to allow fast node access from an account ID.
|
|
73
75
|
__classPrivateFieldSet(this, _AccountTreeController_accountIdToContext, new Map(), "f");
|
|
74
76
|
// Reverse map to allow fast wallet node access from a group ID.
|
|
75
77
|
__classPrivateFieldSet(this, _AccountTreeController_groupIdToWalletId, new Map(), "f");
|
|
78
|
+
// Temporary map to track which groups contain new accounts (for naming optimization)
|
|
79
|
+
__classPrivateFieldSet(this, _AccountTreeController_newGroupsMap, new WeakMap(), "f");
|
|
76
80
|
// Rules to apply to construct the wallets tree.
|
|
77
81
|
__classPrivateFieldSet(this, _AccountTreeController_rules, [
|
|
78
82
|
// 1. We group by entropy-source
|
|
@@ -329,7 +333,7 @@ export class AccountTreeController extends BaseController {
|
|
|
329
333
|
});
|
|
330
334
|
}
|
|
331
335
|
}
|
|
332
|
-
_AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
|
|
336
|
+
_AccountTreeController_serviceStartTime = new WeakMap(), _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeController_groupIdToWalletId = new WeakMap(), _AccountTreeController_newGroupsMap = new WeakMap(), _AccountTreeController_rules = new WeakMap(), _AccountTreeController_instances = new WeakSet(), _AccountTreeController_getEntropyRule = function _AccountTreeController_getEntropyRule() {
|
|
333
337
|
return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[0];
|
|
334
338
|
}, _AccountTreeController_getSnapRule = function _AccountTreeController_getSnapRule() {
|
|
335
339
|
return __classPrivateFieldGet(this, _AccountTreeController_rules, "f")[1];
|
|
@@ -356,6 +360,15 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
356
360
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).getDefaultAccountWalletName(wallet);
|
|
357
361
|
}
|
|
358
362
|
}
|
|
363
|
+
}, _AccountTreeController_getRuleForWallet = function _AccountTreeController_getRuleForWallet(wallet) {
|
|
364
|
+
switch (wallet.type) {
|
|
365
|
+
case AccountWalletType.Entropy:
|
|
366
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this);
|
|
367
|
+
case AccountWalletType.Snap:
|
|
368
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this);
|
|
369
|
+
default:
|
|
370
|
+
return __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this);
|
|
371
|
+
}
|
|
359
372
|
}, _AccountTreeController_applyAccountGroupMetadata = function _AccountTreeController_applyAccountGroupMetadata(wallet, group) {
|
|
360
373
|
const persistedMetadata = this.state.accountGroupsMetadata[group.id];
|
|
361
374
|
// Apply persisted name if available (including empty strings)
|
|
@@ -363,21 +376,32 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
363
376
|
group.metadata.name = persistedMetadata.name.value;
|
|
364
377
|
}
|
|
365
378
|
else if (!group.metadata.name) {
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
// Get the appropriate rule for this wallet type
|
|
380
|
+
const rule = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getRuleForWallet).call(this, wallet);
|
|
381
|
+
const typedWallet = wallet;
|
|
382
|
+
const typedGroup = typedWallet.groups[group.id];
|
|
383
|
+
// Calculate group index based on position within sorted group IDs
|
|
384
|
+
// We sort to ensure consistent ordering across all wallet types:
|
|
385
|
+
// - Entropy: group IDs like "entropy:abc/0", "entropy:abc/1" sort to logical order
|
|
386
|
+
// - Snap/Keyring: group IDs like "keyring:ledger/0xABC" get consistent alphabetical order
|
|
387
|
+
const sortedGroupIds = Object.keys(wallet.groups).sort();
|
|
388
|
+
let groupIndex = sortedGroupIds.indexOf(group.id);
|
|
389
|
+
// Defensive fallback: if group.id is not found in sortedGroupIds (should never happen
|
|
390
|
+
// in normal operation since we iterate over wallet.groups), use index 0 to prevent
|
|
391
|
+
// passing -1 to getDefaultAccountGroupName which would result in "Account 0"
|
|
392
|
+
/* istanbul ignore next */
|
|
393
|
+
if (groupIndex === -1) {
|
|
394
|
+
groupIndex = 0;
|
|
376
395
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
396
|
+
// For new groups, use default naming. For existing groups, try computed name first
|
|
397
|
+
const isNewGroup = __classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").get(group) || false;
|
|
398
|
+
group.metadata.name = isNewGroup
|
|
399
|
+
? rule.getDefaultAccountGroupName(groupIndex)
|
|
400
|
+
: rule.getComputedAccountGroupName(typedGroup) ||
|
|
401
|
+
rule.getDefaultAccountGroupName(groupIndex);
|
|
402
|
+
// Clear the flag after use to prevent stale state across rebuilds
|
|
403
|
+
if (isNewGroup) {
|
|
404
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").delete(group);
|
|
381
405
|
}
|
|
382
406
|
}
|
|
383
407
|
// Apply persisted UI states
|
|
@@ -466,6 +490,8 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
466
490
|
const result = __classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getEntropyRule).call(this).match(account) ??
|
|
467
491
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getSnapRule).call(this).match(account) ??
|
|
468
492
|
__classPrivateFieldGet(this, _AccountTreeController_instances, "m", _AccountTreeController_getKeyringRule).call(this).match(account); // This one cannot fail.
|
|
493
|
+
// Determine if this account is new (created after service start)
|
|
494
|
+
const isNewAccount = account.metadata.importTime > __classPrivateFieldGet(this, _AccountTreeController_serviceStartTime, "f");
|
|
469
495
|
// Update controller's state.
|
|
470
496
|
const walletId = result.wallet.id;
|
|
471
497
|
let wallet = wallets[walletId];
|
|
@@ -498,10 +524,17 @@ _AccountTreeController_accountIdToContext = new WeakMap(), _AccountTreeControlle
|
|
|
498
524
|
// the union tag `result.group.type`.
|
|
499
525
|
};
|
|
500
526
|
group = wallet.groups[groupId];
|
|
527
|
+
// Store whether this is a new group (has new accounts) for naming logic
|
|
528
|
+
// We use a WeakMap to avoid polluting the group object with temporary data
|
|
529
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").set(group, isNewAccount);
|
|
501
530
|
// Map group ID to its containing wallet ID for efficient direct access
|
|
502
531
|
__classPrivateFieldGet(this, _AccountTreeController_groupIdToWalletId, "f").set(groupId, walletId);
|
|
503
532
|
}
|
|
504
533
|
else {
|
|
534
|
+
// If adding to existing group, update the "new" status if this account is new
|
|
535
|
+
if (isNewAccount) {
|
|
536
|
+
__classPrivateFieldGet(this, _AccountTreeController_newGroupsMap, "f").set(group, true);
|
|
537
|
+
}
|
|
505
538
|
group.accounts.push(account.id);
|
|
506
539
|
}
|
|
507
540
|
// Update the reverse mapping for this account.
|