From 3d03a62e21965498a7cd6593e22c1d02734bc07d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Fri, 18 Sep 2026 21:10:59 +0200 Subject: [PATCH] Add thread-safety and BLE support for Ledger and Trezor devices (#3638) * feat: support BLE device detection for Ledger and Trezor wallets, update trezor-flutter dependency * feat: add BLE connection state listener for Trezor devices, handle disconnect events, and clean up resources on close * feat: add mutex locking to MoneroTrezorService for thread-safe operations --- cw_bitcoin/pubspec.yaml | 2 +- cw_monero/lib/trezor.dart | 17 ++++++++--- cw_monero/pubspec.yaml | 2 +- .../connect_device/connect_device_page.dart | 19 ++++++++++++ .../hardware_wallet_view_model.dart | 2 ++ .../hardware_wallet/ledger_view_model.dart | 8 +++++ .../trezor_connect_view_model.dart | 30 +++++++++++++++++++ pubspec_base.yaml | 2 +- pubspec_overrides.yaml | 4 +-- 9 files changed, 77 insertions(+), 9 deletions(-) diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index d3ea50c80..65e264227 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -58,7 +58,7 @@ dependencies: trezor_flutter: git: url: https://github.com/cake-tech/trezor-flutter - ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c + ref: 591d4b8c821b9c10561f848b866324a9a8dcb489 path: trezor-flutter bitbox_flutter: path: ../scripts/bitbox_flutter diff --git a/cw_monero/lib/trezor.dart b/cw_monero/lib/trezor.dart index 884eba4e5..bc8cc1168 100644 --- a/cw_monero/lib/trezor.dart +++ b/cw_monero/lib/trezor.dart @@ -1,12 +1,17 @@ import "dart:convert"; import "package:cw_core/hardware/hardware_wallet_service.dart"; +import "package:mutex/mutex.dart"; import "package:trezor_flutter/trezor_flutter.dart"; class MoneroTrezorService extends HardwareWalletService { MoneroTrezorService(this.client); final TrezorClient client; + + static final Mutex _mutex = Mutex(); + + static Future runBlocking(Future Function() operation) => _mutex.protect(operation); } class MoneroTrezorWatchCredentials { @@ -22,7 +27,7 @@ class Trezor { final MoneroTrezorService service; Future getWatchCredentials() async { - final credentials = await TrezorMonero(service.client).getWatchCredentials(); + final credentials = await MoneroTrezorService.runBlocking(TrezorMonero(service.client).getWatchCredentials); return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2); } @@ -45,11 +50,15 @@ class Trezor { ), ); } - final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds); + final keyImages = await MoneroTrezorService.runBlocking( + () => TrezorMonero(service.client).syncKeyImages(txIds), + ); return jsonEncode(keyImages.toMap()); } - Future signTransaction(String json) => - TrezorMonero(service.client).signTransaction(jsonDecode(json) as Map); + Future signTransaction(String json) => MoneroTrezorService.runBlocking( + () => + TrezorMonero(service.client).signTransaction(jsonDecode(json) as Map), + ); } diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 2478f0534..2f82f5726 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: trezor_flutter: git: url: https://github.com/cake-tech/trezor-flutter.git - ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c + ref: 591d4b8c821b9c10561f848b866324a9a8dcb489 path: trezor-flutter dev_dependencies: diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 746910348..36170d94a 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -108,6 +108,8 @@ class ConnectDevicePageBodyState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { + _loadConnectedDevices(); + _bleStateTimer = Timer.periodic( const Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState(), @@ -131,6 +133,23 @@ class ConnectDevicePageBodyState extends State { }); } + Future _loadConnectedDevices() async { + try { + final connected = await widget.hardwareWalletVM.getConnectedBleDevices(); + + printV(connected); + if (!mounted || connected.isEmpty) { + return; + } + setState(() { + bleDevices.addAll(connected); + longWait = false; + }); + } catch (e) { + printV(e); + } + } + @override void dispose() { _bleRefreshTimer?.cancel(); 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 ce5473914..ce7beb372 100644 --- a/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart +++ b/lib/view_model/hardware_wallet/hardware_wallet_view_model.dart @@ -18,6 +18,8 @@ abstract class HardwareWalletViewModel { Future> getAllUsbDevices(); + Future> getConnectedBleDevices() async => []; + Future stopScanning(); Future connectDevice(HardwareWalletDevice device, WalletType type); diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 113ce51ff..b923e9eae 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -120,6 +120,14 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store { Future> getAllUsbDevices() => ledgerPlusUSB.devices.then((devices) => devices.map(LedgerHardwareWalletDevice.new).toList()); + @override + Future> getConnectedBleDevices() async { + if (!_bleIsInitialized) { + return const []; + } + return (await ledgerPlusBLE.devices).map(LedgerHardwareWalletDevice.new).toList(); + } + @override Future stopScanning() async { if (_bleIsInitialized) { 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 084bffd04..153608a68 100644 --- a/lib/view_model/hardware_wallet/trezor_connect_view_model.dart +++ b/lib/view_model/hardware_wallet/trezor_connect_view_model.dart @@ -48,6 +48,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S } } + StreamSubscription? _connectionChangeSubscription; final connect_sdk.TrezorConnect trezorConnect; final SecureStorage _secureStorage; @@ -125,6 +126,14 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S Future> getAllUsbDevices() => trezorUSB.devices.then((devices) => devices.map(TrezorHardwareWalletDevice.new).toList()); + @override + Future> getConnectedBleDevices() async { + if (!_bleIsInitialized) { + return const []; + } + return (await trezorBLE.devices).map(TrezorHardwareWalletDevice.new).toList(); + } + @override Future stopScanning() async { if (_bleIsInitialized) { @@ -168,6 +177,9 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB; final connection = await trezorInterface.connect(device.device); + _connectionChangeSubscription ??= + trezorInterface.deviceStateChanges.listen(_connectionChangeListener); + if (!isRetry) { unawaited( showModalBottomSheet( @@ -342,6 +354,13 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S } } + void _connectionChangeListener(sdk.BleConnectionState event) { + printV("Ledger Device State Changed: $event"); + if (event == sdk.BleConnectionState.disconnected && !isConnecting) { + _client = null; + } + } + Future syncKeyImages(WalletBase wallet) async { if (wallet.type == WalletType.monero) { try { @@ -352,6 +371,17 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S } return true; } + + @override + Future close() async { + try { + await _connectionChangeSubscription?.cancel(); + + _connectionChangeSubscription = null; + isConnecting = false; + await stopScanning(); + } catch (_) {} + } } abstract class TrezorParingState { diff --git a/pubspec_base.yaml b/pubspec_base.yaml index b7d8acb8d..e818609b5 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -131,7 +131,7 @@ dependencies: trezor_flutter: git: url: https://github.com/cake-tech/trezor-flutter.git - ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c + ref: 591d4b8c821b9c10561f848b866324a9a8dcb489 path: trezor-flutter bitbox_flutter: path: ./scripts/bitbox_flutter diff --git a/pubspec_overrides.yaml b/pubspec_overrides.yaml index 358b5d5d5..46fe3cc93 100644 --- a/pubspec_overrides.yaml +++ b/pubspec_overrides.yaml @@ -1481,12 +1481,12 @@ dependency_overrides: trezor_flutter: git: url: https://github.com/cake-tech/trezor-flutter.git - ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c + ref: 591d4b8c821b9c10561f848b866324a9a8dcb489 path: ./trezor-flutter trezor_usb_transport: git: url: https://github.com/cake-tech/trezor-flutter.git - ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c + ref: 591d4b8c821b9c10561f848b866324a9a8dcb489 path: ./trezor_usb_transport tuple: git: