mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
CW-1581-trezor-fixes-enhancements (#3462)
* feat: add Trezor auto connect * chore: update `trezor_flutter` * fix: correct method name for retrieving Trezor auto-pairing credentials * fix: update device connection prompt text across all languages [skip ci] * feat: add Trezor key image synchronization ui and improve hardware wallet UX * feat: add sync-balance icon asset for settings row visuals [skip ci] * feat: add sync-balance icon asset for settings row visuals [skip ci] * fix: remove unused Monero and sync key images settings actions from menus [skip ci] * fix: re-enable routing for UR QR Animated Page [skip ci]
This commit is contained in:
@@ -1,43 +1,43 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import "dart:async";
|
||||
import "dart:convert";
|
||||
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;
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/secure_storage.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/encryption_file_utils.dart";
|
||||
import "package:cw_core/hardware/device_connection_type.dart";
|
||||
import "package:cw_core/hardware/hardware_wallet_service.dart";
|
||||
import "package:cw_core/key.dart";
|
||||
import "package:cw_core/root_dir.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';
|
||||
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) {
|
||||
TrezorConnectViewModelBase(this.trezorConnect, this._secureStorage) {
|
||||
if (_doesSupportHardwareWallets) {
|
||||
reaction((_) => isBleEnabled, (_) {
|
||||
if (isBleEnabled) _initBLE();
|
||||
if (isBleEnabled) {
|
||||
_initBLE();
|
||||
}
|
||||
});
|
||||
updateBleState();
|
||||
|
||||
@@ -47,11 +47,21 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
}
|
||||
}
|
||||
|
||||
final connect_sdk.TrezorConnect trezorConnect;
|
||||
final SecureStorage _secureStorage;
|
||||
|
||||
late final sdk.TrezorInterface trezorBLE;
|
||||
late final sdk.TrezorInterface trezorUSB;
|
||||
|
||||
sdk.ThpState? _state;
|
||||
|
||||
bool get _doesSupportHardwareWallets {
|
||||
if (isMoneroOnly) {
|
||||
return DeviceConnectionType.supportedConnectionTypes(
|
||||
WalletType.monero, HardwareWalletType.trezor, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
WalletType.monero,
|
||||
HardwareWalletType.trezor,
|
||||
Platform.isIOS,
|
||||
).isNotEmpty;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -63,9 +73,11 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
if (isBleEnabled && !_bleIsInitialized) {
|
||||
trezorBLE = sdk.TrezorInterface.ble(
|
||||
onPermissionRequest: (_) async {
|
||||
if (Platform.isMacOS) return true;
|
||||
if (Platform.isMacOS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
final Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.bluetoothScan,
|
||||
Permission.bluetoothConnect,
|
||||
Permission.bluetoothAdvertise,
|
||||
@@ -73,7 +85,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -99,21 +111,27 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
final newState = bleState == sdk.AvailabilityState.poweredOn;
|
||||
|
||||
if (newState != isBleEnabled) isBleEnabled = newState;
|
||||
if (newState != isBleEnabled) {
|
||||
isBleEnabled = newState;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() =>
|
||||
trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
|
||||
trezorBLE.scan().map(TrezorHardwareWalletDevice.new);
|
||||
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => trezorUSB.devices
|
||||
.then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
|
||||
trezorUSB.devices.then((devices) => devices.map(TrezorHardwareWalletDevice.new).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {
|
||||
if (_bleIsInitialized) await trezorBLE.stopScanning();
|
||||
if (!Platform.isIOS) await trezorUSB.stopScanning();
|
||||
if (_bleIsInitialized) {
|
||||
await trezorBLE.stopScanning();
|
||||
}
|
||||
if (!Platform.isIOS) {
|
||||
await trezorUSB.stopScanning();
|
||||
}
|
||||
}
|
||||
|
||||
sdk.TrezorClient? _client;
|
||||
@@ -127,10 +145,17 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
|
||||
[bool isRetry = false]) async {
|
||||
if (!(device is TrezorHardwareWalletDevice)) return false;
|
||||
if (isConnecting) return false;
|
||||
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;
|
||||
|
||||
@@ -140,16 +165,19 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
final connection = await trezorInterface.connect(device.device);
|
||||
|
||||
if (!isRetry) {
|
||||
showModalBottomSheet(
|
||||
context: navigatorKey.currentContext!,
|
||||
isScrollControlled: true,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
useSafeArea: true,
|
||||
builder: (context) => HardwareWalletProceedOnDeviceSheet(
|
||||
unawaited(
|
||||
showModalBottomSheet(
|
||||
context: navigatorKey.currentContext!,
|
||||
isScrollControlled: true,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
useSafeArea: true,
|
||||
builder: (_) => HardwareWalletProceedOnDeviceSheet(
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
trezorConnectVM: this,
|
||||
onRetry: () => connectDevice(device, type, true)),
|
||||
onRetry: () => connectDevice(device, type, true),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,15 +187,18 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
final res = await _pinCompleter!.future;
|
||||
paringState = TrezorParingState.verifyingPin;
|
||||
if (res == null) throw Exception();
|
||||
if (res == null) {
|
||||
throw Exception();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
final deviceInfo = await _deviceName;
|
||||
|
||||
_state ??= await _getState();
|
||||
_client = sdk.TrezorClient.getClientForConnection(
|
||||
connection,
|
||||
_state,
|
||||
_state!,
|
||||
"Cake Wallet",
|
||||
deviceInfo,
|
||||
onPinCode,
|
||||
@@ -175,6 +206,16 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
await _client!.createChannel();
|
||||
|
||||
if (!_state!.pairingCredentials.any((c) => c.autoconnect == true)) {
|
||||
if (_client case final sdk.TrezorClientV2 clientV2) {
|
||||
try {
|
||||
final auto = await clientV2.getAutoPairingCredentials();
|
||||
_state!.setPairingCredentials([auto]);
|
||||
await _saveState();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
paringState = TrezorParingState.success;
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -192,8 +233,12 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
Future<String> get _deviceName async {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isAndroid) return (await deviceInfo.androidInfo).model;
|
||||
if (Platform.isIOS) return (await deviceInfo.iosInfo).name;
|
||||
if (Platform.isAndroid) {
|
||||
return (await deviceInfo.androidInfo).model;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return (await deviceInfo.iosInfo).name;
|
||||
}
|
||||
return "Computer";
|
||||
}
|
||||
|
||||
@@ -231,9 +276,61 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
case WalletType.polygon:
|
||||
return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
|
||||
throw Exception("Unexpected wallet type: ${wallet.type} for trezor");
|
||||
}
|
||||
}
|
||||
|
||||
final EncryptionFileUtils _encryptionFileUtils = encryptionFileUtilsFor(true);
|
||||
static const String _secureStorageKey = "com.cakewallet.trezor/thp_state";
|
||||
|
||||
Future<String> get _thpJsonFile async => "${(await getAppDir()).path}/thp_state.json.enc";
|
||||
|
||||
Future<sdk.ThpState> _getState() async {
|
||||
final password = await _secureStorage.read(key: _secureStorageKey);
|
||||
if (password == null) {
|
||||
return _saveState();
|
||||
}
|
||||
try {
|
||||
final state = await _encryptionFileUtils.read(path: await _thpJsonFile, password: password);
|
||||
return sdk.ThpState.fromJson(state);
|
||||
} catch (_) {
|
||||
return _saveState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<sdk.ThpState> _saveState() async {
|
||||
try {
|
||||
final password = generateKey();
|
||||
await _secureStorage.write(key: _secureStorageKey, value: password);
|
||||
|
||||
final file = File(await _thpJsonFile);
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
}
|
||||
|
||||
final state = _state ?? sdk.ThpState();
|
||||
await _encryptionFileUtils.write(
|
||||
path: file.path,
|
||||
password: password,
|
||||
data: jsonEncode(state.toMap()),
|
||||
);
|
||||
_state = state;
|
||||
return state;
|
||||
} catch (_) {
|
||||
throw Exception("Unable to save Trezor State");
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> syncKeyImages(WalletBase wallet) async {
|
||||
if (wallet.type == WalletType.monero) {
|
||||
try {
|
||||
await monero!.syncTrezor(wallet);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrezorParingState {
|
||||
@@ -241,6 +338,7 @@ abstract class TrezorParingState {
|
||||
static TrezorParingState enterPin = EnterPinTrezorParingState();
|
||||
static TrezorParingState verifyingPin = VerifyingPinTrezorParingState();
|
||||
static TrezorParingState success = SuccessTrezorParingState();
|
||||
|
||||
static TrezorParingState fail(String message) => FailTrezorParingState(message);
|
||||
}
|
||||
|
||||
@@ -253,7 +351,7 @@ class VerifyingPinTrezorParingState extends TrezorParingState {}
|
||||
class SuccessTrezorParingState extends TrezorParingState {}
|
||||
|
||||
class FailTrezorParingState extends TrezorParingState {
|
||||
final String message;
|
||||
|
||||
FailTrezorParingState(this.message);
|
||||
|
||||
final String message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user