From 04bba2bf2a2905f96e3eda51806a308c7d3f0b51 Mon Sep 17 00:00:00 2001 From: julian <julian@cypherstack.com> Date: Tue, 22 Aug 2023 15:07:08 -0600 Subject: [PATCH] new wallet mnemonic options for supported coins --- .../name_your_wallet_view.dart | 92 ++-- .../new_wallet_options_view.dart | 409 ++++++++++++++++++ ...w_wallet_recovery_phrase_warning_view.dart | 32 +- lib/route_generator.dart | 16 + lib/services/coins/banano/banano_wallet.dart | 4 +- .../coins/bitcoin/bitcoin_wallet.dart | 25 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 25 +- lib/services/coins/coin_service.dart | 4 +- .../coins/dogecoin/dogecoin_wallet.dart | 22 +- lib/services/coins/ecash/ecash_wallet.dart | 25 +- .../coins/epiccash/epiccash_wallet.dart | 9 +- .../coins/ethereum/ethereum_wallet.dart | 25 +- lib/services/coins/firo/firo_wallet.dart | 22 +- .../coins/litecoin/litecoin_wallet.dart | 22 +- lib/services/coins/manager.dart | 4 +- lib/services/coins/monero/monero_wallet.dart | 4 +- .../coins/namecoin/namecoin_wallet.dart | 22 +- lib/services/coins/nano/nano_wallet.dart | 4 +- .../coins/particl/particl_wallet.dart | 22 +- .../coins/stellar/stellar_wallet.dart | 26 +- .../coins/wownero/wownero_wallet.dart | 5 +- lib/utilities/enums/coin_enum.dart | 29 ++ 22 files changed, 745 insertions(+), 103 deletions(-) create mode 100644 lib/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart index 4e04fcb80..4f88f99b3 100644 --- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart +++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -336,46 +337,59 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> { ref.read(walletsServiceChangeNotifierProvider); final name = textEditingController.text; - if (await walletsService.checkForDuplicate(name)) { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "Wallet name already in use.", - iconAsset: Assets.svg.circleAlert, - context: context, - )); - } else { - // hide keyboard if has focus - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future<void>.delayed( - const Duration(milliseconds: 50)); - } + final hasDuplicateName = + await walletsService.checkForDuplicate(name); - if (mounted) { - switch (widget.addWalletType) { - case AddWalletType.New: - unawaited(Navigator.of(context).pushNamed( - NewWalletRecoveryPhraseWarningView.routeName, - arguments: Tuple2( - name, - coin, - ), - )); - break; - case AddWalletType.Restore: - ref - .read(mnemonicWordCountStateProvider.state) - .state = Constants.possibleLengthsForCoin( - coin) - .first; - unawaited(Navigator.of(context).pushNamed( - RestoreOptionsView.routeName, - arguments: Tuple2( - name, - coin, - ), - )); - break; + if (mounted) { + if (hasDuplicateName) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Wallet name already in use.", + iconAsset: Assets.svg.circleAlert, + context: context, + )); + } else { + // hide keyboard if has focus + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future<void>.delayed( + const Duration(milliseconds: 50)); + } + + if (mounted) { + ref + .read(mnemonicWordCountStateProvider.state) + .state = + Constants.possibleLengthsForCoin(coin).last; + + switch (widget.addWalletType) { + case AddWalletType.New: + unawaited( + Navigator.of(context).pushNamed( + coin.hasMnemonicPassphraseSupport + ? NewWalletOptionsView.routeName + : NewWalletRecoveryPhraseWarningView + .routeName, + arguments: Tuple2( + name, + coin, + ), + ), + ); + break; + + case AddWalletType.Restore: + unawaited( + Navigator.of(context).pushNamed( + RestoreOptionsView.routeName, + arguments: Tuple2( + name, + coin, + ), + ), + ); + break; + } } } } diff --git a/lib/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart b/lib/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart new file mode 100644 index 000000000..4f2dfb180 --- /dev/null +++ b/lib/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart @@ -0,0 +1,409 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart'; +import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; +import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:tuple/tuple.dart'; + +final pNewWalletOptions = + StateProvider<({String mnemonicPassphrase, int mnemonicWordsCount})?>( + (ref) => null); + +enum NewWalletOptions { + Default, + Advanced; +} + +class NewWalletOptionsView extends ConsumerStatefulWidget { + const NewWalletOptionsView({ + Key? key, + required this.walletName, + required this.coin, + }) : super(key: key); + + static const routeName = "/newWalletOptionsView"; + + final String walletName; + final Coin coin; + + @override + ConsumerState<NewWalletOptionsView> createState() => + _NewWalletOptionsViewState(); +} + +class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> { + late final FocusNode passwordFocusNode; + late final TextEditingController passwordController; + + bool hidePassword = true; + NewWalletOptions _selectedOptions = NewWalletOptions.Default; + + @override + void initState() { + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + passwordController.dispose(); + passwordFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final lengths = Constants.possibleLengthsForCoin(widget.coin).toList(); + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) => DesktopScaffold( + background: Theme.of(context).extension<StackColors>()!.background, + appBar: const DesktopAppBar( + isCompactHeight: false, + leading: AppBarBackButton(), + trailing: ExitToMyStackButton(), + ), + body: SizedBox( + width: 480, + child: child, + ), + ), + child: ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension<StackColors>()!.background, + appBar: AppBar( + leading: const AppBarBackButton(), + title: Text( + "Wallet Options", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ), + child: Column( + children: [ + if (Util.isDesktop) + const Spacer( + flex: 10, + ), + if (!Util.isDesktop) + CoinImage( + coin: widget.coin, + height: 100, + width: 100, + ), + SizedBox( + height: Util.isDesktop ? 0 : 16, + ), + Text( + "Wallet options", + textAlign: TextAlign.center, + style: Util.isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox( + height: Util.isDesktop ? 32 : 16, + ), + DropdownButtonHideUnderline( + child: DropdownButton2<NewWalletOptions>( + value: _selectedOptions, + items: [ + ...NewWalletOptions.values.map( + (e) => DropdownMenuItem( + value: e, + child: Text( + e.name, + style: STextStyles.desktopTextMedium(context), + ), + ), + ), + ], + onChanged: (value) { + if (value is NewWalletOptions) { + setState(() { + _selectedOptions = value; + }); + } + }, + isExpanded: true, + iconStyleData: IconStyleData( + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight, + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + ), + ), + ), + const SizedBox( + height: 24, + ), + if (_selectedOptions == NewWalletOptions.Advanced) + Column( + children: [ + if (Util.isDesktop) + DropdownButtonHideUnderline( + child: DropdownButton2<int>( + value: ref + .watch(mnemonicWordCountStateProvider.state) + .state, + items: [ + ...lengths.map( + (e) => DropdownMenuItem( + value: e, + child: Text( + "$e word seed", + style: STextStyles.desktopTextMedium(context), + ), + ), + ), + ], + onChanged: (value) { + if (value is int) { + ref + .read(mnemonicWordCountStateProvider.state) + .state = value; + } + }, + isExpanded: true, + iconStyleData: IconStyleData( + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context) + .extension<StackColors>()! + .textFieldActiveSearchIconRight, + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of(context) + .extension<StackColors>()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + ), + ), + ), + if (!Util.isDesktop) + MobileMnemonicLengthSelector( + chooseMnemonicLength: () { + showModalBottomSheet<dynamic>( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) { + return MnemonicWordCountSelectSheet( + lengthOptions: lengths, + ); + }, + ); + }, + ), + const SizedBox( + height: 24, + ), + RoundedWhiteContainer( + child: Center( + child: Text( + "You may protect the wallet seed with an optional passphrase. " + "If you lose this passphrase you will not be able " + "to restore using just your seed words.", + style: Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension<StackColors>()! + .textSubtitle1, + ) + : STextStyles.itemSubtitle(context), + ), + ), + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("mnemonicPassphraseFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ) + : STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Recovery phrase password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) => SizedBox( + height: 70, + child: child, + ), + child: Row( + children: [ + SizedBox( + width: Util.isDesktop ? 24 : 16, + ), + GestureDetector( + key: const Key( + "mnemonicPassphraseFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension<StackColors>()! + .textDark3, + width: Util.isDesktop ? 24 : 16, + height: Util.isDesktop ? 24 : 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + if (!Util.isDesktop) const Spacer(), + SizedBox( + height: Util.isDesktop ? 32 : 16, + ), + PrimaryButton( + label: "Continue", + onPressed: () { + if (_selectedOptions == NewWalletOptions.Advanced) { + ref.read(pNewWalletOptions.notifier).state = ( + mnemonicWordsCount: + ref.read(mnemonicWordCountStateProvider.state).state, + mnemonicPassphrase: passwordController.text, + ); + } else { + ref.read(pNewWalletOptions.notifier).state = null; + } + + Navigator.of(context).pushNamed( + NewWalletRecoveryPhraseWarningView.routeName, + arguments: Tuple2( + widget.walletName, + widget.coin, + ), + ); + }, + ), + if (!Util.isDesktop) + const SizedBox( + height: 16, + ), + if (Util.isDesktop) + const Spacer( + flex: 15, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index e373e817b..f237fd4ae 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -13,6 +13,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/recovery_phrase_explanation_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -38,7 +39,7 @@ import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; -class NewWalletRecoveryPhraseWarningView extends StatefulWidget { +class NewWalletRecoveryPhraseWarningView extends ConsumerStatefulWidget { const NewWalletRecoveryPhraseWarningView({ Key? key, required this.coin, @@ -51,12 +52,12 @@ class NewWalletRecoveryPhraseWarningView extends StatefulWidget { final String walletName; @override - State<NewWalletRecoveryPhraseWarningView> createState() => + ConsumerState<NewWalletRecoveryPhraseWarningView> createState() => _NewWalletRecoveryPhraseWarningViewState(); } class _NewWalletRecoveryPhraseWarningViewState - extends State<NewWalletRecoveryPhraseWarningView> { + extends ConsumerState<NewWalletRecoveryPhraseWarningView> { late final Coin coin; late final String walletName; late final bool isDesktop; @@ -72,6 +73,10 @@ class _NewWalletRecoveryPhraseWarningViewState @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + final options = ref.read(pNewWalletOptions.state).state; + + final seedCount = options?.mnemonicWordsCount ?? + Constants.defaultSeedPhraseLengthFor(coin: coin); return MasterScaffold( isDesktop: isDesktop, @@ -172,7 +177,7 @@ class _NewWalletRecoveryPhraseWarningViewState child: isDesktop ? Text( "On the next screen you will see " - "${Constants.defaultSeedPhraseLengthFor(coin: coin)} " + "$seedCount " "words that make up your recovery phrase.\n\nPlease " "write it down. Keep it safe and never share it with " "anyone. Your recovery phrase is the only way you can" @@ -496,7 +501,24 @@ class _NewWalletRecoveryPhraseWarningViewState final manager = Manager(wallet); - await manager.initializeNew(); + if (coin.hasMnemonicPassphraseSupport && + ref + .read(pNewWalletOptions.state) + .state != + null) { + await manager.initializeNew(( + mnemonicPassphrase: ref + .read(pNewWalletOptions.state) + .state! + .mnemonicPassphrase, + wordCount: ref + .read(pNewWalletOptions.state) + .state! + .mnemonicWordsCount, + )); + } else { + await manager.initializeNew(null); + } // pop progress dialog if (mounted) { diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 2b7aa662d..344eb7876 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_to import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart'; @@ -1101,6 +1102,21 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case NewWalletOptionsView.routeName: + if (args is Tuple2<String, Coin>) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => NewWalletOptionsView( + walletName: args.item1, + coin: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case RestoreWalletView.routeName: if (args is Tuple5<String, Coin, int, DateTime, String>) { return getRoute( diff --git a/lib/services/coins/banano/banano_wallet.dart b/lib/services/coins/banano/banano_wallet.dart index e2032aa09..50b4a3f9e 100644 --- a/lib/services/coins/banano/banano_wallet.dart +++ b/lib/services/coins/banano/banano_wallet.dart @@ -600,7 +600,9 @@ class BananoWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index f40be0e3c..cb8eadeef 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1290,7 +1290,9 @@ class BitcoinWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1301,7 +1303,7 @@ class BitcoinWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1499,7 +1501,9 @@ class BitcoinWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1533,10 +1537,21 @@ class BitcoinWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); - await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + value: bip39.generateMnemonic(strength: strength)); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: data?.mnemonicPassphrase ?? "", + ); // Generate and add addresses to relevant arrays final initialAddresses = await Future.wait([ diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index d0b360b0c..1f4057353 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1161,7 +1161,9 @@ class BitcoinCashWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1171,7 +1173,7 @@ class BitcoinCashWallet extends CoinServiceAPI } await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1402,7 +1404,9 @@ class BitcoinCashWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1436,10 +1440,21 @@ class BitcoinCashWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); - await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + value: bip39.generateMnemonic(strength: strength)); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: data?.mnemonicPassphrase ?? "", + ); // Generate and add addresses to relevant arrays final initialAddresses = await Future.wait([ diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 5aa6e382d..94fa569e4 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -348,7 +348,9 @@ abstract class CoinServiceAPI { required int height, }); - Future<void> initializeNew(); + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ); Future<void> initializeExisting(); Future<void> exit(); diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 754834ad1..3d6ae5794 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1147,7 +1147,9 @@ class DogecoinWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1157,7 +1159,7 @@ class DogecoinWallet extends CoinServiceAPI } await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1349,7 +1351,9 @@ class DogecoinWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1383,12 +1387,20 @@ class DogecoinWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); + value: bip39.generateMnemonic(strength: strength)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: data?.mnemonicPassphrase ?? "", ); // Generate and add addresses diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index e891db692..c35f7f4c6 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -506,7 +506,9 @@ class ECashWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -544,10 +546,21 @@ class ECashWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); - await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + value: bip39.generateMnemonic(strength: strength)); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: data?.mnemonicPassphrase ?? "", + ); const int startingIndex = 0; const int receiveChain = 0; @@ -2778,7 +2791,9 @@ class ECashWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -2789,7 +2804,7 @@ class ECashWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index b62129248..9d25a5a47 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -766,7 +766,9 @@ class EpicCashWallet extends CoinServiceAPI } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { await _prefs.init(); await updateNode(false); final mnemonic = await _getMnemonicList(); @@ -1738,7 +1740,6 @@ class EpicCashWallet extends CoinServiceAPI final isIncoming = (tx["tx_type"] == "TxReceived" || tx["tx_type"] == "TxReceivedCancelled"); - final txn = isar_models.Transaction( walletId: walletId, txid: commitId ?? tx["id"].toString(), @@ -1763,7 +1764,9 @@ class EpicCashWallet extends CoinServiceAPI otherData: tx['onChainNote'].toString(), inputs: [], outputs: [], - numberOfMessages: ((tx["numberOfMessages"] == null) ? 0 : tx["numberOfMessages"]) as int, + numberOfMessages: ((tx["numberOfMessages"] == null) + ? 0 + : tx["numberOfMessages"]) as int, ); // txn.address = diff --git a/lib/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart index 62f3020e0..b3800a4e4 100644 --- a/lib/services/coins/ethereum/ethereum_wallet.dart +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -310,7 +310,9 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance.log( "Generating new ${coin.prettyName} wallet.", level: LogLevel.Info, @@ -324,7 +326,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log( "Exception rethrown from initializeNew(): $e\n$s", @@ -338,7 +340,9 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { ]); } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { // Logging.instance // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); // if (!integrationTestFlag) { @@ -366,14 +370,23 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { "Attempted to overwrite mnemonic on generate new wallet!"); } - final String mnemonic = bip39.generateMnemonic(strength: 128); + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } + final String mnemonic = bip39.generateMnemonic(strength: strength); + final String passphrase = data?.mnemonicPassphrase ?? ""; await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: passphrase, ); - await _generateAndSaveAddress(mnemonic, ""); + await _generateAndSaveAddress(mnemonic, passphrase); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index a4cea6393..d58cf4402 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -1875,7 +1875,9 @@ class FiroWallet extends CoinServiceAPI } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1886,7 +1888,7 @@ class FiroWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -2124,7 +2126,9 @@ class FiroWallet extends CoinServiceAPI } /// Generates initial wallet values such as mnemonic, chain (receive/change) arrays and indexes. - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -2158,12 +2162,20 @@ class FiroWallet extends CoinServiceAPI longMutex = false; throw Exception("Attempted to overwrite mnemonic on initialize new!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); + value: bip39.generateMnemonic(strength: strength)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: data?.mnemonicPassphrase ?? "", ); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 703ec5cf6..26718bc4e 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1285,7 +1285,9 @@ class LitecoinWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1296,7 +1298,7 @@ class LitecoinWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1535,7 +1537,9 @@ class LitecoinWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1570,12 +1574,20 @@ class LitecoinWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); + value: bip39.generateMnemonic(strength: strength)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: data?.mnemonicPassphrase ?? "", ); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index 1f5ae55f3..05f27d503 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -180,7 +180,9 @@ class Manager with ChangeNotifier { Future<bool> testNetworkConnection() => _currentWallet.testNetworkConnection(); - Future<void> initializeNew() => _currentWallet.initializeNew(); + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data) => + _currentWallet.initializeNew(data); Future<void> initializeExisting() => _currentWallet.initializeExisting(); Future<void> recoverFromMnemonic({ required String mnemonic, diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 5911e8592..a5284cc60 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -307,7 +307,9 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { await _prefs.init(); // this should never fail diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index d0f30ecac..8b27fe426 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1268,7 +1268,9 @@ class NamecoinWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1279,7 +1281,7 @@ class NamecoinWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1517,7 +1519,9 @@ class NamecoinWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1544,12 +1548,20 @@ class NamecoinWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); + value: bip39.generateMnemonic(strength: strength)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: data?.mnemonicPassphrase ?? "", ); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/nano/nano_wallet.dart b/lib/services/coins/nano/nano_wallet.dart index 391303675..34de1034d 100644 --- a/lib/services/coins/nano/nano_wallet.dart +++ b/lib/services/coins/nano/nano_wallet.dart @@ -607,7 +607,9 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 324e8d9e5..33064ef85 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1195,7 +1195,9 @@ class ParticlWallet extends CoinServiceAPI bool get isConnected => _isConnected; @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); @@ -1206,7 +1208,7 @@ class ParticlWallet extends CoinServiceAPI await _prefs.init(); try { - await _generateNewWallet(); + await _generateNewWallet(data); } catch (e, s) { Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", level: LogLevel.Fatal); @@ -1432,7 +1434,9 @@ class ParticlWallet extends CoinServiceAPI } } - Future<void> _generateNewWallet() async { + Future<void> _generateNewWallet( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { @@ -1459,12 +1463,20 @@ class ParticlWallet extends CoinServiceAPI throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } await _secureStore.write( key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: 128)); + value: bip39.generateMnemonic(strength: strength)); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', - value: "", + value: data?.mnemonicPassphrase ?? "", ); // Generate and add addresses to relevant arrays diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index c4c92373f..2b06d3eef 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bip39/bip39.dart' as bip39; import 'package:http/http.dart' as http; import 'package:isar/isar.dart'; import 'package:stackwallet/db/isar/main_db.dart'; @@ -326,7 +327,9 @@ class StellarWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew() async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, + ) async { if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); @@ -334,11 +337,26 @@ class StellarWallet extends CoinServiceAPI with WalletCache, WalletDB { await _prefs.init(); - String mnemonic = await Wallet.generate24WordsMnemonic(); + final int strength; + if (data == null || data.wordCount == 12) { + strength = 128; + } else if (data.wordCount == 24) { + strength = 256; + } else { + throw Exception("Invalid word count"); + } + final String mnemonic = bip39.generateMnemonic(strength: strength); + final String passphrase = data?.mnemonicPassphrase ?? ""; await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); - await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: passphrase, + ); - Wallet wallet = await Wallet.from(mnemonic); + Wallet wallet = await Wallet.from( + mnemonic, + passphrase: passphrase, + ); KeyPair keyPair = await wallet.getKeyPair(index: 0); String address = keyPair.accountId; String secretSeed = diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 6f0863334..7b3691846 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -333,7 +333,10 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { } @override - Future<void> initializeNew({int seedWordsLength = 14}) async { + Future<void> initializeNew( + ({String mnemonicPassphrase, int wordCount})? data, { + int seedWordsLength = 14, + }) async { await _prefs.init(); // this should never fail diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 677d474e7..6fe2ffa87 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -236,6 +236,35 @@ extension CoinExt on Coin { } } + bool get hasMnemonicPassphraseSupport { + switch (this) { + case Coin.bitcoin: + case Coin.bitcoinTestNet: + case Coin.litecoin: + case Coin.litecoinTestNet: + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + case Coin.dogecoin: + case Coin.dogecoinTestNet: + case Coin.firo: + case Coin.firoTestNet: + case Coin.namecoin: + case Coin.particl: + case Coin.ethereum: + case Coin.eCash: + case Coin.stellar: + case Coin.stellarTestnet: + return true; + + case Coin.epicCash: + case Coin.monero: + case Coin.wownero: + case Coin.nano: + case Coin.banano: + return false; + } + } + bool get hasBuySupport { switch (this) { case Coin.bitcoin: