feat: add testnet option to restore flow

This commit is contained in:
Rafael Saes 2023-11-30 07:03:25 -03:00
parent 5c205e4cc8
commit 343817b1a7
17 changed files with 123 additions and 111 deletions

View file

@ -84,11 +84,13 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
}
@override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
throw UnimplementedError();
@override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
@ -98,6 +100,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
networkType: isTestnet == true ? bitcoin.testnet : bitcoin.bitcoin,
);
await wallet.save();
await wallet.init();

View file

@ -92,8 +92,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
try {
receiveAddresses.forEach(
(element) => print('element: ${element.address} ${element.type} $addressPageType'));
return receiveAddresses
.firstWhere((address) => addressPageType == bitcoin.AddressType.p2wpkh
? address.type == null || address.type == addressPageType

View file

@ -82,12 +82,12 @@ class LitecoinWalletService extends WalletService<
@override
Future<LitecoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
throw UnimplementedError();
@override
Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async {
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}

View file

@ -4,23 +4,19 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:crypto/crypto.dart';
String scriptHash(String address, {required bitcoin.NetworkType networkType}) {
try {
final outputScript = bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';
final outputScript = bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';
for (var i = parts.length - 1; i >= 0; i--) {
final char = parts[i];
i--;
final nextChar = parts[i];
res += nextChar;
res += char;
}
for (var i = parts.length - 1; i >= 0; i--) {
final char = parts[i];
i--;
final nextChar = parts[i];
res += nextChar;
res += char;
}
return res;
} catch (e) {}
return "";
return res;
}
String scriptHashFromScript(Uint8List outputScript, {required bitcoin.NetworkType networkType}) {

View file

@ -88,14 +88,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override
Future<BitcoinCashWallet>
restoreFromKeys(credentials) {
restoreFromKeys(credentials, {bool? isTestnet}) {
// TODO: implement restoreFromKeys
throw UnimplementedError('restoreFromKeys() is not implemented');
}
@override
Future<BitcoinCashWallet> restoreFromSeed(
BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
BitcoinCashRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinCashMnemonicIsIncorrectException();
}

View file

@ -10,9 +10,9 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
Future<WalletBase> create(N credentials, {bool? isTestnet});
Future<WalletBase> restoreFromSeed(RFS credentials);
Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
Future<WalletBase> restoreFromKeys(RFK credentials);
Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});
Future<WalletBase> openWallet(String name, String password);

View file

@ -73,7 +73,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
}
@override
Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async {
Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials, {bool? isTestnet}) async {
final wallet = EthereumWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
@ -89,7 +89,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
@override
Future<EthereumWallet> restoreFromSeed(
EthereumRestoreWalletFromSeedCredentials credentials) async {
EthereumRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
}

View file

@ -173,7 +173,7 @@ class HavenWalletService extends WalletService<
@override
Future<HavenWallet> restoreFromKeys(
HavenRestoreWalletFromKeysCredentials credentials) async {
HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.restoreFromKeys(
@ -197,7 +197,8 @@ class HavenWalletService extends WalletService<
@override
Future<HavenWallet> restoreFromSeed(
HavenRestoreWalletFromSeedCredentials credentials) async {
HavenRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.restoreFromSeed(

View file

@ -189,7 +189,7 @@ class MoneroWalletService extends WalletService<
@override
Future<MoneroWallet> restoreFromKeys(
MoneroRestoreWalletFromKeysCredentials credentials) async {
MoneroRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.restoreFromKeys(
@ -214,7 +214,7 @@ class MoneroWalletService extends WalletService<
@override
Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
MoneroRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.restoreFromSeed(

View file

@ -78,7 +78,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
@override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!");
} else {
@ -112,7 +112,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
@override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException();

View file

@ -88,12 +88,12 @@ class WalletCreationService {
return wallet;
}
Future<WalletBase> restoreFromSeed(WalletCredentials credentials) async {
Future<WalletBase> restoreFromSeed(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials);
final wallet = await _service!.restoreFromSeed(credentials, isTestnet: isTestnet);
if (wallet.type == WalletType.monero) {
await sharedPreferences.setBool(

View file

@ -176,12 +176,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
validator: WalletNameValidator(),
),
Observer(builder: (context) {
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: widget._walletNewVM.useTestnet,
onValueChange: (_, __) => widget._walletNewVM.toggleUseTestnet());
}),
],
),
),
@ -203,7 +197,13 @@ class _WalletNameFormState extends State<WalletNameForm> {
child: SeedLanguageSelector(
key: _languageSelectorKey, initialSelected: defaultSeedLanguage),
)
]
],
Observer(builder: (context) {
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: widget._walletNewVM.useTestnet,
onValueChange: (_, __) => widget._walletNewVM.toggleUseTestnet());
}),
],
),
),

View file

@ -1,29 +1,30 @@
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.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/generated/i18n.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm(
{Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.type,
this.blockHeightFocusNode,
this.onHeightOrDateEntered,
this.onSeedChange,
this.onLanguageChange})
: super(key: key);
WalletRestoreFromSeedForm({
Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.type,
this.blockHeightFocusNode,
this.onHeightOrDateEntered,
this.onSeedChange,
this.onLanguageChange,
required this.toggleTestnet,
required this.isTestnet,
}) : super(key: key);
final WalletType type;
final bool displayLanguageSelector;
@ -33,9 +34,11 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
final void Function(String)? onSeedChange;
final void Function(String)? onLanguageChange;
bool isTestnet;
final Function() toggleTestnet;
@override
WalletRestoreFromSeedFormState createState() =>
WalletRestoreFromSeedFormState('English');
WalletRestoreFromSeedFormState createState() => WalletRestoreFromSeedFormState('English');
}
class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
@ -65,43 +68,44 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
padding: EdgeInsets.only(left: 24, right: 24),
child: Column(children: [
Form(
key: formKey,
child: Stack(
alignment: Alignment.centerRight,
children: [
BaseTextFormField(
controller: nameTextEditingController,
hintText: S.of(context).wallet_name,
suffixIcon: IconButton(
onPressed: () async {
final rName = await generateName();
FocusManager.instance.primaryFocus?.unfocus();
key: formKey,
child: Stack(
alignment: Alignment.centerRight,
children: [
BaseTextFormField(
controller: nameTextEditingController,
hintText: S.of(context).wallet_name,
suffixIcon: IconButton(
onPressed: () async {
final rName = await generateName();
FocusManager.instance.primaryFocus?.unfocus();
setState(() {
nameTextEditingController.text = rName;
nameTextEditingController.selection =
TextSelection.fromPosition(TextPosition(
offset: nameTextEditingController.text.length));
});
},
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme.of(context).hintColor,
),
width: 34,
height: 34,
child: Image.asset(
'assets/images/refresh_icon.png',
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
setState(() {
nameTextEditingController.text = rName;
nameTextEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: nameTextEditingController.text.length));
});
},
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme.of(context).hintColor,
),
width: 34,
height: 34,
child: Image.asset(
'assets/images/refresh_icon.png',
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
),
),
),
validator: WalletNameValidator(),
),
),
validator: WalletNameValidator(),
),
],
)),
],
)),
Container(height: 20),
SeedWidget(
key: seedWidgetStateKey,
@ -113,8 +117,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) => SeedLanguagePicker(
selected: language, onItemSelected: _changeLanguage));
builder: (_) =>
SeedLanguagePicker(selected: language, onItemSelected: _changeLanguage));
},
child: Container(
color: Colors.transparent,
@ -129,7 +133,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
focusNode: widget.blockHeightFocusNode,
key: blockchainHeightKey,
onHeightOrDateEntered: widget.onHeightOrDateEntered,
hasDatePicker: widget.type == WalletType.monero)
hasDatePicker: widget.type == WalletType.monero),
Observer(builder: (_) {
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: widget.isTestnet,
onValueChange: (_, __) => widget.toggleTestnet());
}),
]));
}
@ -142,6 +152,5 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
});
}
void _setLanguageLabel(String language) =>
languageController.text = '$language (Seed language)';
void _setLanguageLabel(String language) => languageController.text = '$language (Seed language)';
}

View file

@ -36,6 +36,8 @@ class WalletRestorePage extends BasePage {
switch (mode) {
case WalletRestoreMode.seed:
_pages.add(WalletRestoreFromSeedForm(
isTestnet: walletRestoreViewModel.useTestnet,
toggleTestnet: walletRestoreViewModel.toggleUseTestnet,
displayBlockHeightSelector:
walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector,
@ -163,7 +165,8 @@ class WalletRestorePage extends BasePage {
color: Theme.of(context).colorScheme.background,
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
constraints:
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -219,8 +222,8 @@ class WalletRestorePage extends BasePage {
const SizedBox(height: 25),
GestureDetector(
onTap: () {
Navigator.of(context)
.pushNamed(Routes.advancedPrivacySettings, arguments: walletRestoreViewModel.type);
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings,
arguments: walletRestoreViewModel.type);
},
child: Text(S.of(context).advanced_privacy_settings),
),

View file

@ -23,6 +23,12 @@ abstract class WalletCreationVMBase with Store {
: state = InitialExecutionState(),
name = '';
@observable
bool _useTestnet = false;
@computed
bool get useTestnet => _useTestnet;
@observable
String name;
@ -39,7 +45,7 @@ abstract class WalletCreationVMBase with Store {
bool typeExists(WalletType type) => walletCreationService.typeExists(type);
Future<void> create({dynamic options, RestoredWallet? restoreWallet, bool? isTestnet}) async {
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
final type = restoreWallet?.type ?? this.type;
try {
state = IsExecutingState();
@ -94,4 +100,9 @@ abstract class WalletCreationVMBase with Store {
Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) =>
throw UnimplementedError();
@action
void toggleUseTestnet() {
_useTestnet = !_useTestnet;
}
}

View file

@ -27,12 +27,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
: selectedMnemonicLanguage = '',
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false);
@observable
bool _useTestnet = false;
@computed
bool get useTestnet => _useTestnet;
@observable
String selectedMnemonicLanguage;
@ -65,9 +59,6 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
@override
Future<WalletBase> process(WalletCredentials credentials) async {
walletCreationService.changeWalletType(type: type);
return walletCreationService.create(credentials, isTestnet: useTestnet);
return walletCreationService.create(credentials);
}
@action
void toggleUseTestnet() => _useTestnet = !_useTestnet;
}

View file

@ -188,6 +188,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return walletCreationService.restoreFromKeys(credentials);
}
return walletCreationService.restoreFromSeed(credentials);
return walletCreationService.restoreFromSeed(credentials, isTestnet: useTestnet);
}
}