mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-13 09:04:09 +01:00
CW-1135-add-support-for-bitbox-hww (#2476)
* chore: add ffi and ffigen dependencies to pubspec.yaml * refactor: replace method channel with FFI implementation for MWeb module * fix: correct `cd` command in build_mwebd.sh script * chore: add go.sum file with dependency checksums * fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path * fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script * refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code * Update scripts/android/build_mwebd.sh Co-authored-by: cyan <cyjan@mrcyjanek.net> * refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci] * refactor: extract hardware wallet service abstraction * refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method * feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components * refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget * fix: mwebd ffi ios * feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations * feat: add bitbox support and refactor the old ledger implementation * feat: add BitBox hardware wallet support and refactor hardware wallet service implementations * feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration * fix: BitBox hardware wallet ethereum transaction signing * chore: update BitBox Flutter path references and include build steps in CI workflows * chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds * refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory * feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations * feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner) * feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling * fix: validate required BIP353 fields in transaction builder to prevent potential null value errors * feat: improve BitBox transaction signing logs with detailed PSBT information * refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging * feat: add BitBox coin type constant and improve hardware wallet service extensibility * chore: remove unused iOS/Android build scripts for mwebd * feat: add BitBox SVG asset and update localization for hardware wallet connection instructions * feat: update manufacturer tags and add Toast Easter egg for device selection interactions * refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling * refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly * feat: add Ledger Nano X SVG asset to hardware wallet resources * chore: unify mwebd build process in workflows, remove unused gomobile init command * refactor: remove redundant print statement and unnecessary whitespace * chore: add `gomobile init` to Android PR test build workflow * chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh` * chore: override `ledger_usb_plus` dependency in pubspec_base.yaml * feat: add verbose logging for BitBox connection and initialization process * Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci] * CW-1206-refactor-hardware-wallets-code (#2507) * feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling - Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates. - Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices. - Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning. - Refactored routing, view models, and dependency injection to accommodate new hardware wallet types. - Enhanced UI for device manufacturer selection and QR Code format dialogs. * feat: update hardware wallet assets and restoration flow * feat: add changing airgapped wallet type * feat: add Cupcake Man SVG asset to hardware wallet resources * refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets * fix: debugging keystone integration feat: support for zpub view only restore * fix: seed signer restore * fix: disable bitbox restoring (for now) [skip-ci] * refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci] --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * refactor: standardize hardware wallet connection handling across wallet types --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Czarek Nakamoto
Omar Hatem
parent
e1bb0e4a69
commit
e4672c75b7
@@ -28,7 +28,7 @@ abstract class ReceiveOptionViewModelBase with Store {
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
_options = [
|
||||
...bitcoin!.getLitecoinReceivePageOptions(),
|
||||
...bitcoin!.getLitecoinReceivePageOptions(_wallet),
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
];
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:ur/cbor_lite.dart';
|
||||
import 'package:ur/ur.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:ur/ur_decoder.dart';
|
||||
|
||||
part 'qr_tools_view_model.g.dart';
|
||||
|
||||
class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel;
|
||||
|
||||
enum DevQrType {
|
||||
zpub,
|
||||
xpub,
|
||||
bbqr,
|
||||
bcur,
|
||||
unknown,
|
||||
}
|
||||
|
||||
abstract class QRToolsViewModelBase with Store {
|
||||
@observable
|
||||
String input = '';
|
||||
|
||||
|
||||
|
||||
@computed
|
||||
Map<String, dynamic> get data => _getResult(input);
|
||||
|
||||
Uint8List _decodeCBOR(Uint8List cbor) {
|
||||
final cborDecoder = CBORDecoder(cbor);
|
||||
final out = cborDecoder.decodeBytes();
|
||||
return out.$1;
|
||||
}
|
||||
|
||||
|
||||
Map<String, dynamic> _getResult(String input) {
|
||||
try {
|
||||
final decoder = URDecoder();
|
||||
for (var part in input.split('\n')) {
|
||||
part = part.toLowerCase();
|
||||
part = part.trim();
|
||||
if (part.startsWith('-')) {
|
||||
part = part.substring(1).trim();
|
||||
}
|
||||
if (!part.startsWith('ur:')) {
|
||||
continue;
|
||||
}
|
||||
decoder.receivePart(part);
|
||||
}
|
||||
return {
|
||||
"result": (decoder.result != null) ? switch (decoder.result.runtimeType) {
|
||||
UR => {
|
||||
"cbor": (decoder.result as UR).cbor,
|
||||
"_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)),
|
||||
"type": (decoder.result as UR).type,
|
||||
"toString": (decoder.result as UR).toString(),
|
||||
},
|
||||
_ => "unknown type: ${decoder.result.runtimeType}"
|
||||
} : null,
|
||||
"expectedType": decoder.expectedType,
|
||||
"estimatedPercentComplete": decoder.estimatedPercentComplete(),
|
||||
"processedPartsCount": decoder.processedPartsCount(),
|
||||
"expectedPartCount": decoder.expectedPartCount(),
|
||||
"isComplete": decoder.isComplete(),
|
||||
"isFailure": decoder.isFailure(),
|
||||
"isSuccess": decoder.isSuccess(),
|
||||
"lastPartIndexes": decoder.lastPartIndexes(),
|
||||
"receivedPartIndexes": decoder.receivedPartIndexes(),
|
||||
"resultError": decoder.resultError(),
|
||||
"resultMessage": decoder.resultMessage(),
|
||||
"toString": decoder.toString(),
|
||||
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
"error": e.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.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/hardware_wallet_service.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_type.dart';
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitbox_view_model.g.dart';
|
||||
|
||||
class BitboxViewModel = BitboxViewModelBase with _$BitboxViewModel;
|
||||
|
||||
abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
|
||||
late final sdk.BitboxManager bitboxManager;
|
||||
|
||||
BitboxViewModelBase() {
|
||||
if (!Platform.isIOS && !isMoneroOnly) {
|
||||
bitboxManager = sdk.BitboxManager();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@override
|
||||
bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
|
||||
|
||||
@override
|
||||
Future<void> updateBleState() async {}
|
||||
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => bitboxManager.devices
|
||||
.then((devices) => devices.map((d) => BitboxHardwareWalletDevice(d)).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {}
|
||||
|
||||
@override
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
|
||||
if (!(device is BitboxHardwareWalletDevice)) return false;
|
||||
if (_isConnecting) return false;
|
||||
_isConnecting = true;
|
||||
|
||||
try {
|
||||
final appDocDir = await getAppDir();
|
||||
|
||||
await bitboxManager.connect(device.device);
|
||||
printV("Got Connection", file: '${appDocDir.path}/error.txt');
|
||||
await bitboxManager.initBitBox();
|
||||
printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
|
||||
await bitboxManager.channelHashVerify();
|
||||
printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
|
||||
_isConnecting = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
_isConnecting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isConnecting = false;
|
||||
|
||||
@override
|
||||
bool get isConnected => false;
|
||||
|
||||
@override
|
||||
HardwareWalletService getHardwareWalletService(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.getBitboxHardwareWalletService(bitboxManager, true);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getBitboxHardwareWalletService(bitboxManager);
|
||||
case WalletType.polygon:
|
||||
return polygon!.getBitboxHardwareWalletService(bitboxManager);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initWallet(WalletBase wallet) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.polygon:
|
||||
return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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_type.dart';
|
||||
|
||||
abstract class HardwareWalletViewModel {
|
||||
bool get isConnected;
|
||||
bool get isBleEnabled;
|
||||
bool get hasBluetooth;
|
||||
|
||||
Future<void> updateBleState();
|
||||
|
||||
Stream<HardwareWalletDevice> scanForBleDevices();
|
||||
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices();
|
||||
|
||||
Future<void> stopScanning();
|
||||
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
|
||||
|
||||
HardwareWalletService getHardwareWalletService(WalletType type);
|
||||
|
||||
void initWallet(WalletBase wallet);
|
||||
|
||||
String? interpretErrorCode(String error) => null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
@@ -9,10 +10,13 @@ import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.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;
|
||||
@@ -23,15 +27,14 @@ part 'ledger_view_model.g.dart';
|
||||
|
||||
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
|
||||
|
||||
abstract class LedgerViewModelBase with Store {
|
||||
// late final Ledger ledger;
|
||||
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, Platform.isIOS)
|
||||
WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
}
|
||||
|
||||
@@ -40,8 +43,8 @@ abstract class LedgerViewModelBase with Store {
|
||||
|
||||
LedgerViewModelBase() {
|
||||
if (_doesSupportHardwareWallets) {
|
||||
reaction((_) => bleIsEnabled, (_) {
|
||||
if (bleIsEnabled) _initBLE();
|
||||
reaction((_) => isBleEnabled, (_) {
|
||||
if (isBleEnabled) _initBLE();
|
||||
});
|
||||
updateBleState();
|
||||
|
||||
@@ -51,13 +54,17 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool bleIsEnabled = false;
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@override
|
||||
bool get hasBluetooth => true;
|
||||
|
||||
bool _bleIsInitialized = false;
|
||||
|
||||
Future<void> _initBLE() async {
|
||||
if (bleIsEnabled && !_bleIsInitialized) {
|
||||
if (isBleEnabled && !_bleIsInitialized) {
|
||||
ledgerPlusBLE = sdk.LedgerInterface.ble(
|
||||
onPermissionRequest: (_) async {
|
||||
if (Platform.isMacOS) return true;
|
||||
@@ -76,24 +83,32 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBleState() async {
|
||||
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
|
||||
|
||||
final newState = bleState == sdk.AvailabilityState.poweredOn;
|
||||
|
||||
if (newState != bleIsEnabled) bleIsEnabled = newState;
|
||||
if (newState != isBleEnabled) isBleEnabled = newState;
|
||||
}
|
||||
|
||||
Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() =>
|
||||
ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
|
||||
|
||||
Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
|
||||
.then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {
|
||||
if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
|
||||
if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
|
||||
}
|
||||
|
||||
Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
|
||||
@override
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
|
||||
if (!(device is LedgerHardwareWalletDevice)) return false;
|
||||
if (_isConnecting) return false;
|
||||
_isConnecting = true;
|
||||
_connectingWalletType = type;
|
||||
@@ -103,18 +118,18 @@ abstract class LedgerViewModelBase with Store {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
final ledger = device.connectionType == sdk.ConnectionType.ble
|
||||
final ledger = device.connectionType == HardwareWalletConnectionType.ble
|
||||
? ledgerPlusBLE
|
||||
: ledgerPlusUSB;
|
||||
|
||||
if (_connectionChangeSubscription == null) {
|
||||
_connectionChangeSubscription = ledger
|
||||
.deviceStateChanges(device.id)
|
||||
.deviceStateChanges(device.device.id)
|
||||
.listen(_connectionChangeListener);
|
||||
}
|
||||
|
||||
try {
|
||||
_connection = await ledger.connect(device);
|
||||
_connection = await ledger.connect(device.device);
|
||||
_isConnecting = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -140,6 +155,7 @@ abstract class LedgerViewModelBase with Store {
|
||||
Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: WalletType.monero,
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
allowChangeWallet: true,
|
||||
isReconnect: true,
|
||||
onConnectDevice: (context, ledgerVM) async {
|
||||
@@ -153,26 +169,47 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
|
||||
|
||||
sdk.LedgerConnection get connection => _connection!;
|
||||
|
||||
void setLedger(WalletBase wallet) {
|
||||
@override
|
||||
void initWallet(WalletBase wallet) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.setLedgerConnection(wallet, connection);
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.setLedgerConnection(wallet, connection);
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.setLedgerConnection(wallet, connection);
|
||||
return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.polygon:
|
||||
return polygon!.setLedgerConnection(wallet, connection);
|
||||
return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
HardwareWalletService getHardwareWalletService(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.getLedgerHardwareWalletService(connection, true);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getLedgerHardwareWalletService(connection, false);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getLedgerHardwareWalletService(connection);
|
||||
case WalletType.polygon:
|
||||
return polygon!.getLedgerHardwareWalletService(connection);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
String? interpretErrorCode(String error) {
|
||||
if (error.contains("Make sure no other program is communicating with the Ledger")) {
|
||||
return error;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cw_core/utils/zpub.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class RestoredWallet {
|
||||
@@ -44,6 +45,9 @@ class RestoredWallet {
|
||||
} catch (e) {
|
||||
// fine, we don't care, it is only for monero anyway
|
||||
}
|
||||
if (json['zpub'] != null) {
|
||||
json['xpub'] = convertZpubToXpub(json['zpub'] as String);
|
||||
}
|
||||
json['view_key'] ??= json['xpub'];
|
||||
final height = json['height'] as String?;
|
||||
return RestoredWallet(
|
||||
|
||||
@@ -58,6 +58,7 @@ class WalletRestoreFromQRCode {
|
||||
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
|
||||
|
||||
if (code.startsWith("xpub")) return WalletType.bitcoin;
|
||||
if (code.startsWith("zpub")) return WalletType.bitcoin;
|
||||
|
||||
if (extracted == null) {
|
||||
// Special case for view-only monero wallet
|
||||
@@ -109,9 +110,11 @@ class WalletRestoreFromQRCode {
|
||||
if (code == null) throw Exception("Unexpected scan QR code value: aborted");
|
||||
if (code.isEmpty) throw Exception('Unexpected scan QR code value: value is empty');
|
||||
|
||||
if (code.startsWith("[")) code = code.substring(code.indexOf("]") + 1);
|
||||
|
||||
String formattedUri = '';
|
||||
WalletType? walletType = _extractWalletType(code);
|
||||
|
||||
final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
|
||||
if (walletType == null) {
|
||||
await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
|
||||
walletType =
|
||||
@@ -122,14 +125,14 @@ class WalletRestoreFromQRCode {
|
||||
|
||||
formattedUri = seedPhrase != null
|
||||
? '$walletType:?seed=$seedPhrase'
|
||||
: code.startsWith('xpub')
|
||||
? '$walletType:?xpub=$code'
|
||||
: code.startsWith(prefix)
|
||||
? '$walletType:?$prefix=$code'
|
||||
: throw Exception('Failed to determine valid seed phrase');
|
||||
} else {
|
||||
final index = code.indexOf(':');
|
||||
final query = code.substring(index + 1).replaceAll('?', '&');
|
||||
formattedUri = code.startsWith('xpub')
|
||||
? '$walletType:?xpub=$code'
|
||||
formattedUri = code.startsWith(prefix)
|
||||
? '$walletType:?$prefix=$code'
|
||||
:'$walletType:?$query';
|
||||
}
|
||||
|
||||
@@ -167,7 +170,8 @@ class WalletRestoreFromQRCode {
|
||||
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
|
||||
}
|
||||
|
||||
if (credentials.containsKey("xpub")) {
|
||||
if (credentials.containsKey("xpub") ||
|
||||
credentials.containsKey("zpub")) {
|
||||
return WalletRestoreMode.keys;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
@@ -93,7 +94,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
this.balanceViewModel,
|
||||
this.contactListViewModel,
|
||||
this.transactionDescriptionBox,
|
||||
this.ledgerViewModel,
|
||||
this.hardwareWalletViewModel,
|
||||
this.unspentCoinsListViewModel,
|
||||
this.feesViewModel,
|
||||
this.walletInfoSource, {
|
||||
@@ -368,7 +369,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final SendTemplateViewModel sendTemplateViewModel;
|
||||
final BalanceViewModel balanceViewModel;
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final LedgerViewModel? ledgerViewModel;
|
||||
final HardwareWalletViewModel? hardwareWalletViewModel;
|
||||
final FeesViewModel feesViewModel;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/package_info.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
// import 'package:package_info/package_info.dart';
|
||||
import 'package:collection/collection.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/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -58,20 +56,28 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
bool get changeRepresentativeEnabled =>
|
||||
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
|
||||
[WalletType.nano, WalletType.banano].contains(_wallet.type);
|
||||
|
||||
@computed
|
||||
bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
|
||||
_wallet.type == WalletType.solana ||
|
||||
_wallet.type == WalletType.tron);
|
||||
bool get changeHardwareWalletTypeEnabled => [
|
||||
HardwareWalletType.cupcake,
|
||||
HardwareWalletType.coldcard,
|
||||
HardwareWalletType.seedsigner,
|
||||
].contains(_wallet.hardwareWalletType);
|
||||
|
||||
@computed
|
||||
bool get displayTransactionPriority =>
|
||||
!(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
|
||||
|
||||
String getDisplayPriority(dynamic priority) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash ||
|
||||
_wallet.type == WalletType.dogecoin) {
|
||||
if ([
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin,
|
||||
].contains(_wallet.type)) {
|
||||
final rate = bitcoin!.getFeeRate(_wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
|
||||
}
|
||||
@@ -82,10 +88,12 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
String getDisplayBitcoinPriority(dynamic priority, int customValue) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash ||
|
||||
_wallet.type == WalletType.dogecoin) {
|
||||
if ([
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin,
|
||||
].contains(_wallet.type)) {
|
||||
final rate = bitcoin!.getFeeRate(_wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
|
||||
customRate: customValue);
|
||||
@@ -105,6 +113,12 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
_settingsStore.priority[_wallet.type] = priority;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> onHardwareWalletTypeChanged(HardwareWalletType hwType) async {
|
||||
_wallet.walletInfo.hardwareWalletType = hwType;
|
||||
return _wallet.walletInfo.save();
|
||||
}
|
||||
|
||||
@computed
|
||||
double get customBitcoinFeeRate =>
|
||||
_settingsStore.customBitcoinFeeRate.toDouble();
|
||||
|
||||
@@ -473,7 +473,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
||||
@computed
|
||||
bool get isCupcake =>
|
||||
bool get isBitcoinViewOnly =>
|
||||
wallet.type == WalletType.bitcoin &&
|
||||
(bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.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/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
@@ -24,12 +25,12 @@ class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase
|
||||
with _$WalletHardwareRestoreViewModel;
|
||||
|
||||
abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
final LedgerViewModel ledgerViewModel;
|
||||
final HardwareWalletViewModel hardwareWalletVM;
|
||||
|
||||
int _nextIndex = 0;
|
||||
|
||||
WalletHardwareRestoreViewModelBase(
|
||||
this.ledgerViewModel,
|
||||
this.hardwareWalletVM,
|
||||
AppStore appStore,
|
||||
WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource,
|
||||
@@ -56,33 +57,16 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
@action
|
||||
Future<void> getNextAvailableAccounts(int limit) async {
|
||||
try {
|
||||
List<HardwareAccountData> accounts;
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
accounts = await bitcoin!
|
||||
.getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
accounts = await bitcoin!
|
||||
.getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
accounts = await ethereum!
|
||||
.getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.polygon:
|
||||
accounts = await polygon!
|
||||
.getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
List<HardwareAccountData> accounts = await hardwareWalletVM
|
||||
.getHardwareWalletService(type)
|
||||
.getAvailableAccounts(index: _nextIndex, limit: limit);
|
||||
|
||||
availableAccounts.addAll(accounts);
|
||||
_nextIndex += limit;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
|
||||
error =
|
||||
hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
|
||||
}
|
||||
|
||||
isLoadingMoreAccounts = false;
|
||||
@@ -110,7 +94,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
|
||||
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
|
||||
name: name,
|
||||
ledgerConnection: ledgerViewModel.connection,
|
||||
ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
|
||||
password: password,
|
||||
height: _options['height'] as int? ?? 0,
|
||||
);
|
||||
@@ -118,7 +102,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
credentials.hardwareWalletType = HardwareWalletType.ledger;
|
||||
if (hardwareWalletVM is LedgerViewModel)
|
||||
credentials.hardwareWalletType = HardwareWalletType.ledger;
|
||||
else
|
||||
credentials.hardwareWalletType = HardwareWalletType.bitbox;
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi
|
||||
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
|
||||
{required WalletType type, this.restoredWallet})
|
||||
{required WalletType type, this.restoredWallet, this.hardwareWalletType})
|
||||
: isButtonEnabled = restoredWallet != null,
|
||||
hasPassphrase = false,
|
||||
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
|
||||
@@ -102,6 +102,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
].contains(type);
|
||||
|
||||
final RestoredWallet? restoredWallet;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
|
||||
@observable
|
||||
WalletRestoreMode mode;
|
||||
@@ -226,6 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
name: name,
|
||||
password: password,
|
||||
xpub: viewKey!,
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
);
|
||||
|
||||
case WalletType.monero:
|
||||
|
||||
Reference in New Issue
Block a user