From cd068eefc725e8d9b0779b724c391efbac7591fe Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 4 Dec 2024 11:00:22 -0600 Subject: [PATCH] xmr/wow show tx spend key option --- .../tx_v2/transaction_v2_details_view.dart | 22 +++- .../intermediate/lib_monero_wallet.dart | 7 ++ lib/widgets/tx_key_widget.dart | 102 ++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 lib/widgets/tx_key_widget.dart diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart index a1d4bb3fd..7081f5929 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/models/spark_coin.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -57,6 +58,7 @@ import '../../../../widgets/icon_widgets/copy_icon.dart'; import '../../../../widgets/icon_widgets/pencil_icon.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; +import '../../../../widgets/tx_key_widget.dart'; import '../../sub_widgets/tx_icon.dart'; import '../../wallet_view.dart'; import '../dialogs/cancelling_transaction_progress_dialog.dart'; @@ -96,6 +98,7 @@ class _TransactionV2DetailsViewState late final int minConfirms; late final EthContract? ethContract; late final bool supportsRbf; + late final bool hasTxKeyProbably; bool get isTokenTx => ethContract != null; @@ -180,10 +183,16 @@ class _TransactionV2DetailsViewState _transaction = widget.transaction; walletId = widget.walletId; + final wallet = ref.read(pWallets).getWallet(walletId); + + hasTxKeyProbably = wallet is LibMoneroWallet && + (_transaction.type == TransactionType.outgoing || + _transaction.type == TransactionType.sentToSelf); + if (_transaction.type case TransactionType.sentToSelf || TransactionType.outgoing) { supportsRbf = _transaction.subType == TransactionSubType.none && - ref.read(pWallets).getWallet(walletId) is RbfInterface; + wallet is RbfInterface; } else { supportsRbf = false; } @@ -1704,6 +1713,17 @@ class _TransactionV2DetailsViewState ], ), ), + if (hasTxKeyProbably) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (hasTxKeyProbably) + TxKeyWidget( + walletId: walletId, + txid: _transaction.txid, + ), isDesktop ? const _Divider() : const SizedBox( diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index 76009f729..e515a570f 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -164,6 +164,13 @@ abstract class LibMoneroWallet bool walletExists(String path); + String getTxKeyFor({required String txid}) { + if (libMoneroWallet == null) { + throw Exception("Cannot get tx key in uninitialized libMoneroWallet"); + } + return libMoneroWallet!.getTxKey(txid); + } + void _setListener() { if (libMoneroWallet != null && libMoneroWallet!.getListeners().isEmpty) { libMoneroWallet?.addListener( diff --git a/lib/widgets/tx_key_widget.dart b/lib/widgets/tx_key_widget.dart new file mode 100644 index 000000000..ba8e9b33c --- /dev/null +++ b/lib/widgets/tx_key_widget.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../pages/pinpad_views/pinpad_dialog.dart'; +import '../pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../pages_desktop_specific/password/request_desktop_auth_dialog.dart'; +import '../providers/global/wallets_provider.dart'; +import '../utilities/text_styles.dart'; +import '../utilities/util.dart'; +import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import 'custom_buttons/blue_text_button.dart'; +import 'custom_buttons/simple_copy_button.dart'; +import 'detail_item.dart'; + +class TxKeyWidget extends ConsumerStatefulWidget { + /// The [walletId] MUST be the id of a [LibMoneroWallet]! + const TxKeyWidget({ + super.key, + required this.walletId, + required this.txid, + }); + + final String walletId; + final String txid; + + @override + ConsumerState createState() => _TxKeyWidgetState(); +} + +class _TxKeyWidgetState extends ConsumerState { + String? _private; + + bool _lock = false; + + Future _loadTxKey() async { + if (_lock) { + return; + } + _lock = true; + + try { + final verified = await showDialog( + context: context, + builder: (context) => Util.isDesktop + ? const RequestDesktopAuthDialog(title: "Show private view key") + : const PinpadDialog( + biometricsAuthenticationTitle: "Show private view key", + biometricsLocalizedReason: + "Authenticate to show private view key", + biometricsCancelButtonString: "CANCEL", + ), + barrierDismissible: !Util.isDesktop, + ); + + if (verified == "verified success" && mounted) { + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet; + + _private = wallet.getTxKeyFor(txid: widget.txid); + if (_private!.isEmpty) { + _private = "Unavailable"; + } + + if (context.mounted) { + setState(() {}); + } else { + _private == null; + } + } + } finally { + _lock = false; + } + } + + @override + Widget build(BuildContext context) { + return DetailItemBase( + button: _private == null + ? CustomTextButton( + text: "Show", + onTap: _loadTxKey, + enabled: _private == null, + ) + : Util.isDesktop + ? IconCopyButton( + data: _private!, + ) + : SimpleCopyButton( + data: _private!, + ), + title: Text( + "Private view key", + style: STextStyles.itemSubtitle(context), + ), + detail: SelectableText( + // TODO + _private ?? "*" * 52, // 52 is approx length + style: STextStyles.w500_14(context), + ), + ); + } +}