From eebe1df050176ec706f6f683cba8de1bee5a5bfd Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 Jul 2024 15:17:05 -0600 Subject: [PATCH] xmr/wow keys --- lib/models/keys/cw_key_data.dart | 21 ++ lib/models/keys/key_data_interface.dart | 3 + lib/models/keys/xpriv_data.dart | 17 ++ .../wallet_backup_views/cn_wallet_keys.dart | 199 ++++++++++++++++++ .../wallet_backup_view.dart | 58 +++-- .../wallet_backup_views/wallet_xprivs.dart | 4 +- .../wallet_settings_view.dart | 13 +- .../firo_rescan_recovery_error_dialog.dart | 10 +- .../unlock_wallet_keys_desktop.dart | 23 +- .../wallet_keys_desktop_popup.dart | 31 ++- lib/route_generator.dart | 25 +-- lib/wallets/wallet/impl/monero_wallet.dart | 20 ++ lib/wallets/wallet/impl/wownero_wallet.dart | 20 ++ .../cw_based_interface.dart | 3 + .../extended_keys_interface.dart | 6 +- 15 files changed, 392 insertions(+), 61 deletions(-) create mode 100644 lib/models/keys/cw_key_data.dart create mode 100644 lib/models/keys/key_data_interface.dart create mode 100644 lib/models/keys/xpriv_data.dart create mode 100644 lib/pages/settings_views/wallet_settings_view/wallet_backup_views/cn_wallet_keys.dart 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/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..a673e5a7d 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 @@ -16,6 +16,9 @@ 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,7 +28,6 @@ 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'; @@ -36,6 +38,7 @@ 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 { @@ -45,7 +48,7 @@ class WalletBackupView extends ConsumerWidget { required this.mnemonic, this.frostWalletData, this.clipboardInterface = const ClipboardWrapper(), - this.xprivData, + this.keyData, }); static const String routeName = "/walletBackup"; @@ -59,7 +62,7 @@ class WalletBackupView extends ConsumerWidget { ({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,24 +84,30 @@ 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) { + XPrivData() => "xpriv(s)", + CWKeyData() => "keys", + Type() => throw UnimplementedError( + "Don't forget to add your KeyDataInterface here!", + ), + }, onTap: () { Navigator.pushNamed( context, - MobileXPrivsView.routeName, + MobileKeyDataView.routeName, arguments: ( walletId: walletId, - xprivData: xprivData!, + keyData: keyData!, ), ); }, ), ), - if (!frost && xprivData == null) + if (!frost && keyData == null) Padding( padding: const EdgeInsets.all(10), child: AspectRatio( @@ -433,19 +442,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 +468,13 @@ class MobileXPrivsView extends StatelessWidget { }, ), title: Text( - "Wallet xpriv(s)", + "Wallet ${switch (keyData.runtimeType) { + XPrivData() => "xpriv(s)", + CWKeyData() => "keys", + Type() => throw UnimplementedError( + "Don't forget to add your KeyDataInterface here!", + ), + }}", style: STextStyles.navBarTitle(context), ), ), @@ -475,10 +490,19 @@ class MobileXPrivsView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Expanded( - child: WalletXPrivs( - walletId: walletId, - xprivData: xprivData, - ), + child: switch (keyData.runtimeType) { + XPrivData() => WalletXPrivs( + walletId: walletId, + xprivData: keyData as XPrivData, + ), + CWKeyData() => CNWalletKeys( + walletId: walletId, + cwKeyData: keyData as CWKeyData, + ), + Type() => 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/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..75c851989 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( 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/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_mixin_interfaces/cw_based_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart index 069571ead..de1f7a699 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'; @@ -195,6 +196,8 @@ mixin CwBasedInterface on CryptonoteWallet Address addressFor({required int index, int account = 0}); + Future getKeys(); + // ============ Private ====================================================== Future _refreshTxDataHelper() async { if (_txRefreshLock) return; 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: [ (