Cw 1291 ledger fixes (#2741)

* fix: handle unexpected ViewModel in ledger connection process

* fix: add missing exception handler for `_QueuedFuture.execute` in `exception_handler.dart`

* fix: handle hardware wallet connection flow in exchange trade page

* refactor: replace hardware wallet conditional logic with switch statement for improved readability and future extensibility

* feat: add hardware wallet support for dEURO savings actions and approval flow

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
Omar Hatem
2025-12-20 01:58:22 +02:00
committed by GitHub
co-authored by Konstantin Ullrich
parent c7e9a4e68d
commit 6c12ed157d
5 changed files with 112 additions and 38 deletions
+27 -17
View File
@@ -210,23 +210,33 @@ abstract class EVMChainWalletBase
// check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯
await _checkForExistingScamTokens();
if (walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
_evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
} else if (walletInfo.hardwareWalletType == HardwareWalletType.bitbox) {
_evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
} else if (walletInfo.hardwareWalletType == HardwareWalletType.trezor) {
_evmChainPrivateKey = EvmTrezorCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
} else {
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
passphrase: passphrase,
);
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
switch(walletInfo.hardwareWalletType) {
case HardwareWalletType.ledger:
_evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
break;
case HardwareWalletType.bitbox:
_evmChainPrivateKey = EvmBitboxCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
break;
case HardwareWalletType.trezor:
_evmChainPrivateKey = EvmTrezorCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
break;
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
case HardwareWalletType.keystone:
throw UnimplementedError();
case null:
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
passphrase: passphrase,
);
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
break;
}
await save();
}
+10 -6
View File
@@ -1612,13 +1612,17 @@ Future<void> setup({
getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get<ExchangeProviderLogsViewModel>()));
getIt.registerFactory(() => StartTorPage(StartTorViewModel(),));
getIt.registerFactory(() => DEuroViewModel(
getIt<AppStore>(),
getIt<BalanceViewModel>(),
getIt<SettingsStore>(),
getIt<FiatConversionStore>(),
));
getIt<AppStore>(),
getIt<BalanceViewModel>(),
getIt<SettingsStore>(),
getIt<FiatConversionStore>(),
getIt.get<AppStore>().wallet!.isHardwareWallet
? getIt<HardwareWalletViewModel>(
param1: getIt.get<AppStore>().wallet!.hardwareWalletType!)
: null,
));
getIt.registerFactory(() => DEuroSavingsPage(getIt<DEuroViewModel>()));
@@ -1,6 +1,7 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
import 'package:cake_wallet/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart';
@@ -190,7 +191,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty ||
sendingState is ExecutedSuccessfullyState,
isLoading: sendingState is IsExecutingState,
onPressed: () => widget.exchangeTradeViewModel.confirmSending(),
onPressed: _onPressedSendFromCakeWallet,
text: S.current.send_from_cake_wallet,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
@@ -204,6 +205,30 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
);
}
Future<void> _onPressedSendFromCakeWallet() async {
final sendVM = widget.exchangeTradeViewModel.sendViewModel;
if (sendVM.wallet.isHardwareWallet) {
if (!sendVM.hardwareWalletViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendVM.walletType,
hardwareWalletType:
sendVM.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, _) {
print(sendVM.wallet);
sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
Navigator.of(context).pop();
},
));
} else {
sendVM.hardwareWalletViewModel!.initWallet(sendVM.wallet);
}
}
widget.exchangeTradeViewModel.confirmSending();
}
BuildContext? dialogContext;
BuildContext? loadingBottomSheetContext;
@@ -1,6 +1,8 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/info_chip.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/interest_card_widget.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
@@ -62,7 +64,7 @@ class DEuroSavingsPage extends BasePage {
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
onAddSavingsPressed: () => _onSavingsAdd(context),
onRemoveSavingsPressed: () => _onSavingsRemove(context),
onApproveSavingsPressed: _dEuroViewModel.prepareApproval,
onApproveSavingsPressed: () => _onApproval(context),
onTooltipPressed: () => _onSavingsTooltipPressed(context),
isEnabled: _dEuroViewModel.isEnabled,
isLoading: _dEuroViewModel.isLoading,
@@ -74,8 +76,8 @@ class DEuroSavingsPage extends BasePage {
fiatAccruedInterest: _dEuroViewModel.fiatAccruedInterestFormated,
fiatCurrency: _dEuroViewModel.isFiatDisabled ? null : _dEuroViewModel.fiat,
accruedInterest: _dEuroViewModel.accruedInterestFormated,
onCollectInterest: _onCollectInterest,
onReinvestInterest: _onReinvestInterest,
onCollectInterest: () => _onCollectInterest(context),
onReinvestInterest: () => _onReinvestInterest(context),
onTooltipPressed: () => _onInterestTooltipPressed(context),
isEnabled: _dEuroViewModel.isSavingsActionsEnabled,
),
@@ -117,7 +119,10 @@ class DEuroSavingsPage extends BasePage {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
final amount = await _showEditBottomSheet(context, isAdding: true);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, true);
if (amount != null) {
await _requireHardwareWallet(context);
_dEuroViewModel.prepareSavingsEdit(amount, true);
}
_editSheetIsOpen = false;
}
@@ -125,24 +130,52 @@ class DEuroSavingsPage extends BasePage {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
final amount = await _showEditBottomSheet(context, isAdding: false);
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, false);
_editSheetIsOpen = false;
if (amount != null) {
await _requireHardwareWallet(context);
_dEuroViewModel.prepareSavingsEdit(amount, false);
}
_editSheetIsOpen = false;
}
Future<void> _onReinvestInterest() async {
Future<void> _onReinvestInterest(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
await _requireHardwareWallet(context);
await _dEuroViewModel.prepareReinvestInterest();
_editSheetIsOpen = false;
}
Future<void> _onCollectInterest() async {
Future<void> _onCollectInterest(BuildContext context) async {
if (_editSheetIsOpen) return;
_editSheetIsOpen = true;
await _requireHardwareWallet(context);
await _dEuroViewModel.prepareCollectInterest();
_editSheetIsOpen = false;
}
Future<void> _onApproval(BuildContext context) async {
await _requireHardwareWallet(context);
_dEuroViewModel.prepareApproval();
}
Future<void> _requireHardwareWallet(BuildContext context) async {
if (_dEuroViewModel.wallet.isHardwareWallet) {
if (!_dEuroViewModel.hardwareWalletViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: _dEuroViewModel.wallet.type,
hardwareWalletType: _dEuroViewModel.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, _) {
_dEuroViewModel.hardwareWalletViewModel!.initWallet(_dEuroViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
_dEuroViewModel.hardwareWalletViewModel!.initWallet(_dEuroViewModel.wallet);
}
}
}
bool _isReactionsSet = false;
void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) {
@@ -7,10 +7,12 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/parse_fixed.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@@ -23,20 +25,20 @@ abstract class DEuroViewModelBase with Store {
static BigInt get MIN_ACCRUED_INTEREST => BigInt.parse("1000000000000");
DEuroViewModelBase(
this._appStore,
this.balanceViewModel,
this._settingsStore,
this._fiatConversationStore,
) {
DEuroViewModelBase(this._appStore, this.balanceViewModel, this._settingsStore,
this._fiatConversationStore,
[this.hardwareWalletViewModel]) {
reloadInterestRate();
reloadSavingsUserData();
}
final BalanceViewModel balanceViewModel;
final HardwareWalletViewModel? hardwareWalletViewModel;
final SettingsStore _settingsStore;
final FiatConversionStore _fiatConversationStore;
WalletBase get wallet => this._appStore.wallet!;
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;