1
- import {
2
- AccountWalletType ,
3
- AccountGroupType ,
4
- select ,
5
- } from '@metamask/account-api' ;
1
+ import { AccountWalletType , select } from '@metamask/account-api' ;
6
2
import type {
7
3
AccountGroupId ,
8
4
AccountWalletId ,
9
5
AccountSelector ,
10
6
MultichainAccountWalletId ,
7
+ AccountGroupType ,
11
8
} from '@metamask/account-api' ;
12
9
import type { MultichainAccountWalletStatus } from '@metamask/account-api' ;
13
10
import { type AccountId } from '@metamask/accounts-controller' ;
@@ -259,23 +256,35 @@ export class AccountTreeController extends BaseController<
259
256
260
257
// Once we have the account tree, we can apply persisted metadata (names + UI states).
261
258
let previousSelectedAccountGroupStillExists = false ;
262
- for ( const wallet of Object . values ( wallets ) ) {
263
- this . #applyAccountWalletMetadata( wallet ) ;
264
-
265
- for ( const group of Object . values ( wallet . groups ) ) {
266
- if ( group . id === previousSelectedAccountGroup ) {
267
- previousSelectedAccountGroupStillExists = true ;
268
- }
269
- }
270
- }
271
-
272
259
this . update ( ( state ) => {
273
260
state . accountTree . wallets = wallets ;
274
261
275
262
// Apply group metadata within the state update
276
263
for ( const wallet of Object . values ( state . accountTree . wallets ) ) {
264
+ this . #applyAccountWalletMetadata( state , wallet . id ) ;
265
+
266
+ // Used for default group default names (so we use human-indexing here).
267
+ let nextNaturalNameIndex = 1 ;
277
268
for ( const group of Object . values ( wallet . groups ) ) {
278
- this . #applyAccountGroupMetadata( state , wallet . id , group . id ) ;
269
+ this . #applyAccountGroupMetadata(
270
+ state ,
271
+ wallet . id ,
272
+ group . id ,
273
+ // FIXME: We should not need this kind of logic if we were not inserting accounts
274
+ // 1 by 1. Instead, we should be inserting wallets and groups directly. This would
275
+ // allow us to naturally insert a group in the tree AND update its metadata right
276
+ // away...
277
+ // But here, we have to wait for the entire group to be ready before updating
278
+ // its metadata (mainly because we're dealing with single accounts rather than entire
279
+ // groups).
280
+ // That is why we need this kind of extra parameter.
281
+ nextNaturalNameIndex ,
282
+ ) ;
283
+
284
+ if ( group . id === previousSelectedAccountGroup ) {
285
+ previousSelectedAccountGroupStillExists = true ;
286
+ }
287
+ nextNaturalNameIndex += 1 ;
279
288
}
280
289
}
281
290
@@ -342,10 +351,15 @@ export class AccountTreeController extends BaseController<
342
351
* first, and then fallbacks to default values (based on the wallet's
343
352
* type).
344
353
*
345
- * @param wallet Account wallet object to update.
354
+ * @param state Controller state to update for persistence.
355
+ * @param walletId The wallet ID to update.
346
356
*/
347
- #applyAccountWalletMetadata( wallet : AccountWalletObject ) {
348
- const persistedMetadata = this . state . accountWalletsMetadata [ wallet . id ] ;
357
+ #applyAccountWalletMetadata(
358
+ state : AccountTreeControllerState ,
359
+ walletId : AccountWalletId ,
360
+ ) {
361
+ const wallet = state . accountTree . wallets [ walletId ] ;
362
+ const persistedMetadata = state . accountWalletsMetadata [ walletId ] ;
349
363
350
364
// Apply persisted name if available (including empty strings)
351
365
if ( persistedMetadata ?. name !== undefined ) {
@@ -402,11 +416,13 @@ export class AccountTreeController extends BaseController<
402
416
* @param state Controller state to update for persistence.
403
417
* @param walletId The wallet ID containing the group.
404
418
* @param groupId The account group ID to update.
419
+ * @param nextNaturalNameIndex The next natural name index for this group (only used for default names).
405
420
*/
406
421
#applyAccountGroupMetadata(
407
422
state : AccountTreeControllerState ,
408
423
walletId : AccountWalletId ,
409
424
groupId : AccountGroupId ,
425
+ nextNaturalNameIndex ?: number ,
410
426
) {
411
427
const wallet = state . accountTree . wallets [ walletId ] ;
412
428
const group = wallet . groups [ groupId ] ;
@@ -420,15 +436,14 @@ export class AccountTreeController extends BaseController<
420
436
// Get the appropriate rule for this wallet type
421
437
const rule = this . #getRuleForWallet( wallet ) ;
422
438
439
+ // Get the prefix for groups of this wallet
440
+ const namePrefix = rule . getDefaultAccountGroupPrefix ( wallet ) ;
441
+
423
442
// Skip computed names for now - use default naming with per-wallet logic
424
443
// TODO: Implement computed names in a future iteration
425
444
426
- // Generate default name and ensure it's unique within the wallet
427
- let proposedName = '' ;
428
- let proposedNameIndex : number ;
429
-
430
445
// Parse the highest account index being used (similar to accounts-controller)
431
- let highestAccountNameIndex = 0 ;
446
+ let highestNameIndex = 0 ;
432
447
for ( const existingGroup of Object . values (
433
448
wallet . groups ,
434
449
) as AccountGroupObject [ ] ) {
@@ -437,50 +452,53 @@ export class AccountTreeController extends BaseController<
437
452
continue ;
438
453
}
439
454
// Parse the existing group name to extract the numeric index
440
- // TODO: This regex only matches "Account N" pattern. Hardware wallets (Trezor, Ledger, etc.)
441
- // use different patterns like "Trezor N", "Ledger N" per keyringTypeToName().
442
- // We'll enhance this to handle all keyring types in a future iteration.
443
455
const nameMatch = existingGroup . metadata . name . match ( / A c c o u n t ( \d + ) $ / u) ;
444
456
if ( nameMatch ) {
445
457
const nameIndex = parseInt ( nameMatch [ 1 ] , 10 ) ;
446
- if ( nameIndex > highestAccountNameIndex ) {
447
- highestAccountNameIndex = nameIndex ;
458
+ if ( nameIndex > highestNameIndex ) {
459
+ highestNameIndex = nameIndex ;
448
460
}
449
461
}
450
462
}
451
463
452
- // For entropy-based multichain groups, start with the actual groupIndex
453
- if (
454
- group . type === AccountGroupType . MultichainAccount &&
455
- group . metadata . entropy
456
- ) {
457
- proposedNameIndex = group . metadata . entropy . groupIndex ;
458
- } else {
459
- // For other wallet types, start with the number of existing groups
460
- // This gives us the next logical sequential number
461
- proposedNameIndex = Object . keys ( wallet . groups ) . length - 1 ;
462
- }
463
-
464
- // Use the higher of the two: highest parsed index or computed index
465
- proposedNameIndex = Math . min ( highestAccountNameIndex , proposedNameIndex ) ;
464
+ // We just use the highest known index no matter the wallet type.
465
+ //
466
+ // For entropy-based wallets (bip44), if a multichain account group with group index 1
467
+ // is inserted before another one with group index 0, then the naming will be:
468
+ // - "Account 1" (group index 1)
469
+ // - "Account 2" (group index 0)
470
+ // This naming makes more sense for the end-user.
471
+ //
472
+ // For other type of wallets, since those wallets can create arbitrary gaps, we still
473
+ // rely on the highest know index to avoid back-filling account with "old names".
474
+ let proposedNameIndex = Math . max (
475
+ // Use + 1 to use the next available index.
476
+ highestNameIndex + 1 ,
477
+ // In case all accounts have been renamed differently than the usual "Account <index>"
478
+ // pattern, we want to use the next "natural" index, which is just the number of groups
479
+ // in that wallet (e.g. ["Account A", "Another Account"], next natural index would be
480
+ // "Account 3" in this case).
481
+ nextNaturalNameIndex ?? Object . keys ( wallet . groups ) . length ,
482
+ ) ;
466
483
467
484
// Find a unique name by checking for conflicts and incrementing if needed
468
- let nameExists : boolean ;
485
+ let proposedNameExists : boolean ;
486
+ let proposedName = '' ;
469
487
do {
470
- proposedName = rule . getDefaultAccountGroupName ( proposedNameIndex ) ;
488
+ proposedName = ` ${ namePrefix } ${ proposedNameIndex } ` ;
471
489
472
490
// Check if this name already exists in the wallet (excluding current group)
473
- nameExists = ! isAccountGroupNameUniqueFromWallet (
491
+ proposedNameExists = ! isAccountGroupNameUniqueFromWallet (
474
492
wallet ,
475
493
group . id ,
476
494
proposedName ,
477
495
) ;
478
496
479
497
/* istanbul ignore next */
480
- if ( nameExists ) {
498
+ if ( proposedNameExists ) {
481
499
proposedNameIndex += 1 ; // Try next number
482
500
}
483
- } while ( nameExists ) ;
501
+ } while ( proposedNameExists ) ;
484
502
485
503
state . accountTree . wallets [ walletId ] . groups [ groupId ] . metadata . name =
486
504
proposedName ;
@@ -608,7 +626,7 @@ export class AccountTreeController extends BaseController<
608
626
609
627
const wallet = state . accountTree . wallets [ walletId ] ;
610
628
if ( wallet ) {
611
- this . #applyAccountWalletMetadata( wallet ) ;
629
+ this . #applyAccountWalletMetadata( state , walletId ) ;
612
630
this . #applyAccountGroupMetadata( state , walletId , groupId ) ;
613
631
}
614
632
}
0 commit comments