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]
This commit is contained in:
Omar Hatem 2023-08-23 15:33:20 +03:00 committed by GitHub
parent 43cf8a896e
commit 3577730de8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 250 additions and 433 deletions

View file

@ -42,7 +42,9 @@ abstract class WalletBase<
set syncStatus(SyncStatus status); set syncStatus(SyncStatus status);
String get seed; String? get seed;
String? get privateKey => null;
Object get keys; Object get keys;

View file

@ -44,12 +44,14 @@ abstract class EthereumWalletBase
with Store { with Store {
EthereumWalletBase({ EthereumWalletBase({
required WalletInfo walletInfo, required WalletInfo walletInfo,
required String mnemonic, String? mnemonic,
String? privateKey,
required String password, required String password,
ERC20Balance? initialBalance, ERC20Balance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(), }) : syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_mnemonic = mnemonic, _mnemonic = mnemonic,
_hexPrivateKey = privateKey,
_isTransactionUpdating = false, _isTransactionUpdating = false,
_client = EthereumClient(), _client = EthereumClient(),
walletAddresses = EthereumWalletAddresses(walletInfo), walletAddresses = EthereumWalletAddresses(walletInfo),
@ -66,12 +68,13 @@ abstract class EthereumWalletBase
_sharedPrefs.complete(SharedPreferences.getInstance()); _sharedPrefs.complete(SharedPreferences.getInstance());
} }
final String _mnemonic; final String? _mnemonic;
final String? _hexPrivateKey;
final String _password; final String _password;
late final Box<Erc20Token> erc20TokensBox; late final Box<Erc20Token> erc20TokensBox;
late final EthPrivateKey _privateKey; late final EthPrivateKey _ethPrivateKey;
late EthereumClient _client; late EthereumClient _client;
@ -99,8 +102,12 @@ abstract class EthereumWalletBase
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName); erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
await walletAddresses.init(); await walletAddresses.init();
await transactionHistory.init(); await transactionHistory.init();
_privateKey = await getPrivateKey(_mnemonic, _password); _ethPrivateKey = await getPrivateKey(
walletAddresses.address = _privateKey.address.toString(); mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _ethPrivateKey.address.toString();
await save(); await save();
} }
@ -108,8 +115,7 @@ abstract class EthereumWalletBase
int calculateEstimatedFee(TransactionPriority priority, int? amount) { int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try { try {
if (priority is EthereumTransactionPriority) { if (priority is EthereumTransactionPriority) {
final priorityFee = final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt();
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
} }
@ -142,7 +148,7 @@ abstract class EthereumWalletBase
throw Exception("Ethereum Node connection failed"); throw Exception("Ethereum Node connection failed");
} }
_client.setListeners(_privateKey.address, _onNewTransaction); _client.setListeners(_ethPrivateKey.address, _onNewTransaction);
_setTransactionUpdateTimer(); _setTransactionUpdateTimer();
@ -202,7 +208,7 @@ abstract class EthereumWalletBase
} }
final pendingEthereumTransaction = await _client.signTransaction( final pendingEthereumTransaction = await _client.signTransaction(
privateKey: _privateKey, privateKey: _ethPrivateKey,
toAddress: _credentials.outputs.first.isParsedAddress toAddress: _credentials.outputs.first.isParsedAddress
? _credentials.outputs.first.extractedAddress! ? _credentials.outputs.first.extractedAddress!
: _credentials.outputs.first.address, : _credentials.outputs.first.address,
@ -240,7 +246,7 @@ abstract class EthereumWalletBase
@override @override
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async { Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
final address = _privateKey.address.hex; final address = _ethPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address); final transactions = await _client.fetchTransactions(address);
final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = []; final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = [];
@ -300,7 +306,10 @@ abstract class EthereumWalletBase
} }
@override @override
String get seed => _mnemonic; String? get seed => _mnemonic;
@override
String get privateKey => HEX.encode(_ethPrivateKey.privateKey);
@action @action
@override @override
@ -327,6 +336,7 @@ abstract class EthereumWalletBase
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': _mnemonic, 'mnemonic': _mnemonic,
'private_key': privateKey,
'balance': balance[currency]!.toJSON(), 'balance': balance[currency]!.toJSON(),
}); });
@ -338,13 +348,15 @@ abstract class EthereumWalletBase
final path = await pathForWallet(name: name, type: walletInfo.type); final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password); final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map; 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); final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
return EthereumWallet( return EthereumWallet(
walletInfo: walletInfo, walletInfo: walletInfo,
password: password, password: password,
mnemonic: mnemonic, mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance, initialBalance: balance,
); );
} }
@ -357,7 +369,7 @@ abstract class EthereumWalletBase
} }
Future<ERC20Balance> _fetchEthBalance() async { Future<ERC20Balance> _fetchEthBalance() async {
final balance = await _client.getBalance(_privateKey.address); final balance = await _client.getBalance(_ethPrivateKey.address);
return ERC20Balance(balance.getInWei); return ERC20Balance(balance.getInWei);
} }
@ -366,7 +378,7 @@ abstract class EthereumWalletBase
try { try {
if (token.enabled) { if (token.enabled) {
balance[token] = await _client.fetchERC20Balances( balance[token] = await _client.fetchERC20Balances(
_privateKey.address, _ethPrivateKey.address,
token.contractAddress, token.contractAddress,
); );
} else { } else {
@ -376,8 +388,15 @@ abstract class EthereumWalletBase
} }
} }
Future<EthPrivateKey> getPrivateKey(String mnemonic, String password) async { Future<EthPrivateKey> getPrivateKey(
final seed = bip39.mnemonicToSeed(mnemonic); {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); final root = bip32.BIP32.fromSeed(seed);
@ -413,7 +432,7 @@ abstract class EthereumWalletBase
if (_token.enabled) { if (_token.enabled) {
balance[_token] = await _client.fetchERC20Balances( balance[_token] = await _client.fetchERC20Balances(
_privateKey.address, _ethPrivateKey.address,
_token.contractAddress, _token.contractAddress,
); );
} else { } else {

View file

@ -8,16 +8,22 @@ class EthereumNewWalletCredentials extends WalletCredentials {
class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials { class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials {
EthereumRestoreWalletFromSeedCredentials( 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); : super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic; final String mnemonic;
} }
class EthereumRestoreWalletFromWIFCredentials extends WalletCredentials { class EthereumRestoreWalletFromPrivateKey extends WalletCredentials {
EthereumRestoreWalletFromWIFCredentials( EthereumRestoreWalletFromPrivateKey(
{required String name, required String password, required this.wif, WalletInfo? walletInfo}) {required String name,
required String password,
required this.privateKey,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo); : super(name: name, password: password, walletInfo: walletInfo);
final String wif; final String privateKey;
} }

View file

@ -13,7 +13,7 @@ import 'package:bip39/bip39.dart' as bip39;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
class EthereumWalletService extends WalletService<EthereumNewWalletCredentials, class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> { EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> {
EthereumWalletService(this.walletInfoSource); EthereumWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -66,8 +66,18 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
} }
@override @override
Future<EthereumWallet> restoreFromKeys(credentials) { Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async {
throw UnimplementedError(); final wallet = EthereumWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
} }
@override @override

View file

@ -174,12 +174,12 @@ DEPENDENCIES:
- in_app_review (from `.symlinks/plugins/in_app_review/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- package_info (from `.symlinks/plugins/package_info/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`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- platform_device_id (from `.symlinks/plugins/platform_device_id/ios`) - platform_device_id (from `.symlinks/plugins/platform_device_id/ios`)
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/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`) - uni_links (from `.symlinks/plugins/uni_links/ios`)
- UnstoppableDomainsResolution (~> 4.0.0) - UnstoppableDomainsResolution (~> 4.0.0)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@ -236,7 +236,7 @@ EXTERNAL SOURCES:
package_info: package_info:
:path: ".symlinks/plugins/package_info/ios" :path: ".symlinks/plugins/package_info/ios"
path_provider_foundation: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
platform_device_id: platform_device_id:
@ -246,7 +246,7 @@ EXTERNAL SOURCES:
share_plus: share_plus:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
uni_links: uni_links:
:path: ".symlinks/plugins/uni_links/ios" :path: ".symlinks/plugins/uni_links/ios"
url_launcher_ios: url_launcher_ios:

View file

@ -177,8 +177,6 @@ import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.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/core/wallet_creation_service.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -320,25 +318,6 @@ Future setup({
getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource, getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type)); 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, _) { getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
return WalletRestorationFromQRVM(getIt.get<AppStore>(), return WalletRestorationFromQRVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type); getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type);

View file

@ -22,6 +22,14 @@ class CWEthereum extends Ethereum {
}) => }) =>
EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); 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 @override
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address; String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;

View file

@ -57,7 +57,6 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.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:cake_wallet/exchange/trade.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.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_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_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/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/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/faq/faq_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart';
@ -398,16 +396,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => builder: (_) =>
getIt.get<BuyWebViewPage>(param1: args)); 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: case Routes.exchange:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,

