Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## 2025-10-22

### Changes

---

Packages with breaking changes:

- There are no breaking changes in this release.

Packages with other changes:

- [`nekoton_repository` - `v2.0.3-dev.0`](#nekoton_repository---v203-dev0)

---

#### `nekoton_repository` - `v2.0.3-dev.0`

- **FIX**(EWM-397): fix tests & create tests for new naming logic. ([81c66a2d](https://github.com/broxus/nekoton_repository/commit/81c66a2dd6bbf2e0e78477af910cac045b910205))
- **FIX**(EWM-397): formatting. ([5a26933b](https://github.com/broxus/nekoton_repository/commit/5a26933bb70e622259d2d71196a95430df7604ae))

## 2.0.3-dev.0

- **FIX**(EWM-397): fix tests & create tests for new naming logic. ([81c66a2d](https://github.com/broxus/nekoton_repository/commit/81c66a2dd6bbf2e0e78477af910cac045b910205))
- **FIX**(EWM-397): formatting. ([5a26933b](https://github.com/broxus/nekoton_repository/commit/5a26933bb70e622259d2d71196a95430df7604ae))


## 2025-10-20

### Changes
Expand Down
6 changes: 5 additions & 1 deletion lib/src/models/account_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class AccountList extends Equatable {

/// Add account to key with [publicKey] and [walletType].
/// [workchain] specify Transport network that should be used for this account
/// [name] is optional and if not specified, auto-generated name will be used.
/// [name] is optional and if not specified, auto-generated name will be used
/// in format "Account N.M".
Future<Address> addAccount({
required WalletType walletType,
required int workchain,
Expand All @@ -46,6 +47,9 @@ class AccountList extends Equatable {
AccountToAdd(
name:
name ??
GetIt.instance<NekotonRepository>().generateDefaultAccountName(
publicKey,
) ??
GetIt.instance<TransportRepository>().currentTransport
.defaultAccountName(walletType),
publicKey: publicKey,
Expand Down
55 changes: 55 additions & 0 deletions lib/src/nekoton_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,61 @@ class NekotonRepository
_keyStore.keysStream.listen((keys) => _hasSeeds.add(keys.isNotEmpty));
}

/// Generates default account name in format "Account N.M"
/// where N is the key position in the seed (1-based) and M is the account
/// position within that key (1-based).
///
/// Returns null if the publicKey is not found in any seed.
String? generateDefaultAccountName(PublicKey publicKey) {
final seed = seedList.findSeedByAnyPublicKey(publicKey);
if (seed == null) return null;

// Find the key within the seed
final keyIndex = seed.allKeys.indexWhere(
(key) => key.publicKey == publicKey,
);
if (keyIndex == -1) return null;

// Key position is 1-based (master key = 1, first derived = 2, etc.)
final keyPosition = keyIndex + 1;

// Get next account number by finding max existing number + 1
final accountPosition = _getNextAccountNumber(
seed.allKeys[keyIndex].accountList.allAccounts,
keyPosition,
);

return 'Account $keyPosition.$accountPosition';
}

/// Gets the next available account number for a specific key position
/// by finding the maximum number in existing account names
/// (format: "Account N.M") and returning max M + 1 for the given N.
/// Returns 1 if no accounts exist or no default names are found.
int _getNextAccountNumber(List<KeyAccount> accounts, int keyPosition) {
if (accounts.isEmpty) return 1;

var maxNumber = 0;

for (final account in accounts) {
final name = account.name;

// Parse "Account N.M" format
final match = RegExp(r'^Account (\d+)\.(\d+)$').firstMatch(name);
if (match != null) {
final n = int.tryParse(match.group(1) ?? '');
final m = int.tryParse(match.group(2) ?? '');

// Only consider accounts for this specific key position
if (n == keyPosition && m != null && m > maxNumber) {
maxNumber = m;
}
}
}

return maxNumber + 1;
}

void _logHandler(fnb.LogEntry logEntry) {
final logLevel = _toLogLevel(logEntry.level);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:get_it/get_it.dart';
import 'package:nekoton_repository/nekoton_repository.dart';

/// Implementation of AccountRepository.
Expand Down Expand Up @@ -63,6 +64,9 @@ mixin AccountRepositoryImpl on TransportRepository
AccountToAdd(
name:
name ??
GetIt.instance<NekotonRepository>().generateDefaultAccountName(
publicKey,
) ??
currentTransport.defaultAccountName(
existingWalletInfo.walletType,
),
Expand Down
64 changes: 59 additions & 5 deletions lib/src/repositories/seed_repository/seed_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:get_it/get_it.dart';
import 'package:nekoton_repository/nekoton_repository.dart';
import 'package:rxdart/rxdart.dart';

const seedPrefix = 'Seed ';

/// Implementation of SeedRepository.
/// Usage
/// ```
Expand Down Expand Up @@ -159,7 +161,11 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
String? name,
SeedAddType addType = SeedAddType.create,
}) async {
name = name?.isEmpty ?? true ? null : name;
// Generate default seed name if not provided
if (name?.isEmpty ?? true) {
name = '$seedPrefix${_getNextSeedNumber()}';
}

mnemonicType ??= phrase.length == 24
? const MnemonicType.legacy()
: const MnemonicType.bip39(
Expand Down Expand Up @@ -239,6 +245,11 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
required int workchainId,
String? name,
}) async {
// Generate default seed name if not provided
if (name?.isEmpty ?? true) {
name = '$seedPrefix${_getNextSeedNumber()}';
}

final publicKey = await keyStore.addKey(
LedgerKeyCreateInput(accountId: accountId, name: name),
);
Expand Down Expand Up @@ -266,8 +277,16 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
final transport = currentTransport;
if (transport.transport.disposed) return;

// For newly created seeds, this will be the first account on master key
// At this point the seed might not be in seedList yet, so we use fallback
final accountName =
GetIt.instance<NekotonRepository>().generateDefaultAccountName(
publicKey,
) ??
transport.defaultAccountName(transport.defaultWalletType);

final defaultAccount = AccountToAdd(
name: transport.defaultAccountName(transport.defaultWalletType),
name: accountName,
publicKey: publicKey,
contract: transport.defaultWalletType,
workchain: workchainId,
Expand Down Expand Up @@ -356,7 +375,10 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
publicKey: a.publicKey,
contract: a.walletType,
workchain: a.address.workchain,
name: transport.defaultAccountName(a.walletType),
name:
GetIt.instance<NekotonRepository>()
.generateDefaultAccountName(a.publicKey) ??
transport.defaultAccountName(a.walletType),
),
),
);
Expand Down Expand Up @@ -434,7 +456,10 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
if (activeAccounts.isEmpty) {
accountsToAdd.add(
AccountToAdd(
name: transport.defaultAccountName(transport.defaultWalletType),
name:
GetIt.instance<NekotonRepository>()
.generateDefaultAccountName(key) ??
transport.defaultAccountName(transport.defaultWalletType),
publicKey: key,
contract: transport.defaultWalletType,
workchain: workchainId,
Expand All @@ -451,7 +476,10 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
publicKey: a.publicKey,
contract: a.walletType,
workchain: a.address.workchain,
name: transport.defaultAccountName(a.walletType),
name:
GetIt.instance<NekotonRepository>()
.generateDefaultAccountName(a.publicKey) ??
transport.defaultAccountName(a.walletType),
),
),
);
Expand Down Expand Up @@ -659,4 +687,30 @@ mixin SeedKeyRepositoryImpl implements SeedKeyRepository {
}
await GetIt.instance<AccountRepository>().removeAccounts(accountsToRemove);
}

/// Gets the next available seed number by finding the maximum number
/// in existing seed names (format: "Seed N") and returning max + 1.
/// Returns 1 if no seeds exist or no default names are found.
int _getNextSeedNumber() {
final seedMeta = storageRepository.seedMeta;
if (seedMeta.isEmpty) return 1;

var maxNumber = 0;

for (final metadata in seedMeta.values) {
final name = metadata.name;
if (name == null) continue;

// Parse "Seed " format
final match = RegExp('^$seedPrefix(\\d+)\$').firstMatch(name);
if (match != null) {
final number = int.tryParse(match.group(1) ?? '');
if (number != null && number > maxNumber) {
maxNumber = number;
}
}
}

return maxNumber + 1;
}
}
17 changes: 9 additions & 8 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: nekoton_repository
description: Nekoton repository package
version: 2.0.1
version: 2.0.3-dev.0
repository: https://github.com/broxus/nekoton_repository

environment:
Expand Down Expand Up @@ -49,8 +49,7 @@ flutter_gen:
package_parameter_enabled: true

flutter:
assets:
- assets/abi/
assets: [assets/abi/]

melos:
useRootAsPackage: true
Expand All @@ -61,10 +60,12 @@ melos:

codegen:
description: Generate code for all packages
exec: "find . -type f -name \"*.gen.dart\" -delete && flutter packages pub run build_runner build --delete-conflicting-outputs && dart format lib/generated/assets.gen.dart lib/nekoton_repository.module.dart"
exec: find . -type f -name "*.gen.dart" -delete && flutter packages pub run
build_runner build --delete-conflicting-outputs && dart format lib/generated/assets.gen.dart
lib/nekoton_repository.module.dart
failFast: true
packageFilters:
dependsOn: "build_runner"
dependsOn: build_runner

analyze:
description: Analyze a specific package in this project.
Expand All @@ -82,15 +83,16 @@ melos:

test:
description: Run Flutter tests for a specific package in this project.
exec: "flutter test test"
exec: flutter test test
failFast: true
packageFilters:
flutter: true
dirExists: test

test:integration:
run: melos exec -c 1 --fail-fast -- "flutter test integration_test"
description: Run Flutter teintegration teststs for a specific package in this project.
description: Run Flutter teintegration teststs for a specific package in this
project.
packageFilters:
flutter: true
dirExists: integration_test
Expand All @@ -112,4 +114,3 @@ melos:
version:
hooks:
preCommit: melos bs && git add --all

Loading