CW-1291-ledger-fixes (#3390)
What changed, and why it matters
This commit fixes bugs in how Cake Wallet connects to Ledger hardware wallets. It adds guards so the app doesn't crash or behave oddly when no Ledger connection exists, prevents duplicate connection attempts, improves cleanup when switching wallets, and updates the underlying Ledger Bluetooth/USB library versions. There is no obvious new security vulnerability here; the changes look like stability and reliability improvements for hardware wallet users.
Review the updated ledger_flutter_plus and universal_ble dependency commits for any security-relevant changes, since the app now relies on upstream versions. Verify that the new `close()` path always cancels subscriptions and disconnects cleanly to avoid stale Ledger sessions. Otherwise treat as a routine reliability fix.
Security signals we found
Null/guard added for missing global LedgerConnection before wallet load
Duplicate connection attempt prevented via isConnecting flag
Ledger connection lifecycle cleanup added on wallet change
Hardware wallet dependency versions bumped to newer git refs
No cryptographic, authentication bypass, or secret-handling changes observed
Evidence from the diff
The patch refactors Ledger hardware wallet integration: it introduces hasGlobalLedgerConnection() checks before reconnect prompts, suppresses repeated reconnect dialogs during authentication, converts private _isConnecting flags to observable MobX isConnecting fields, adds a close() lifecycle method to reset Ledger subscriptions/connections when the wallet changes, and bumps ledger_flutter_plus and universal_ble git refs. Dependency changes move the override from a Cake Wallet fork to the upstream vespr-wallet/ledger-flutter-plus and drop the ledger_usb_plus override. UI changes add a connecting indicator and prevent double-tap connection races.
Changed components
lib/monero/cw_monero.dartlib/reactions/on_authentication_state_change.dartlib/view_model/hardware_wallet/ledger_view_model.dartlib/view_model/hardware_wallet/bitbox_view_model.dartlib/view_model/hardware_wallet/trezor_connect_view_model.dartlib/view_model/hardware_wallet/hardware_wallet_view_model.dartlib/src/screens/connect_device/connect_device_page.dartpubspec_base.yamlInspect captured patch +420 / −285
diff --git a/lib/di.dart b/lib/di.dart
index 1415bf0..6bb8bd9 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -421,16 +421,19 @@ Future<void> setup({
case HardwareWalletType.seedsigner:
case HardwareWalletType.keystone:
throw Exception(
- "This should not have happened, because airgapped Wallets don't need View Models");
+ "This should not have happened, because airgapped Wallets don't need View Models",
+ );
}
});
- getIt.registerLazySingleton(() => LedgerViewModel());
+ getIt.registerLazySingleton(() => LedgerViewModel(getIt<AppStore>()));
- getIt.registerLazySingleton(() => BitboxViewModel());
+ getIt.registerLazySingleton(BitboxViewModel.new);
getIt.registerLazySingleton(
- () => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet"));
+ () => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet"),
+ );
+
getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt<TrezorConnect>()));
getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart
index e6ddaba..1551b95 100644
--- a/lib/monero/cw_monero.dart
+++ b/lib/monero/cw_monero.dart
@@ -433,6 +433,9 @@ class CWMonero extends Monero {
@override
void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
+ @override
+ bool hasGlobalLedgerConnection() => gLedger != null;
+
@override
String? getLastLedgerCommand() => latestLedgerCommand;
diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart
index 49a3c61..02ddd56 100644
--- a/lib/reactions/on_authentication_state_change.dart
+++ b/lib/reactions/on_authentication_state_change.dart
@@ -75,6 +75,23 @@ void startAuthenticationStateChange(
);
return;
}
+
+ ledgerVM.suppressMoneroReconnectPrompt = true;
+
+ if (!monero!.hasGlobalLedgerConnection()) {
+ await showPopUp<void>(
+ context: context,
+ builder: (context) => AlertWithOneAction(
+ alertTitle: "Unexpected Error",
+ alertContent:
+ "Unexpected Error while trying to connect to the device (has no global LedgerConnection)",
+ buttonText: S.of(context).try_again,
+ buttonAction: Navigator.of(context).pop,
+ ),
+ );
+ return;
+ }
+
showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
@@ -85,14 +102,17 @@ void startAuthenticationStateChange(
buttonAction: () => Navigator.of(context).pop(),
),
);
- bool tryOpening = true;
+
+ var isConnected = false;
+ var tryOpening = true;
while (tryOpening) {
try {
await loadCurrentWallet();
+ isConnected = true;
tryOpening = false;
} on Exception catch (e) {
final ledgerErrorMessage = ledgerVM.interpretErrorCode(e.toString());
- if (ledgerErrorMessage != null) {
+ if (ledgerErrorMessage != null && context.mounted) {
await showPopUp<void>(
context: context,
builder: (context) => AlertWithTwoActions(
@@ -116,16 +136,17 @@ void startAuthenticationStateChange(
}
getIt.get<BottomSheetService>().showNext();
- await navigatorKey.currentState!
- .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
+ if (isConnected) {
+ ledgerVM.suppressMoneroReconnectPrompt = false;
+ await navigatorKey.currentState!
+ .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
+ }
},
allowChangeWallet: true,
),
);
-
- // await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.connectDevices, (route) => false, arguments: ConnectDevicePageParams(walletType: walletType, onConnectDevice: onConnectDevice));
} else {
- navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
+ await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
final linkViewModel = getIt.get<LinkViewModel>();
diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart
index c96ec27..21cbd02 100644
--- a/lib/src/screens/connect_device/connect_device_page.dart
+++ b/lib/src/screens/connect_device/connect_device_page.dart
@@ -1,54 +1,54 @@
-import 'dart:async';
-import 'dart:io';
-
-import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/main.dart';
-import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/base_page.dart';
-import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
-import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
-import 'package:cake_wallet/src/widgets/primary_button.dart';
-import 'package:cake_wallet/themes/core/material_base_theme.dart';
-import 'package:cake_wallet/utils/responsive_layout_util.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:cw_core/wallet_info.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_mobx/flutter_mobx.dart';
+import "dart:async";
+import "dart:io";
+
+import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/main.dart";
+import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
+import "package:cake_wallet/routes.dart";
+import "package:cake_wallet/src/screens/base_page.dart";
+import "package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart";
+import "package:cake_wallet/src/widgets/cake_image_widget.dart";
+import "package:cake_wallet/src/widgets/primary_button.dart";
+import "package:cake_wallet/themes/core/material_base_theme.dart";
+import "package:cake_wallet/utils/responsive_layout_util.dart";
+import "package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:cw_core/wallet_info.dart";
+import "package:cw_core/wallet_type.dart";
+import "package:flutter/cupertino.dart";
+import "package:flutter/material.dart";
+import "package:flutter_mobx/flutter_mobx.dart";
typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
class ConnectDevicePageParams {
- final WalletType walletType;
- final OnConnectDevice onConnectDevice;
- final bool allowChangeWallet;
- final bool isReconnect;
- final HardwareWalletType hardwareWalletType;
-
- ConnectDevicePageParams({
+ const ConnectDevicePageParams({
required this.walletType,
required this.hardwareWalletType,
required this.onConnectDevice,
this.allowChangeWallet = false,
this.isReconnect = true,
});
-}
-class ConnectDevicePage extends BasePage {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
- final HardwareWalletViewModel hardwareWalletVM;
+ final HardwareWalletType hardwareWalletType;
+}
+class ConnectDevicePage extends BasePage {
ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet,
isReconnect = params.isReconnect;
+ final WalletType walletType;
+ final OnConnectDevice onConnectDevice;
+ final bool allowChangeWallet;
+ final bool isReconnect;
+ final HardwareWalletViewModel hardwareWalletVM;
@override
String get title => isReconnect
@@ -60,31 +60,32 @@ class ConnectDevicePage extends BasePage {
@override
Widget body(BuildContext context) => PopScope(
- canPop: !isReconnect,
- child: ConnectDevicePageBody(
- walletType,
- onConnectDevice,
- allowChangeWallet,
- hardwareWalletVM,
- currentTheme,
- ));
+ canPop: !isReconnect,
+ child: ConnectDevicePageBody(
+ walletType,
+ onConnectDevice,
+ hardwareWalletVM,
+ currentTheme,
+ allowChangeWallet: allowChangeWallet,
+ ),
+ );
}
class ConnectDevicePageBody extends StatefulWidget {
+ const ConnectDevicePageBody(
+ this.walletType,
+ this.onConnectDevice,
+ this.hardwareWalletVM,
+ this.currentTheme, {
+ this.allowChangeWallet = false,
+ });
+
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final HardwareWalletViewModel hardwareWalletVM;
final MaterialThemeBase currentTheme;
- const ConnectDevicePageBody(
- this.walletType,
- this.onConnectDevice,
- this.allowChangeWallet,
- this.hardwareWalletVM,
- this.currentTheme,
- );
-
@override
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
}
@@ -95,10 +96,10 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
List<HardwareWalletDevice> get allDevices => [...bleDevices, ...usbDevices];
- late Timer? _usbRefreshTimer = null;
- late Timer? _bleRefreshTimer = null;
- late Timer? _bleStateTimer = null;
- late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
+ Timer? _usbRefreshTimer;
+ Timer? _bleRefreshTimer;
+ Timer? _bleStateTimer;
+ StreamSubscription<HardwareWalletDevice>? _bleRefresh;
bool longWait = false;
Timer? _longWaitTimer;
@@ -107,19 +108,22 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
- _bleStateTimer =
- Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
+ _bleStateTimer = Timer.periodic(
+ const Duration(seconds: 1),
+ (_) => widget.hardwareWalletVM.updateBleState(),
+ );
- _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
+ _bleRefreshTimer = Timer.periodic(const Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) {
- _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
+ _usbRefreshTimer = Timer.periodic(const Duration(seconds: 1), (_) => _refreshUsbDevices());
}
if (widget.hardwareWalletVM.hasBluetooth) {
- _longWaitTimer = Timer(Duration(seconds: 10), () {
- if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
+ _longWaitTimer = Timer(const Duration(seconds: 10), () {
+ if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty) {
setState(() => longWait = true);
+ }
});
}
});
@@ -134,22 +138,29 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_longWaitTimer?.cancel();
widget.hardwareWalletVM.stopScanning();
+ _isConnectPressed = false;
super.dispose();
}
Future<void> _refreshUsbDevices() async {
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
- if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
+
+ if (usbDevices.length != dev.length) {
+ setState(() => usbDevices = dev);
+ }
}
Future<void> _refreshBleDevices() async {
try {
if (widget.hardwareWalletVM.isBleEnabled) {
- _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
- bleDevices.add(device);
- if (longWait) longWait = false;
- }))
- ..onError((Object e) {
+ _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen(
+ (device) => setState(() {
+ bleDevices.add(device);
+ if (longWait) {
+ longWait = false;
+ }
+ }),
+ )..onError((e) {
printV(e);
});
_bleRefreshTimer?.cancel();
@@ -160,48 +171,66 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
}
}
+ var _isConnectPressed = false;
+
Future<void> _connectToDevice(HardwareWalletDevice device) async {
- final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
+ if (_isConnectPressed) {
+ return;
+ }
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (isConnected)
- widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
- });
+ _isConnectPressed = true;
+ try {
+ final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
+ _isConnectPressed = false;
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (isConnected) {
+ widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
+ }
+ });
+ } catch (_) {
+ _isConnectPressed = false;
+ }
}
String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
+ printV(deviceType);
switch (deviceType) {
case HardwareWalletDeviceType.ledgerNanoX:
- return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
case HardwareWalletDeviceType.ledgerNanoGen5:
- return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg';
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg";
case HardwareWalletDeviceType.ledgerStax:
- return 'assets/new-ui/hardware_wallets/ledger_stax.svg';
+ return "assets/new-ui/hardware_wallets/device_ledger_stax.svg";
case HardwareWalletDeviceType.ledgerFlex:
- return 'assets/new-ui/hardware_wallets/ledger_flex.svg';
+ return "assets/new-ui/hardware_wallets/device_ledger_flex.svg";
case HardwareWalletDeviceType.BitBox02:
case HardwareWalletDeviceType.BitBox02Nova:
- return 'assets/new-ui/hardware_wallets/device_bitbox.svg';
+ return "assets/new-ui/hardware_wallets/device_bitbox.svg";
case HardwareWalletDeviceType.trezorModelOne:
- return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg';
+ return "assets/new-ui/hardware_wallets/device_trezor_model_one.svg";
case HardwareWalletDeviceType.trezorModelT:
- return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg';
+ return "assets/new-ui/hardware_wallets/device_trezor_model_t.svg";
case HardwareWalletDeviceType.trezorSafe3:
- return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg';
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_3.svg";
case HardwareWalletDeviceType.trezorSafe5:
- return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg';
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
case HardwareWalletDeviceType.trezorSafe7:
- return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg';
+ return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
default:
- return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
+ return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
}
}
String get description {
- if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
+ if (!Platform.isAndroid) {
+ return S.of(context).connect_your_hardware_wallet_ble;
+ }
- if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
+ if (widget.hardwareWalletVM.hasBluetooth) {
+ return S.of(context).connect_your_hardware_wallet;
+ }
return S.of(context).connect_your_hardware_wallet_usb;
}
@@ -210,7 +239,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Widget build(BuildContext context) => Center(
child: Container(
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
- padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
+ padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 24),
child: Column(
children: [
Expanded(
@@ -218,9 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: Column(
spacing: 24,
children: [
- CakeImageWidget(
- imageUrl: "assets/new-ui/hardware_wallet.svg",
- ),
+ const CakeImageWidget(imageUrl: "assets/new-ui/hardware_wallet.svg"),
Text(
description,
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
@@ -231,7 +258,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
offstage: widget.hardwareWalletVM.isBleEnabled ||
!widget.hardwareWalletVM.hasBluetooth,
child: Padding(
- padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
+ padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: Theme.of(context).textTheme.titleMedium,
@@ -242,67 +269,89 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
),
Container(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surfaceContainer,
- borderRadius: BorderRadius.circular(18)),
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(18),
+ ),
child: ListView.separated(
- shrinkWrap: true,
- itemBuilder: (context, index) {
- final item = allDevices[index];
- return GestureDetector(
- onTap: () => _connectToDevice(item),
- behavior: HitTestBehavior.opaque,
- child: SizedBox(
- height: 48,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Row(
- spacing: 12,
- children: [
+ shrinkWrap: true,
+ padding: EdgeInsets.zero,
+ itemBuilder: (context, index) {
+ final item = allDevices[index];
+ return GestureDetector(
+ onTap: () => _connectToDevice(item),
+ behavior: HitTestBehavior.opaque,
+ child: SizedBox(
+ height: 48,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ spacing: 12,
+ children: [
+ Observer(
+ builder: (_) {
+ if (widget.hardwareWalletVM.isConnecting) {
+ return CupertinoActivityIndicator(
+ animating: true,
+ color: Theme.of(context)
+ .colorScheme
+ .onSurfaceVariant,
+ radius: 12,
+ );
+ }
+
+ return CakeImageWidget(
+ imageUrl: _getDeviceTileLeading(item.type),
+ width: 24,
+ height: 24,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ );
+ },
+ ),
+ Text(item.name),
+ ],
+ ),
+ Row(
+ spacing: 12,
+ children: [
+ if (item.connectionType ==
+ HardwareWalletConnectionType.ble)
CakeImageWidget(
- imageUrl: _getDeviceTileLeading(item.type),
+ imageUrl: "assets/new-ui/bluetooth.svg",
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
- Theme.of(context).colorScheme.onSurfaceVariant,
- BlendMode.srcIn),
- ),
- Text(item.name)
- ],
- ),
- Row(
- spacing: 12,
- children: [
- if (item.connectionType ==
- HardwareWalletConnectionType.ble)
- CakeImageWidget(
- imageUrl: "assets/new-ui/bluetooth.svg",
- width: 24,
- height: 24,
- colorFilter: ColorFilter.mode(
- Theme.of(context).colorScheme.primary,
- BlendMode.srcIn),
+ Theme.of(context).colorScheme.primary,
+ BlendMode.srcIn,
),
- CakeImageWidget(
- imageUrl: "assets/new-ui/arrow_forward.svg",
- height: 16,
- colorFilter: ColorFilter.mode(
- Theme.of(context).colorScheme.onSurfaceVariant,
- BlendMode.srcIn))
- ],
- )
- ],
- ),
+ ),
+ CakeImageWidget(
+ imageUrl: "assets/new-ui/arrow_forward.svg",
+ height: 16,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSurfaceVariant,
+ BlendMode.srcIn,
+ ),
+ ),
+ ],
+ ),
+ ],
),
),
- );
- },
- separatorBuilder: (context, index) => Container(
- height: 1,
- color: Theme.of(context).colorScheme.surfaceContainerHigh),
- itemCount: allDevices.length),
+ ),
+ );
+ },
+ separatorBuilder: (context, index) => Container(
+ height: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ ),
+ itemCount: allDevices.length,
+ ),
),
if (widget.allowChangeWallet) ...[
PrimaryButton(
@@ -310,7 +359,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: _onChangeWallet,
- )
+ ),
],
],
),
@@ -321,7 +370,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.primary,
onPressed: () => _onHowToConnect(context),
- )
+ ),
],
),
),
@@ -339,17 +388,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
showModalBottomSheet(
context: context,
isScrollControlled: true,
- builder: (BuildContext bottomSheetContext) => InfoStepsBottomSheet(
+ builder: (_) => InfoStepsBottomSheet(
titleText: S.of(context).how_to_connect,
steps: [
- InfoStep('assets/images/wallet_connect_step_icons/step1_power.svg',
- S.of(context).connect_hw_info_step_1),
- InfoStep('assets/images/wallet_connect_step_icons/step2_connect.svg',
- S.of(context).connect_hw_info_step_2),
- InfoStep('assets/images/wallet_connect_step_icons/step3_unlock.svg',
- S.of(context).connect_hw_info_step_3),
- InfoStep('assets/images/wallet_connect_step_icons/step4_select.svg',
- S.of(context).connect_hw_info_step_4),
+ InfoStep(
+ "assets/images/wallet_connect_step_icons/step1_power.svg",
+ S.of(context).connect_hw_info_step_1,
+ ),
+ InfoStep(
+ "assets/images/wallet_connect_step_icons/step2_connect.svg",
+ S.of(context).connect_hw_info_step_2,
+ ),
+ InfoStep(
+ "assets/images/wallet_connect_step_icons/step3_unlock.svg",
+ S.of(context).connect_hw_info_step_3,
+ ),
+ InfoStep(
+ "assets/images/wallet_connect_step_icons/step4_select.svg",
+ S.of(context).connect_hw_info_step_4,
+ ),
],
),
);
diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart
index b3ac158..85a9bbb 100644
--- a/lib/store/app_store.dart
+++ b/lib/store/app_store.dart
@@ -1,25 +1,25 @@
-import 'dart:async';
-
-import 'package:cake_wallet/core/amount_parsing_proxy.dart';
-import 'package:cake_wallet/di.dart';
-import 'package:cake_wallet/entities/preferences_key.dart';
-import 'package:cake_wallet/reactions/wallet_connect.dart';
-import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
-import 'package:cake_wallet/themes/core/theme_store.dart';
-import 'package:cake_wallet/utils/exception_handler.dart';
-import 'package:cw_core/transaction_info.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:mobx/mobx.dart';
-import 'package:cw_core/balance.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cw_core/transaction_history.dart';
-import 'package:cake_wallet/store/wallet_list_store.dart';
-import 'package:cake_wallet/store/authentication_store.dart';
-import 'package:cake_wallet/store/settings_store.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-part 'app_store.g.dart';
+import "dart:async";
+
+import "package:cake_wallet/core/amount_parsing_proxy.dart";
+import "package:cake_wallet/di.dart";
+import "package:cake_wallet/entities/preferences_key.dart";
+import "package:cake_wallet/reactions/wallet_connect.dart";
+import "package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart";
+import "package:cake_wallet/store/authentication_store.dart";
+import "package:cake_wallet/store/settings_store.dart";
+import "package:cake_wallet/store/wallet_list_store.dart";
+import "package:cake_wallet/themes/core/theme_store.dart";
+import "package:cake_wallet/utils/exception_handler.dart";
+import "package:cw_core/balance.dart";
+import "package:cw_core/transaction_history.dart";
+import "package:cw_core/transaction_info.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:cw_core/wallet_base.dart";
+import "package:cw_core/wallet_type.dart";
+import "package:mobx/mobx.dart";
+import "package:shared_preferences/shared_preferences.dart";
+
+part "app_store.g.dart";
class AppStore = AppStoreBase with _$AppStore;
@@ -59,8 +59,9 @@ abstract class AppStoreBase with Store {
@action
Future<void> changeCurrentWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) async {
- bool changingToSameWalletType = this.wallet?.type == wallet.type;
- this.wallet?.close(shouldCleanup: !changingToSameWalletType);
+ final changingToSameWalletType = this.wallet?.type == wallet.type;
+
+ await this.wallet?.close(shouldCleanup: !changingToSameWalletType);
this.wallet = wallet;
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
@@ -80,7 +81,7 @@ abstract class AppStoreBase with Store {
wcService.create();
await wcService.init();
} catch (e, s) {
- printV('WalletConnect setup failed: $e\n$s');
+ printV("WalletConnect setup failed: $e\n$s");
}
}
}
diff --git a/lib/view_model/hardware_wallet/bitbox_view_model.dart b/lib/view_model/hardware_wallet/bitbox_view_model.dart
index 39c4b51..a07b63c 100644
--- a/lib/view_model/hardware_wallet/bitbox_view_model.dart
+++ b/lib/view_model/hardware_wallet/bitbox_view_model.dart
@@ -35,6 +35,10 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
@observable
bool isBleEnabled = false;
+ @override
+ @observable
+ bool isConnecting = false;
+
@override
bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
@@ -52,10 +56,11 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
Future<void> stopScanning() async {}
@override
+ @action
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
if (!(device is BitboxHardwareWalletDevice)) return false;
- if (_isConnecting) return false;
- _isConnecting = true;
+ if (isConnecting) return false;
+ isConnecting = true;
try {
final appDocDir = await getAppDir();
@@ -66,17 +71,15 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
await bitboxManager.channelHashVerify();
printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
- _isConnecting = false;
+ isConnecting = false;
return true;
} catch (e) {
printV(e);
}
- _isConnecting = false;
+ isConnecting = false;
return false;
}
- bool _isConnecting = false;
-
@override
bool isConnected(WalletType type) => false;
diff --git a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
index 1b87abf..ce54739 100644
--- a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
@@ -1,13 +1,14 @@
-import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
-import 'package:cw_core/hardware/hardware_wallet_service.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cw_core/wallet_info.dart';
-import 'package:cw_core/wallet_type.dart';
+import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
+import "package:cw_core/hardware/hardware_wallet_service.dart";
+import "package:cw_core/wallet_base.dart";
+import "package:cw_core/wallet_info.dart";
+import "package:cw_core/wallet_type.dart";
abstract class HardwareWalletViewModel {
HardwareWalletType get hardwareWalletType;
bool get isBleEnabled;
bool get hasBluetooth;
+ bool get isConnecting;
bool isConnected(WalletType type);
@@ -26,4 +27,6 @@ abstract class HardwareWalletViewModel {
Future<void> initWallet(WalletBase wallet);
String? interpretErrorCode(String error) => null;
+
+ Future<void> close() async {}
}
diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart
index dc608d8..f5efaa5 100644
--- a/lib/view_model/hardware_wallet/ledger_view_model.dart
+++ b/lib/view_model/hardware_wallet/ledger_view_model.dart
@@ -1,50 +1,47 @@
-import 'dart:async';
-import 'dart:io';
-
-import 'package:cake_wallet/bitcoin/bitcoin.dart';
-import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
-import 'package:cake_wallet/evm/evm.dart';
-import 'package:cake_wallet/generated/i18n.dart';
-import 'package:cake_wallet/main.dart';
-import 'package:cake_wallet/monero/monero.dart';
-import 'package:cake_wallet/routes.dart';
-import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
-import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
-import 'package:cake_wallet/wallet_type_utils.dart';
-import 'package:cw_core/hardware/device_connection_type.dart';
-import 'package:cw_core/hardware/hardware_wallet_service.dart';
-import 'package:cw_core/utils/print_verbose.dart';
-import 'package:cw_core/wallet_base.dart';
-import 'package:cw_core/wallet_info.dart';
-import 'package:cw_core/wallet_type.dart';
-import 'package:flutter/widgets.dart';
-import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
-import 'package:mobx/mobx.dart';
-import 'package:permission_handler/permission_handler.dart';
-
-part 'ledger_view_model.g.dart';
+import "dart:async";
+import "dart:io";
+
+import "package:cake_wallet/bitcoin/bitcoin.dart";
+import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
+import "package:cake_wallet/evm/evm.dart";
+import "package:cake_wallet/generated/i18n.dart";
+import "package:cake_wallet/main.dart";
+import "package:cake_wallet/monero/monero.dart";
+import "package:cake_wallet/routes.dart";
+import "package:cake_wallet/src/screens/connect_device/connect_device_page.dart";
+import "package:cake_wallet/store/app_store.dart";
+import "package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart";
+import "package:cake_wallet/wallet_type_utils.dart";
+import "package:cw_core/hardware/device_connection_type.dart";
+import "package:cw_core/hardware/hardware_wallet_service.dart";
+import "package:cw_core/utils/print_verbose.dart";
+import "package:cw_core/wallet_base.dart";
+import "package:cw_core/wallet_info.dart";
+import "package:cw_core/wallet_type.dart";
+import "package:flutter/widgets.dart";
+import "package:ledger_flutter_plus/ledger_flutter_plus.dart" as sdk;
+import "package:mobx/mobx.dart";
+import "package:permission_handler/permission_handler.dart";
+
+part "ledger_view_model.g.dart";
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
- late final sdk.LedgerInterface ledgerPlusBLE;
- late final sdk.LedgerInterface ledgerPlusUSB;
-
- bool get _doesSupportHardwareWallets {
- if (isMoneroOnly) {
- return DeviceConnectionType.supportedConnectionTypes(
- WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
- .isNotEmpty;
- }
-
- return true;
- }
-
- LedgerViewModelBase() {
+ LedgerViewModelBase(AppStore appStore) {
if (_doesSupportHardwareWallets) {
reaction((_) => isBleEnabled, (_) {
- if (isBleEnabled) _initBLE();
+ if (isBleEnabled) {
+ _initBLE();
+ }
});
+
+ reaction((_) => appStore.wallet, (wallet) async {
+ if (wallet != null && wallet.type != _connectingWalletType) {
+ await close();
+ }
+ });
+
updateBleState();
if (!Platform.isIOS) {
@@ -53,6 +50,21 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
}
}
+ late final sdk.LedgerInterface ledgerPlusBLE;
+ late final sdk.LedgerInterface ledgerPlusUSB;
+
+ bool get _doesSupportHardwareWallets {
+ if (isMoneroOnly) {
+ return DeviceConnectionType.supportedConnectionTypes(
+ WalletType.monero,
+ HardwareWalletType.ledger,
+ Platform.isIOS,
+ ).isNotEmpty;
+ }
+
+ return true;
+ }
+
@override
HardwareWalletType get hardwareWalletType => HardwareWalletType.ledger;
@@ -60,6 +72,10 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
@observable
bool isBleEnabled = false;
+ @override
+ @observable
+ bool isConnecting = false;
+
@override
bool get hasBluetooth => true;
@@ -69,9 +85,11 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
if (isBleEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(
onPermissionRequest: (_) async {
- if (Platform.isMacOS) return true;
+ if (Platform.isMacOS) {
+ return true;
+ }
- Map<Permission, PermissionStatus> statuses = await [
+ final statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
@@ -79,7 +97,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
return statuses.values.where((status) => status.isDenied).isEmpty;
},
- bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
+ bleOptions: sdk.BluetoothOptions(maxScanDuration: const Duration(minutes: 5)),
);
_bleIsInitialized = true;
}
@@ -91,28 +109,41 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
final newState = bleState == sdk.AvailabilityState.poweredOn;
- if (newState != isBleEnabled) isBleEnabled = newState;
+ if (newState != isBleEnabled) {
+ isBleEnabled = newState;
+ }
}
@override
Stream<HardwareWalletDevice> scanForBleDevices() =>
- ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
+ ledgerPlusBLE.scan().map(LedgerHardwareWalletDevice.new);
@override
- Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
- .then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
+ Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
+ ledgerPlusUSB.devices.then((devices) => devices.map(LedgerHardwareWalletDevice.new).toList());
@override
Future<void> stopScanning() async {
- if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
- if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
+ if (_bleIsInitialized) {
+ await ledgerPlusBLE.stopScanning();
+ }
+ if (!Platform.isIOS) {
+ await ledgerPlusUSB.stopScanning();
+ }
}
@override
+ @action
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
- if (!(device is LedgerHardwareWalletDevice)) return false;
- if (_isConnecting) return false;
- _isConnecting = true;
+ if (device is! LedgerHardwareWalletDevice) {
+ return false;
+ }
+
+ if (isConnecting) {
+ return false;
+ }
+
+ isConnecting = true;
_connectingWalletType = type;
if (isConnected(type)) {
try {
@@ -123,34 +154,36 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
final ledger =
device.connectionType == HardwareWalletConnectionType.ble ? ledgerPlusBLE : ledgerPlusUSB;
- if (_connectionChangeSubscription == null) {
- _connectionChangeSubscription =
- ledger.deviceStateChanges(device.device.id).listen(_connectionChangeListener);
- }
+ _connectionChangeSubscription ??=
+ ledger.deviceStateChanges(device.device.id).listen(_connectionChangeListener);
try {
_connection = await ledger.connect(device.device);
- _isConnecting = false;
+ isConnecting = false;
return true;
} catch (e) {
printV(e);
}
- _isConnecting = false;
+ isConnecting = false;
return false;
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
sdk.LedgerConnection? _connection;
- bool _isConnecting = false;
WalletType? _connectingWalletType;
+ bool suppressMoneroReconnectPrompt = false;
void _connectionChangeListener(sdk.BleConnectionState event) {
- printV('Ledger Device State Changed: $event');
- if (event == sdk.BleConnectionState.disconnected && !_isConnecting) {
+ printV("Ledger Device State Changed: $event");
+ if (event == sdk.BleConnectionState.disconnected && !isConnecting) {
_connection = null;
if (_connectingWalletType == WalletType.monero) {
monero!.resetLedgerConnection();
+ if (suppressMoneroReconnectPrompt) {
+ return;
+ }
+
Navigator.of(navigatorKey.currentContext!).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
@@ -158,7 +191,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
hardwareWalletType: HardwareWalletType.ledger,
allowChangeWallet: true,
isReconnect: true,
- onConnectDevice: (context, ledgerVM) async {
+ onConnectDevice: (context, ledgerVM) {
if (context.mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
@@ -170,26 +203,24 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
}
@override
- bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected);
+ bool isConnected(WalletType type) => _connection != null && !_connection!.isDisconnected;
sdk.LedgerConnection get connection => _connection!;
@override
- Future<void> initWallet(WalletBase wallet) async {
+ Future<void> initWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.monero:
return monero!.setLedgerConnection(wallet, connection);
case WalletType.bitcoin:
- return bitcoin!
- .setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.litecoin:
- return bitcoin!
- .setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
+ return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.ethereum:
case WalletType.polygon:
- return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
+ return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
default:
- throw Exception('Unexpected wallet type: ${wallet.type} for ledger');
+ throw Exception("Unexpected wallet type: ${wallet.type} for ledger");
}
}
@@ -216,16 +247,31 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
return error;
}
- var errorRegex = RegExp(r'(?:0x\S*?|[0-9a-f]{4})(?= )').firstMatch(error.toString());
+ final errorRegex = RegExp(r"(?:0x\S*?|[0-9a-f]{4})(?= )").firstMatch(error.toString());
- String errorCode = errorRegex?.group(0).toString().replaceAll("0x", "") ?? "";
+ final errorCode = errorRegex?.group(0).toString().replaceAll("0x", "") ?? "";
if (errorCode.contains("6985")) {
return S.current.ledger_error_tx_rejected_by_user;
} else if (errorCode.contains("5515")) {
return S.current.ledger_error_device_locked;
- } else if (["6e01", "6a87", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
+ } else if (["6e01", "6a87", "6d02", "6511", "6e00"].any(errorCode.contains)) {
return S.current.ledger_error_wrong_app;
}
return null;
}
+
+ @override
+ Future<void> close() async {
+ try {
+ printV("Resetting ledger VM");
+ await _connectionChangeSubscription?.cancel();
+ await _connection?.disconnect().catchError((_) {});
+
+ _connectingWalletType = null;
+ _connectionChangeSubscription = null;
+ _connection = null;
+ isConnecting = false;
+ await stopScanning();
+ } catch (_) {}
+ }
}
diff --git a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart
index 7115cdf..c048edf 100644
--- a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart
+++ b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart
@@ -82,6 +82,10 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
@override
HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
+ @override
+ @observable
+ bool isConnecting = false;
+
@override
@observable
bool isBleEnabled = false;
@@ -112,7 +116,6 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
if (!Platform.isIOS) await trezorUSB.stopScanning();
}
- bool _isConnecting = false;
sdk.TrezorClient? _client;
Completer<String?>? _pinCompleter;
@@ -123,11 +126,12 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
void setParingPin(String pin) => _pinCompleter?.complete(pin);
@override
+ @action
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
[bool isRetry = false]) async {
if (!(device is TrezorHardwareWalletDevice)) return false;
- if (_isConnecting) return false;
- _isConnecting = true;
+ if (isConnecting) return false;
+ isConnecting = true;
paringState = TrezorParingState.initial;
try {
@@ -182,7 +186,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
printV(e);
return false;
} finally {
- _isConnecting = false;
+ isConnecting = false;
}
}
diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart
index d8ae44e..78b6793 100644
--- a/lib/view_model/wallet_list/wallet_list_view_model.dart
+++ b/lib/view_model/wallet_list/wallet_list_view_model.dart
@@ -77,8 +77,7 @@ abstract class WalletListViewModelBase with Store {
if (walletItem.type == WalletType.haven) {
return;
}
- // bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type;
- // await _appStore.wallet?.close(shouldCleanup: !switchingToSameWalletType);
+
final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name);
await _appStore.changeCurrentWallet(wallet);
updateList();
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index 90c72af..ee0c0f8 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -125,7 +125,7 @@ dependencies:
ledger_flutter_plus:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
- ref: 0ccfbe26298e8974dfd4e891f34719c8348562f9
+ ref: eaf49764ad0863e8dec97ad30ff6aea0a1d1bd28
trezor_connect:
git:
url: https://github.com/cake-tech/trezor_connect
@@ -201,14 +201,8 @@ dependency_overrides:
ffi: 2.1.0
ledger_flutter_plus:
git:
- url: https://github.com/cake-tech/ledger-flutter-plus
- ref: 7bcb00380783f8611f68f5cd63fec96261fea2c2
- ledger_usb_plus:
- git:
- # url: https://github.com/konstantinullrich/ledger-usb-plus
- # ref: dad482c847c0d0eedceabbb40d3478e64b65e630
- url: https://github.com/cake-tech/ledger-usb-plus
- ref: 734c3d6f8267ba52ad73b6e1e78e570c07298890
+ url: https://github.com/vespr-wallet/ledger-flutter-plus
+ ref: eaf49764ad0863e8dec97ad30ff6aea0a1d1bd28
socks5_proxy:
git:
url: https://github.com/LacticWhale/socks_dart.git
@@ -231,7 +225,7 @@ dependency_overrides:
universal_ble:
git:
url: https://github.com/cake-tech/universal_ble.git
- ref: f7044558ad111a156987614971e08c583c80175c
+ ref: 6735c1bdf7f42c501d51c20564eb957c3a12b981
flutter_icons:
image_path: "assets/images/app_logo.png"
diff --git a/tool/configure.dart b/tool/configure.dart
index 54febef..d6b76d7 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -475,6 +475,7 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir
Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection);
void resetLedgerConnection();
void setGlobalLedgerConnection(ledger.LedgerConnection connection);
+ bool hasGlobalLedgerConnection();
String? getLastLedgerCommand();
void setHardwareWalletService(Object wallet, HardwareWalletService service);
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
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.