View file

@ -30,7 +30,6 @@ class Routes {
static const tradeDetails = '/trade_details'; static const tradeDetails = '/trade_details';
static const exchangeFunds = '/exchange_funds'; static const exchangeFunds = '/exchange_funds';
static const exchangeTrade = '/exchange_trade'; static const exchangeTrade = '/exchange_trade';
static const restoreWalletFromSeedDetails = '/restore_from_seed_details';
static const exchange = '/exchange'; static const exchange = '/exchange';
static const settings = '/settings'; static const settings = '/settings';
static const desktop_settings_page = '/desktop_settings_page'; static const desktop_settings_page = '/desktop_settings_page';

View file

@ -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,
);
}),
),
);
}
}

View file

@ -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:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:cw_core/wallet_type.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:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.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/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/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart'; import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:flutter/services.dart';
class WalletRestoreFromKeysFrom extends StatefulWidget { class WalletRestoreFromKeysFrom extends StatefulWidget {
WalletRestoreFromKeysFrom({ WalletRestoreFromKeysFrom({
required this.walletRestoreViewModel, required this.walletRestoreViewModel,
required this.displayPrivateKeyField,
required this.onHeightOrDateEntered,
Key? key, Key? key,
this.onHeightOrDateEntered,}) }) : super(key: key);
: super(key: key);
final Function(bool)? onHeightOrDateEntered; final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel; final WalletRestoreViewModel walletRestoreViewModel;
final bool displayPrivateKeyField;
@override @override
WalletRestoreFromKeysFromState createState() => WalletRestoreFromKeysFromState createState() => WalletRestoreFromKeysFromState();
WalletRestoreFromKeysFromState();
} }
class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> { class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
@ -35,6 +34,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
addressController = TextEditingController(), addressController = TextEditingController(),
viewKeyController = TextEditingController(), viewKeyController = TextEditingController(),
spendKeyController = TextEditingController(), spendKeyController = TextEditingController(),
privateKeyController = TextEditingController(),
nameTextEditingController = TextEditingController(); nameTextEditingController = TextEditingController();
final GlobalKey<FormState> formKey; final GlobalKey<FormState> formKey;
@ -44,6 +44,18 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
final TextEditingController viewKeyController; final TextEditingController viewKeyController;
final TextEditingController spendKeyController; final TextEditingController spendKeyController;
final TextEditingController nameTextEditingController; final TextEditingController nameTextEditingController;
final TextEditingController privateKeyController;
@override
void initState() {
super.initState();
privateKeyController.addListener(() {
if (privateKeyController.text.isNotEmpty) {
widget.onHeightOrDateEntered(true);
}
});
}
@override @override
void dispose() { void dispose() {
@ -51,16 +63,18 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
addressController.dispose(); addressController.dispose();
viewKeyController.dispose(); viewKeyController.dispose();
spendKeyController.dispose(); spendKeyController.dispose();
privateKeyController.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.only(left: 24, right: 24), padding: EdgeInsets.only(left: 24, right: 24),
child: Form( child: Form(
key: formKey, key: formKey,
child: Column(children: <Widget>[ child: Column(
children: <Widget>[
Stack( Stack(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
children: [ children: [
@ -75,9 +89,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
setState(() { setState(() {
nameTextEditingController.text = rName; nameTextEditingController.text = rName;
nameTextEditingController.selection = nameTextEditingController.selection = TextSelection.fromPosition(
TextSelection.fromPosition(TextPosition( TextPosition(offset: nameTextEditingController.text.length));
offset: nameTextEditingController.text.length));
}); });
}, },
icon: Container( icon: Container(
@ -90,7 +103,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
height: 34, height: 34,
child: Image.asset( child: Image.asset(
'assets/images/refresh_icon.png', '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), Container(height: 20),
BaseTextFormField( _restoreFromKeysFormFields(),
controller: addressController, ],
keyboardType: TextInputType.multiline, ),
maxLines: null, ),
hintText: S.of(context).restore_address), );
Container( }
padding: EdgeInsets.only(top: 20.0),
child: BaseTextFormField( Widget _restoreFromKeysFormFields() {
controller: viewKeyController, if (widget.displayPrivateKeyField) {
hintText: S.of(context).restore_view_key_private, return AddressTextField(
maxLines: null)), controller: privateKeyController,
Container( placeholder: S.of(context).private_key,
padding: EdgeInsets.only(top: 20.0), options: [AddressTextFieldOption.paste],
child: BaseTextFormField( buttonColor: Theme.of(context).hintColor,
controller: spendKeyController, onPushPasteButton: (_) {
hintText: S.of(context).restore_spend_key_private, _pasteText();
maxLines: null)), },
BlockchainHeightWidget( );
key: blockchainHeightKey, }
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
onHeightChange: (_) => null, return Column(
onHeightOrDateEntered: widget.onHeightOrDateEntered) 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!;
}
} }
} }

View file

@ -1,10 +1,7 @@
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; 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/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:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.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/screens/restore/wallet_restore_from_seed_form.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/utils/show_pop_up.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/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
@ -76,6 +69,7 @@ class WalletRestorePage extends BasePage {
_pages.add(WalletRestoreFromKeysFrom( _pages.add(WalletRestoreFromKeysFrom(
key: walletRestoreFromKeysFormKey, key: walletRestoreFromKeysFormKey,
walletRestoreViewModel: walletRestoreViewModel, walletRestoreViewModel: walletRestoreViewModel,
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break; break;
default: default:
@ -193,8 +187,12 @@ class WalletRestorePage extends BasePage {
return LoadingPrimaryButton( return LoadingPrimaryButton(
onPressed: _confirmForm, onPressed: _confirmForm,
text: S.of(context).restore_recover, text: S.of(context).restore_recover,
color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor, color: Theme.of(context)
textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor, .extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor,
textColor: Theme.of(context)
.extension<WalletListTheme>()!
.restoreWalletButtonTextColor,
isLoading: walletRestoreViewModel.state is IsExecutingState, isLoading: walletRestoreViewModel.state is IsExecutingState,
isDisabled: !walletRestoreViewModel.isButtonEnabled, isDisabled: !walletRestoreViewModel.isButtonEnabled,
); );
@ -246,11 +244,18 @@ class WalletRestorePage extends BasePage {
credentials['name'] = credentials['name'] =
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
} else { } else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; if (walletRestoreViewModel.hasRestoreFromPrivateKey) {
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; credentials['private_key'] =
credentials['spendKey'] = walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
credentials['height'] = } else {
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; 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'] = credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} }

View file

@ -1,16 +1,9 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cw_core/wallet_type.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/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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/core/seed_validator.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'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
class SeedWidget extends StatefulWidget { class SeedWidget extends StatefulWidget {

View file

@ -148,6 +148,7 @@ class ExceptionHandler {
"CERTIFICATE_VERIFY_FAILED", "CERTIFICATE_VERIFY_FAILED",
"Handshake error in client", "Handshake error in client",
"Error while launching http", "Error while launching http",
"OS Error: Network is unreachable",
]; ];
static Future<void> _addDeviceInfo(File file) async { static Future<void> _addDeviceInfo(File file) async {

View file

@ -107,7 +107,7 @@ abstract class DashboardViewModelBase with Store {
name = wallet.name; name = wallet.name;
type = wallet.type; type = wallet.type;
isOutdatedElectrumWallet = isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
isShowFirstYatIntroduction = false; isShowFirstYatIntroduction = false;
isShowSecondYatIntroduction = false; isShowSecondYatIntroduction = false;
isShowThirdYatIntroduction = false; isShowThirdYatIntroduction = false;
@ -327,7 +327,7 @@ abstract class DashboardViewModelBase with Store {
type = wallet.type; type = wallet.type;
name = wallet.name; name = wallet.name;
isOutdatedElectrumWallet = isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
updateActions(); updateActions();
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {

View file

@ -66,6 +66,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials(
name: name, password: password, wif: wif); name: name, password: password, wif: wif);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name, password: password, privateKey: restoreWallet.privateKey!);
default: default:
throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
} }

View file

@ -13,7 +13,8 @@ class RestoredWallet {
this.txAmount, this.txAmount,
this.txDescription, this.txDescription,
this.recipientName, this.recipientName,
this.height}); this.height,
this.privateKey});
final WalletRestoreMode restoreMode; final WalletRestoreMode restoreMode;
final WalletType type; final WalletType type;
@ -26,6 +27,7 @@ class RestoredWallet {
final String? txDescription; final String? txDescription;
final String? recipientName; final String? recipientName;
final int? height; final int? height;
final String? privateKey;
factory RestoredWallet.fromKey(Map<String, dynamic> json) { factory RestoredWallet.fromKey(Map<String, dynamic> json) {
final height = json['height'] as String?; final height = json['height'] as String?;
@ -36,6 +38,7 @@ class RestoredWallet {
spendKey: json['spend_key'] as String?, spendKey: json['spend_key'] as String?,
viewKey: json['view_key'] as String?, viewKey: json['view_key'] as String?,
height: height != null ? int.parse(height) : 0, height: height != null ? int.parse(height) : 0,
privateKey: json['private_key'] as String?,
); );
} }

View file

@ -33,7 +33,10 @@ class WalletRestoreFromQRCode {
getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType); getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType);
if (seed != null) { if (seed != null) {
credentials['seed'] = seed; credentials['seed'] = seed;
} else {
credentials['private_key'] = queryParameters['private_key'];
} }
credentials.addAll(queryParameters); credentials.addAll(queryParameters);
credentials['mode'] = getWalletRestoreMode(credentials); credentials['mode'] = getWalletRestoreMode(credentials);
@ -69,6 +72,8 @@ class WalletRestoreFromQRCode {
case 'litecoin': case 'litecoin':
case 'litecoin-wallet': case 'litecoin-wallet':
return WalletType.litecoin; return WalletType.litecoin;
case 'ethereum-wallet':
return WalletType.ethereum;
default: default:
throw Exception('Unexpected wallet type: ${scheme.toString()}'); throw Exception('Unexpected wallet type: ${scheme.toString()}');
} }
@ -101,6 +106,7 @@ class WalletRestoreFromQRCode {
} }
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum:
RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b');
RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b');
RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\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'); : 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'); throw Exception('Unexpected restore mode: restore params are invalid');
} }
} }

View file

@ -69,7 +69,7 @@ abstract class WalletKeysViewModelBase with Store {
StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!), StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!),
if (keys['privateViewKey'] != null) if (keys['privateViewKey'] != null)
StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), 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']!), StandartListItem(title: S.current.view_key_public, value: keys['publicViewKey']!),
if (keys['privateViewKey'] != null) if (keys['privateViewKey'] != null)
StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), 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 || if (_appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.litecoin) {
_appStore.wallet!.type == WalletType.ethereum) {
items.addAll([ 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 { Future<Map<String, String>> get _queryParams async {
final restoreHeightResult = await restoreHeight; final restoreHeightResult = await restoreHeight;
return { 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} if (restoreHeightResult != null) ...{'height': restoreHeightResult}
}; };
} }

View file

@ -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);
}

View file

@ -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);
}

