Cw 1379 add monero support to trezor rebase dev (#3273)

* refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services

* refactor: migrate hardware wallet resources to `new-ui`, add Trezor model support, and streamline hardware wallet handling across services

* fix: correctly check for Ledger hardware wallet type in `isHardwareWallet` logic

* refactor: unify `isConnected` method for hardware wallets, add Trezor-specific support, and update Monero sync logic

* refactor: add Trezor transaction signing support, streamline hardware wallet handling, and update Monero dependency

* refactor: improve Trezor transaction handling, adjust hardware wallet state logic, and update Monero dependency references

* fix: non-hww tx commit() in xmr
fix: prod monero_c
fix: keyImage import workaround for Trezor/Cupcake
chore: add kotlin's .salive to .gitignore

* fix: universal_ble builds on iOS
chore: remove prints
fix: proper CI prebuilt for linux

* new trezor connection ui

* fix page title

* typo

* fix page title anim

* refactor: enhance Trezor pairing flow, update state management, and simplify UI logic in hardware wallet integration

* fix: correct widget indentation in Trezor pairing failure state UI

* fix: extract Trezor pairing failure state UI to `_errorBox` widget and update Trezor dependency reference

* refactor: simplify hardware wallet pairing error UI, optimize `_errorBox` layout, and streamline widget logic

* Update cw_monero/lib/pending_monero_transaction.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update lib/buy/robinhood/robinhood_buy_provider.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* feat: add USB intent filters, device filter for hardware wallets, and update Trezor dependency reference

* Update cw_monero/pubspec.yaml [skip ci]

* Update cw_wownero/pubspec.yaml [skip ci]

* Update cw_zano/pubspec.yaml [skip ci]

* fix: use CryptoCurrency.title instead of amount-refactor-only .symbol getter

new_wallet_type_page used curr.symbol, which only exists on the
amount-refactor branch (String get symbol => title). On dev the
equivalent is curr.title. Adapts the Trezor feature to dev's API
without pulling in amount-refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix wallet list screen not scrollable
minor fixes

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
Co-authored-by: Konstantin Ullrich <konstantin@cakewallet.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Omar Hatem
2026-06-02 02:33:17 +02:00
committed by GitHub
co-authored by Konstantin Ullrich Czarek Nakamoto Robert Malikowski Konstantin Ullrich Claude Opus 4.8
parent 6c1ee16513
commit 5b2a8f6755
73 changed files with 1851 additions and 451 deletions
@@ -78,7 +78,7 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
bool _isConnecting = false;
@override
bool get isConnected => false;
bool isConnected(WalletType type) => false;
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
@@ -6,10 +6,11 @@ import 'package:cw_core/wallet_type.dart';
abstract class HardwareWalletViewModel {
HardwareWalletType get hardwareWalletType;
bool get isConnected;
bool get isBleEnabled;
bool get hasBluetooth;
bool isConnected(WalletType type);
Future<void> updateBleState();
Stream<HardwareWalletDevice> scanForBleDevices();
@@ -114,7 +114,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
if (_isConnecting) return false;
_isConnecting = true;
_connectingWalletType = type;
if (isConnected) {
if (isConnected(type)) {
try {
await _connection!.disconnect().catchError((_) {});
} catch (_) {}
@@ -126,7 +126,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription = ledger
.deviceStateChanges
.deviceStateChanges(device.device.id)
.listen(_connectionChangeListener);
}
@@ -172,7 +172,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
}
@override
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected);
sdk.LedgerConnection get connection => _connection!;
@@ -196,6 +196,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
switch (type) {
case WalletType.monero:
return monero!.getLedgerHardwareWalletService(connection);
case WalletType.bitcoin:
return bitcoin!.getLedgerHardwareWalletService(connection, true);
case WalletType.litecoin:
@@ -0,0 +1,258 @@
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/main.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.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:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:trezor_connect/trezor_connect.dart' as connect_sdk;
import 'package:trezor_flutter/trezor_flutter.dart' as sdk;
part 'trezor_connect_view_model.g.dart';
class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel;
abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store {
final connect_sdk.TrezorConnect trezorConnect;
late final sdk.TrezorInterface trezorBLE;
late final sdk.TrezorInterface trezorUSB;
sdk.ThpState _state = sdk.ThpState();
TrezorConnectViewModelBase(this.trezorConnect) {
if (_doesSupportHardwareWallets) {
reaction((_) => isBleEnabled, (_) {
if (isBleEnabled) _initBLE();
});
updateBleState();
if (!Platform.isIOS) {
trezorUSB = sdk.TrezorInterface.usb();
}
}
}
bool get _doesSupportHardwareWallets {
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(
WalletType.monero, HardwareWalletType.trezor, Platform.isIOS)
.isNotEmpty;
}
return true;
}
bool _bleIsInitialized = false;
Future<void> _initBLE() async {
if (isBleEnabled && !_bleIsInitialized) {
trezorBLE = sdk.TrezorInterface.ble(
onPermissionRequest: (_) async {
if (Platform.isMacOS) return true;
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
},
bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
);
_bleIsInitialized = true;
}
}
@override
HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
@override
@observable
bool isBleEnabled = false;
@override
bool get hasBluetooth => true;
@override
Future<void> updateBleState() async {
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
final newState = bleState == sdk.AvailabilityState.poweredOn;
if (newState != isBleEnabled) isBleEnabled = newState;
}
@override
Stream<HardwareWalletDevice> scanForBleDevices() =>
trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
@override
Future<List<HardwareWalletDevice>> getAllUsbDevices() => trezorUSB.devices
.then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
@override
Future<void> stopScanning() async {
if (_bleIsInitialized) await trezorBLE.stopScanning();
if (!Platform.isIOS) await trezorUSB.stopScanning();
}
bool _isConnecting = false;
sdk.TrezorClient? _client;
Completer<String?>? _pinCompleter;
@observable
TrezorParingState paringState = TrezorParingState.initial;
void setParingPin(String pin) => _pinCompleter?.complete(pin);
@override
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
[bool isRetry = false]) async {
if (!(device is TrezorHardwareWalletDevice)) return false;
if (_isConnecting) return false;
_isConnecting = true;
paringState = TrezorParingState.initial;
try {
final trezorInterface =
device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
final connection = await trezorInterface.connect(device.device);
if (!isRetry) {
showModalBottomSheet(
context: navigatorKey.currentContext!,
isScrollControlled: true,
isDismissible: false,
enableDrag: false,
useSafeArea: true,
builder: (context) =>
HardwareWalletProceedOnDeviceSheet(
hardwareWalletType: hardwareWalletType,
trezorConnectVM: this,
onRetry: () => connectDevice(device, type, true)
),
);
}
Future<String> onPinCode() async {
_pinCompleter = Completer<String?>();
paringState = TrezorParingState.enterPin;
final res = await _pinCompleter!.future;
paringState = TrezorParingState.verifyingPin;
if (res == null) throw Exception();
return res;
}
final deviceInfo = await _deviceName;
_client = sdk.TrezorClient.getClientForConnection(
connection,
_state,
"Cake Wallet",
deviceInfo,
onPinCode,
);
await _client!.createChannel();
paringState = TrezorParingState.success;
return true;
} catch (e) {
await _client?.connection.disconnect();
_client = null;
_state = sdk.ThpState();
// rethrow;
paringState = TrezorParingState.fail(e.toString());
printV(e);
return false;
} finally {
_isConnecting = false;
}
}
Future<String> get _deviceName async {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) return (await deviceInfo.androidInfo).model;
if (Platform.isIOS) return (await deviceInfo.iosInfo).name;
return "Computer";
}
@override
bool isConnected(WalletType type) => type == WalletType.monero
? _client != null && _client?.connection.isDisconnected == false
: true;
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
switch (type) {
case WalletType.monero:
return monero!.getTrezorHardwareWalletService(_client!);
case WalletType.bitcoin:
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
case WalletType.litecoin:
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
case WalletType.ethereum:
case WalletType.polygon:
return evm!.getTrezorHardwareWalletService(trezorConnect);
default:
throw UnimplementedError();
}
}
@override
Future<void> initWallet(WalletBase wallet) async {
switch (wallet.type) {
case WalletType.monero:
return monero!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
case WalletType.ethereum:
case WalletType.polygon:
return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
}
}
}
abstract class TrezorParingState {
static TrezorParingState initial = InitialTrezorParingState();
static TrezorParingState enterPin = EnterPinTrezorParingState();
static TrezorParingState verifyingPin = VerifyingPinTrezorParingState();
static TrezorParingState success = SuccessTrezorParingState();
static TrezorParingState fail(String message) => FailTrezorParingState(message);
}
class InitialTrezorParingState extends TrezorParingState {}
class EnterPinTrezorParingState extends TrezorParingState {}
class VerifyingPinTrezorParingState extends TrezorParingState {}
class SuccessTrezorParingState extends TrezorParingState {}
class FailTrezorParingState extends TrezorParingState {
final String message;
FailTrezorParingState(this.message);
}
@@ -1,79 +0,0 @@
import 'dart:async';
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/view_model/hardware_wallet/hardware_wallet_view_model.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:trezor_connect/trezor_connect.dart' as sdk;
import 'package:mobx/mobx.dart';
part 'trezor_view_model.g.dart';
class TrezorViewModel = TrezorViewModelBase with _$TrezorViewModel;
abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
final sdk.TrezorConnect trezorConnect;
TrezorViewModelBase(this.trezorConnect);
@override
HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
@override
@observable
bool isBleEnabled = false;
@override
bool get hasBluetooth => false;
@override
Future<void> updateBleState() async {}
@override
Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
@override
Future<List<HardwareWalletDevice>> getAllUsbDevices() async => [];
@override
Future<void> stopScanning() async {}
@override
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async => true;
@override
bool get isConnected => true;
@override
HardwareWalletService getHardwareWalletService(WalletType type) {
switch (type) {
case WalletType.bitcoin:
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, true);
case WalletType.litecoin:
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
case WalletType.ethereum:
case WalletType.polygon:
return evm!.getTrezorHardwareWalletService(trezorConnect);
default:
throw UnimplementedError();
}
}
@override
Future<void> initWallet(WalletBase wallet) async {
switch (wallet.type) {
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.ethereum:
case WalletType.polygon:
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
}
}
}
+6 -2
View File
@@ -619,7 +619,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (!(state is IsExecutingState)) state = IsExecutingState();
if (wallet.isHardwareWallet) {
state = IsAwaitingDeviceResponseState();
if (walletType == WalletType.monero) {
_ledgerTxStateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
if (monero!.getLastLedgerCommand() == "INS_CLSAG") {
@@ -627,6 +626,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = IsDeviceSigningResponseState();
}
});
} else {
state = IsAwaitingDeviceResponseState();
}
}
@@ -970,8 +971,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
throw Exception("Pending transaction doesn't exist. It should not be happened.");
}
try {
state = TransactionCommitting();
state = wallet.isHardwareWallet && walletType == WalletType.monero
? IsAwaitingDeviceResponseState()
: TransactionCommitting();
if (ocpRequest != null) {
await _handleOcpRequest();
@@ -89,7 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
name: name,
ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type),
password: password,
height: _options['height'] as int? ?? 0,
);