mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-21 06:18:42 +02:00
CW-229 Improved restore options from QR code (#793)
* add restoring wallet from qr * add restore mode * add alert for exceptions * add restore from seed * add check for create wallet state * convert sweeping page into stateful * fix parsing url * restoration flow update * update restoring from key mode * update config * fix restor of BTC and LTC wallets * fix pin code issue * wallet Seed/keys uri or code fix * fix key restore credentials * update the restore workflow * update from main * PR coments fixes * update * update * PR fixes
This commit is contained in:
+12
-1
@@ -53,6 +53,8 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
@@ -320,6 +322,13 @@ Future setup(
|
||||
type: type, language: language);
|
||||
});
|
||||
|
||||
getIt
|
||||
.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
|
||||
return WalletRestorationFromQRVM(getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type),
|
||||
_walletInfoSource, type);
|
||||
});
|
||||
|
||||
getIt.registerFactory<WalletAddressListViewModel>(() =>
|
||||
WalletAddressListViewModel(
|
||||
appStore: getIt.get<AppStore>(), yatStore: getIt.get<YatStore>(),
|
||||
@@ -743,7 +752,9 @@ Future setup(
|
||||
getIt.registerFactory(
|
||||
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => RestoreOptionsPage());
|
||||
getIt.registerFactoryParam<RestoreOptionsPage, bool, void>((bool isNewInstall, _) =>
|
||||
RestoreOptionsPage(isNewInstall: isNewInstall));
|
||||
|
||||
|
||||
getIt.registerFactory(
|
||||
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
|
||||
|
||||
@@ -33,7 +33,7 @@ class AddressResolver {
|
||||
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
|
||||
|
||||
if (addressPattern == null) {
|
||||
throw 'Unexpected token: $type for getAddressFromStringPattern';
|
||||
throw Exception('Unexpected token: $type for getAddressFromStringPattern');
|
||||
}
|
||||
|
||||
final match = RegExp(addressPattern).firstMatch(raw);
|
||||
|
||||
+17
-3
@@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/payfura_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
|
||||
@@ -40,6 +41,8 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
@@ -158,8 +161,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
param2: false));
|
||||
|
||||
case Routes.restoreOptions:
|
||||
final isNewInstall = settings.arguments as bool;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<RestoreOptionsPage>());
|
||||
builder: (_) => getIt.get<RestoreOptionsPage>(param1: isNewInstall));
|
||||
|
||||
case Routes.restoreWalletOptions:
|
||||
final type = WalletType.monero; //settings.arguments as WalletType;
|
||||
@@ -189,12 +193,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
}));
|
||||
|
||||
case Routes.restoreWalletOptionsFromWelcome:
|
||||
return CupertinoPageRoute<void>(
|
||||
final isNewInstall = settings.arguments as bool;
|
||||
return isNewInstall ? CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<SetupPinCodePage>(
|
||||
param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
|
||||
Navigator.pushNamed(
|
||||
context.context, Routes.restoreWalletType)),
|
||||
fullscreenDialog: true);
|
||||
fullscreenDialog: true) : CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<NewWalletTypePage>(
|
||||
param1: (BuildContext context, WalletType type) =>
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.restoreWallet, arguments: type),
|
||||
param2: false));
|
||||
|
||||
case Routes.seed:
|
||||
return MaterialPageRoute<void>(
|
||||
@@ -224,6 +234,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
builder: (_) => RestoreWalletFromKeysPage(
|
||||
walletRestorationFromKeysVM: walletRestorationFromKeysVM));
|
||||
|
||||
case Routes.sweepingWalletPage:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<SweepingWalletPage>());
|
||||
|
||||
case Routes.dashboard:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<DashboardPage>());
|
||||
|
||||
@@ -84,6 +84,7 @@ class Routes {
|
||||
static const displaySettingsPage = '/display_settings_page';
|
||||
static const otherSettingsPage = '/other_settings_page';
|
||||
static const advancedPrivacySettings = '/advanced_privacy_settings';
|
||||
static const sweepingWalletPage = '/sweeping_wallet_page';
|
||||
static const anonPayInvoicePage = '/anon_pay_invoice_page';
|
||||
static const anonPayReceivePage = '/anon_pay_receive_page';
|
||||
static const anonPayDetailsPage = '/anon_pay_details_page';
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/language_list.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
||||
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
@@ -7,15 +15,16 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class RestoreOptionsPage extends BasePage {
|
||||
RestoreOptionsPage();
|
||||
|
||||
static const _aspectRatioImage = 2.086;
|
||||
RestoreOptionsPage({required this.isNewInstall});
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_restore_wallet;
|
||||
|
||||
|
||||
final bool isNewInstall;
|
||||
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png');
|
||||
final imageBackup = Image.asset('assets/images/backup.png');
|
||||
final qrCode = Image.asset('assets/images/qr_code_icon.png');
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
@@ -28,24 +37,69 @@ class RestoreOptionsPage extends BasePage {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome),
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context, Routes.restoreWalletOptionsFromWelcome,
|
||||
arguments: isNewInstall),
|
||||
image: imageSeedKeys,
|
||||
title: S.of(context).restore_title_from_seed_keys,
|
||||
description:
|
||||
S.of(context).restore_description_from_seed_keys),
|
||||
description: S.of(context).restore_description_from_seed_keys),
|
||||
if (isNewInstall)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup),
|
||||
onPressed: () async {
|
||||
bool isPinSet = false;
|
||||
if (isNewInstall) {
|
||||
await Navigator.pushNamed(context, Routes.setupPin,
|
||||
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
||||
setupPinContext.close();
|
||||
isPinSet = true;
|
||||
});
|
||||
}
|
||||
if (!isNewInstall || isPinSet) {
|
||||
try {
|
||||
final restoreWallet =
|
||||
await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||
|
||||
final restoreFromQRViewModel = getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
|
||||
|
||||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||
if (restoreFromQRViewModel.state is FailureState) {
|
||||
_onWalletCreateFailure(context,
|
||||
'Create wallet state: ${restoreFromQRViewModel.state.runtimeType.toString()}');
|
||||
}
|
||||
} catch (e) {
|
||||
_onWalletCreateFailure(context, e.toString());
|
||||
}
|
||||
}
|
||||
},
|
||||
image: qrCode,
|
||||
title: S.of(context).scan_qr_code,
|
||||
description: S.of(context).cold_or_recover_wallet),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _onWalletCreateFailure(BuildContext context, String error) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.error,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class SweepingWalletPage extends BasePage {
|
||||
SweepingWalletPage();
|
||||
|
||||
static const aspectRatioImage = 1.25;
|
||||
final welcomeImageLight = Image.asset('assets/images/welcome_light.png');
|
||||
final welcomeImageDark = Image.asset('assets/images/welcome.png');
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: body(context));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight;
|
||||
|
||||
return SweepingWalletWidget(
|
||||
aspectRatioImage: aspectRatioImage,
|
||||
welcomeImage: welcomeImage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SweepingWalletWidget extends StatefulWidget {
|
||||
const SweepingWalletWidget({
|
||||
required this.aspectRatioImage,
|
||||
required this.welcomeImage,
|
||||
});
|
||||
|
||||
final double aspectRatioImage;
|
||||
final Image welcomeImage;
|
||||
|
||||
@override
|
||||
State<SweepingWalletWidget> createState() => _SweepingWalletWidgetState();
|
||||
}
|
||||
|
||||
class _SweepingWalletWidgetState extends State<SweepingWalletWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: AspectRatio(
|
||||
aspectRatio: widget.aspectRatioImage,
|
||||
child: FittedBox(child: widget.welcomeImage, fit: BoxFit.fill))),
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Text(
|
||||
S.of(context).please_wait,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).accentTextTheme!.headline2!.color!,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
S.of(context).sweeping_wallet,
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
S.of(context).sweeping_wallet_alert,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).accentTextTheme!.headline2!.color!,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ 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';
|
||||
|
||||
class WalletRestorePage extends BasePage {
|
||||
WalletRestorePage(this.walletRestoreViewModel)
|
||||
|
||||
@@ -178,7 +178,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
Navigator.of(context).pushNamed(Routes.restoreWallet,
|
||||
arguments: widget.walletListViewModel.currentWalletType);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.restoreWalletType);
|
||||
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
|
||||
}
|
||||
},
|
||||
image: restoreWalletImage,
|
||||
|
||||
@@ -148,7 +148,8 @@ class WelcomePage extends BasePage {
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: PrimaryImageButton(
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, Routes.restoreOptions);
|
||||
Navigator.pushNamed(context, Routes.restoreOptions,
|
||||
arguments: true);
|
||||
},
|
||||
image: restoreWalletImage,
|
||||
text: S.of(context).restore_wallet,
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.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';
|
||||
|
||||
part 'restore_from_qr_vm.g.dart';
|
||||
|
||||
class WalletRestorationFromQRVM = WalletRestorationFromQRVMBase with _$WalletRestorationFromQRVM;
|
||||
|
||||
abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store {
|
||||
WalletRestorationFromQRVMBase(AppStore appStore, WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource, WalletType type)
|
||||
: 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;
|
||||
|
||||
@override
|
||||
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) {
|
||||
final password = generateWalletPassword();
|
||||
|
||||
switch (restoreWallet.restoreMode) {
|
||||
case WalletRestoreMode.keys:
|
||||
switch (restoreWallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.createMoneroRestoreWalletFromKeysCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
language: 'English',
|
||||
address: restoreWallet.address ?? '',
|
||||
viewKey: restoreWallet.viewKey ?? '',
|
||||
spendKey: restoreWallet.spendKey ?? '',
|
||||
height: restoreWallet.height ?? 0);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials(
|
||||
name: name, password: password, wif: wif);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
|
||||
}
|
||||
case WalletRestoreMode.seed:
|
||||
switch (restoreWallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.createMoneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
height: restoreWallet.height ?? 0,
|
||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||
password: password);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) async {
|
||||
try {
|
||||
switch (restoreWallet.restoreMode) {
|
||||
case WalletRestoreMode.keys:
|
||||
return walletCreationService.restoreFromKeys(credentials);
|
||||
case WalletRestoreMode.seed:
|
||||
return walletCreationService.restoreFromSeed(credentials);
|
||||
default:
|
||||
throw Exception('Unexpected restore mode: ${restoreWallet.restoreMode.toString()}');
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Unexpected restore mode: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
enum WalletRestoreMode { seed, keys, txids }
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class RestoredWallet {
|
||||
RestoredWallet(
|
||||
{required this.restoreMode,
|
||||
required this.type,
|
||||
required this.address,
|
||||
this.txId,
|
||||
this.spendKey,
|
||||
this.viewKey,
|
||||
this.mnemonicSeed,
|
||||
this.txAmount,
|
||||
this.txDescription,
|
||||
this.recipientName,
|
||||
this.height});
|
||||
|
||||
final WalletRestoreMode restoreMode;
|
||||
final WalletType type;
|
||||
final String? address;
|
||||
final String? txId;
|
||||
final String? spendKey;
|
||||
final String? viewKey;
|
||||
final String? mnemonicSeed;
|
||||
final String? txAmount;
|
||||
final String? txDescription;
|
||||
final String? recipientName;
|
||||
final int? height;
|
||||
|
||||
factory RestoredWallet.fromKey(Map<String, dynamic> json) {
|
||||
final height = json['height'] as String?;
|
||||
return RestoredWallet(
|
||||
restoreMode: json['mode'] as WalletRestoreMode,
|
||||
type: json['type'] as WalletType,
|
||||
address: json['address'] as String?,
|
||||
spendKey: json['spend_key'] as String?,
|
||||
viewKey: json['view_key'] as String?,
|
||||
height: height != null ? int.parse(height) : 0,
|
||||
);
|
||||
}
|
||||
|
||||
factory RestoredWallet.fromSeed(Map<String, dynamic> json) {
|
||||
final height = json['height'] as String?;
|
||||
final mnemonic_seed = json['mnemonic_seed'] as String?;
|
||||
final seed = json['seed'] as String?;
|
||||
return RestoredWallet(
|
||||
restoreMode: json['mode'] as WalletRestoreMode,
|
||||
type: json['type'] as WalletType,
|
||||
address: json['address'] as String?,
|
||||
mnemonicSeed: mnemonic_seed ?? seed,
|
||||
height: height != null ? int.parse(height) : 0,
|
||||
);
|
||||
}
|
||||
|
||||
factory RestoredWallet.fromTxIds(Map<String, dynamic> json) {
|
||||
return RestoredWallet(
|
||||
restoreMode: json['mode'] as WalletRestoreMode,
|
||||
type: json['type'] as WalletType,
|
||||
address: json['address'] as String?,
|
||||
txId: json['tx_payment_id'] as String,
|
||||
txAmount: json['tx_amount'] as String,
|
||||
txDescription: json['tx_description'] as String?,
|
||||
recipientName: json['recipient_name'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/core/seed_validator.dart';
|
||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class WalletRestoreFromQRCode {
|
||||
WalletRestoreFromQRCode();
|
||||
|
||||
static Future<RestoredWallet> scanQRCodeForRestoring(BuildContext context) async {
|
||||
String code = await presentQRScanner();
|
||||
Map<String, dynamic> credentials = {};
|
||||
|
||||
if (code.isEmpty) {
|
||||
throw Exception('Unexpected scan QR code value: value is empty');
|
||||
}
|
||||
final formattedUri = getFormattedUri(code);
|
||||
final uri = Uri.parse(formattedUri);
|
||||
final queryParameters = uri.queryParameters;
|
||||
credentials['type'] = getWalletTypeFromUrl(uri.scheme);
|
||||
|
||||
final address = getAddressFromUrl(
|
||||
type: credentials['type'] as WalletType,
|
||||
rawString: queryParameters.toString(),
|
||||
);
|
||||
if (address != null) {
|
||||
credentials['address'] = address;
|
||||
}
|
||||
|
||||
final seed =
|
||||
getSeedPhraseFromUrl(queryParameters.toString(), credentials['type'] as WalletType);
|
||||
if (seed != null) {
|
||||
credentials['seed'] = seed;
|
||||
}
|
||||
credentials.addAll(queryParameters);
|
||||
credentials['mode'] = getWalletRestoreMode(credentials);
|
||||
|
||||
switch (credentials['mode']) {
|
||||
case WalletRestoreMode.txids:
|
||||
return RestoredWallet.fromTxIds(credentials);
|
||||
case WalletRestoreMode.seed:
|
||||
return RestoredWallet.fromSeed(credentials);
|
||||
case WalletRestoreMode.keys:
|
||||
return RestoredWallet.fromKey(credentials);
|
||||
default:
|
||||
throw Exception('Unexpected restore mode: ${credentials['mode']}');
|
||||
}
|
||||
}
|
||||
|
||||
static String getFormattedUri(String code) {
|
||||
final index = code.indexOf(':');
|
||||
final scheme = code.substring(0, index).replaceAll('_', '-');
|
||||
final query = code.substring(index + 1).replaceAll('?', '&');
|
||||
final formattedUri = '$scheme:?$query';
|
||||
return formattedUri;
|
||||
}
|
||||
|
||||
static WalletType getWalletTypeFromUrl(String scheme) {
|
||||
switch (scheme) {
|
||||
case 'monero':
|
||||
case 'monero-wallet':
|
||||
return WalletType.monero;
|
||||
case 'bitcoin':
|
||||
case 'bitcoin-wallet':
|
||||
return WalletType.bitcoin;
|
||||
case 'litecoin':
|
||||
case 'litecoin-wallet':
|
||||
return WalletType.litecoin;
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${scheme.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
static String? getAddressFromUrl({required WalletType type, required String rawString}) {
|
||||
return AddressResolver.extractAddressByType(
|
||||
raw: rawString, type: walletTypeToCryptoCurrency(type));
|
||||
}
|
||||
|
||||
static String? getSeedPhraseFromUrl(String rawString, WalletType walletType) {
|
||||
switch (walletType) {
|
||||
case WalletType.monero:
|
||||
RegExp regex25 = RegExp(r'\b(\S+\b\s+){24}\S+\b');
|
||||
RegExp regex14 = RegExp(r'\b(\S+\b\s+){13}\S+\b');
|
||||
RegExp regex13 = RegExp(r'\b(\S+\b\s+){12}\S+\b');
|
||||
|
||||
if (regex25.firstMatch(rawString) == null) {
|
||||
if (regex14.firstMatch(rawString) == null) {
|
||||
if (regex13.firstMatch(rawString) == null) {
|
||||
return null;
|
||||
} else {
|
||||
return regex13.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
} else {
|
||||
return regex14.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
} else {
|
||||
return regex25.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
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');
|
||||
|
||||
if (regex24.firstMatch(rawString) == null) {
|
||||
if (regex18.firstMatch(rawString) == null) {
|
||||
if (regex12.firstMatch(rawString) == null) {
|
||||
return null;
|
||||
} else {
|
||||
return regex12.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
} else {
|
||||
return regex18.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
} else {
|
||||
return regex24.firstMatch(rawString)!.group(0)!;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static WalletRestoreMode getWalletRestoreMode(Map<String, dynamic> credentials) {
|
||||
final type = credentials['type'] as WalletType;
|
||||
if (credentials.containsKey('tx_payment_id')) {
|
||||
final txIdValue = credentials['tx_payment_id'] as String? ?? '';
|
||||
return txIdValue.isNotEmpty
|
||||
? WalletRestoreMode.txids
|
||||
: throw Exception('Unexpected restore mode: tx_payment_id is invalid');
|
||||
}
|
||||
|
||||
if (credentials.containsKey('seed')) {
|
||||
final seedValue = credentials['seed'] as String;
|
||||
final words = SeedValidator.getWordList(type: type, language: 'english');
|
||||
seedValue.split(' ').forEach((element) {
|
||||
if (!words.contains(element)) {
|
||||
throw Exception('Unexpected restore mode: mnemonic_seed is invalid');
|
||||
}
|
||||
});
|
||||
return WalletRestoreMode.seed;
|
||||
}
|
||||
|
||||
if (credentials.containsKey('spend_key') || credentials.containsKey('view_key')) {
|
||||
final spendKeyValue = credentials['spend_key'] as String? ?? '';
|
||||
final viewKeyValue = credentials['view_key'] as String? ?? '';
|
||||
|
||||
return spendKeyValue.isNotEmpty || viewKeyValue.isNotEmpty
|
||||
? WalletRestoreMode.keys
|
||||
: throw Exception('Unexpected restore mode: spend_key or view_key is invalid');
|
||||
}
|
||||
|
||||
throw Exception('Unexpected restore mode: restore params are invalid');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
@@ -39,7 +39,8 @@ abstract class WalletCreationVMBase with Store {
|
||||
bool typeExists(WalletType type)
|
||||
=> walletCreationService.typeExists(type);
|
||||
|
||||
Future<void> create({dynamic options}) async {
|
||||
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
|
||||
final type = restoreWallet?.type ?? this.type;
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
if (name.isEmpty) {
|
||||
@@ -49,7 +50,9 @@ abstract class WalletCreationVMBase with Store {
|
||||
walletCreationService.checkIfExists(name);
|
||||
final dirPath = await pathForWalletDir(name: name, type: type);
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
final credentials = getCredentials(options);
|
||||
final credentials = restoreWallet != null
|
||||
? getCredentialsFromRestoredWallet(options, restoreWallet)
|
||||
: getCredentials(options);
|
||||
final walletInfo = WalletInfo.external(
|
||||
id: WalletBase.idFor(name, type),
|
||||
name: name,
|
||||
@@ -62,7 +65,9 @@ abstract class WalletCreationVMBase with Store {
|
||||
address: '',
|
||||
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven);
|
||||
credentials.walletInfo = walletInfo;
|
||||
final wallet = await process(credentials);
|
||||
final wallet = restoreWallet != null
|
||||
? await processFromRestoredWallet(credentials, restoreWallet)
|
||||
: await process(credentials);
|
||||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await _walletInfoSource.add(walletInfo);
|
||||
_appStore.changeCurrentWallet(wallet);
|
||||
@@ -72,10 +77,15 @@ abstract class WalletCreationVMBase with Store {
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
WalletCredentials getCredentials(dynamic options) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
Future<WalletBase> process(WalletCredentials credentials) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@@ -88,16 +88,17 @@ abstract class WalletKeysViewModelBase with Store {
|
||||
return null;
|
||||
}
|
||||
|
||||
String get _path {
|
||||
|
||||
String get _scheme {
|
||||
switch (_appStore.wallet!.type) {
|
||||
case WalletType.monero:
|
||||
return 'monero_wallet:';
|
||||
return 'monero-wallet';
|
||||
case WalletType.bitcoin:
|
||||
return 'bitcoin_wallet:';
|
||||
return 'bitcoin-wallet';
|
||||
case WalletType.litecoin:
|
||||
return 'litecoin_wallet:';
|
||||
return 'litecoin-wallet';
|
||||
case WalletType.haven:
|
||||
return 'haven_wallet:';
|
||||
return 'haven-wallet';
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
|
||||
}
|
||||
@@ -124,7 +125,7 @@ abstract class WalletKeysViewModelBase with Store {
|
||||
|
||||
Future<Uri> get url async {
|
||||
return Uri(
|
||||
path: _path,
|
||||
scheme: _scheme,
|
||||
queryParameters: await _queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -66,7 +67,7 @@ abstract class WalletRestorationFromKeysVMBase extends WalletCreationVM
|
||||
return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials(
|
||||
name: name, password: password, wif: wif);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');;
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -13,10 +14,10 @@ import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
|
||||
part 'wallet_restore_view_model.g.dart';
|
||||
|
||||
enum WalletRestoreMode { seed, keys }
|
||||
|
||||
class WalletRestoreViewModel = WalletRestoreViewModelBase
|
||||
with _$WalletRestoreViewModel;
|
||||
|
||||
Reference in New Issue
Block a user