From 3577730de8cd84cec50f0b1722615218f90b1a82 Mon Sep 17 00:00:00 2001 From: Omar Hatem <omarh.ismail1@gmail.com> Date: Wed, 23 Aug 2023 15:33:20 +0300 Subject: [PATCH] Add restore from private key to Ethereum (#1055) * Add restore from private key to Ethereum Add restore from QR code for Ethereum in both seeds/keys * Add node network issue to ignored errors [skip ci] --- cw_core/lib/wallet_base.dart | 4 +- cw_ethereum/lib/ethereum_wallet.dart | 53 +++++-- .../ethereum_wallet_creation_credentials.dart | 16 +- cw_ethereum/lib/ethereum_wallet_service.dart | 16 +- ios/Podfile.lock | 8 +- lib/di.dart | 21 --- lib/ethereum/cw_ethereum.dart | 8 + lib/router.dart | 12 -- lib/routes.dart | 1 - .../restore_wallet_from_seed_details.dart | 145 ------------------ .../wallet_restore_from_keys_form.dart | 132 +++++++++++----- .../screens/restore/wallet_restore_page.dart | 33 ++-- lib/src/widgets/seed_widget.dart | 7 - lib/utils/exception_handler.dart | 1 + .../dashboard/dashboard_view_model.dart | 4 +- .../restore/restore_from_qr_vm.dart | 3 + lib/view_model/restore/restore_wallet.dart | 5 +- .../restore/wallet_restore_from_qr_code.dart | 14 ++ lib/view_model/wallet_keys_view_model.dart | 21 ++- .../wallet_restoration_from_keys_vm.dart | 77 ---------- .../wallet_restoration_from_seed_vm.dart | 58 ------- lib/view_model/wallet_restore_view_model.dart | 41 +++-- lib/view_model/wallet_seed_view_model.dart | 2 +- tool/configure.dart | 1 + 24 files changed, 250 insertions(+), 433 deletions(-) delete mode 100644 lib/src/screens/restore/restore_wallet_from_seed_details.dart delete mode 100644 lib/view_model/wallet_restoration_from_keys_vm.dart delete mode 100644 lib/view_model/wallet_restoration_from_seed_vm.dart diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 019f87631..5bc5ef914 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -42,7 +42,9 @@ abstract class WalletBase< set syncStatus(SyncStatus status); - String get seed; + String? get seed; + + String? get privateKey => null; Object get keys; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 404b78ca2..8d7c477e1 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -44,12 +44,14 @@ abstract class EthereumWalletBase with Store { EthereumWalletBase({ required WalletInfo walletInfo, - required String mnemonic, + String? mnemonic, + String? privateKey, required String password, ERC20Balance? initialBalance, }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, + _hexPrivateKey = privateKey, _isTransactionUpdating = false, _client = EthereumClient(), walletAddresses = EthereumWalletAddresses(walletInfo), @@ -66,12 +68,13 @@ abstract class EthereumWalletBase _sharedPrefs.complete(SharedPreferences.getInstance()); } - final String _mnemonic; + final String? _mnemonic; + final String? _hexPrivateKey; final String _password; late final Box<Erc20Token> erc20TokensBox; - late final EthPrivateKey _privateKey; + late final EthPrivateKey _ethPrivateKey; late EthereumClient _client; @@ -99,8 +102,12 @@ abstract class EthereumWalletBase erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName); await walletAddresses.init(); await transactionHistory.init(); - _privateKey = await getPrivateKey(_mnemonic, _password); - walletAddresses.address = _privateKey.address.toString(); + _ethPrivateKey = await getPrivateKey( + mnemonic: _mnemonic, + privateKey: _hexPrivateKey, + password: _password, + ); + walletAddresses.address = _ethPrivateKey.address.toString(); await save(); } @@ -108,8 +115,7 @@ abstract class EthereumWalletBase int calculateEstimatedFee(TransactionPriority priority, int? amount) { try { if (priority is EthereumTransactionPriority) { - final priorityFee = - EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt(); + final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); } @@ -142,7 +148,7 @@ abstract class EthereumWalletBase throw Exception("Ethereum Node connection failed"); } - _client.setListeners(_privateKey.address, _onNewTransaction); + _client.setListeners(_ethPrivateKey.address, _onNewTransaction); _setTransactionUpdateTimer(); @@ -202,7 +208,7 @@ abstract class EthereumWalletBase } final pendingEthereumTransaction = await _client.signTransaction( - privateKey: _privateKey, + privateKey: _ethPrivateKey, toAddress: _credentials.outputs.first.isParsedAddress ? _credentials.outputs.first.extractedAddress! : _credentials.outputs.first.address, @@ -240,7 +246,7 @@ abstract class EthereumWalletBase @override Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async { - final address = _privateKey.address.hex; + final address = _ethPrivateKey.address.hex; final transactions = await _client.fetchTransactions(address); final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = []; @@ -300,7 +306,10 @@ abstract class EthereumWalletBase } @override - String get seed => _mnemonic; + String? get seed => _mnemonic; + + @override + String get privateKey => HEX.encode(_ethPrivateKey.privateKey); @action @override @@ -327,6 +336,7 @@ abstract class EthereumWalletBase String toJSON() => json.encode({ 'mnemonic': _mnemonic, + 'private_key': privateKey, 'balance': balance[currency]!.toJSON(), }); @@ -338,13 +348,15 @@ abstract class EthereumWalletBase final path = await pathForWallet(name: name, type: walletInfo.type); final jsonSource = await read(path: path, password: password); final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String; + final mnemonic = data['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero); return EthereumWallet( walletInfo: walletInfo, password: password, mnemonic: mnemonic, + privateKey: privateKey, initialBalance: balance, ); } @@ -357,7 +369,7 @@ abstract class EthereumWalletBase } Future<ERC20Balance> _fetchEthBalance() async { - final balance = await _client.getBalance(_privateKey.address); + final balance = await _client.getBalance(_ethPrivateKey.address); return ERC20Balance(balance.getInWei); } @@ -366,7 +378,7 @@ abstract class EthereumWalletBase try { if (token.enabled) { balance[token] = await _client.fetchERC20Balances( - _privateKey.address, + _ethPrivateKey.address, token.contractAddress, ); } else { @@ -376,8 +388,15 @@ abstract class EthereumWalletBase } } - Future<EthPrivateKey> getPrivateKey(String mnemonic, String password) async { - final seed = bip39.mnemonicToSeed(mnemonic); + Future<EthPrivateKey> getPrivateKey( + {String? mnemonic, String? privateKey, required String password}) async { + assert(mnemonic != null || privateKey != null); + + if (privateKey != null) { + return EthPrivateKey.fromHex(privateKey); + } + + final seed = bip39.mnemonicToSeed(mnemonic!); final root = bip32.BIP32.fromSeed(seed); @@ -413,7 +432,7 @@ abstract class EthereumWalletBase if (_token.enabled) { balance[_token] = await _client.fetchERC20Balances( - _privateKey.address, + _ethPrivateKey.address, _token.contractAddress, ); } else { diff --git a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart b/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart index 12d0d53e2..6546f2fae 100644 --- a/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart +++ b/cw_ethereum/lib/ethereum_wallet_creation_credentials.dart @@ -8,16 +8,22 @@ class EthereumNewWalletCredentials extends WalletCredentials { class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials { EthereumRestoreWalletFromSeedCredentials( - {required String name, required String password, required this.mnemonic, WalletInfo? walletInfo}) + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); final String mnemonic; } -class EthereumRestoreWalletFromWIFCredentials extends WalletCredentials { - EthereumRestoreWalletFromWIFCredentials( - {required String name, required String password, required this.wif, WalletInfo? walletInfo}) +class EthereumRestoreWalletFromPrivateKey extends WalletCredentials { + EthereumRestoreWalletFromPrivateKey( + {required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo}) : super(name: name, password: password, walletInfo: walletInfo); - final String wif; + final String privateKey; } diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 318f287fc..16dbc0b04 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -13,7 +13,7 @@ import 'package:bip39/bip39.dart' as bip39; import 'package:collection/collection.dart'; class EthereumWalletService extends WalletService<EthereumNewWalletCredentials, - EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> { + EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> { EthereumWalletService(this.walletInfoSource); final Box<WalletInfo> walletInfoSource; @@ -66,8 +66,18 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials, } @override - Future<EthereumWallet> restoreFromKeys(credentials) { - throw UnimplementedError(); + Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async { + final wallet = EthereumWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; } @override diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f13c68629..6f441c587 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -174,12 +174,12 @@ DEPENDENCIES: - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - platform_device_id (from `.symlinks/plugins/platform_device_id/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -236,7 +236,7 @@ EXTERNAL SOURCES: package_info: :path: ".symlinks/plugins/package_info/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" platform_device_id: @@ -246,7 +246,7 @@ EXTERNAL SOURCES: share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/ios" + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" uni_links: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: diff --git a/lib/di.dart b/lib/di.dart index 2a54c85a3..c37575337 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -177,8 +177,6 @@ import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/wallet_type.dart'; @@ -320,25 +318,6 @@ Future setup({ getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type: type)); - getIt.registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) { - final type = args.first as WalletType; - final language = args[1] as String; - final mnemonic = args[2] as String; - - return WalletRestorationFromSeedVM( - getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource, - type: type, language: language, seed: mnemonic); - }); - - getIt.registerFactoryParam<WalletRestorationFromKeysVM, List, void>((args, _) { - final type = args.first as WalletType; - final language = args[1] as String; - - return WalletRestorationFromKeysVM( - getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource, - type: type, language: language); - }); - getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) { return WalletRestorationFromQRVM(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type); diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index f38cafbbd..f2db7741e 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -22,6 +22,14 @@ class CWEthereum extends Ethereum { }) => EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); + @override + WalletCredentials createEthereumRestoreWalletFromPrivateKey({ + required String name, + required String privateKey, + required String password, + }) => + EthereumRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + @override String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address; diff --git a/lib/router.dart b/lib/router.dart index 7d63ad1ea..0ef91e16f 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -57,7 +57,6 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -80,7 +79,6 @@ import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_c import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; -import 'package:cake_wallet/src/screens/restore/restore_wallet_from_seed_details.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart'; @@ -398,16 +396,6 @@ Route<dynamic> createRoute(RouteSettings settings) { builder: (_) => getIt.get<BuyWebViewPage>(param1: args)); - case Routes.restoreWalletFromSeedDetails: - final args = settings.arguments as List; - final walletRestorationFromSeedVM = - getIt.get<WalletRestorationFromSeedVM>(param1: args); - - return CupertinoPageRoute<void>( - fullscreenDialog: true, - builder: (_) => RestoreWalletFromSeedDetailsPage( - walletRestorationFromSeedVM: walletRestorationFromSeedVM)); - case Routes.exchange: return CupertinoPageRoute<void>( fullscreenDialog: true, diff --git a/lib/routes.dart b/lib/routes.dart index 62c303ce8..bcb4c38b5 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -30,7 +30,6 @@ class Routes { static const tradeDetails = '/trade_details'; static const exchangeFunds = '/exchange_funds'; static const exchangeTrade = '/exchange_trade'; - static const restoreWalletFromSeedDetails = '/restore_from_seed_details'; static const exchange = '/exchange'; static const settings = '/settings'; static const desktop_settings_page = '/desktop_settings_page'; diff --git a/lib/src/screens/restore/restore_wallet_from_seed_details.dart b/lib/src/screens/restore/restore_wallet_from_seed_details.dart deleted file mode 100644 index 8d08c441d..000000000 --- a/lib/src/screens/restore/restore_wallet_from_seed_details.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:mobx/mobx.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/wallet_name_validator.dart'; -import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart'; - -class RestoreWalletFromSeedDetailsPage extends BasePage { - RestoreWalletFromSeedDetailsPage( - {required this.walletRestorationFromSeedVM}); - - final WalletRestorationFromSeedVM walletRestorationFromSeedVM; - - @override - String get title => S.current.restore_wallet_restore_description; - - @override - Widget body(BuildContext context) => RestoreFromSeedDetailsForm( - walletRestorationFromSeedVM: walletRestorationFromSeedVM); -} - -class RestoreFromSeedDetailsForm extends StatefulWidget { - RestoreFromSeedDetailsForm({required this.walletRestorationFromSeedVM}); - - final WalletRestorationFromSeedVM walletRestorationFromSeedVM; - - @override - _RestoreFromSeedDetailsFormState createState() => - _RestoreFromSeedDetailsFormState(); -} - -class _RestoreFromSeedDetailsFormState - extends State<RestoreFromSeedDetailsForm> { - final _formKey = GlobalKey<FormState>(); - final _blockchainHeightKey = GlobalKey<BlockchainHeightState>(); - final _nameController = TextEditingController(); - ReactionDisposer? _stateReaction; - - @override - void initState() { - _stateReaction = reaction((_) => widget.walletRestorationFromSeedVM.state, - (ExecutionState state) { - if (state is ExecutedSuccessfullyState) { - Navigator.of(context).popUntil((route) => route.isFirst); - } - - if (state is FailureState) { - WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp<void>( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.current.restore_title_from_seed, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); - }); - } - }); - - _nameController.addListener( - () => widget.walletRestorationFromSeedVM.name = _nameController.text); - super.initState(); - } - - @override - void dispose() { - _nameController.dispose(); - _stateReaction?.reaction.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(bottom: 24.0), - content: Form( - key: _formKey, - child: Column(children: <Widget>[ - Row( - children: <Widget>[ - Flexible( - child: Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: _nameController, - hintText: S.of(context).restore_wallet_name, - validator: WalletNameValidator(), - ), - )) - ], - ), - if (widget.walletRestorationFromSeedVM.hasRestorationHeight) ... [ - BlockchainHeightWidget( - key: _blockchainHeightKey, - onHeightChange: (height) { - widget.walletRestorationFromSeedVM.height = height; - print(height); - }), - Padding( - padding: EdgeInsets.only(left: 40, right: 40, top: 24), - child: Text( - S.of(context).restore_from_date_or_blockheight, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).hintColor - ), - ), - ) - ] - ]), - ), - bottomSectionPadding: EdgeInsets.only(bottom: 24), - bottomSection: Observer(builder: (_) { - return LoadingPrimaryButton( - onPressed: () { - if (_formKey.currentState != null && _formKey.currentState!.validate()) { - widget.walletRestorationFromSeedVM.create(); - } - }, - isLoading: - widget.walletRestorationFromSeedVM.state is IsExecutingState, - text: S.of(context).restore_recover, - color: Theme.of(context).primaryColor, - textColor: Colors.white, - isDisabled: _nameController.text.isNotEmpty, - ); - }), - ), - ); - } -} diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 4712cfb10..a1e2a82f0 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -1,30 +1,29 @@ +import 'package:cake_wallet/src/widgets/address_text_field.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/entities/generate_name.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:flutter/services.dart'; class WalletRestoreFromKeysFrom extends StatefulWidget { WalletRestoreFromKeysFrom({ required this.walletRestoreViewModel, + required this.displayPrivateKeyField, + required this.onHeightOrDateEntered, Key? key, - this.onHeightOrDateEntered,}) - : super(key: key); + }) : super(key: key); - final Function(bool)? onHeightOrDateEntered; + final Function(bool) onHeightOrDateEntered; final WalletRestoreViewModel walletRestoreViewModel; + final bool displayPrivateKeyField; @override - WalletRestoreFromKeysFromState createState() => - WalletRestoreFromKeysFromState(); + WalletRestoreFromKeysFromState createState() => WalletRestoreFromKeysFromState(); } class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { @@ -35,6 +34,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { addressController = TextEditingController(), viewKeyController = TextEditingController(), spendKeyController = TextEditingController(), + privateKeyController = TextEditingController(), nameTextEditingController = TextEditingController(); final GlobalKey<FormState> formKey; @@ -44,6 +44,18 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { final TextEditingController viewKeyController; final TextEditingController spendKeyController; final TextEditingController nameTextEditingController; + final TextEditingController privateKeyController; + + @override + void initState() { + super.initState(); + + privateKeyController.addListener(() { + if (privateKeyController.text.isNotEmpty) { + widget.onHeightOrDateEntered(true); + } + }); + } @override void dispose() { @@ -51,16 +63,18 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { addressController.dispose(); viewKeyController.dispose(); spendKeyController.dispose(); + privateKeyController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.only(left: 24, right: 24), - child: Form( - key: formKey, - child: Column(children: <Widget>[ + padding: EdgeInsets.only(left: 24, right: 24), + child: Form( + key: formKey, + child: Column( + children: <Widget>[ Stack( alignment: Alignment.centerRight, children: [ @@ -75,9 +89,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { setState(() { nameTextEditingController.text = rName; - nameTextEditingController.selection = - TextSelection.fromPosition(TextPosition( - offset: nameTextEditingController.text.length)); + nameTextEditingController.selection = TextSelection.fromPosition( + TextPosition(offset: nameTextEditingController.text.length)); }); }, icon: Container( @@ -90,7 +103,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { height: 34, child: Image.asset( 'assets/images/refresh_icon.png', - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor, + color: + Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor, ), ), ), @@ -98,29 +112,65 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { ], ), Container(height: 20), - BaseTextFormField( - controller: addressController, - keyboardType: TextInputType.multiline, - maxLines: null, - hintText: S.of(context).restore_address), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: viewKeyController, - hintText: S.of(context).restore_view_key_private, - maxLines: null)), - Container( - padding: EdgeInsets.only(top: 20.0), - child: BaseTextFormField( - controller: spendKeyController, - hintText: S.of(context).restore_spend_key_private, - maxLines: null)), - BlockchainHeightWidget( - key: blockchainHeightKey, - hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, - onHeightChange: (_) => null, - onHeightOrDateEntered: widget.onHeightOrDateEntered) - ]), - )); + _restoreFromKeysFormFields(), + ], + ), + ), + ); + } + + Widget _restoreFromKeysFormFields() { + if (widget.displayPrivateKeyField) { + return AddressTextField( + controller: privateKeyController, + placeholder: S.of(context).private_key, + options: [AddressTextFieldOption.paste], + buttonColor: Theme.of(context).hintColor, + onPushPasteButton: (_) { + _pasteText(); + }, + ); + } + + return Column( + children: [ + BaseTextFormField( + controller: addressController, + keyboardType: TextInputType.multiline, + maxLines: null, + hintText: S.of(context).restore_address, + ), + Container( + padding: EdgeInsets.only(top: 20.0), + child: BaseTextFormField( + controller: viewKeyController, + hintText: S.of(context).restore_view_key_private, + maxLines: null, + ), + ), + Container( + padding: EdgeInsets.only(top: 20.0), + child: BaseTextFormField( + controller: spendKeyController, + hintText: S.of(context).restore_spend_key_private, + maxLines: null, + ), + ), + BlockchainHeightWidget( + key: blockchainHeightKey, + hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, + onHeightChange: (_) => null, + onHeightOrDateEntered: widget.onHeightOrDateEntered, + ), + ], + ); + } + + Future<void> _pasteText() async { + final value = await Clipboard.getData('text/plain'); + + if (value?.text?.isNotEmpty ?? false) { + privateKeyController.text = value!.text!; + } } } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 10b5a521d..bea84a7c9 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -1,10 +1,7 @@ import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; @@ -19,10 +16,6 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.da import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/core/validator.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/core/seed_validator.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; @@ -76,6 +69,7 @@ class WalletRestorePage extends BasePage { _pages.add(WalletRestoreFromKeysFrom( key: walletRestoreFromKeysFormKey, walletRestoreViewModel: walletRestoreViewModel, + displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey, onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); break; default: @@ -193,8 +187,12 @@ class WalletRestorePage extends BasePage { return LoadingPrimaryButton( onPressed: _confirmForm, text: S.of(context).restore_recover, - color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor, - textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor, + color: Theme.of(context) + .extension<WalletListTheme>()! + .createNewWalletButtonBackgroundColor, + textColor: Theme.of(context) + .extension<WalletListTheme>()! + .restoreWalletButtonTextColor, isLoading: walletRestoreViewModel.state is IsExecutingState, isDisabled: !walletRestoreViewModel.isButtonEnabled, ); @@ -246,11 +244,18 @@ class WalletRestorePage extends BasePage { credentials['name'] = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; } else { - credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; - credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; - credentials['spendKey'] = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; - credentials['height'] = - walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; + if (walletRestoreViewModel.hasRestoreFromPrivateKey) { + credentials['private_key'] = + walletRestoreFromKeysFormKey.currentState!.privateKeyController.text; + } else { + credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; + credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; + credentials['spendKey'] = + walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; + credentials['height'] = + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; + } + credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; } diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index ddafa924b..7015e0acf 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -1,16 +1,9 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/core/seed_validator.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/entities/mnemonic_item.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:flutter/widgets.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; class SeedWidget extends StatefulWidget { diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 9d9328879..e8e7702fa 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -148,6 +148,7 @@ class ExceptionHandler { "CERTIFICATE_VERIFY_FAILED", "Handshake error in client", "Error while launching http", + "OS Error: Network is unreachable", ]; static Future<void> _addDeviceInfo(File file) async { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 49bb5304e..7035130c0 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -107,7 +107,7 @@ abstract class DashboardViewModelBase with Store { name = wallet.name; type = wallet.type; isOutdatedElectrumWallet = - wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; + wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24; isShowFirstYatIntroduction = false; isShowSecondYatIntroduction = false; isShowThirdYatIntroduction = false; @@ -327,7 +327,7 @@ abstract class DashboardViewModelBase with Store { type = wallet.type; name = wallet.name; isOutdatedElectrumWallet = - wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; + wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24; updateActions(); if (wallet.type == WalletType.monero) { diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 996b3b3fb..39a7b682f 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -66,6 +66,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.litecoin: return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( name: name, password: password, wif: wif); + case WalletType.ethereum: + return ethereum!.createEthereumRestoreWalletFromPrivateKey( + name: name, password: password, privateKey: restoreWallet.privateKey!); default: throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); } diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart index 0f872d8cc..264b3d421 100644 --- a/lib/view_model/restore/restore_wallet.dart +++ b/lib/view_model/restore/restore_wallet.dart @@ -13,7 +13,8 @@ class RestoredWallet { this.txAmount, this.txDescription, this.recipientName, - this.height}); + this.height, + this.privateKey}); final WalletRestoreMode restoreMode; final WalletType type; @@ -26,6 +27,7 @@ class RestoredWallet { final String? txDescription; final String? recipientName; final int? height; + final String? privateKey; factory RestoredWallet.fromKey(Map<String, dynamic> json) { final height = json['height'] as String?; @@ -36,6 +38,7 @@ class RestoredWallet { spendKey: json['spend_key'] as String?, viewKey: json['view_key'] as String?, height: height != null ? int.parse(height) : 0, + privateKey: json['private_key'] as String?, ); } diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index c4b772f9c..e9aed55c6 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -33,7 +33,10 @@ class WalletRestoreFromQRCode { getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType); if (seed != null) { credentials['seed'] = seed; + } else { + credentials['private_key'] = queryParameters['private_key']; } + credentials.addAll(queryParameters); credentials['mode'] = getWalletRestoreMode(credentials); @@ -69,6 +72,8 @@ class WalletRestoreFromQRCode { case 'litecoin': case 'litecoin-wallet': return WalletType.litecoin; + case 'ethereum-wallet': + return WalletType.ethereum; default: throw Exception('Unexpected wallet type: ${scheme.toString()}'); } @@ -101,6 +106,7 @@ class WalletRestoreFromQRCode { } case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.ethereum: RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); @@ -152,6 +158,14 @@ class WalletRestoreFromQRCode { : throw Exception('Unexpected restore mode: spend_key or view_key is invalid'); } + if (type == WalletType.ethereum && credentials.containsKey('private_key')) { + final privateKey = credentials['private_key'] as String; + if (privateKey.isEmpty) { + throw Exception('Unexpected restore mode: private_key'); + } + return WalletRestoreMode.keys; + } + throw Exception('Unexpected restore mode: restore params are invalid'); } } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 0a758ccfb..0233e13e9 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -69,7 +69,7 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), if (keys['privateViewKey'] != null) StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); } @@ -85,15 +85,23 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), if (keys['privateViewKey'] != null) StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); } if (_appStore.wallet!.type == WalletType.bitcoin || - _appStore.wallet!.type == WalletType.litecoin || - _appStore.wallet!.type == WalletType.ethereum) { + _appStore.wallet!.type == WalletType.litecoin) { items.addAll([ - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed), + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + ]); + } + + if (_appStore.wallet!.type == WalletType.ethereum) { + items.addAll([ + if (_appStore.wallet!.privateKey != null) + StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!), + if (_appStore.wallet!.seed != null) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); } } @@ -139,7 +147,8 @@ abstract class WalletKeysViewModelBase with Store { Future<Map<String, String>> get _queryParams async { final restoreHeightResult = await restoreHeight; return { - 'seed': _appStore.wallet!.seed, + if (_appStore.wallet!.seed != null) 'seed': _appStore.wallet!.seed!, + if (_appStore.wallet!.privateKey != null) 'private_key': _appStore.wallet!.privateKey!, if (restoreHeightResult != null) ...{'height': restoreHeightResult} }; } diff --git a/lib/view_model/wallet_restoration_from_keys_vm.dart b/lib/view_model/wallet_restoration_from_keys_vm.dart deleted file mode 100644 index 97cb8d519..000000000 --- a/lib/view_model/wallet_restoration_from_keys_vm.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/core/generate_wallet_password.dart'; -import 'package:cake_wallet/core/wallet_creation_service.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; - -part 'wallet_restoration_from_keys_vm.g.dart'; - -class WalletRestorationFromKeysVM = WalletRestorationFromKeysVMBase - with _$WalletRestorationFromKeysVM; - -abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM - with Store { - WalletRestorationFromKeysVMBase(AppStore appStore, - WalletCreationService walletCreationService, Box<WalletInfo> walletInfoSource, - {required WalletType type, required this.language}) - : height = 0, - viewKey = '', - spendKey = '', - wif = '', - address = '', - super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true); - - @observable - int height; - - @observable - String viewKey; - - @observable - String spendKey; - - @observable - String wif; - - @observable - String address; - - bool get hasRestorationHeight => type == WalletType.monero; - - final String language; - - @override - WalletCredentials getCredentials(dynamic options) { - final password = generateWalletPassword(); - - switch (type) { - case WalletType.monero: - return monero!.createMoneroRestoreWalletFromKeysCredentials( - name: name, - password: password, - language: language, - address: address, - viewKey: viewKey, - spendKey: spendKey, - height: height); - case WalletType.bitcoin: - return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( - name: name, password: password, wif: wif); - default: - throw Exception('Unexpected type: ${type.toString()}'); - } - } - - @override - Future<WalletBase> process(WalletCredentials credentials) async => - walletCreationService.restoreFromKeys(credentials); -} diff --git a/lib/view_model/wallet_restoration_from_seed_vm.dart b/lib/view_model/wallet_restoration_from_seed_vm.dart deleted file mode 100644 index 0caa7f37d..000000000 --- a/lib/view_model/wallet_restoration_from_seed_vm.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/core/generate_wallet_password.dart'; -import 'package:cake_wallet/core/wallet_creation_service.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; -import 'package:cw_core/wallet_info.dart'; - -part 'wallet_restoration_from_seed_vm.g.dart'; - -class WalletRestorationFromSeedVM = WalletRestorationFromSeedVMBase - with _$WalletRestorationFromSeedVM; - -abstract class WalletRestorationFromSeedVMBase extends WalletCreationVM - with Store { - WalletRestorationFromSeedVMBase(AppStore appStore, - WalletCreationService walletCreationService, Box<WalletInfo> walletInfoSource, - {required WalletType type, required this.language, this.seed = ''}) - : height = 0, - super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true); - - @observable - String seed; - - @observable - int height; - - bool get hasRestorationHeight => type == WalletType.monero; - - final String language; - - @override - WalletCredentials getCredentials(dynamic options) { - final password = generateWalletPassword(); - - switch (type) { - case WalletType.monero: - return monero!.createMoneroRestoreWalletFromSeedCredentials( - name: name, height: height, mnemonic: seed, password: password); - case WalletType.bitcoin: - return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( - name: name, mnemonic: seed, password: password); - default: - throw Exception('Unexpected type: ${type.toString()}'); - } - } - - @override - Future<WalletBase> process(WalletCredentials credentials) async => - walletCreationService.restoreFromSeed(credentials); -} diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 6bf87f22c..65f122bf0 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -1,7 +1,4 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/core/mnemonic_length.dart'; -import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; -import 'package:flutter/foundation.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -19,7 +16,6 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart'; part 'wallet_restore_view_model.g.dart'; - class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel; @@ -27,11 +23,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, Box<WalletInfo> walletInfoSource, {required WalletType type}) - : availableModes = (type == WalletType.monero || type == WalletType.haven) - ? WalletRestoreMode.values - : [WalletRestoreMode.seed], + : availableModes = + (type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum) + ? WalletRestoreMode.values + : [WalletRestoreMode.seed], hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, + hasRestoreFromPrivateKey = type == WalletType.ethereum, isButtonEnabled = false, mode = WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { @@ -47,13 +45,14 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { final List<WalletRestoreMode> availableModes; final bool hasSeedLanguageSelector; final bool hasBlockchainHeightLanguageSelector; + final bool hasRestoreFromPrivateKey; @observable WalletRestoreMode mode; @observable bool isButtonEnabled; - + @override WalletCredentials getCredentials(dynamic options) { final password = generateWalletPassword(); @@ -97,17 +96,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { } if (mode == WalletRestoreMode.keys) { - final viewKey = options['viewKey'] as String; - final spendKey = options['spendKey'] as String; - final address = options['address'] as String; + final viewKey = options['viewKey'] as String?; + final spendKey = options['spendKey'] as String?; + final address = options['address'] as String?; if (type == WalletType.monero) { return monero!.createMoneroRestoreWalletFromKeysCredentials( name: name, height: height, - spendKey: spendKey, - viewKey: viewKey, - address: address, + spendKey: spendKey!, + viewKey: viewKey!, + address: address!, password: password, language: 'English'); } @@ -116,12 +115,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { return haven!.createHavenRestoreWalletFromKeysCredentials( name: name, height: height, - spendKey: spendKey, - viewKey: viewKey, - address: address, + spendKey: spendKey!, + viewKey: viewKey!, + address: address!, password: password, language: 'English'); } + + if (type == WalletType.ethereum) { + return ethereum!.createEthereumRestoreWalletFromPrivateKey( + name: name, + privateKey: options['private_key'] as String, + password: password, + ); + } } throw Exception('Unexpected type: ${type.toString()}'); diff --git a/lib/view_model/wallet_seed_view_model.dart b/lib/view_model/wallet_seed_view_model.dart index 4a9c0009b..8923a99da 100644 --- a/lib/view_model/wallet_seed_view_model.dart +++ b/lib/view_model/wallet_seed_view_model.dart @@ -8,7 +8,7 @@ class WalletSeedViewModel = WalletSeedViewModelBase with _$WalletSeedViewModel; abstract class WalletSeedViewModelBase with Store { WalletSeedViewModelBase(WalletBase wallet) : name = wallet.name, - seed = wallet.seed; + seed = wallet.seed!; @observable String name; diff --git a/tool/configure.dart b/tool/configure.dart index 3d846e8a6..3893847e2 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -507,6 +507,7 @@ abstract class Ethereum { WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource); WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo}); WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); + WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); String getAddress(WalletBase wallet); TransactionPriority getDefaultTransactionPriority(); List<TransactionPriority> getTransactionPriorities();