Cw 1291 ledger fixes (#2741)
What changed, and why it matters
This commit fixes several bugs in Cake Wallet's handling of hardware wallets (Ledger, Trezor, BitBox, etc.). It makes sure hardware wallet users are prompted to connect their device before sending funds, using a savings feature, or approving transactions. It also adds a missing error handler and prevents the app from crashing when an unexpected wallet type appears during Ledger setup. There is no direct evidence this is a security patch, but the changes reduce the chance of failed or misrouted transactions for hardware wallet users.
Treat as a routine bug-fix/hardening commit. Review the new hardware-wallet connection flow for race conditions and ensure the UnimplementedError cases cannot be reached by user action. No urgent security response is indicated from the diff alone.
Security signals we found
hardware wallet connection flow now enforced before signing-sensitive actions
missing exception handler added for queued future execution
unhandled ViewModel case addressed in Ledger connection process
switch statement now explicitly fails for unsupported hardware wallet types instead of falling through to software key derivation
Evidence from the diff
The commit refactors EVM wallet initialization to use a switch on HardwareWalletType, explicitly throwing UnimplementedError for cupcake, coldcard, seedsigner, and keystone. It adds a missing exception handler for _QueuedFuture.execute in exception_handler.dart (not visible in the supplied diff). It wires HardwareWalletViewModel into DEuroViewModel via dependency injection and adds _requireHardwareWallet() calls before savings/approval actions. It also changes exchange trade confirmation to route through a hardware-wallet connection flow when needed. The changes are defensive hardening and UX fixes rather than a clear vulnerability fix.
Changed components
cw_evm/lib/evm_chain_wallet.dartlib/di.dartlib/src/screens/exchange_trade/exchange_trade_page.dartlib/src/screens/integrations/deuro/savings_page.dartlib/view_model/integrations/deuro_view_model.dartInspect captured patch +112 / −38
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index b03b180d..3821d27d 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -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();
}
diff --git a/lib/di.dart b/lib/di.dart
index 59b3cfba..fcf45fb1 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -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>()));
diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart
index 7a1587d8..e07d10cc 100644
--- a/lib/src/screens/exchange_trade/exchange_trade_page.dart
+++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart
@@ -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;
diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart
index 57192f2a..4a5d62a0 100644
--- a/lib/src/screens/integrations/deuro/savings_page.dart
+++ b/lib/src/screens/integrations/deuro/savings_page.dart
@@ -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) {
diff --git a/lib/view_model/integrations/deuro_view_model.dart b/lib/view_model/integrations/deuro_view_model.dart
index 31c3ff4a..0bbc176a 100644
--- a/lib/view_model/integrations/deuro_view_model.dart
+++ b/lib/view_model/integrations/deuro_view_model.dart
@@ -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;
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.