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:
Konstantin Ullrich
2025-10-01 18:20:40 +03:00
committed by GitHub
co-authored by Czarek Nakamoto Omar Hatem
parent e1bb0e4a69
commit e4672c75b7
117 changed files with 2048 additions and 723 deletions
+22 -21
View File
@@ -23,8 +23,10 @@ class CWBitcoin extends Bitcoin {
required String name,
required String password,
required String xpub,
HardwareWalletType? hardwareWalletType,
}) =>
BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
BitcoinWalletFromKeysCredentials(
name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType);
@override
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
@@ -315,8 +317,12 @@ class CWBitcoin extends Bitcoin {
}
@override
List<ReceivePageOption> getLitecoinReceivePageOptions() {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
final litecoinWallet = wallet as ElectrumWallet;
if (Platform.isLinux ||
Platform.isMacOS ||
Platform.isWindows ||
litecoinWallet.isHardwareWallet) {
return BitcoinReceivePageOption.allLitecoin
.where((element) => element != BitcoinReceivePageOption.mweb)
.toList();
@@ -536,27 +542,22 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
printV(err);
throw err;
}
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
(wallet as ElectrumWallet).hardwareWalletService = service;
}
@override
Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
printV(err);
throw err;
}
HardwareWalletService getLedgerHardwareWalletService(
ledger.LedgerConnection connection, bool isBitcoin) {
if (isBitcoin) return BitcoinLedgerService(connection);
return LitecoinLedgerService(connection);
}
@override
HardwareWalletService getBitboxHardwareWalletService(
bitbox.BitboxManager manager, bool isBitcoin) {
if (isBitcoin) return BitcoinBitboxService(manager);
return LitecoinBitboxService(manager);
}
@override
+3 -3
View File
@@ -4,7 +4,7 @@ import 'package:cake_wallet/order/order.dart';
import 'package:cake_wallet/buy/pairs_utils.dart';
import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
@@ -13,14 +13,14 @@ abstract class BuyProvider {
BuyProvider({
required this.wallet,
required this.isTestEnvironment,
required this.ledgerVM,
required this.hardwareWalletVM,
required this.supportedCryptoList,
required this.supportedFiatList
});
final WalletBase wallet;
final bool isTestEnvironment;
final LedgerViewModel? ledgerVM;
final HardwareWalletViewModel? hardwareWalletVM;
final List<TradePair<dynamic, dynamic>> supportedCryptoList;
final List<TradePair<dynamic, dynamic>> supportedFiatList;
+25 -28
View File
@@ -10,8 +10,8 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -24,16 +24,16 @@ class DFXBuyProvider extends BuyProvider {
DFXBuyProvider({
required WalletBase wallet,
bool isTestEnvironment = false,
LedgerViewModel? ledgerVM,
HardwareWalletViewModel? hardwareWalletVM,
}) : super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: ledgerVM,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto)
);
wallet: wallet,
isTestEnvironment: isTestEnvironment,
hardwareWalletVM: hardwareWalletVM,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
);
static const _baseUrl = 'api.dfx.swiss';
@@ -104,8 +104,7 @@ class DFXBuyProvider extends BuyProvider {
// }
Future<String> auth(String walletAddress) async {
final signMessage = await getSignature(
await getSignMessage(walletAddress), walletAddress);
final signMessage = await getSignature(await getSignMessage(walletAddress), walletAddress);
final requestBody = jsonEncode({
'wallet': walletName,
@@ -119,7 +118,6 @@ class DFXBuyProvider extends BuyProvider {
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
@@ -139,14 +137,13 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
final r = await wallet.signMessage(message);
return r;
return wallet.signMessage(message);
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.zano:
return await wallet.signMessage(message, address: walletAddress);
return wallet.signMessage(message, address: walletAddress);
default:
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
@@ -336,16 +333,17 @@ class DFXBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
if (!ledgerVM!.isConnected) {
if (!hardwareWalletVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
ledgerVM.setLedger(wallet);
hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, hwwVM) {
hwwVM.initWallet(wallet);
Navigator.of(context).pop();
}));
} else {
ledgerVM!.setLedger(wallet);
hardwareWalletVM!.initWallet(wallet);
}
}
@@ -370,14 +368,13 @@ class DFXBuyProvider extends BuyProvider {
}
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "DFX.swiss",
alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: "DFX.swiss",
alertContent: '${S.of(context).buy_provider_unavailable}: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()),
);
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ class KryptonimBuyProvider extends BuyProvider {
: super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: null,
hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
+1 -1
View File
@@ -22,7 +22,7 @@ class MeldBuyProvider extends BuyProvider {
: super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: null,
hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
+1 -1
View File
@@ -36,7 +36,7 @@ class MoonPayProvider extends BuyProvider {
super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: null,
hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
+1 -2
View File
@@ -9,7 +9,6 @@ import 'package:cake_wallet/buy/payment_method.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -22,7 +21,7 @@ class OnRamperBuyProvider extends BuyProvider {
{required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: null,
hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
+33 -30
View File
@@ -11,9 +11,9 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
@@ -22,16 +22,19 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class RobinhoodBuyProvider extends BuyProvider {
RobinhoodBuyProvider(
{required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
: super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: ledgerVM,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto));
RobinhoodBuyProvider({
required WalletBase wallet,
bool isTestEnvironment = false,
HardwareWalletViewModel? hardwareWalletVM,
}) : super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
hardwareWalletVM: hardwareWalletVM,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
);
static const _baseUrl = 'applink.robinhood.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@@ -69,8 +72,8 @@ class RobinhoodBuyProvider extends BuyProvider {
final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId});
try {
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
final response =
await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body) as Map<String, dynamic>;
@@ -104,11 +107,11 @@ class RobinhoodBuyProvider extends BuyProvider {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return await wallet.signMessage(message);
return wallet.signMessage(message);
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
return await wallet.signMessage(message, address: wallet.walletAddresses.address);
return wallet.signMessage(message, address: wallet.walletAddresses.address);
default:
throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
}
@@ -123,14 +126,14 @@ class RobinhoodBuyProvider extends BuyProvider {
final uri = Uri.https(_cIdBaseUrl, "/api/robinhood");
var response = await ProxyWrapper().post(
final response = await ProxyWrapper().post(
clearnetUri: uri,
headers: {'Content-Type': 'application/json'},
body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
body: json
.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
);
if (response.statusCode == 200) {
return (jsonDecode(response.body) as Map<String, dynamic>)['connectId'] as String;
} else {
throw Exception('Provider currently unavailable. Status: ${response.statusCode}');
@@ -158,16 +161,17 @@ class RobinhoodBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
if (wallet.isHardwareWallet) {
if (!ledgerVM!.isConnected) {
if (!hardwareWalletVM!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: wallet.walletInfo.type,
onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
ledgerVM.setLedger(wallet);
hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (context, hwwVM) {
hwwVM.initWallet(wallet);
Navigator.of(context).pop();
}));
} else {
ledgerVM!.setLedger(wallet);
hardwareWalletVM!.initWallet(wallet);
}
}
@@ -176,14 +180,13 @@ class RobinhoodBuyProvider extends BuyProvider {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "Robinhood Connect",
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: "Robinhood Connect",
alertContent: '${S.of(context).buy_provider_unavailable}: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()),
);
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ class WyreBuyProvider extends BuyProvider {
super(
wallet: wallet,
isTestEnvironment: isTestEnvironment,
ledgerVM: null,
hardwareWalletVM: null,
supportedCryptoList: supportedCryptoToFiatPairs(
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
supportedFiatList: supportedFiatToCryptoPairs(
+3 -2
View File
@@ -1,14 +1,15 @@
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class NewWalletTypeArguments {
final void Function(BuildContext, WalletType)? onTypeSelected;
final bool isCreate;
final bool isHardwareWallet;
final HardwareWalletType? hardwareWalletType;
NewWalletTypeArguments({
required this.onTypeSelected,
required this.isCreate,
required this.isHardwareWallet,
this.hardwareWalletType,
});
}
+53 -20
View File
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
@@ -53,6 +54,8 @@ import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -393,8 +396,22 @@ Future<void> setup({
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
getIt.registerFactoryParam<HardwareWalletViewModel, HardwareWalletType, void>((type, _) {
switch(type) {
case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
case HardwareWalletType.cupcake:
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
case HardwareWalletType.keystone:
throw Exception("This should not have happened, because airgapped Wallets don't need View Models");
}
});
getIt.registerLazySingleton(() => LedgerViewModel());
getIt.registerLazySingleton(() => BitboxViewModel());
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
getIt.registerSingleton<SecretStore>(secretStore);
@@ -505,9 +522,9 @@ Future<void> setup({
walletType: args.walletType ?? currentWalletType);
});
getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
WalletHardwareRestoreViewModel(
getIt.get<LedgerViewModel>(),
getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, HardwareWalletViewModel>(
(type, hardwareWalletVM) => WalletHardwareRestoreViewModel(
hardwareWalletVM,
getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
@@ -816,7 +833,10 @@ Future<void> setup({
getIt.get<BalanceViewModel>(),
getIt.get<ContactListViewModel>(),
_transactionDescriptionBox,
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
getIt.get<AppStore>().wallet!.isHardwareWallet
? getIt<HardwareWalletViewModel>(
param1: getIt.get<AppStore>().wallet!.hardwareWalletType!)
: null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
getIt.get<FeesViewModel>(),
@@ -1077,14 +1097,18 @@ Future<void> setup({
isSelected: isSelected));
getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
wallet: getIt.get<AppStore>().wallet!,
hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
: null,
));
getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
wallet: getIt.get<AppStore>().wallet!,
ledgerVM:
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
wallet: getIt<AppStore>().wallet!,
hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
: null,
));
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
appStore: getIt.get<AppStore>(),
@@ -1235,19 +1259,25 @@ Future<void> setup({
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, RestoredWallet?>((WalletType type,
restoredWallet) =>
WalletRestoreViewModel(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type),
_walletInfoSource, getIt.get<SeedSettingsViewModel>(),
type: type, restoredWallet: restoredWallet));
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
(type, additionalParams) {
final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?;
getIt.registerFactoryParam<WalletRestorePage, WalletType, RestoredWallet?>((WalletType type,
restoredWallet) {
return WalletRestorePage(
getIt.get<WalletRestoreViewModel>(param1: type, param2: restoredWallet),
getIt.get<SeedSettingsViewModel>());
return WalletRestoreViewModel(
getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
getIt.get<SeedSettingsViewModel>(),
type: type,
restoredWallet: restoredWallet,
hardwareWalletType: hardwareWalletType);
});
getIt.registerFactoryParam<WalletRestorePage, WalletType, Map<String, dynamic>?>((type, additionalParams) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type, param2: additionalParams),
getIt.get<SeedSettingsViewModel>()));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
@@ -1565,9 +1595,12 @@ Future<void> setup({
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
getIt.registerFactory(() => SocketHealthLogsViewModel());
getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
getIt.registerFactory(() => DevNetworkRequests());
getIt.registerFactory(() => DevQRToolsPage());
getIt.registerFactory(() => ExchangeProviderLogsViewModel());
getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get<ExchangeProviderLogsViewModel>()));
@@ -1,22 +1,44 @@
import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
class HardwareWalletDevice {
final String name;
final HardwareWalletDeviceType type;
final HardwareWalletConnectionType connectionType;
abstract class HardwareWalletDevice {
String get name;
const HardwareWalletDevice({
required this.name,
required this.type,
required this.connectionType,
});
HardwareWalletDeviceType get type;
factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
HardwareWalletDevice(
name: device.name,
type: device.deviceInfo.toGeneric(),
connectionType: device.connectionType.toGeneric(),
);
HardwareWalletConnectionType get connectionType;
}
class LedgerHardwareWalletDevice extends HardwareWalletDevice {
final ledger.LedgerDevice device;
LedgerHardwareWalletDevice(this.device);
@override
String get name => device.name;
@override
HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
@override
HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
}
class BitboxHardwareWalletDevice extends HardwareWalletDevice {
final bitbox.BitboxDevice device;
BitboxHardwareWalletDevice(this.device);
@override
String get name => device.productName;
@override
HardwareWalletDeviceType get type => name.contains("BitBox02 Nova")
? HardwareWalletDeviceType.BitBox02Nova
: HardwareWalletDeviceType.BitBox02;
@override
HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
}
enum HardwareWalletDeviceType {
@@ -25,13 +47,16 @@ enum HardwareWalletDeviceType {
ledgerNanoX,
ledgerNanoSPlus,
ledgerStax,
ledgerFlex;
ledgerFlex,
BitBox02,
BitBox02Nova;
}
enum HardwareWalletConnectionType {
usb,
ble,
nfc;
nfc,
qr;
}
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
+14 -14
View File
@@ -181,22 +181,22 @@ class CWEthereum extends Ethereum {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
.setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath);
}
@override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
printV(err);
throw err;
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
}
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager);
@override
List<String> getDefaultTokenContractAddresses() {
+14 -18
View File
@@ -199,27 +199,23 @@ class CWPolygon extends Polygon {
);
@override
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
connection,
wallet.walletInfo.derivationInfo?.derivationPath,
);
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
}
}
@override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(
LedgerViewModel ledgerVM, {
int index = 0,
int limit = 5,
}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
printV(err);
throw err;
}
}
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 137);
@override
List<String> getDefaultTokenContractAddresses() =>
@@ -14,6 +14,8 @@ import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart';
@@ -60,8 +62,10 @@ void startAuthenticationStateChange(
(route) => false,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
hardwareWalletType: HardwareWalletType.ledger,
onConnectDevice: (context, ledgerVM) async {
monero!.setGlobalLedgerConnection(ledgerVM.connection);
if (ledgerVM is LedgerViewModel)
monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (context) => AlertWithOneAction(
+45 -18
View File
@@ -23,6 +23,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
@@ -39,6 +40,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
@@ -130,6 +132,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/sign_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/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -213,7 +216,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
arguments: NewWalletArguments(type: type),
),
isCreate: true,
isHardwareWallet: false,
),
),
);
@@ -245,12 +247,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.chooseHardwareWalletAccount:
final arguments = settings.arguments as List<dynamic>;
final type = arguments[0] as WalletType;
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
final hardwareWallet = arguments [1] as HardwareWalletType;
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(
param1: type, param2: getIt<HardwareWalletViewModel>(param1: hardwareWallet));
if (type == WalletType.monero)
return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
return handleRouteWithPlatformAwareness((_) => MoneroHardwareWalletOptionsPage(walletVM));
return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
return handleRouteWithPlatformAwareness((_) => SelectHardwareWalletAccountPage(walletVM));
case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback;
@@ -272,7 +277,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
},
isCreate: false,
isHardwareWallet: false,
),
),
);
@@ -301,41 +305,59 @@ Route<dynamic> createRoute(RouteSettings settings) {
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
},
isCreate: false,
isHardwareWallet: false,
),
),
);
case Routes.restoreWalletFromHardwareWallet:
final arguments = settings.arguments as Map<String, dynamic>?;
final showUnavailable = (arguments?['showUnavailable'] as bool?) ?? true;
final onSelect = arguments?['onSelect'] as void Function(BuildContext, HardwareWalletType)?;
final availableHardwareWalletTypes =
arguments?['availableHardwareWalletTypes'] as List<HardwareWalletType>?;
return handleRouteWithPlatformAwareness((_) => SelectDeviceManufacturerPage(
showUnavailable: showUnavailable,
onSelect: onSelect,
availableHardwareWalletTypes: availableHardwareWalletTypes,
));
case Routes.connectHardwareWallet:
final arguments = settings.arguments as List<dynamic>;
final hardwareWalletType = (arguments[0] as HardwareWalletType?) ?? HardwareWalletType.ledger;
if (isSingleCoin) {
return MaterialPageRoute<void>(
builder: (_) => ConnectDevicePage(
return handleRouteWithPlatformAwareness(
(_) => ConnectDevicePage(
ConnectDevicePageParams(
walletType: availableWalletTypes.first,
hardwareWalletType: hardwareWalletType,
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
Routes.chooseHardwareWalletAccount,
arguments: [availableWalletTypes.first]),
arguments: [availableWalletTypes.first, hardwareWalletType]),
isReconnect: false,
),
getIt.get<LedgerViewModel>(),
),
);
}
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
return handleRouteWithPlatformAwareness(
(_) => getIt.get<NewWalletTypePage>(
param1: NewWalletTypeArguments(
onTypeSelected: (BuildContext context, WalletType type) {
final arguments = ConnectDevicePageParams(
walletType: type,
onConnectDevice: (BuildContext context, _) => Navigator.of(context)
.pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
hardwareWalletType: hardwareWalletType,
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
Routes.chooseHardwareWalletAccount,
arguments: [type, hardwareWalletType]),
isReconnect: false,
);
Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
},
isCreate: false,
isHardwareWallet: true,
hardwareWalletType: hardwareWalletType,
),
),
);
@@ -347,7 +369,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
onTypeSelected: (BuildContext context, WalletType type) =>
Navigator.of(context).pop(type),
isCreate: false,
isHardwareWallet: false,
),
),
);
@@ -360,9 +381,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWallet:
final args = settings.arguments as Map<String, dynamic>?;
final walletType = args?['walletType'] as WalletType;
final restoredWallet = args?['restoredWallet'] as RestoredWallet?;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: restoredWallet));
builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: args));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
@@ -873,8 +893,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.connectDevices:
final params = settings.arguments as ConnectDevicePageParams;
return MaterialPageRoute<void>(
builder: (_) => ConnectDevicePage(params, getIt.get<LedgerViewModel>()));
builder: (_) => ConnectDevicePage(
params, getIt.get<HardwareWalletViewModel>(param1: params.hardwareWalletType)));
case Routes.walletGroupDescription:
final walletType = settings.arguments as WalletType;
@@ -920,6 +942,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<DevSocketHealthLogsPage>(),
);
case Routes.devQRTools:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<DevQRToolsPage>(),
);
case Routes.devNetworkRequests:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<DevNetworkRequests>(),
+2
View File
@@ -11,6 +11,7 @@ class Routes {
static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet';
static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const connectHardwareWallet = '/restore/hardware_wallet/devices';
static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts';
static const dashboard = '/dashboard';
static const send = '/send';
@@ -123,6 +124,7 @@ class Routes {
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
static const devSocketHealthLogs = '/dev/socket_health_logs';
static const devNetworkRequests = '/dev/network_requests';
static const devQRTools = '/dev/qr_tools';
static const devExchangeProviderLogs = '/dev/exchange_provider_logs';
static const signPage = '/sign_page';
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@@ -9,23 +10,25 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_wid
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
class ConnectDevicePageParams {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
final HardwareWalletType hardwareWalletType;
ConnectDevicePageParams({
required this.walletType,
required this.hardwareWalletType,
required this.onConnectDevice,
this.allowChangeWallet = false,
this.isReconnect = true,
@@ -37,9 +40,9 @@ class ConnectDevicePage extends BasePage {
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final bool isReconnect;
final LedgerViewModel ledgerVM;
final HardwareWalletViewModel hardwareWalletVM;
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
: walletType = params.walletType,
onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet,
@@ -51,8 +54,7 @@ class ConnectDevicePage extends BasePage {
: S.current.restore_title_from_hardware_wallet;
@override
Widget? leading(BuildContext context) =>
!isReconnect ? super.leading(context) : null;
Widget? leading(BuildContext context) => !isReconnect ? super.leading(context) : null;
@override
Widget body(BuildContext context) => PopScope(
@@ -61,7 +63,7 @@ class ConnectDevicePage extends BasePage {
walletType,
onConnectDevice,
allowChangeWallet,
ledgerVM,
hardwareWalletVM,
currentTheme,
));
}
@@ -70,14 +72,14 @@ class ConnectDevicePageBody extends StatefulWidget {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final LedgerViewModel ledgerVM;
final HardwareWalletViewModel hardwareWalletVM;
final MaterialThemeBase currentTheme;
const ConnectDevicePageBody(
this.walletType,
this.onConnectDevice,
this.allowChangeWallet,
this.ledgerVM,
this.hardwareWalletVM,
this.currentTheme,
);
@@ -86,13 +88,13 @@ class ConnectDevicePageBody extends StatefulWidget {
}
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
var bleDevices = <LedgerDevice>[];
var usbDevices = <LedgerDevice>[];
var bleDevices = <HardwareWalletDevice>[];
var usbDevices = <HardwareWalletDevice>[];
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
bool longWait = false;
Timer? _longWaitTimer;
@@ -101,21 +103,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_bleStateTimer = Timer.periodic(
Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
_bleStateTimer =
Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
_bleRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
_bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) {
_usbRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
}
_longWaitTimer = Timer(Duration(seconds: 10), () {
if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
setState(() => longWait = true);
});
if (widget.hardwareWalletVM.hasBluetooth) {
_longWaitTimer = Timer(Duration(seconds: 10), () {
if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
setState(() => longWait = true);
});
}
});
}
@@ -127,35 +129,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_bleRefresh?.cancel();
_longWaitTimer?.cancel();
widget.ledgerVM.stopScanning();
widget.hardwareWalletVM.stopScanning();
super.dispose();
}
Future<void> _refreshUsbDevices() async {
final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
// _usbRefresh = widget.ledgerVM
// .scanForUsbDevices()
// .listen((device) => setState(() => usbDevices.add(device)))
// ..onError((e) {
// throw e.toString();
// });
// Keep polling until the lfp lib gets updated
// _usbRefreshTimer?.cancel();
// _usbRefreshTimer = null;
}
Future<void> _refreshBleDevices() async {
try {
if (widget.ledgerVM.bleIsEnabled) {
_bleRefresh =
widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
bleDevices.add(device);
if (longWait) longWait = false;
}))
..onError((e) {
throw e.toString();
});
if (widget.hardwareWalletVM.isBleEnabled) {
_bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
bleDevices.add(device);
if (longWait) longWait = false;
}))
..onError((e) {
throw e.toString();
});
_bleRefreshTimer?.cancel();
_bleRefreshTimer = null;
}
@@ -164,25 +156,35 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
}
}
Future<void> _connectToDevice(LedgerDevice device) async {
final isConnected =
await widget.ledgerVM.connectLedger(device, widget.walletType);
if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
Future<void> _connectToDevice(HardwareWalletDevice device) async {
final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
}
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
switch (deviceInfo) {
case LedgerDeviceType.nanoX:
String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
switch (deviceType) {
case HardwareWalletDeviceType.ledgerNanoX:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
case LedgerDeviceType.stax:
case HardwareWalletDeviceType.ledgerStax:
return 'assets/images/hardware_wallet/ledger_stax.png';
case LedgerDeviceType.flex:
case HardwareWalletDeviceType.ledgerFlex:
return 'assets/images/hardware_wallet/ledger_flex.png';
case HardwareWalletDeviceType.BitBox02:
return 'assets/images/hardware_wallet/device_bitbox.svg';
default:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
}
}
String get description {
if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
return S.of(context).connect_your_hardware_wallet_usb;
}
@override
Widget build(BuildContext context) {
return Container(
@@ -197,11 +199,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
Platform.isAndroid
? S.of(context).connect_your_hardware_wallet
: S.of(context).connect_your_hardware_wallet_ios,
style: Theme.of(context)
.textTheme.titleMedium,
description,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
@@ -211,22 +210,20 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).if_you_dont_see_your_device,
style: Theme.of(context)
.textTheme.titleMedium,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
),
Observer(
builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled,
offstage: widget.hardwareWalletVM.isBleEnabled ||
!widget.hardwareWalletVM.hasBluetooth,
child: Padding(
padding:
EdgeInsets.only(left: 20, right: 20, bottom: 20),
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: Theme.of(context)
.textTheme.titleMedium,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
@@ -239,8 +236,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
width: double.infinity,
child: Text(
S.of(context).bluetooth,
style: Theme.of(context)
.textTheme.bodyMedium,
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
@@ -251,7 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
leading: _getDeviceTileLeading(device.type),
connectionType: device.connectionType,
),
),
@@ -265,8 +261,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
width: double.infinity,
child: Text(
S.of(context).usb,
style: Theme.of(context)
.textTheme.bodyMedium,
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
@@ -277,7 +272,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
leading: _getDeviceTileLeading(device.type),
connectionType: device.connectionType,
),
),
@@ -287,10 +282,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
color: Theme.of(context)
.colorScheme.primary,
textColor: Theme.of(context)
.colorScheme.onPrimary,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: _onChangeWallet,
)
],
@@ -312,8 +305,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void _onChangeWallet() {
Navigator.of(context).pushNamed(
Routes.walletList,
arguments: (BuildContext context) => Navigator.of(context)
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
arguments: (BuildContext context) =>
Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
);
}
@@ -0,0 +1,183 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/dotted_divider.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/manufacturer_option_tile.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:permission_handler/permission_handler.dart';
class SelectDeviceManufacturerPage extends BasePage {
SelectDeviceManufacturerPage({
this.showUnavailable = true,
this.onSelect,
this.availableHardwareWalletTypes,
});
final bool showUnavailable;
final void Function(BuildContext, HardwareWalletType)? onSelect;
final List<HardwareWalletType>? availableHardwareWalletTypes;
@override
String get title => S.current.select_manufacturer_title;
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
List<_DeviceManufacturer> get availableManufacturers => [
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.ledger,
),
// _DeviceManufacturer(
// image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
// hardwareWalletType: HardwareWalletType.bitbox,
// tag: S.current.new_tag,
// ),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.cupcake,
tag: S.current.new_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.coldcard,
tag: S.current.new_tag,
),
// _DeviceManufacturer(
// image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
// hardwareWalletType: HardwareWalletType.seedsigner,
// tag: S.current.new_tag,
// ),
]
.where((e) => availableHardwareWalletTypes == null
? ![HardwareWalletType.cupcake].contains(e.hardwareWalletType)
: availableHardwareWalletTypes!.contains(e.hardwareWalletType))
.toList();
List<_DeviceManufacturer> get comingManufacturers => [
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.bitbox,
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
hardwareWalletType: HardwareWalletType.seedsigner,
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
tag: S.current.coming_soon_tag,
),
_DeviceManufacturer(
image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
tag: S.current.coming_soon_tag,
),
];
@override
Widget body(BuildContext context) => Container(
child: Center(
child: Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: Column(
children: [
...availableManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
onPressed: () {
if (onSelect != null)
return onSelect!.call(context, manufacturer.hardwareWalletType!);
if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
_onScanQRCode(context, manufacturer.hardwareWalletType!);
} else if (manufacturer.hardwareWalletType != null) {
Navigator.pushNamed(context, Routes.connectHardwareWallet,
arguments: [manufacturer.hardwareWalletType]);
}
},
isDarkTheme: currentTheme.isDark,
),
),
),
if (showUnavailable) ...[
Padding(
padding: EdgeInsets.only(top: 20, bottom: 10),
child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
),
...comingManufacturers.map(
(manufacturer) => Padding(
padding: EdgeInsets.only(top: 10),
child: ManufacturerOptionTile(
image: manufacturer.image,
tag: manufacturer.tag,
onPressed: () =>
Fluttertoast.showToast(msg: 'One more tap and it might work'),
// Ester egg
isDarkTheme: currentTheme.isDark,
isUnavailable: true,
),
),
),
]
],
),
),
),
);
bool isAirgappedWallet(HardwareWalletType? type) => [
HardwareWalletType.cupcake,
HardwareWalletType.coldcard,
HardwareWalletType.seedsigner,
HardwareWalletType.keystone,
].contains(type);
bool isRestoring = false;
Future<void> _onScanQRCode(BuildContext context, HardwareWalletType type) async {
final isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
try {
if (isRestoring) return;
isRestoring = true;
final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
final params = {
'walletType': restoredWallet.type,
'restoredWallet': restoredWallet,
'hardwareWalletType': type,
};
Navigator.pushNamed(context, Routes.restoreWallet, arguments: params)
.then((_) => isRestoring = false);
} catch (e) {
printV(e.toString());
}
}
}
class _DeviceManufacturer {
final SvgPicture image;
final HardwareWalletType? hardwareWalletType;
final String? tag;
_DeviceManufacturer({required this.image, this.hardwareWalletType, this.tag});
}
@@ -1,5 +1,6 @@
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:flutter/material.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DeviceTile extends StatelessWidget {
const DeviceTile({
@@ -12,15 +13,15 @@ class DeviceTile extends StatelessWidget {
final VoidCallback onPressed;
final String title;
final String? leading;
final ConnectionType? connectionType;
final HardwareWalletConnectionType? connectionType;
String? get connectionTypeIcon {
switch (connectionType) {
case ConnectionType.ble:
case HardwareWalletConnectionType.ble:
return 'assets/images/bluetooth.png';
case ConnectionType.usb:
case HardwareWalletConnectionType.usb:
return 'assets/images/usb.png';
case null:
default:
return null;
}
}
@@ -42,12 +43,14 @@ class DeviceTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
if (leading != null)
if (leading != null && !leading!.endsWith(".svg"))
Image.asset(
leading!,
height: 30,
color: Theme.of(context).colorScheme.onSurface,
),
if (leading != null && leading!.endsWith(".svg"))
SvgPicture.asset(leading!, height: 30),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
class DottedDivider extends StatelessWidget {
final Color color;
const DottedDivider({super.key, required this.color});
@override
Widget build(BuildContext context) => SizedBox(
width: double.infinity,
child: CustomPaint(
painter: _DashedLinePainter(color: color),
),
);
}
class _DashedLinePainter extends CustomPainter {
final Color color;
const _DashedLinePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
double dashWidth = 9, dashSpace = 5, startX = 0;
final paint = Paint()
..color = color
..strokeWidth = 1;
while (startX < size.width) {
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
startX += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
@@ -0,0 +1,80 @@
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
import 'package:flutter/material.dart';
class ManufacturerOptionTile extends StatelessWidget {
const ManufacturerOptionTile({
required this.onPressed,
required this.image,
required this.isDarkTheme,
this.tag,
this.isUnavailable = false,
});
final VoidCallback onPressed;
final Widget image;
final String? tag;
final bool isUnavailable;
final bool isDarkTheme;
@override
Widget build(BuildContext context) => GestureDetector(
onTap: onPressed,
child: Opacity(
opacity: isUnavailable ? 0.5 : 1,
child: Container(
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
padding: EdgeInsets.all(24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: [
Container(height: 25, child: image),
if (tag != null)
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.transparent),
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surfaceContainerLow),
padding: EdgeInsets.symmetric(horizontal: 5),
margin: EdgeInsets.only(left: 5),
child: Text(tag!))
],
),
],
),
),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: Theme.of(context).colorScheme.onSurface,
)
],
),
),
),
);
}
@@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -27,6 +28,23 @@ class CryptoBalanceWidget extends StatelessWidget {
final btcLockLight = 'assets/images/btc_lock_light.png';
final btcLockDark = 'assets/images/btc_lock_dark.png';
String? get hardwareWalletIcon {
switch (dashboardViewModel.wallet.hardwareWalletType) {
case null:
return null;
case HardwareWalletType.ledger:
return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
case HardwareWalletType.bitbox:
return "assets/images/hardware_wallet/device_bitbox.svg";
case HardwareWalletType.cupcake:
return "assets/images/cupcake.svg";
case HardwareWalletType.coldcard:
case HardwareWalletType.seedsigner:
case HardwareWalletType.keystone:
return "assets/images/hardware_wallet/device_qr.svg";
}
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -86,10 +104,10 @@ class CryptoBalanceWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.center,
),
if (dashboardViewModel.wallet.isHardwareWallet)
if (hardwareWalletIcon != null)
Container(
child: Image.asset(
'assets/images/hardware_wallet/ledger_nano_x.png',
child: SvgPicture.asset(
hardwareWalletIcon!,
width: 24,
color: Theme.of(context).colorScheme.onSurface,
),
+39
View File
@@ -0,0 +1,39 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class DevQRToolsPage extends BasePage {
@override
String? get title => "[dev] *QR tools";
final QRToolsViewModel viewModel = QRToolsViewModel();
late final textCtrl = TextEditingController(text: viewModel.input);
@override
Widget body(BuildContext context) {
return Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: textCtrl,
maxLines: 8,
onChanged: (value) {
viewModel.input = value;
}
),
Observer(
builder: (_) {
return Expanded(
child: JsonExplorer(data: viewModel.data, title: "result"),
);
},
),
],
),
);
}
}
@@ -1,7 +1,7 @@
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'dart:io';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/routes.dart';
@@ -11,12 +11,12 @@ import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_aler
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -51,7 +51,7 @@ class NewWalletTypePage extends BasePage {
isCreate: newWalletTypeArguments.isCreate,
newWalletTypeViewModel: newWalletTypeViewModel,
onTypeSelected: newWalletTypeArguments.onTypeSelected,
isHardwareWallet: newWalletTypeArguments.isHardwareWallet,
hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
);
}
@@ -61,14 +61,16 @@ class WalletTypeForm extends StatefulWidget {
required this.isCreate,
required this.newWalletTypeViewModel,
this.onTypeSelected,
required this.isHardwareWallet,
this.hardwareWalletType,
});
final bool isCreate;
final Image walletImage;
final NewWalletTypeViewModel newWalletTypeViewModel;
final void Function(BuildContext, WalletType)? onTypeSelected;
final bool isHardwareWallet;
final HardwareWalletType? hardwareWalletType;
bool get isHardwareWallet => hardwareWalletType != null;
@override
WalletTypeFormState createState() => WalletTypeFormState();
@@ -90,7 +92,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
types = filteredTypes = availableWalletTypes
.where((element) =>
!widget.isHardwareWallet ||
DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
DeviceConnectionType.supportedConnectionTypes(
element, widget.hardwareWalletType!, Platform.isIOS)
.isNotEmpty)
.toList();
super.initState();
@@ -148,7 +152,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
isSelected: selected == type,
onTap: () => setState(() => selected = type),
deviceConnectionTypes: widget.isHardwareWallet
? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
? DeviceConnectionType.supportedConnectionTypes(
type, widget.hardwareWalletType!, Platform.isIOS)
: [],
),
),
@@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget {
),
if (addressListViewModel.isPayjoinUnavailable &&
!addressListViewModel.isSilentPayments &&
!addressListViewModel.isCupcake) ...[
!addressListViewModel.isBitcoinViewOnly) ...[
GestureDetector(
onTap: () => _onPayjoinInactivePressed(context),
child: Row(
@@ -1,10 +1,9 @@
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/permission_handler.dart';
@@ -12,11 +11,9 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
class RestoreOptionsPage extends BasePage {
RestoreOptionsPage({required this.isNewInstall});
@@ -27,9 +24,8 @@ class RestoreOptionsPage extends BasePage {
final bool isNewInstall;
@override
Widget body(BuildContext context) {
return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
}
Widget body(BuildContext context) =>
_RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
}
class _RestoreOptionsBody extends StatefulWidget {
@@ -45,41 +41,25 @@ class _RestoreOptionsBody extends StatefulWidget {
class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
bool isRestoring = false;
bool get _doesSupportHardwareWallets {
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
.isNotEmpty;
}
String get imageRestoreHWPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_hw_dark.png'
: 'assets/images/restore_hw.png';
return true;
}
String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark
? 'assets/images/restore_cupcake_dark.png'
: 'assets/images/restore_cupcake.png';
String get imageRestoreHWPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_hw_dark.png'
: 'assets/images/restore_hw.png';
String get imageRestoreQRPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_qr_dark.png'
: 'assets/images/restore_qr.png';
String get imageRestoreCupcakePath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_cupcake_dark.png'
: 'assets/images/restore_cupcake.png';
String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_hot_wallet_dark.png'
: 'assets/images/restore_hot_wallet.png';
String get imageRestoreQRPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_qr_dark.png'
: 'assets/images/restore_qr.png';
String get imageRestoreHotWalletPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_hot_wallet_dark.png'
: 'assets/images/restore_hot_wallet.png';
String get imageRestoreBackupPath =>
widget.themeType == ThemeType.dark
? 'assets/images/restore_backup_dark.png'
: 'assets/images/restore_backup.png';
String get imageRestoreBackupPath => widget.themeType == ThemeType.dark
? 'assets/images/restore_backup_dark.png'
: 'assets/images/restore_backup.png';
@override
Widget build(BuildContext context) {
@@ -116,18 +96,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
tag: S.of(context).new_tag,
),
),
if (_doesSupportHardwareWallets)
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
onPressed: () =>
Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet),
image: imageRestoreHW,
title: S.of(context).restore_title_from_hardware_wallet,
description: S.of(context).restore_description_from_hardware_wallet,
),
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
onPressed: () => Navigator.pushNamed(
context,
isSingleCoin
? Routes.connectHardwareWallet
: Routes.restoreWalletFromHardwareWallet),
image: imageRestoreHW,
title: S.of(context).restore_title_from_hardware_wallet,
description: S.of(context).restore_description_from_hardware_wallet,
),
),
if (widget.isNewInstall)
Padding(
padding: EdgeInsets.only(top: 24),
@@ -143,11 +125,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
key: ValueKey('restore_options_from_qr_button_key'),
onPressed: () => _onScanQRCode(context),
image: imageRestoreQR,
title: S.of(context).scan_qr_code,
description: S.of(context).cold_or_recover_wallet,
key: ValueKey('restore_options_from_qr_button_key'),
onPressed: () => _onScanQRCode(context),
image: imageRestoreQR,
title: S.of(context).scan_qr_code,
description: S.of(context).cold_or_recover_wallet,
),
),
],
@@ -184,7 +166,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
final params = {'walletType': restoredWallet.type, 'restoredWallet': restoredWallet};
final params = {
'walletType': restoredWallet.type,
'restoredWallet': restoredWallet,
'hardwareWalletType': HardwareWalletType.cupcake,
};
Navigator.pushNamed(context, Routes.restoreWallet, arguments: params).then((_) {
if (mounted) setState(() => isRestoring = false);
+7 -4
View File
@@ -440,18 +440,21 @@ class SendPage extends BasePage {
}
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
hardwareWalletType:
sendViewModel.wallet.walletInfo.hardwareWalletType!,
onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
sendViewModel.hardwareWalletViewModel!
.initWallet(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
sendViewModel.hardwareWalletViewModel!
.initWallet(sendViewModel.wallet);
}
}
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -26,16 +27,14 @@ class OtherSettingsPage extends BasePage {
final OtherSettingsViewModel _otherSettingsViewModel;
@override
Widget body(BuildContext context) {
return Observer(
builder: (_) {
return Container(
Widget body(BuildContext context) => Observer(
builder: (_) => Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
if (_otherSettingsViewModel.displayTransactionPriority)
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
SettingsPriorityPickerCell(
_otherSettingsViewModel.walletType == WalletType.bitcoin
? SettingsPriorityPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
@@ -44,8 +43,8 @@ class OtherSettingsPage extends BasePage {
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
) :
SettingsPickerCell(
)
: SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
@@ -55,75 +54,96 @@ class OtherSettingsPage extends BasePage {
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
handler: (context) => Navigator.of(context).pushNamed(Routes.changeRep),
),
if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
SettingsCellWithArrow(
title: "Hardware wallet manufacturer",
handler: (context) => Navigator.of(context)
.pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
"showUnavailable": false,
"availableHardwareWalletTypes": [
HardwareWalletType.cupcake,
HardwareWalletType.coldcard,
HardwareWalletType.seedsigner,
],
"onSelect": (BuildContext context, HardwareWalletType hwType) async {
await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
Navigator.pop(context);
},
}),
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.readDisclaimer),
handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
),
if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero)
if (FeatureFlag.hasDevOptions &&
_otherSettingsViewModel.walletType == WalletType.monero)
SettingsCellWithArrow(
title: '[dev] monero background sync',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
),
if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType))
if (FeatureFlag.hasDevOptions &&
[WalletType.monero, WalletType.wownero, WalletType.zano]
.contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr call profiler',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
),
if (FeatureFlag.hasDevOptions && [WalletType.monero].contains(_otherSettingsViewModel.walletType))
if (FeatureFlag.hasDevOptions &&
[WalletType.monero].contains(_otherSettingsViewModel.walletType))
SettingsCellWithArrow(
title: '[dev] xmr wallet cache debug',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] shared preferences',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] secure storage preferences',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devSecurePreferences),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] background sync logs',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] socket health logs',
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] network requests logs',
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.devNetworkRequests),
handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] exchange provider logs',
handler: (BuildContext context) =>
handler: (context) =>
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
),
if (FeatureFlag.hasDevOptions)
SettingsCellWithArrow(
title: '[dev] *QR tools',
handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools),
),
Spacer(),
SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
title: S.of(context).version(_otherSettingsViewModel.currentVersion),
),
],
),
);
},
);
}
),
);
}
+2 -1
View File
@@ -61,6 +61,7 @@ class AnimatedURPage extends BasePage {
child: URQR(
urqr: urQr,
walletType: animatedURmodel.wallet.type,
hardwareWalletType: animatedURmodel.wallet.hardwareWalletType,
),
),
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
@@ -125,4 +126,4 @@ class AnimatedURPage extends BasePage {
});
}
}
}
}
+83 -101
View File
@@ -1,4 +1,3 @@
import 'dart:async';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
@@ -7,67 +6,65 @@ import 'package:cake_wallet/src/screens/ur/widgets/qr_selection_dialog.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
class URQR extends StatefulWidget {
URQR({super.key, required this.urqr, required this.walletType});
const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType});
final Map<String, String> urqr;
final WalletType walletType;
final HardwareWalletType? hardwareWalletType;
@override
// ignore: library_private_types_in_public_api
_URQRState createState() => _URQRState();
}
const urFrameTime = 1000 ~/ 5;
class _URQRState extends State<URQR> {
Timer? t;
Timer? _timer;
int frame = 0;
int selectedInt = 0;
@override
void initState() {
super.initState();
setState(() {
t = Timer.periodic(const Duration(milliseconds: urFrameTime), (timer) {
_nextFrame();
});
});
}
final keys = widget.urqr.keys.toList();
if (widget.hardwareWalletType == HardwareWalletType.coldcard) {
selectedInt = 1;
selected = keys[(selectedInt) % keys.length];
} else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) {
selectedInt = 2;
selected = keys[(selectedInt) % keys.length];
}
void _nextFrame() {
setState(() {
frame++;
});
_timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame());
super.initState();
}
@override
void dispose() {
t?.cancel();
_timer?.cancel();
super.dispose();
}
late String selected =
(widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
int selectedInt = 0;
List<String> get frames {
return widget.urqr[selected]?.split("\n") ?? [];
}
late String nextLabel =
widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
void next() {
String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
void next() => setState(() {
final keys = widget.urqr.keys.toList();
setState(() {
selectedInt++;
nextLabel = keys[(selectedInt + 1) % keys.length];
selected = keys[(selectedInt) % keys.length];
});
}
selectedInt++;
selected = keys[(selectedInt) % keys.length];
});
late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
List<String> get frames => widget.urqr[selected]?.split("\n") ?? [];
void _nextFrame() => setState(() => frame++);
@override
Widget build(BuildContext context) {
@@ -107,45 +104,44 @@ All frames:
);
}
Widget _legacySwitch(BuildContext context) {
return SizedBox(
width: double.maxFinite,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: next,
text: nextLabel,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
Widget _legacySwitch(BuildContext context) => SizedBox(
width: double.maxFinite,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: double.maxFinite,
child: PrimaryButton(
onPressed: next,
text: nextLabel,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),
),
),
);
}
);
Widget _newSwitch(BuildContext context) {
return Column(
children: [
SizedBox(height: 32),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.qr_code,
color: Theme.of(context).colorScheme.primary, size: 24),
SizedBox(width: 4),
Text("QR Code Format",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
)),
Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24),
const SizedBox(width: 4),
Text(
"QR Code Format",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
SizedBox(height: 16),
const SizedBox(height: 16),
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(12),
@@ -176,7 +172,7 @@ All frames:
),
),
),
SizedBox(width: 16),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -193,10 +189,7 @@ All frames:
_getCurrentFormatSubtitle(),
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.6),
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
@@ -204,10 +197,7 @@ All frames:
),
Icon(
Icons.chevron_right,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.4),
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
size: 24,
),
],
@@ -216,7 +206,7 @@ All frames:
),
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
GestureDetector(
onTap: () => _showInfoDialog(context),
child: Row(
@@ -226,35 +216,32 @@ All frames:
"What's this for?",
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
SizedBox(width: 4),
const SizedBox(width: 4),
Icon(
Icons.info_outline,
size: 16,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
],
),
),
SizedBox(height: 32),
const SizedBox(height: 32),
],
);
}
String _getCurrentFormatName() {
if (widget.urqr.isEmpty) return "Unknown";
final currentKey =
widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
return currentKey.split(' ')[0];
}
String _getCurrentFormatSubtitle() {
if (widget.urqr.isEmpty) return "";
final currentKey =
widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
return currentKey.substring(currentKey.indexOf(' ') + 1);
}
@@ -264,30 +251,25 @@ All frames:
final keys = widget.urqr.keys.toList();
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return QRFormatSelectionDialog(
formats: keys,
currentIndex: selectedInt,
onFormatSelected: (index) {
Navigator.pop(context);
setState(() {
selectedInt = index;
selected = keys[index];
nextLabel = keys[(index + 1) % keys.length];
});
},
);
});
context: context,
builder: (context) => QRFormatSelectionDialog(
formats: keys,
currentIndex: selectedInt,
onFormatSelected: (index) {
Navigator.pop(context);
setState(() {
selectedInt = index;
selected = keys[index];
});
},
),
);
}
Future<void> _showInfoDialog(BuildContext context) async {
await showModalBottomSheet(
Future<void> _showInfoDialog(BuildContext context) => showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return QRFormatInfoBottomSheet();
});
}
builder: (_) => QRFormatInfoBottomSheet(),
);
}
@@ -21,9 +21,11 @@ import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -477,8 +479,9 @@ class WalletListBodyState extends State<WalletListBody> {
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
hardwareWalletType: HardwareWalletType.ledger,
onConnectDevice: (context, ledgerVM) async {
monero!.setGlobalLedgerConnection(ledgerVM.connection);
if (ledgerVM is LedgerViewModel) monero!.setGlobalLedgerConnection(ledgerVM.connection);
didConnect = true;
Navigator.of(context).pop();
},
@@ -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;
}
+3 -2
View File
@@ -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: