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 73acccb9d..aa49a9038 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 @@ -26,18 +26,20 @@ import '../../../services/transaction_notification_tracker.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/logger.dart'; +import '../../../utilities/show_loading.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../wallets/isar/models/wallet_info.dart'; +import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/desktop_app_bar.dart'; import '../../../widgets/desktop/desktop_scaffold.dart'; -import '../../../widgets/loading_indicator.dart'; import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; import '../new_wallet_options/new_wallet_options_view.dart'; import '../new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; import 'recovery_phrase_explanation_dialog.dart'; @@ -65,6 +67,221 @@ class _NewWalletRecoveryPhraseWarningViewState late final String walletName; late final bool isDesktop; + Future _initNewWallet() async { + Exception? ex; + final result = await showLoading( + whileFuture: _initNewFuture(), + context: context, + message: "Generating...", + onException: (e) => ex = e, + ); + + // on failure show error message + if (result == null) { + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Create Wallet Error", + message: ex?.toString() ?? "Unknown error", + maxWidth: 600, + ), + ); + } + return; + } else { + if (mounted) { + final nav = Navigator.of(context); + unawaited( + nav.pushNamed( + NewWalletRecoveryPhraseView.routeName, + arguments: Tuple2( + result.$1, + result.$2, + ), + ), + ); + } + } + } + + Future<(Wallet, List)> _initNewFuture() async { + try { + String? otherDataJsonString; + if (widget.coin is Tezos) { + otherDataJsonString = jsonEncode({ + WalletInfoKeys.tezosDerivationPath: + Tezos.standardDerivationPath.value, + }); + // }//todo: probably not needed (broken anyways) + // else if (widget.coin is Epiccash) { + // final int secondsSinceEpoch = + // DateTime.now().millisecondsSinceEpoch ~/ 1000; + // const int epicCashFirstBlock = 1565370278; + // const double overestimateSecondsPerBlock = 61; + // int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; + // int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + // / + // // debugPrint( + // // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); + // height = approximateHeight; + // if (height < 0) { + // height = 0; + // } + // + // otherDataJsonString = jsonEncode( + // { + // WalletInfoKeys.epiccashData: jsonEncode( + // ExtraEpiccashWalletInfo( + // receivingIndex: 0, + // changeIndex: 0, + // slatesToAddresses: {}, + // slatesToCommits: {}, + // lastScannedBlock: epicCashFirstBlock, + // restoreHeight: height, + // creationHeight: height, + // ).toMap(), + // ), + // }, + // ); + } else if (widget.coin is Firo) { + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.lelantusCoinIsarRescanRequired: false, + }, + ); + } + + final info = WalletInfo.createNew( + coin: widget.coin, + name: widget.walletName, + otherDataJsonString: otherDataJsonString, + ); + + var node = ref + .read( + nodeServiceChangeNotifierProvider, + ) + .getPrimaryNodeFor( + currency: coin, + ); + + if (node == null) { + node = coin.defaultNode; + await ref + .read( + nodeServiceChangeNotifierProvider, + ) + .setPrimaryNodeFor( + coin: coin, + node: node, + ); + } + + final txTracker = TransactionNotificationTracker( + walletId: info.walletId, + ); + + String? mnemonicPassphrase; + String? mnemonic; + String? privateKey; + + // set some sane default + int wordCount = info.coin.defaultSeedPhraseLength; + + // TODO: Refactor these to generate each coin in their respective classes + // This code should not be in a random view page file + if (coin is Monero || coin is Wownero) { + // currently a special case due to the + // xmr/wow libraries handling their + // own mnemonic generation + wordCount = ref.read(pNewWalletOptions)?.mnemonicWordsCount ?? + info.coin.defaultSeedPhraseLength; + } else if (wordCount > 0) { + if (ref + .read( + pNewWalletOptions.state, + ) + .state != + null) { + if (coin.hasMnemonicPassphraseSupport) { + mnemonicPassphrase = ref + .read( + pNewWalletOptions.state, + ) + .state! + .mnemonicPassphrase; + } else { + // this may not be epiccash specific? + if (coin is Epiccash) { + mnemonicPassphrase = ""; + } + } + + wordCount = ref + .read( + pNewWalletOptions.state, + ) + .state! + .mnemonicWordsCount; + } else { + mnemonicPassphrase = ""; + } + + if (wordCount < 12 || 24 < wordCount || wordCount % 3 != 0) { + throw Exception( + "Invalid word count", + ); + } + + final strength = (wordCount ~/ 3) * 32; + + mnemonic = bip39.generateMnemonic( + strength: strength, + ); + } + + final wallet = await Wallet.create( + walletInfo: info, + mainDB: ref.read(mainDBProvider), + secureStorageInterface: ref.read(secureStoreProvider), + nodeService: ref.read( + nodeServiceChangeNotifierProvider, + ), + prefs: ref.read( + prefsChangeNotifierProvider, + ), + mnemonicPassphrase: mnemonicPassphrase, + mnemonic: mnemonic, + privateKey: privateKey, + ); + + if (wallet is LibMoneroWallet) { + await wallet.init(wordCount: wordCount); + } else { + await wallet.init(); + } + + // set checkbox back to unchecked to annoy users to agree again :P + ref + .read( + checkBoxStateProvider.state, + ) + .state = false; + + final fetchedMnemonic = + await (wallet as MnemonicInterface).getMnemonicAsWords(); + + return (wallet, fetchedMnemonic); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + rethrow; + } + } + @override void initState() { coin = widget.coin; @@ -454,222 +671,7 @@ class _NewWalletRecoveryPhraseWarningViewState onPressed: ref .read(checkBoxStateProvider.state) .state - ? () async { - try { - unawaited( - showDialog( - context: context, - barrierDismissible: false, - useSafeArea: true, - builder: (ctx) { - return const Center( - child: LoadingIndicator( - width: 50, - height: 50, - ), - ); - }, - ), - ); - String? otherDataJsonString; - if (widget.coin is Tezos) { - otherDataJsonString = jsonEncode({ - WalletInfoKeys - .tezosDerivationPath: - Tezos.standardDerivationPath - .value, - }); - // }//todo: probably not needed (broken anyways) - // else if (widget.coin is Epiccash) { - // final int secondsSinceEpoch = - // DateTime.now().millisecondsSinceEpoch ~/ 1000; - // const int epicCashFirstBlock = 1565370278; - // const double overestimateSecondsPerBlock = 61; - // int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; - // int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; - // / - // // debugPrint( - // // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); - // height = approximateHeight; - // if (height < 0) { - // height = 0; - // } - // - // otherDataJsonString = jsonEncode( - // { - // WalletInfoKeys.epiccashData: jsonEncode( - // ExtraEpiccashWalletInfo( - // receivingIndex: 0, - // changeIndex: 0, - // slatesToAddresses: {}, - // slatesToCommits: {}, - // lastScannedBlock: epicCashFirstBlock, - // restoreHeight: height, - // creationHeight: height, - // ).toMap(), - // ), - // }, - // ); - } else if (widget.coin is Firo) { - otherDataJsonString = jsonEncode( - { - WalletInfoKeys - .lelantusCoinIsarRescanRequired: - false, - }, - ); - } - - final info = WalletInfo.createNew( - coin: widget.coin, - name: widget.walletName, - otherDataJsonString: - otherDataJsonString, - ); - - var node = ref - .read( - nodeServiceChangeNotifierProvider, - ) - .getPrimaryNodeFor( - currency: coin, - ); - - if (node == null) { - node = coin.defaultNode; - await ref - .read( - nodeServiceChangeNotifierProvider, - ) - .setPrimaryNodeFor( - coin: coin, - node: node, - ); - } - - final txTracker = - TransactionNotificationTracker( - walletId: info.walletId, - ); - - int? wordCount; - String? mnemonicPassphrase; - String? mnemonic; - String? privateKey; - - wordCount = info - .coin.defaultSeedPhraseLength; - - // TODO: Refactor these to generate each coin in their respective classes - // This code should not be in a random view page file - if (coin is Monero || - coin is Wownero) { - // currently a special case due to the - // xmr/wow libraries handling their - // own mnemonic generation - } else if (wordCount > 0) { - if (ref - .read( - pNewWalletOptions.state, - ) - .state != - null) { - if (coin - .hasMnemonicPassphraseSupport) { - mnemonicPassphrase = ref - .read( - pNewWalletOptions.state, - ) - .state! - .mnemonicPassphrase; - } else { - // this may not be epiccash specific? - if (coin is Epiccash) { - mnemonicPassphrase = ""; - } - } - - wordCount = ref - .read( - pNewWalletOptions.state, - ) - .state! - .mnemonicWordsCount; - } else { - mnemonicPassphrase = ""; - } - - if (wordCount < 12 || - 24 < wordCount || - wordCount % 3 != 0) { - throw Exception( - "Invalid word count", - ); - } - - final strength = - (wordCount ~/ 3) * 32; - - mnemonic = bip39.generateMnemonic( - strength: strength, - ); - } - - final wallet = await Wallet.create( - walletInfo: info, - mainDB: ref.read(mainDBProvider), - secureStorageInterface: - ref.read(secureStoreProvider), - nodeService: ref.read( - nodeServiceChangeNotifierProvider, - ), - prefs: ref.read( - prefsChangeNotifierProvider, - ), - mnemonicPassphrase: - mnemonicPassphrase, - mnemonic: mnemonic, - privateKey: privateKey, - ); - - await wallet.init(); - - // pop progress dialog - if (context.mounted) { - Navigator.pop(context); - } - // set checkbox back to unchecked to annoy users to agree again :P - ref - .read( - checkBoxStateProvider.state, - ) - .state = false; - - if (context.mounted) { - final nav = Navigator.of(context); - unawaited( - nav.pushNamed( - NewWalletRecoveryPhraseView - .routeName, - arguments: Tuple2( - wallet, - await (wallet - as MnemonicInterface) - .getMnemonicAsWords(), - ), - ), - ); - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - // TODO: handle gracefully - // any network/socket exception here will break new wallet creation - rethrow; - } - } + ? _initNewWallet : null, style: ref .read(checkBoxStateProvider.state) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index d20faf66b..0ffa6bcf9 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -458,7 +458,7 @@ abstract class SWB { mnemonic: mnemonic, mnemonicPassphrase: mnemonicPassphrase, ); - + Wallet? wallet; try { String? serializedKeys; String? multisigConfig; @@ -491,7 +491,7 @@ abstract class SWB { }); } - final wallet = await Wallet.create( + wallet = await Wallet.create( walletInfo: info, mainDB: MainDB.instance, secureStorageInterface: secureStorageInterface, @@ -614,6 +614,8 @@ abstract class SWB { mnemonicPassphrase: mnemonicPassphrase, ); return false; + } finally { + await wallet?.exit(); } return true; } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 4948876bd..8e889ed7c 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -316,9 +316,10 @@ class _WalletSettingsViewState extends ConsumerState { if (wallet is MnemonicInterface) { if (wallet is ViewOnlyOptionInterface && - !(wallet - as ViewOnlyOptionInterface) + (wallet as ViewOnlyOptionInterface) .isViewOnly) { + // TODO: is something needed here? + } else { mnemonic = await wallet .getMnemonicAsWords(); } diff --git a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart index 313f1afb6..c1701d8b7 100644 --- a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart @@ -85,12 +85,26 @@ class BitcoinFrostWallet extends Wallet await mainDB.isar.frostWalletInfo.put(frostWalletInfo); }); - final address = await _generateAddress( - change: 0, - index: kFrostSecureStartingIndex, - serializedKeys: serializedKeys, - secure: true, - ); + Address? address; + int index = kFrostSecureStartingIndex; + while (address == null) { + try { + address = await _generateAddress( + change: 0, + index: index, + serializedKeys: serializedKeys, + secure: true, + ); + } on FrostdartException catch (e) { + if (e.errorCode == 72) { + // rust doesn't like the addressDerivationData + index++; + continue; + } else { + rethrow; + } + } + } await mainDB.putAddresses([address]); } catch (e, s) { diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 96d6b112e..7533925c8 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -701,7 +701,7 @@ class EpiccashWallet extends Bip39Wallet { isar: mainDB.isar, ); - unawaited(_startScans()); + unawaited(refresh(doScan: true)); } else { await updateNode(); final String password = generatePassword(); @@ -759,9 +759,8 @@ class EpiccashWallet extends Bip39Wallet { epicData.receivingIndex, ); } + unawaited(refresh(doScan: false)); }); - - unawaited(refresh()); } catch (e, s) { Logging.instance.log( "Exception rethrown from electrumx_mixin recover(): $e\n$s", @@ -773,7 +772,7 @@ class EpiccashWallet extends Bip39Wallet { } @override - Future refresh() async { + Future refresh({bool doScan = true}) async { // Awaiting this lock could be dangerous. // Since refresh is periodic (generally) if (refreshMutex.isLocked) { @@ -803,9 +802,11 @@ class EpiccashWallet extends Bip39Wallet { final int curAdd = await _getCurrentIndex(); await _generateAndStoreReceivingAddressForIndex(curAdd); - await _startScans(); + if (doScan) { + await _startScans(); - unawaited(_startSync()); + unawaited(_startSync()); + } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); await updateChainHeight(); @@ -1157,6 +1158,7 @@ class EpiccashWallet extends Bip39Wallet { @override Future exit() async { + epiccash.LibEpiccash.stopEpicboxListener(); timer?.cancel(); timer = null; await super.exit(); diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart index bcec63851..d890e06e1 100644 --- a/lib/wallets/wallet/impl/monero_wallet.dart +++ b/lib/wallets/wallet/impl/monero_wallet.dart @@ -75,13 +75,28 @@ class MoneroWallet extends LibMoneroWallet { Future getCreatedWallet({ required String path, required String password, - }) async => - await lib_monero.MoneroWallet.create( - path: path, - password: password, - seedType: lib_monero.MoneroSeedType - .sixteen, // TODO: check we want to actually use 16 here - ); + required int wordCount, + }) async { + final lib_monero.MoneroSeedType type; + switch (wordCount) { + case 16: + type = lib_monero.MoneroSeedType.sixteen; + break; + + case 25: + type = lib_monero.MoneroSeedType.twentyFive; + break; + + default: + throw Exception("Invalid mnemonic word count: $wordCount"); + } + + return await lib_monero.MoneroWallet.create( + path: path, + password: password, + seedType: type, + ); + } @override Future getRestoredWallet({ diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart index a33bd2da7..8df6de1d0 100644 --- a/lib/wallets/wallet/impl/wownero_wallet.dart +++ b/lib/wallets/wallet/impl/wownero_wallet.dart @@ -111,14 +111,33 @@ class WowneroWallet extends LibMoneroWallet { Future getCreatedWallet({ required String path, required String password, - }) async => - await lib_monero.WowneroWallet.create( - path: path, - password: password, - seedType: lib_monero.WowneroSeedType - .fourteen, // TODO: check we want to actually use 14 here - overrideDeprecated14WordSeedException: true, - ); + required int wordCount, + }) async { + final lib_monero.WowneroSeedType type; + switch (wordCount) { + case 14: + type = lib_monero.WowneroSeedType.fourteen; + break; + + case 16: + type = lib_monero.WowneroSeedType.sixteen; + break; + + case 25: + type = lib_monero.WowneroSeedType.twentyFive; + break; + + default: + throw Exception("Invalid mnemonic word count: $wordCount"); + } + + return await lib_monero.WowneroWallet.create( + path: path, + password: password, + seedType: type, + overrideDeprecated14WordSeedException: true, + ); + } @override Future getRestoredWallet({ diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index 597853a62..76009f729 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -142,6 +142,7 @@ abstract class LibMoneroWallet Future getCreatedWallet({ required String path, required String password, + required int wordCount, }); Future getRestoredWallet({ @@ -313,19 +314,26 @@ abstract class LibMoneroWallet } @override - Future init({bool? isRestore}) async { + Future init({bool? isRestore, int? wordCount}) async { final path = await pathForWallet( name: walletId, type: compatType, ); if (!(walletExists(path)) && isRestore != true) { + if (wordCount == null) { + throw Exception("Missing word count for new xmr/wow wallet!"); + } try { final password = generatePassword(); await secureStorageInterface.write( key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), value: password, ); - final wallet = await getCreatedWallet(path: path, password: password); + final wallet = await getCreatedWallet( + path: path, + password: password, + wordCount: wordCount, + ); final height = wallet.getRefreshFromBlockHeight(); diff --git a/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml b/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml index 78a92aa5c..204663635 100644 --- a/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml +++ b/scripts/app_config/templates/android/app/src/main/AndroidManifest.xml @@ -23,8 +23,7 @@ android:requestLegacyExternalStorage="true" android:icon="@mipmap/ic_launcher" android:allowBackup="false" - android:fullBackupContent="false" - android:extractNativeLibs="true"> + android:fullBackupContent="false">