diff --git a/asset_sources/default_themes/campfire/light.zip b/asset_sources/default_themes/campfire/light.zip index 5dd67bcb1..898d97dc3 100644 Binary files a/asset_sources/default_themes/campfire/light.zip and b/asset_sources/default_themes/campfire/light.zip differ diff --git a/lib/models/keys/cw_key_data.dart b/lib/models/keys/cw_key_data.dart new file mode 100644 index 000000000..c20c7938c --- /dev/null +++ b/lib/models/keys/cw_key_data.dart @@ -0,0 +1,21 @@ +import 'key_data_interface.dart'; + +class CWKeyData with KeyDataInterface { + CWKeyData({ + required this.walletId, + required String? privateSpendKey, + required String? privateViewKey, + required String? publicSpendKey, + required String? publicViewKey, + }) : keys = List.unmodifiable([ + (label: "Public View Key", key: publicViewKey), + (label: "Private View Key", key: privateViewKey), + (label: "Public Spend Key", key: publicSpendKey), + (label: "Private Spend Key", key: privateSpendKey), + ]); + + @override + final String walletId; + + final List<({String label, String key})> keys; +} diff --git a/lib/models/keys/key_data_interface.dart b/lib/models/keys/key_data_interface.dart new file mode 100644 index 000000000..1b4572f5c --- /dev/null +++ b/lib/models/keys/key_data_interface.dart @@ -0,0 +1,3 @@ +mixin KeyDataInterface { + String get walletId; +} diff --git a/lib/models/keys/xpriv_data.dart b/lib/models/keys/xpriv_data.dart new file mode 100644 index 000000000..cf0959b5d --- /dev/null +++ b/lib/models/keys/xpriv_data.dart @@ -0,0 +1,17 @@ +import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; +import 'key_data_interface.dart'; + +class XPrivData with KeyDataInterface { + XPrivData({ + required this.walletId, + required this.fingerprint, + required List xprivs, + }) : xprivs = List.unmodifiable(xprivs); + + @override + final String walletId; + + final String fingerprint; + + final List xprivs; +} diff --git a/lib/pages/cashfusion/fusion_progress_view.dart b/lib/pages/cashfusion/fusion_progress_view.dart index fd2921c7f..e4d787903 100644 --- a/lib/pages/cashfusion/fusion_progress_view.dart +++ b/lib/pages/cashfusion/fusion_progress_view.dart @@ -12,6 +12,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:wakelock/wakelock.dart'; import '../../pages_desktop_specific/cashfusion/sub_widgets/fusion_progress.dart'; import '../../providers/cash_fusion/fusion_progress_ui_state_provider.dart'; @@ -84,6 +85,8 @@ class _FusionProgressViewState extends ConsumerState { message: "Stopping fusion", ); + await Wakelock.disable(); + return true; } else { return false; @@ -96,6 +99,12 @@ class _FusionProgressViewState extends ConsumerState { super.initState(); } + @override + void dispose() { + Wakelock.disable(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool _succeeded = @@ -108,6 +117,8 @@ class _FusionProgressViewState extends ConsumerState { .watch(fusionProgressUIStateProvider(widget.walletId)) .fusionRoundsCompleted; + Wakelock.enable(); + return WillPopScope( onWillPop: () async { return await _requestAndProcessCancel(); diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart index de0ade7df..c96f15233 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart @@ -175,7 +175,7 @@ class _FrostSendStep3State extends ConsumerState { PrimaryButton( label: "Generate transaction", enabled: _userVerifyContinue && - !fieldIsEmptyFlags.reduce((v, e) => v |= e), + !fieldIsEmptyFlags.fold(false, (v, e) => v |= e), onPressed: () async { // collect Share strings final sharesCollected = controllers.map((e) => e.text).toList(); diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart new file mode 100644 index 000000000..14cae2ee0 --- /dev/null +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart @@ -0,0 +1,199 @@ +import 'dart:async'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../models/keys/cw_key_data.dart'; +import '../../../../notifications/show_flush_bar.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/clipboard_interface.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/detail_item.dart'; +import '../../../../widgets/qr.dart'; +import '../../../../widgets/rounded_white_container.dart'; + +class CNWalletKeys extends StatefulWidget { + const CNWalletKeys({ + super.key, + required this.cwKeyData, + required this.walletId, + this.clipboardInterface = const ClipboardWrapper(), + }); + + final CWKeyData cwKeyData; + final String walletId; + final ClipboardInterface clipboardInterface; + + @override + State createState() => _CNWalletKeysState(); +} + +class _CNWalletKeysState extends State { + late String _currentDropDownValue; + + String _current(String key) => + widget.cwKeyData.keys.firstWhere((e) => e.label == key).key; + + Future _copy() async { + await widget.clipboardInterface.setData( + ClipboardData(text: _current(_currentDropDownValue)), + ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + } + } + + @override + void initState() { + _currentDropDownValue = widget.cwKeyData.keys.first.label; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: Util.isDesktop + ? const EdgeInsets.symmetric(horizontal: 20) + : EdgeInsets.zero, + child: Column( + mainAxisSize: Util.isDesktop ? MainAxisSize.min : MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: Util.isDesktop ? 12 : 16, + ), + DetailItemBase( + horizontal: true, + borderColor: Util.isDesktop + ? Theme.of(context).extension()!.textFieldDefaultBG + : null, + title: Text( + "Selected key", + style: STextStyles.itemSubtitle(context), + ), + detail: SizedBox( + width: Util.isDesktop ? 200 : 170, + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _currentDropDownValue, + items: [ + ...widget.cwKeyData.keys.map( + (e) => DropdownMenuItem( + value: e.label, + child: Text( + e.label, + style: STextStyles.w500_14(context), + ), + ), + ), + ], + onChanged: (value) { + if (value is String) { + setState(() { + _currentDropDownValue = value; + }); + } + }, + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + ), + ), + ), + ), + ), + SizedBox( + height: Util.isDesktop ? 12 : 16, + ), + QR( + data: _current(_currentDropDownValue), + size: + Util.isDesktop ? 256 : MediaQuery.of(context).size.width / 1.5, + ), + SizedBox( + height: Util.isDesktop ? 12 : 16, + ), + RoundedWhiteContainer( + borderColor: Util.isDesktop + ? Theme.of(context).extension()!.textFieldDefaultBG + : null, + child: SelectableText( + _current(_currentDropDownValue), + style: STextStyles.w500_14(context), + ), + ), + SizedBox( + height: Util.isDesktop ? 12 : 16, + ), + if (!Util.isDesktop) const Spacer(), + Row( + children: [ + if (Util.isDesktop) const Spacer(), + if (Util.isDesktop) + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Copy", + onPressed: _copy, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart index 9c1488fcc..0c91eda12 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart @@ -13,9 +13,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import '../../../../app_config.dart'; +import '../../../../models/keys/cw_key_data.dart'; +import '../../../../models/keys/key_data_interface.dart'; +import '../../../../models/keys/xpriv_data.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; @@ -25,17 +27,19 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../../widgets/custom_buttons/simple_copy_button.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/detail_item.dart'; import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; import '../../../wallet_view/transaction_views/transaction_details_view.dart'; +import 'cn_wallet_keys.dart'; import 'wallet_xprivs.dart'; class WalletBackupView extends ConsumerWidget { @@ -44,8 +48,7 @@ class WalletBackupView extends ConsumerWidget { required this.walletId, required this.mnemonic, this.frostWalletData, - this.clipboardInterface = const ClipboardWrapper(), - this.xprivData, + this.keyData, }); static const String routeName = "/walletBackup"; @@ -58,8 +61,7 @@ class WalletBackupView extends ConsumerWidget { String keys, ({String config, String keys})? prevGen, })? frostWalletData; - final ClipboardInterface clipboardInterface; - final ({List xprivs, String fingerprint})? xprivData; + final KeyDataInterface? keyData; @override Widget build(BuildContext context, WidgetRef ref) { @@ -81,57 +83,29 @@ class WalletBackupView extends ConsumerWidget { style: STextStyles.navBarTitle(context), ), actions: [ - if (xprivData != null) + if (keyData != null) Padding( padding: const EdgeInsets.all(10), child: CustomTextButton( - text: "xpriv(s)", + text: switch (keyData.runtimeType) { + const (XPrivData) => "xpriv(s)", + const (CWKeyData) => "keys", + _ => throw UnimplementedError( + "Don't forget to add your KeyDataInterface here! ${keyData.runtimeType}", + ), + }, onTap: () { Navigator.pushNamed( context, - MobileXPrivsView.routeName, + MobileKeyDataView.routeName, arguments: ( walletId: walletId, - xprivData: xprivData!, + keyData: keyData!, ), ); }, ), ), - if (!frost && xprivData == null) - Padding( - padding: const EdgeInsets.all(10), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - color: - Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.copy, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: () async { - await clipboardInterface - .setData(ClipboardData(text: mnemonic.join(" "))); - if (context.mounted) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ), - ); - } - }, - ), - ), - ), ], ), body: Padding( @@ -152,19 +126,22 @@ class WalletBackupView extends ConsumerWidget { } class _Mnemonic extends ConsumerWidget { - const _Mnemonic({super.key, required this.walletId, required this.mnemonic}); + const _Mnemonic({ + super.key, + required this.walletId, + required this.mnemonic, + this.clipboardInterface = const ClipboardWrapper(), + }); final String walletId; final List mnemonic; + final ClipboardInterface clipboardInterface; @override Widget build(BuildContext context, WidgetRef ref) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox( - height: 4, - ), Text( ref.watch(pWalletName(walletId)), textAlign: TextAlign.center, @@ -212,10 +189,28 @@ class _Mnemonic extends ConsumerWidget { const SizedBox( height: 12, ), - TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), + SecondaryButton( + label: "Copy", + onPressed: () async { + await clipboardInterface + .setData(ClipboardData(text: mnemonic.join(" "))); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + } + }, + ), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Show QR Code", onPressed: () { final String data = AddressUtils.encodeQRSeedData(mnemonic); @@ -284,10 +279,6 @@ class _Mnemonic extends ConsumerWidget { }, ); }, - child: Text( - "Show QR Code", - style: STextStyles.button(context), - ), ), ], ); @@ -301,8 +292,6 @@ class _FrostKeys extends StatelessWidget { this.frostWalletData, }); - static const String routeName = "/walletBackup"; - final String walletId; final ({ String myName, @@ -433,19 +422,19 @@ class _FrostKeys extends StatelessWidget { } } -class MobileXPrivsView extends StatelessWidget { - const MobileXPrivsView({ +class MobileKeyDataView extends StatelessWidget { + const MobileKeyDataView({ super.key, required this.walletId, this.clipboardInterface = const ClipboardWrapper(), - required this.xprivData, + required this.keyData, }); static const String routeName = "/mobileXPrivView"; final String walletId; final ClipboardInterface clipboardInterface; - final ({List xprivs, String fingerprint}) xprivData; + final KeyDataInterface keyData; @override Widget build(BuildContext context) { @@ -459,7 +448,13 @@ class MobileXPrivsView extends StatelessWidget { }, ), title: Text( - "Wallet xpriv(s)", + "Wallet ${switch (keyData.runtimeType) { + const (XPrivData) => "xpriv(s)", + const (CWKeyData) => "keys", + _ => throw UnimplementedError( + "Don't forget to add your KeyDataInterface here!", + ), + }}", style: STextStyles.navBarTitle(context), ), ), @@ -475,10 +470,19 @@ class MobileXPrivsView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: WalletXPrivs( - walletId: walletId, - xprivData: xprivData, - ), + child: switch (keyData.runtimeType) { + const (XPrivData) => WalletXPrivs( + walletId: walletId, + xprivData: keyData as XPrivData, + ), + const (CWKeyData) => CNWalletKeys( + walletId: walletId, + cwKeyData: keyData as CWKeyData, + ), + _ => throw UnimplementedError( + "Don't forget to add your KeyDataInterface here!", + ), + }, ), const SizedBox( height: 16, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart index 133dd95a1..594db33a7 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart @@ -16,6 +16,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import '../../../../models/keys/xpriv_data.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; @@ -23,7 +24,6 @@ import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/detail_item.dart'; import '../../../../widgets/qr.dart'; @@ -37,7 +37,7 @@ class WalletXPrivs extends ConsumerStatefulWidget { this.clipboardInterface = const ClipboardWrapper(), }); - final ({List xprivs, String fingerprint}) xprivData; + final XPrivData xprivData; final String walletId; final ClipboardInterface clipboardInterface; 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 a9fdd52f0..6985bb00c 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 @@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/epicbox_config_model.dart'; +import '../../../models/keys/key_data_interface.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../providers/ui/transaction_filter_provider.dart'; @@ -35,6 +36,7 @@ import '../../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../widgets/background.dart'; @@ -261,10 +263,6 @@ class _WalletSettingsViewState extends ConsumerState { // TODO: [prio=med] take wallets that don't have a mnemonic into account - ({ - List xprivs, - String fingerprint - })? xprivData; List? mnemonic; ({ String myName, @@ -306,8 +304,11 @@ class _WalletSettingsViewState extends ConsumerState { await wallet.getMnemonicAsWords(); } + KeyDataInterface? keyData; if (wallet is ExtendedKeysInterface) { - xprivData = await wallet.getXPrivs(); + keyData = await wallet.getXPrivs(); + } else if (wallet is CwBasedInterface) { + keyData = await wallet.getKeys(); } if (context.mounted) { @@ -323,7 +324,7 @@ class _WalletSettingsViewState extends ConsumerState { mnemonic: mnemonic ?? [], frostWalletData: frostWalletData, - xprivData: xprivData, + keyData: keyData, ), showBackButton: true, routeOnSuccess: diff --git a/lib/pages/special/firo_rescan_recovery_error_dialog.dart b/lib/pages/special/firo_rescan_recovery_error_dialog.dart index 6409646c6..92d226d4b 100644 --- a/lib/pages/special/firo_rescan_recovery_error_dialog.dart +++ b/lib/pages/special/firo_rescan_recovery_error_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../../models/keys/key_data_interface.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart'; import '../../providers/global/wallets_provider.dart'; @@ -11,6 +12,7 @@ import '../../utilities/assets.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../widgets/background.dart'; @@ -265,9 +267,11 @@ class _FiroRescanRecoveryErrorViewState if (wallet is MnemonicInterface) { final mnemonic = await wallet.getMnemonicAsWords(); - ({List xprivs, String fingerprint})? xprivData; + KeyDataInterface? keyData; if (wallet is ExtendedKeysInterface) { - xprivData = await wallet.getXPrivs(); + keyData = await wallet.getXPrivs(); + } else if (wallet is CwBasedInterface) { + keyData = await wallet.getKeys(); } if (context.mounted) { @@ -280,7 +284,7 @@ class _FiroRescanRecoveryErrorViewState routeOnSuccessArguments: ( walletId: widget.walletId, mnemonic: mnemonic, - xprivData: xprivData, + keyData: keyData, ), showBackButton: true, routeOnSuccess: WalletBackupView.routeName, diff --git a/lib/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart b/lib/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart index 51502bf81..09bce3a3b 100644 --- a/lib/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart +++ b/lib/pages_desktop_specific/cashfusion/sub_widgets/fusion_dialog.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:wakelock/wakelock.dart'; import '../../../providers/cash_fusion/fusion_progress_ui_state_provider.dart'; import '../../../providers/global/prefs_provider.dart'; @@ -137,6 +139,8 @@ class _FusionDialogViewState extends ConsumerState { message: "Stopping fusion", ); + await Wakelock.disable(); + return true; } else { return false; @@ -150,6 +154,12 @@ class _FusionDialogViewState extends ConsumerState { super.initState(); } + @override + dispose() { + Wakelock.disable(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool _succeeded = @@ -162,6 +172,10 @@ class _FusionDialogViewState extends ConsumerState { .watch(fusionProgressUIStateProvider(widget.walletId)) .fusionRoundsCompleted; + if (!Platform.isLinux) { + Wakelock.enable(); + } + return DesktopDialog( maxHeight: 600, child: SingleChildScrollView( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 50c76ab7e..09de81b26 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -338,7 +338,7 @@ class _DesktopReceiveState extends ConsumerState { onTap: () { clipboard.setData( ClipboardData( - text: ref.watch(pWalletReceivingAddress(walletId)), + text: address, ), ); showFloatingFlushBar( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart index 76fa097a4..a474fb7bb 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -19,6 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart'; import '../../../../../themes/stack_colors.dart'; import '../../../../../utilities/assets.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/isar/providers/wallet_info_provider.dart'; @@ -32,7 +33,10 @@ import '../../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.da import '../../../../../widgets/custom_buttons/draggable_switch_button.dart'; import '../../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../../../widgets/desktop/primary_button.dart'; +import '../../../../../widgets/desktop/secondary_button.dart'; import '../../../../../widgets/rounded_container.dart'; +import '../../../../../widgets/stack_dialog.dart'; class MoreFeaturesDialog extends ConsumerStatefulWidget { const MoreFeaturesDialog({ @@ -102,6 +106,147 @@ class _MoreFeaturesDialogState extends ConsumerState { } } + bool _switchReuseAddressToggledLock = false; // Mutex. + Future _switchReuseAddressToggled(bool newValue) async { + if (newValue) { + await showDialog( + context: context, + builder: (context) { + final isDesktop = Util.isDesktop; + return isDesktop + ? DesktopDialog( + maxWidth: 576, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Warning!", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + top: 8, + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox( + height: 43, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + onPressed: () { + Navigator.of(context).pop(false); + }, + label: "Cancel", + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + onPressed: () { + Navigator.of(context).pop(true); + }, + label: "Continue", + ), + ), + ], + ), + ], + ), + ), + ], + ), + ) + : StackDialog( + title: "Warning!", + message: + "Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text( + "Continue", + style: STextStyles.button(context), + ), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ); + }, + ).then((confirmed) async { + if (_switchReuseAddressToggledLock) { + return; + } + _switchReuseAddressToggledLock = true; // Lock mutex. + + try { + if (confirmed == true) { + await ref.read(pWalletInfo(widget.walletId)).updateOtherData( + newEntries: { + WalletInfoKeys.reuseAddress: true, + }, + isar: ref.read(mainDBProvider).isar, + ); + } else { + await ref.read(pWalletInfo(widget.walletId)).updateOtherData( + newEntries: { + WalletInfoKeys.reuseAddress: false, + }, + isar: ref.read(mainDBProvider).isar, + ); + } + } finally { + // ensure _switchReuseAddressToggledLock is set to false no matter what. + _switchReuseAddressToggledLock = false; + } + }); + } else { + await ref.read(pWalletInfo(widget.walletId)).updateOtherData( + newEntries: { + WalletInfoKeys.reuseAddress: false, + }, + isar: ref.read(mainDBProvider).isar, + ); + } + } + @override Widget build(BuildContext context) { final wallet = ref.watch( @@ -253,6 +398,38 @@ class _MoreFeaturesDialogState extends ConsumerState { ], ), ), + // reuseAddress preference. + _MoreFeaturesItemBase( + child: Row( + children: [ + const SizedBox(width: 3), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + pWalletInfo(widget.walletId) + .select((value) => value.otherData), + )[WalletInfoKeys.reuseAddress] as bool? ?? + false, + onValueChanged: _switchReuseAddressToggled, + ), + ), + const SizedBox( + width: 16, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Reuse receiving address by default", + style: STextStyles.w600_20(context), + ), + ], + ), + ], + ), + ), const SizedBox( height: 28, ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index 6500b669e..e332c4543 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import '../../../../models/keys/key_data_interface.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../providers/desktop/storage_crypto_handler_provider.dart'; import '../../../../providers/providers.dart'; @@ -22,6 +23,7 @@ import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; @@ -85,7 +87,6 @@ class _UnlockWalletKeysDesktopState final wallet = ref.read(pWallets).getWallet(widget.walletId); ({String keys, String config})? frostData; List? words; - ({List xprivs, String fingerprint})? xprivData; // TODO: [prio=low] handle wallets that don't have a mnemonic // All wallets currently are mnemonic based @@ -102,8 +103,11 @@ class _UnlockWalletKeysDesktopState words = await wallet.getMnemonicAsWords(); } + KeyDataInterface? keyData; if (wallet is ExtendedKeysInterface) { - xprivData = await wallet.getXPrivs(); + keyData = await wallet.getXPrivs(); + } else if (wallet is CwBasedInterface) { + keyData = await wallet.getKeys(); } if (mounted) { @@ -113,7 +117,7 @@ class _UnlockWalletKeysDesktopState mnemonic: words ?? [], walletId: widget.walletId, frostData: frostData, - xprivData: xprivData, + keyData: keyData, ), ); } @@ -327,10 +331,6 @@ class _UnlockWalletKeysDesktopState ({String keys, String config})? frostData; List? words; - ({ - List xprivs, - String fingerprint - })? xprivData; final wallet = ref.read(pWallets).getWallet(widget.walletId); @@ -350,11 +350,14 @@ class _UnlockWalletKeysDesktopState words = await wallet.getMnemonicAsWords(); } + KeyDataInterface? keyData; if (wallet is ExtendedKeysInterface) { - xprivData = await wallet.getXPrivs(); + keyData = await wallet.getXPrivs(); + } else if (wallet is CwBasedInterface) { + keyData = await wallet.getKeys(); } - if (mounted) { + if (context.mounted) { await Navigator.of(context) .pushReplacementNamed( WalletKeysDesktopPopup.routeName, @@ -362,7 +365,7 @@ class _UnlockWalletKeysDesktopState mnemonic: words ?? [], walletId: widget.walletId, frostData: frostData, - xprivData: xprivData, + keyData: keyData, ), ); } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart index a3ec985da..f1bc2d9f6 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart @@ -14,8 +14,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../models/keys/cw_key_data.dart'; +import '../../../../models/keys/key_data_interface.dart'; +import '../../../../models/keys/xpriv_data.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; +import '../../../../pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart'; import '../../../../pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_xprivs.dart'; import '../../../../pages/wallet_view/transaction_views/transaction_details_view.dart'; import '../../../../themes/stack_colors.dart'; @@ -23,7 +27,6 @@ import '../../../../utilities/address_utils.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/text_styles.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../widgets/custom_tab_view.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; @@ -39,14 +42,14 @@ class WalletKeysDesktopPopup extends ConsumerWidget { required this.walletId, this.frostData, this.clipboardInterface = const ClipboardWrapper(), - this.xprivData, + this.keyData, }); final List words; final String walletId; final ({String keys, String config})? frostData; final ClipboardInterface clipboardInterface; - final ({List xprivs, String fingerprint})? xprivData; + final KeyDataInterface? keyData; static const String routeName = "walletKeysDesktopPopup"; @@ -176,9 +179,13 @@ class WalletKeysDesktopPopup extends ConsumerWidget { ), ], ) - : xprivData != null + : keyData != null ? CustomTabView( - titles: const ["Mnemonic", "XPriv(s)"], + titles: [ + "Mnemonic", + if (keyData is XPrivData) "XPriv(s)", + if (keyData is CWKeyData) "Keys", + ], children: [ Padding( padding: const EdgeInsets.only(top: 16), @@ -186,10 +193,16 @@ class WalletKeysDesktopPopup extends ConsumerWidget { words: words, ), ), - WalletXPrivs( - xprivData: xprivData!, - walletId: walletId, - ), + if (keyData is XPrivData) + WalletXPrivs( + xprivData: keyData as XPrivData, + walletId: walletId, + ), + if (keyData is CWKeyData) + CNWalletKeys( + cwKeyData: keyData as CWKeyData, + walletId: walletId, + ), ], ) : _Mnemonic( @@ -264,22 +277,6 @@ class _Mnemonic extends StatelessWidget { children: [ Expanded( child: SecondaryButton( - label: "Show QR code", - onPressed: () { - // TODO: address utils - final String value = AddressUtils.encodeQRSeedData(words); - Navigator.of(context).pushNamed( - QRCodeDesktopPopupContent.routeName, - arguments: value, - ); - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( label: "Copy", onPressed: () async { await clipboardInterface.setData( @@ -298,6 +295,22 @@ class _Mnemonic extends StatelessWidget { }, ), ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Show QR code", + onPressed: () { + // TODO: address utils + final String value = AddressUtils.encodeQRSeedData(words); + Navigator.of(context).pushNamed( + QRCodeDesktopPopupContent.routeName, + arguments: value, + ); + }, + ), + ), ], ), ), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index fe7b36872..b449525b6 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -22,6 +22,7 @@ import 'models/isar/models/blockchain_data/v2/transaction_v2.dart'; import 'models/isar/models/contact_entry.dart'; import 'models/isar/models/isar_models.dart'; import 'models/isar/ordinal.dart'; +import 'models/keys/key_data_interface.dart'; import 'models/paynym/paynym_account_lite.dart'; import 'models/send_view_auto_fill_data.dart'; import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; @@ -1280,14 +1281,14 @@ class RouteGenerator { } else if (args is ({ String walletId, List mnemonic, - ({List xprivs, String fingerprint})? xprivData, + KeyDataInterface? keyData, })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => WalletBackupView( walletId: args.walletId, mnemonic: args.mnemonic, - xprivData: args.xprivData, + keyData: args.keyData, ), settings: RouteSettings( name: settings.name, @@ -1296,7 +1297,7 @@ class RouteGenerator { } else if (args is ({ String walletId, List mnemonic, - ({List xprivs, String fingerprint})? xprivData, + KeyDataInterface? keyData, ({ String myName, String config, @@ -1310,7 +1311,7 @@ class RouteGenerator { walletId: args.walletId, mnemonic: args.mnemonic, frostWalletData: args.frostWalletData, - xprivData: args.xprivData, + keyData: args.keyData, ), settings: RouteSettings( name: settings.name, @@ -1319,16 +1320,16 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case MobileXPrivsView.routeName: + case MobileKeyDataView.routeName: if (args is ({ String walletId, - ({List xprivs, String fingerprint}) xprivData, + KeyDataInterface keyData, })) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => MobileXPrivsView( + builder: (_) => MobileKeyDataView( walletId: args.walletId, - xprivData: args.xprivData, + keyData: args.keyData, ), settings: RouteSettings( name: settings.name, @@ -2369,14 +2370,14 @@ class RouteGenerator { List mnemonic, String walletId, ({String keys, String config})? frostData, - ({List xprivs, String fingerprint})? xprivData, + KeyDataInterface? keyData, })) { return FadePageRoute( WalletKeysDesktopPopup( words: args.mnemonic, walletId: args.walletId, frostData: args.frostData, - xprivData: args.xprivData, + keyData: args.keyData, ), RouteSettings( name: settings.name, @@ -2385,13 +2386,13 @@ class RouteGenerator { } else if (args is ({ List mnemonic, String walletId, - ({List xprivs, String fingerprint})? xprivData, + KeyDataInterface? keyData, })) { return FadePageRoute( WalletKeysDesktopPopup( words: args.mnemonic, walletId: args.walletId, - xprivData: args.xprivData, + keyData: args.keyData, ), RouteSettings( name: settings.name, diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index 6f6e12a42..6a7a9b54c 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -511,4 +511,5 @@ abstract class WalletInfoKeys { static const String firoSparkCacheSetTimestampCache = "firoSparkCacheSetTimestampCacheKey"; static const String enableOptInRbf = "enableOptInRbfKey"; + static const String reuseAddress = "reuseAddressKey"; } diff --git a/lib/wallets/models/tx_data.dart b/lib/wallets/models/tx_data.dart index 774d85e06..99a28a138 100644 --- a/lib/wallets/models/tx_data.dart +++ b/lib/wallets/models/tx_data.dart @@ -133,21 +133,41 @@ class TxData { .reduce((total, amount) => total += amount) : null; - Amount? get amountWithoutChange => - recipients != null && recipients!.isNotEmpty - ? recipients! - .where((e) => !e.isChange) - .map((e) => e.amount) - .reduce((total, amount) => total += amount) - : null; + Amount? get amountWithoutChange { + if (recipients != null && recipients!.isNotEmpty) { + if (recipients!.where((e) => !e.isChange).isEmpty) { + return Amount( + rawValue: BigInt.zero, + fractionDigits: recipients!.first.amount.fractionDigits, + ); + } else { + return recipients! + .where((e) => !e.isChange) + .map((e) => e.amount) + .reduce((total, amount) => total += amount); + } + } else { + return null; + } + } - Amount? get amountSparkWithoutChange => - sparkRecipients != null && sparkRecipients!.isNotEmpty - ? sparkRecipients! - .where((e) => !e.isChange) - .map((e) => e.amount) - .reduce((total, amount) => total += amount) - : null; + Amount? get amountSparkWithoutChange { + if (sparkRecipients != null && sparkRecipients!.isNotEmpty) { + if (sparkRecipients!.where((e) => !e.isChange).isEmpty) { + return Amount( + rawValue: BigInt.zero, + fractionDigits: sparkRecipients!.first.amount.fractionDigits, + ); + } else { + return sparkRecipients! + .where((e) => !e.isChange) + .map((e) => e.amount) + .reduce((total, amount) => total += amount); + } + } else { + return null; + } + } int? get estimatedSatsPerVByte => fee != null && vSize != null ? (fee!.raw ~/ BigInt.from(vSize!)).toInt() diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index 33ae3fb24..f9fcb11b5 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -787,7 +787,9 @@ class FiroWallet extends Bip39HDWallet for (final tuple in receiveResults) { if (tuple.addresses.isEmpty) { - await checkReceivingAddressForTransactions(); + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } } else { highestReceivingIndexWithHistory = max( tuple.index, diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart index 9bcb4c3d7..9c4848b8c 100644 --- a/lib/wallets/wallet/impl/monero_wallet.dart +++ b/lib/wallets/wallet/impl/monero_wallet.dart @@ -25,6 +25,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/transaction.dart'; +import '../../../models/keys/cw_key_data.dart'; import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; @@ -235,6 +236,25 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface { return; } + @override + Future getKeys() async { + final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?); + + if (base == null || + base.walletInfo.name != walletId || + CwBasedInterface.exitMutex.isLocked) { + return null; + } + + return CWKeyData( + walletId: walletId, + publicViewKey: base.keys.publicViewKey, + privateViewKey: base.keys.privateViewKey, + publicSpendKey: base.keys.publicSpendKey, + privateSpendKey: base.keys.privateSpendKey, + ); + } + @override Future updateTransactions() async { final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?); diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart index 69f42a57d..a3ef7d04b 100644 --- a/lib/wallets/wallet/impl/wownero_wallet.dart +++ b/lib/wallets/wallet/impl/wownero_wallet.dart @@ -28,6 +28,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/transaction.dart'; +import '../../../models/keys/cw_key_data.dart'; import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; import '../../../services/event_bus/events/global/tor_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; @@ -214,6 +215,25 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface { return; } + @override + Future getKeys() async { + final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?); + + if (base == null || + base.walletInfo.name != walletId || + CwBasedInterface.exitMutex.isLocked) { + return null; + } + + return CWKeyData( + walletId: walletId, + publicViewKey: base.keys.publicViewKey, + privateViewKey: base.keys.privateViewKey, + publicSpendKey: base.keys.publicSpendKey, + privateSpendKey: base.keys.privateSpendKey, + ); + } + @override Future updateTransactions() async { final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?); diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 1bca3c858..46540eef7 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -522,8 +522,10 @@ abstract class Wallet { // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (this is MultiAddressInterface) { - await (this as MultiAddressInterface) - .checkReceivingAddressForTransactions(); + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await (this as MultiAddressInterface) + .checkReceivingAddressForTransactions(); + } } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart index 069571ead..661b10bd3 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart @@ -14,6 +14,7 @@ import 'package:mutex/mutex.dart'; import '../../../models/balance.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/keys/cw_key_data.dart'; import '../../../models/paymint/fee_object_model.dart'; import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; @@ -24,6 +25,7 @@ import '../../../utilities/amount/amount.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/stack_file_system.dart'; import '../../crypto_currency/intermediate/cryptonote_currency.dart'; +import '../../isar/models/wallet_info.dart'; import '../intermediate/cryptonote_wallet.dart'; import 'multi_address_interface.dart'; @@ -195,6 +197,8 @@ mixin CwBasedInterface on CryptonoteWallet Address addressFor({required int index, int account = 0}); + Future getKeys(); + // ============ Private ====================================================== Future _refreshTxDataHelper() async { if (_txRefreshLock) return; @@ -283,7 +287,9 @@ mixin CwBasedInterface on CryptonoteWallet await updateTransactions(); await updateBalance(); - await checkReceivingAddressForTransactions(); + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } if (cwWalletBase?.syncStatus is SyncedSyncStatus) { refreshMutex.release(); @@ -339,6 +345,17 @@ mixin CwBasedInterface on CryptonoteWallet @override Future checkReceivingAddressForTransactions() async { + if (info.otherData[WalletInfoKeys.reuseAddress] == true) { + try { + throw Exception(); + } catch (_, s) { + Logging.instance.log( + "checkReceivingAddressForTransactions called but reuse address flag set: $s", + level: LogLevel.Error, + ); + } + } + try { int highestIndex = -1; final entries = cwWalletBase?.transactionHistory?.transactions?.entries; @@ -377,8 +394,10 @@ mixin CwBasedInterface on CryptonoteWallet // we need to update the address await mainDB.updateAddress(existing, newReceivingAddress); } - // keep checking until address with no tx history is set as current - await checkReceivingAddressForTransactions(); + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + // keep checking until address with no tx history is set as current + await checkReceivingAddressForTransactions(); + } } } on SocketException catch (se, s) { Logging.instance.log( diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index 035ae649f..c31f7cd4a 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -23,6 +23,7 @@ import '../../../utilities/logger.dart'; import '../../../utilities/paynym_is_api.dart'; import '../../crypto_currency/coins/firo.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; +import '../../isar/models/wallet_info.dart'; import '../../models/tx_data.dart'; import '../impl/bitcoin_wallet.dart'; import '../impl/firo_wallet.dart'; @@ -1315,6 +1316,17 @@ mixin ElectrumXInterface @override Future checkReceivingAddressForTransactions() async { + if (info.otherData[WalletInfoKeys.reuseAddress] == true) { + try { + throw Exception(); + } catch (_, s) { + Logging.instance.log( + "checkReceivingAddressForTransactions called but reuse address flag set: $s", + level: LogLevel.Error, + ); + } + } + try { final currentReceiving = await getCurrentReceivingAddress(); @@ -1336,9 +1348,12 @@ mixin ElectrumXInterface if (needsGenerate) { await generateNewReceivingAddress(); - // TODO: get rid of this? Could cause problems (long loading/infinite loop or something) - // keep checking until address with no tx history is set as current - await checkReceivingAddressForTransactions(); + // TODO: [prio=low] Make sure we scan all addresses but only show one. + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + // TODO: get rid of this? Could cause problems (long loading/infinite loop or something) + // keep checking until address with no tx history is set as current + await checkReceivingAddressForTransactions(); + } } } catch (e, s) { Logging.instance.log( diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart index 2e2836964..1606b1295 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart @@ -1,3 +1,4 @@ +import '../../../models/keys/xpriv_data.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import 'electrumx_interface.dart'; @@ -42,7 +43,7 @@ mixin ExtendedKeysInterface ); } - Future<({List xprivs, String fingerprint})> getXPrivs() async { + Future getXPrivs() async { final paths = cryptoCurrency.supportedDerivationPathTypes.map( (e) => ( path: e, @@ -71,7 +72,8 @@ mixin ExtendedKeysInterface ); }); - return ( + return XPrivData( + walletId: walletId, fingerprint: fingerprint, xprivs: [ ( diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e7eb439f7..a784dbabd 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -171,7 +171,12 @@ dependencies: convert: ^3.1.1 flutter_hooks: ^0.20.3 meta: ^1.9.1 - coinlib_flutter: ^2.0.0 +# coinlib_flutter: ^2.1.0-rc.1 + coinlib_flutter: + git: + url: https://github.com/peercoin/coinlib.git + ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f + path: coinlib_flutter electrum_adapter: git: url: https://github.com/cypherstack/electrum_adapter.git @@ -180,7 +185,7 @@ dependencies: solana: git: # TODO [prio=low]: Revert to official package once Tor support is merged upstream. url: https://github.com/cypherstack/espresso-cash-public.git - ref: a83e375678eb22fe544dc125d29bbec0fb833882 + ref: 706be5f166d31736c443cca4cd311b7fcfc2a9bf path: packages/solana calendar_date_picker2: ^1.0.2 sqlite3: ^2.4.3 @@ -210,6 +215,20 @@ flutter_native_splash: android_disable_fullscreen: true dependency_overrides: + + # coin lib git for testing while waiting for publishing + coinlib: + git: + url: https://github.com/peercoin/coinlib.git + ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f + path: coinlib + + coinlib_flutter: + git: + url: https://github.com/peercoin/coinlib.git + ref: b88e68e2e10ffe54d802deeed6b9e83da7721a1f + path: coinlib_flutter + # adding here due to pure laziness tor_ffi_plugin: git: diff --git a/scripts/prebuild.ps1 b/scripts/prebuild.ps1 index 80a6991b7..04b68bc35 100644 --- a/scripts/prebuild.ps1 +++ b/scripts/prebuild.ps1 @@ -2,7 +2,7 @@ $KEYS = "..\lib\external_api_keys.dart" if (-not (Test-Path $KEYS)) { Write-Host "prebuild.ps1: creating template lib/external_api_keys.dart file" - "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" | Out-File $KEYS -Encoding UTF8 + "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" + "`nconst kNanswapApiKey = '';" + "`nconst kNanoSwapRpcApiKey = '';" | Out-File $KEYS -Encoding UTF8 } # Create template wallet test parameter files if they don't already exist diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index 77d65b253..6c50fbefd 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -4,7 +4,7 @@ KEYS=../lib/external_api_keys.dart if ! test -f "$KEYS"; then echo 'prebuild.sh: creating template lib/external_api_keys.dart file' - printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\n' > $KEYS + printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\nconst kNanswapApiKey = "";\nconst kNanoSwapRpcApiKey = "";\n' > $KEYS fi # Create template wallet test parameter files if they don't already exist