From 9814ad3e76a6f00e4e63cd7ca92fdc0a55df4b48 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 12 Aug 2025 18:14:18 +0000 Subject: [PATCH 1/2] Set wallet type based on HD mode during login process Co-authored-by: charl --- lib/bloc/auth_bloc/auth_bloc.dart | 1 + lib/views/wallets_manager/widgets/wallet_login.dart | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/bloc/auth_bloc/auth_bloc.dart b/lib/bloc/auth_bloc/auth_bloc.dart index da1eda929a..40add5338b 100644 --- a/lib/bloc/auth_bloc/auth_bloc.dart +++ b/lib/bloc/auth_bloc/auth_bloc.dart @@ -105,6 +105,7 @@ class AuthBloc extends Bloc with TrezorAuthMixin { } _log.info('logged in from a wallet'); + await _kdfSdk.setWalletType(event.wallet.config.type); emit(AuthBlocState.loggedIn(currentUser)); _listenToAuthStateChanges(); } catch (e, s) { diff --git a/lib/views/wallets_manager/widgets/wallet_login.dart b/lib/views/wallets_manager/widgets/wallet_login.dart index e094945e49..f5d97ef845 100644 --- a/lib/views/wallets_manager/widgets/wallet_login.dart +++ b/lib/views/wallets_manager/widgets/wallet_login.dart @@ -74,9 +74,7 @@ class _WalletLogInState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { widget.wallet.config.type = - _isHdMode && _user != null && _user!.isBip39Seed == true - ? WalletType.hdwallet - : WalletType.iguana; + _isHdMode ? WalletType.hdwallet : WalletType.iguana; widget.onLogin( _passwordController.text, From ce496c63e839e8a3a764762c01ff1bbf0edcb149 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:17:37 +0200 Subject: [PATCH 2/2] refactor(auth): remove unncessary HD state tracking Remove unnecessary HD state storage in metadata in favour of letting SDK handle it. --- lib/bloc/auth_bloc/auth_bloc.dart | 10 ++- lib/bloc/auth_bloc/trezor_auth_mixin.dart | 1 - lib/bloc/coins_bloc/coins_bloc.dart | 66 ++++++++----------- lib/model/kdf_auth_metadata_extension.dart | 18 +++-- lib/model/wallet.dart | 58 ++++++++-------- .../wallets_manager/widgets/wallet_login.dart | 32 ++++----- 6 files changed, 83 insertions(+), 102 deletions(-) diff --git a/lib/bloc/auth_bloc/auth_bloc.dart b/lib/bloc/auth_bloc/auth_bloc.dart index 40add5338b..ffebd4f66e 100644 --- a/lib/bloc/auth_bloc/auth_bloc.dart +++ b/lib/bloc/auth_bloc/auth_bloc.dart @@ -24,7 +24,7 @@ class AuthBloc extends Bloc with TrezorAuthMixin { /// Handles [AuthBlocEvent]s and emits [AuthBlocState]s. /// [_kdfSdk] is an instance of [KomodoDefiSdk] used for authentication. AuthBloc(this._kdfSdk, this._walletsRepository, this._settingsRepository) - : super(AuthBlocState.initial()) { + : super(AuthBlocState.initial()) { on(_onAuthChanged); on(_onClearState); on(_onLogout); @@ -105,7 +105,6 @@ class AuthBloc extends Bloc with TrezorAuthMixin { } _log.info('logged in from a wallet'); - await _kdfSdk.setWalletType(event.wallet.config.type); emit(AuthBlocState.loggedIn(currentUser)); _listenToAuthStateChanges(); } catch (e, s) { @@ -172,7 +171,6 @@ class AuthBloc extends Bloc with TrezorAuthMixin { ); _log.info('registered from a wallet'); - await _kdfSdk.setWalletType(event.wallet.config.type); await _kdfSdk.confirmSeedBackup(hasBackup: false); await _kdfSdk.addActivatedCoins(enabledByDefaultCoins); @@ -221,7 +219,6 @@ class AuthBloc extends Bloc with TrezorAuthMixin { ); _log.info('restored from a wallet'); - await _kdfSdk.setWalletType(event.wallet.config.type); await _kdfSdk.confirmSeedBackup(hasBackup: event.wallet.config.hasBackup); await _kdfSdk.addActivatedCoins(enabledByDefaultCoins); @@ -343,8 +340,9 @@ class AuthBloc extends Bloc with TrezorAuthMixin { void _listenToAuthStateChanges() { _authChangesSubscription?.cancel(); _authChangesSubscription = _kdfSdk.auth.watchCurrentUser().listen((user) { - final AuthorizeMode event = - user != null ? AuthorizeMode.logIn : AuthorizeMode.noLogin; + final AuthorizeMode event = user != null + ? AuthorizeMode.logIn + : AuthorizeMode.noLogin; add(AuthModeChanged(mode: event, currentUser: user)); }); } diff --git a/lib/bloc/auth_bloc/trezor_auth_mixin.dart b/lib/bloc/auth_bloc/trezor_auth_mixin.dart index 877a5ba998..6816c2d22c 100644 --- a/lib/bloc/auth_bloc/trezor_auth_mixin.dart +++ b/lib/bloc/auth_bloc/trezor_auth_mixin.dart @@ -125,7 +125,6 @@ mixin TrezorAuthMixin on Bloc { ); } - await _sdk.setWalletType(WalletType.trezor); await _sdk.confirmSeedBackup(hasBackup: true); if (authState.user!.wallet.config.activatedCoins.isEmpty) { // If no coins are activated, we assume this is the first time diff --git a/lib/bloc/coins_bloc/coins_bloc.dart b/lib/bloc/coins_bloc/coins_bloc.dart index 3981424fe3..e498af3025 100644 --- a/lib/bloc/coins_bloc/coins_bloc.dart +++ b/lib/bloc/coins_bloc/coins_bloc.dart @@ -20,10 +20,7 @@ part 'coins_state.dart'; /// Responsible for coin activation, deactivation, syncing, and fiat price class CoinsBloc extends Bloc { - CoinsBloc( - this._kdfSdk, - this._coinsRepo, - ) : super(CoinsState.initial()) { + CoinsBloc(this._kdfSdk, this._coinsRepo) : super(CoinsState.initial()) { on(_onCoinsStarted, transformer: droppable()); // TODO: move auth listener to ui layer: bloclistener should fire auth events on(_onCoinsBalanceMonitoringStarted); @@ -75,14 +72,7 @@ class CoinsBloc extends Bloc { final pubkeys = await _kdfSdk.pubkeys.getPubkeys(asset); // Update state with new pubkeys - emit( - state.copyWith( - pubkeys: { - ...state.pubkeys, - event.coinId: pubkeys, - }, - ), - ); + emit(state.copyWith(pubkeys: {...state.pubkeys, event.coinId: pubkeys})); } catch (e, s) { _log.shout('Failed to get pubkeys for ${event.coinId}', e, s); } @@ -201,14 +191,16 @@ class CoinsBloc extends Bloc { emit(_prePopulateListWithActivatingCoins(event.coinIds)); await _activateCoins(event.coinIds, emit); - final currentWallet = await _kdfSdk.currentWallet(); - if (currentWallet?.config.type == WalletType.iguana || - currentWallet?.config.type == WalletType.hdwallet) { + final currentUser = await _kdfSdk.auth.currentUser; + final derivation = currentUser?.walletId.authOptions.derivationMethod; + if (derivation == DerivationMethod.iguana || + derivation == DerivationMethod.hdWallet) { final coinUpdates = _syncIguanaCoinsStates(); await emit.forEach( coinUpdates, - onData: (coin) => state - .copyWith(walletCoins: {...state.walletCoins, coin.id.id: coin}), + onData: (coin) => state.copyWith( + walletCoins: {...state.walletCoins, coin.id.id: coin}, + ), ); } @@ -261,8 +253,9 @@ class CoinsBloc extends Bloc { Map currentCoins, ) { final updatedWalletCoins = Map.fromEntries( - currentWalletCoins.entries - .where((entry) => !coinsToDisable.contains(entry.key)), + currentWalletCoins.entries.where( + (entry) => !coinsToDisable.contains(entry.key), + ), ); final updatedCoins = Map.of(currentCoins); for (final assetId in coinsToDisable) { @@ -337,11 +330,7 @@ class CoinsBloc extends Bloc { ) async { add(CoinsBalanceMonitoringStopped()); - emit( - state.copyWith( - walletCoins: {}, - ), - ); + emit(state.copyWith(walletCoins: {})); _coinsRepo.flushCache(); } @@ -376,12 +365,10 @@ class CoinsBloc extends Bloc { final knownCoins = _coinsRepo.getKnownCoinsMap(); final activatingCoins = Map.fromIterable( coins - .map( - (coin) { - final sdkCoin = knownCoins[coin]; - return sdkCoin?.copyWith(state: CoinState.activating); - }, - ) + .map((coin) { + final sdkCoin = knownCoins[coin]; + return sdkCoin?.copyWith(state: CoinState.activating); + }) .where((coin) => coin != null) .cast(), key: (element) => (element as Coin).id.id, @@ -398,8 +385,7 @@ class CoinsBloc extends Bloc { /// When multiple coins are found for the provided IDs, Stream _syncIguanaCoinsStates() async* { final coinsBlocWalletCoinsState = state.walletCoins; - final previouslyActivatedCoinIds = - (await _kdfSdk.currentWallet())?.config.activatedCoins ?? []; + final previouslyActivatedCoinIds = (await _kdfSdk.getWalletCoinIds()); final walletAssets = []; for (final coinId in previouslyActivatedCoinIds) { @@ -413,8 +399,10 @@ class CoinsBloc extends Bloc { } if (assets.length > 1) { final assetIds = assets.map((a) => a.id.id).join(', '); - _log.shout('Multiple assets found for activated coin ID: $coinId. ' - 'Expected single asset, found ${assets.length}: $assetIds. '); + _log.shout( + 'Multiple assets found for activated coin ID: $coinId. ' + 'Expected single asset, found ${assets.length}: $assetIds. ', + ); } // This is expected to throw if there are multiple assets, to stick @@ -422,8 +410,10 @@ class CoinsBloc extends Bloc { walletAssets.add(assets.single); } - final coinsToSync = - _getWalletCoinsNotInState(walletAssets, coinsBlocWalletCoinsState); + final coinsToSync = _getWalletCoinsNotInState( + walletAssets, + coinsBlocWalletCoinsState, + ); if (coinsToSync.isNotEmpty) { _log.info( 'Found ${coinsToSync.length} wallet coins not in state, ' @@ -434,7 +424,9 @@ class CoinsBloc extends Bloc { } List _getWalletCoinsNotInState( - List walletAssets, Map coinsBlocWalletCoinsState) { + List walletAssets, + Map coinsBlocWalletCoinsState, + ) { final List coinsToSyncToState = []; final enabledAssetsNotInState = walletAssets diff --git a/lib/model/kdf_auth_metadata_extension.dart b/lib/model/kdf_auth_metadata_extension.dart index 92bcac6cd4..ff4386fb84 100644 --- a/lib/model/kdf_auth_metadata_extension.dart +++ b/lib/model/kdf_auth_metadata_extension.dart @@ -31,9 +31,10 @@ extension KdfAuthMetadataExtension on KomodoDefiSdk { } Future addActivatedCoins(Iterable coins) async { - final existingCoins = (await auth.currentUser) - ?.metadata - .valueOrNull>('activated_coins') ?? + final existingCoins = + (await auth.currentUser)?.metadata.valueOrNull>( + 'activated_coins', + ) ?? []; final mergedCoins = {...existingCoins, ...coins}.toList(); @@ -41,9 +42,10 @@ extension KdfAuthMetadataExtension on KomodoDefiSdk { } Future removeActivatedCoins(List coins) async { - final existingCoins = (await auth.currentUser) - ?.metadata - .valueOrNull>('activated_coins') ?? + final existingCoins = + (await auth.currentUser)?.metadata.valueOrNull>( + 'activated_coins', + ) ?? []; existingCoins.removeWhere((coin) => coins.contains(coin)); @@ -53,8 +55,4 @@ extension KdfAuthMetadataExtension on KomodoDefiSdk { Future confirmSeedBackup({bool hasBackup = true}) async { await auth.setOrRemoveActiveUserKeyValue('has_backup', hasBackup); } - - Future setWalletType(WalletType type) async { - await auth.setOrRemoveActiveUserKeyValue('type', type.name); - } } diff --git a/lib/model/wallet.dart b/lib/model/wallet.dart index 2a18f8637f..a3cc25976f 100644 --- a/lib/model/wallet.dart +++ b/lib/model/wallet.dart @@ -1,24 +1,22 @@ import 'package:komodo_defi_sdk/komodo_defi_sdk.dart'; import 'package:komodo_defi_types/komodo_defi_type_utils.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; +import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart' + show PrivateKeyPolicy; import 'package:uuid/uuid.dart'; import 'package:web_dex/app_config/app_config.dart'; import 'package:web_dex/shared/utils/encryption_tool.dart'; class Wallet { - Wallet({ - required this.id, - required this.name, - required this.config, - }); + Wallet({required this.id, required this.name, required this.config}); factory Wallet.fromJson(Map json) => Wallet( - id: json['id'] as String? ?? '', - name: json['name'] as String? ?? '', - config: WalletConfig.fromJson( - json['config'] as Map? ?? {}, - ), - ); + id: json['id'] as String? ?? '', + name: json['name'] as String? ?? '', + config: WalletConfig.fromJson( + json['config'] as Map? ?? {}, + ), + ); /// Creates a wallet from a name and the optional parameters. /// [name] - The name of the wallet. @@ -49,11 +47,7 @@ class Wallet { required String name, required WalletConfig config, }) { - return Wallet( - id: const Uuid().v1(), - name: name, - config: config, - ); + return Wallet(id: const Uuid().v1(), name: name, config: config); } String id; @@ -67,17 +61,13 @@ class Wallet { await EncryptionTool().decryptData(password, config.seedPhrase) ?? ''; Map toJson() => { - 'id': id, - 'name': name, - 'config': config.toJson(), - }; + 'id': id, + 'name': name, + 'config': config.toJson(), + }; Wallet copy() { - return Wallet( - id: id, - name: name, - config: config.copy(), - ); + return Wallet(id: id, name: name, config: config.copy()); } } @@ -93,14 +83,16 @@ class WalletConfig { factory WalletConfig.fromJson(Map json) { return WalletConfig( + // Legacy wallets may still carry a serialized type. We keep it for + // compatibility, but the app should use SDK auth options as source of truth. type: WalletType.fromJson( json['type'] as String? ?? WalletType.iguana.name, ), seedPhrase: json['seed_phrase'] as String? ?? '', pubKey: json['pub_key'] as String?, - activatedCoins: - List.from(json['activated_coins'] as List? ?? []) - .toList(), + activatedCoins: List.from( + json['activated_coins'] as List? ?? [], + ).toList(), hasBackup: json['has_backup'] as bool? ?? false, ); } @@ -114,6 +106,7 @@ class WalletConfig { Map toJson() { return { + // Retain type for legacy export. Runtime uses SDK auth options. 'type': type.name, 'seed_phrase': seedPhrase, 'pub_key': pubKey, @@ -158,8 +151,13 @@ enum WalletType { extension KdfUserWalletExtension on KdfUser { Wallet get wallet { - final walletType = - WalletType.fromJson(metadata['type'] as String? ?? 'iguana'); + // Derive wallet type from SDK auth options rather than metadata + final isTrezor = + walletId.authOptions.privKeyPolicy == const PrivateKeyPolicy.trezor(); + final isHd = walletId.isHd; + final walletType = isTrezor + ? WalletType.trezor + : (isHd ? WalletType.hdwallet : WalletType.iguana); return Wallet( id: walletId.name, name: walletId.name, diff --git a/lib/views/wallets_manager/widgets/wallet_login.dart b/lib/views/wallets_manager/widgets/wallet_login.dart index f5d97ef845..41fcd0f21e 100644 --- a/lib/views/wallets_manager/widgets/wallet_login.dart +++ b/lib/views/wallets_manager/widgets/wallet_login.dart @@ -49,13 +49,14 @@ class _WalletLogInState extends State { Future _fetchKdfUser() async { final kdfSdk = RepositoryProvider.of(context); final users = await kdfSdk.auth.getUsers(); - final user = users - .firstWhereOrNull((user) => user.walletId.name == widget.wallet.name); + final user = users.firstWhereOrNull( + (user) => user.walletId.name == widget.wallet.name, + ); if (user != null) { setState(() { _user = user; - _isHdMode = user.wallet.config.type == WalletType.hdwallet; + _isHdMode = user.walletId.isHd; }); } } @@ -73,13 +74,11 @@ class _WalletLogInState extends State { } WidgetsBinding.instance.addPostFrameCallback((_) { - widget.wallet.config.type = - _isHdMode ? WalletType.hdwallet : WalletType.iguana; + widget.wallet.config.type = _isHdMode + ? WalletType.hdwallet + : WalletType.iguana; - widget.onLogin( - _passwordController.text, - widget.wallet, - ); + widget.onLogin(_passwordController.text, widget.wallet); }); } @@ -89,8 +88,8 @@ class _WalletLogInState extends State { builder: (context, state) { final errorMessage = state.authError?.type == AuthExceptionType.incorrectPassword - ? LocaleKeys.incorrectPassword.tr() - : state.authError?.message; + ? LocaleKeys.incorrectPassword.tr() + : state.authError?.message; return AutofillGroup( child: Column( @@ -98,10 +97,9 @@ class _WalletLogInState extends State { children: [ Text( LocaleKeys.walletLogInTitle.tr(), - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith(fontSize: 18), + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontSize: 18), ), const SizedBox(height: 40), UiTextFormField( @@ -111,9 +109,7 @@ class _WalletLogInState extends State { autocorrect: false, autofillHints: const [AutofillHints.username], ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), PasswordTextField( onFieldSubmitted: state.isLoading ? null : _submitLogin, controller: _passwordController,