View file

@ -1,7 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; 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:cake_wallet/ethereum/ethereum.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.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'; part 'wallet_restore_view_model.g.dart';
class WalletRestoreViewModel = WalletRestoreViewModelBase class WalletRestoreViewModel = WalletRestoreViewModelBase
with _$WalletRestoreViewModel; with _$WalletRestoreViewModel;
@ -27,11 +23,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, Box<WalletInfo> walletInfoSource,
{required WalletType type}) {required WalletType type})
: availableModes = (type == WalletType.monero || type == WalletType.haven) : availableModes =
? WalletRestoreMode.values (type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum)
: [WalletRestoreMode.seed], ? WalletRestoreMode.values
: [WalletRestoreMode.seed],
hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasRestoreFromPrivateKey = type == WalletType.ethereum,
isButtonEnabled = false, isButtonEnabled = false,
mode = WalletRestoreMode.seed, mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
@ -47,6 +45,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final List<WalletRestoreMode> availableModes; final List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector; final bool hasSeedLanguageSelector;
final bool hasBlockchainHeightLanguageSelector; final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey;
@observable @observable
WalletRestoreMode mode; WalletRestoreMode mode;
@ -97,17 +96,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
} }
if (mode == WalletRestoreMode.keys) { if (mode == WalletRestoreMode.keys) {
final viewKey = options['viewKey'] as String; final viewKey = options['viewKey'] as String?;
final spendKey = options['spendKey'] as String; final spendKey = options['spendKey'] as String?;
final address = options['address'] as String; final address = options['address'] as String?;
if (type == WalletType.monero) { if (type == WalletType.monero) {
return monero!.createMoneroRestoreWalletFromKeysCredentials( return monero!.createMoneroRestoreWalletFromKeysCredentials(
name: name, name: name,
height: height, height: height,
spendKey: spendKey, spendKey: spendKey!,
viewKey: viewKey, viewKey: viewKey!,
address: address, address: address!,
password: password, password: password,
language: 'English'); language: 'English');
} }
@ -116,12 +115,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return haven!.createHavenRestoreWalletFromKeysCredentials( return haven!.createHavenRestoreWalletFromKeysCredentials(
name: name, name: name,
height: height, height: height,
spendKey: spendKey, spendKey: spendKey!,
viewKey: viewKey, viewKey: viewKey!,
address: address, address: address!,
password: password, password: password,
language: 'English'); 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()}'); throw Exception('Unexpected type: ${type.toString()}');

View file

@ -8,7 +8,7 @@ class WalletSeedViewModel = WalletSeedViewModelBase with _$WalletSeedViewModel;
abstract class WalletSeedViewModelBase with Store { abstract class WalletSeedViewModelBase with Store {
WalletSeedViewModelBase(WalletBase wallet) WalletSeedViewModelBase(WalletBase wallet)
: name = wallet.name, : name = wallet.name,
seed = wallet.seed; seed = wallet.seed!;
@observable @observable
String name; String name;

View file

@ -507,6 +507,7 @@ abstract class Ethereum {
WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource); WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource);
WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo}); WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo});
WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); 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); String getAddress(WalletBase wallet);
TransactionPriority getDefaultTransactionPriority(); TransactionPriority getDefaultTransactionPriority();
List<TransactionPriority> getTransactionPriorities(); List<TransactionPriority> getTransactionPriorities();