mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-29 06:38:45 +01:00
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:
co-authored by
Konstantin Ullrich
Czarek Nakamoto
Robert Malikowski
Konstantin Ullrich
Claude Opus 4.8
parent
6c1ee16513
commit
5b2a8f6755
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user