From bf970e210856c72b1351130b0ba3130185ab273f Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 27 Oct 2025 11:21:20 -0600 Subject: [PATCH 1/5] update submodules with macos framework fixes --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_libmwc | 2 +- crypto_plugins/frostdart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 5a705486d..c027c4294 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 5a705486d07f13ef0c5a044e7b9588dea4c989ff +Subproject commit c027c4294e4d1763fde7878d13a899793eda3d22 diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 9df277125..941a23101 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 9df27712534c7cccedb19376cb0768b6f538cacb +Subproject commit 941a23101492c3316cd5e896615373762b4235f1 diff --git a/crypto_plugins/frostdart b/crypto_plugins/frostdart index 39171c0f2..7becc39b6 160000 --- a/crypto_plugins/frostdart +++ b/crypto_plugins/frostdart @@ -1 +1 @@ -Subproject commit 39171c0f24af01780a14b969051aa1a574961f85 +Subproject commit 7becc39b62199930252f581b99cbbcaf51659f8a From bed9d1b8ba974ef97655fd273aec082aaf07e3ca Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 27 Oct 2025 13:15:00 -0600 Subject: [PATCH 2/5] update mwebd lib --- pubspec.lock | 4 ++-- scripts/app_config/templates/pubspec.template.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index fd2dde370..67468d4b6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -987,10 +987,10 @@ packages: dependency: "direct main" description: name: flutter_mwebd - sha256: c5d1f628a037a12cd558c3c37fec46438c4d8d07e108a7f7cc8969806de13993 + sha256: faaec843d9749c5d3cd02e9c7afbd7754449f93a4316fec43b2c26f372e0eb55 url: "https://pub.dev" source: hosted - version: "0.0.1-pre.8" + version: "0.0.1-pre.10" flutter_native_splash: dependency: "direct main" description: diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml index 564948796..5bbaf75d7 100644 --- a/scripts/app_config/templates/pubspec.template.yaml +++ b/scripts/app_config/templates/pubspec.template.yaml @@ -71,7 +71,7 @@ dependencies: # %%END_ENABLE_SAL%% # %%ENABLE_MWEBD%% -# flutter_mwebd: ^0.0.1-pre.8 +# flutter_mwebd: ^0.0.1-pre.10 # %%END_ENABLE_MWEBD%% monero_rpc: ^2.0.0 From 64f40768539597fca67ca4783805d793f2912fa2 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 27 Oct 2025 16:52:47 -0600 Subject: [PATCH 3/5] update libs for ios 26 compat --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_libmwc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index c027c4294..7af247de8 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit c027c4294e4d1763fde7878d13a899793eda3d22 +Subproject commit 7af247de8f404206c79452e2286fc119bd1b7bee diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 941a23101..1a81d3c92 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 941a23101492c3316cd5e896615373762b4235f1 +Subproject commit 1a81d3c92da5d20a6a48203e7e39242047d8a754 From a260a93dbb891c8c097f7b86639cad82f188d407 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 28 Oct 2025 15:42:11 -0600 Subject: [PATCH 4/5] separated xmw and wow libs. Dirty dirty minimal get-it-running mess --- lib/main.dart | 7 +- .../restore_options_view.dart | 5 +- .../restore_wallet_view.dart | 3 +- .../verify_recovery_phrase_view.dart | 5 +- lib/pages/send_view/send_view.dart | 23 + .../transaction_fee_selection_sheet.dart | 25 +- .../edit_refresh_height_view.dart | 22 +- .../sub_widgets/desktop_send_fee_form.dart | 5 +- lib/wallets/crypto_currency/coins/monero.dart | 2 +- .../crypto_currency/coins/wownero.dart | 4 +- lib/wallets/wallet/impl/monero_wallet.dart | 13 +- lib/wallets/wallet/impl/wownero_wallet.dart | 27 +- .../intermediate/lib_monero_wallet.dart | 6 +- .../intermediate/lib_wownero_wallet.dart | 1509 +++++++++++++++++ lib/widgets/desktop/desktop_fee_dialog.dart | 25 +- .../interfaces/cs_monero_interface.dart | 13 +- .../interfaces/cs_wownero_interface.dart | 159 ++ pubspec.lock | 140 +- scripts/app_config/configure_stack_wallet.sh | 2 + .../templates/pubspec.template.yaml | 11 +- ...OW_cs_wownero_interface_impl.template.dart | 510 ++++++ ...XMR_cs_monero_interface_impl.template.dart | 120 +- 22 files changed, 2446 insertions(+), 190 deletions(-) create mode 100644 lib/wallets/wallet/intermediate/lib_wownero_wallet.dart create mode 100644 lib/wl_gen/interfaces/cs_wownero_interface.dart create mode 100644 tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart diff --git a/lib/main.dart b/lib/main.dart index 7bfba70f0..7b4d40fde 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,6 +76,7 @@ import 'wallets/isar/providers/all_wallets_info_provider.dart'; import 'wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'widgets/crypto_notifications.dart'; import 'wl_gen/interfaces/cs_monero_interface.dart'; +import 'wl_gen/interfaces/cs_wownero_interface.dart'; import 'wl_gen/interfaces/lib_xelis_interface.dart'; final openedFromSWBFileStringStateProvider = StateProvider( @@ -161,10 +162,12 @@ void main(List args) async { DB.instance.hive.registerAdapter(lib_monero_compat.WalletTypeAdapter()); - if (AppConfig.coins.whereType().isNotEmpty || - AppConfig.coins.whereType().isNotEmpty) { + if (AppConfig.coins.whereType().isNotEmpty) { csMonero.setUseCsMoneroLoggerInternal(kDebugMode); } + if (AppConfig.coins.whereType().isNotEmpty) { + csWownero.setUseCsWowneroLoggerInternal(kDebugMode); + } DB.instance.hive.init( (await StackFileSystem.applicationHiveDirectory()).path, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index 1686eba71..c44c5f264 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -42,6 +42,7 @@ import '../../../../widgets/textfield_icon_button.dart'; import '../../../../widgets/toggle.dart'; import '../../../../wl_gen/interfaces/cs_monero_interface.dart'; import '../../../../wl_gen/interfaces/cs_salvium_interface.dart'; +import '../../../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../../create_or_restore_wallet_view/sub_widgets/coin_image.dart'; import '../restore_view_only_wallet_view.dart'; import '../restore_wallet_view.dart'; @@ -213,10 +214,10 @@ class _RestoreOptionsViewState extends ConsumerState { int height = 0; if (date != null) { if (widget.coin is Monero) { - height = csMonero.getHeightByDate(date, csCoin: CsCoin.monero); + height = csMonero.getHeightByDate(date); } if (widget.coin is Wownero) { - height = csMonero.getHeightByDate(date, csCoin: CsCoin.wownero); + height = csWownero.getHeightByDate(date); } if (widget.coin is Salvium) { height = csSalvium.getHeightByDate( diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 50df15202..e87305fc2 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -61,6 +61,7 @@ import '../../../widgets/table_view/table_view.dart'; import '../../../widgets/table_view/table_view_cell.dart'; import '../../../widgets/table_view/table_view_row.dart'; import '../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../../../wl_gen/interfaces/lib_xelis_interface.dart'; import '../../home_view/home_view.dart'; import '../add_token_view/edit_wallet_tokens_view.dart'; @@ -189,7 +190,7 @@ class _RestoreWalletViewState extends ConsumerState { } } if (widget.coin is Wownero) { - final wowneroWordList = csMonero.getWowneroWordList( + final wowneroWordList = csWownero.getWowneroWordList( "English", widget.seedWordsLength, ); diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index 9494b6630..b5063ae83 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -48,6 +48,7 @@ import '../../../widgets/desktop/desktop_scaffold.dart'; import '../../../widgets/stack_dialog.dart'; import '../../../wl_gen/interfaces/cs_monero_interface.dart'; import '../../../wl_gen/interfaces/cs_salvium_interface.dart'; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../../home_view/home_view.dart'; import '../add_token_view/edit_wallet_tokens_view.dart'; import '../new_wallet_options/new_wallet_options_view.dart'; @@ -116,13 +117,11 @@ class _VerifyRecoveryPhraseViewState if (widget.wallet.cryptoCurrency is Monero) { height = csMonero.getHeightByDate( DateTime.now().subtract(const Duration(days: 7)), - csCoin: CsCoin.monero, ); } if (widget.wallet.cryptoCurrency is Wownero) { - height = csMonero.getHeightByDate( + height = csWownero.getHeightByDate( DateTime.now().subtract(const Duration(days: 7)), - csCoin: CsCoin.wownero, ); } if (widget.wallet.cryptoCurrency is Salvium) { diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index e7fcea333..e56f12c5e 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -75,6 +75,7 @@ import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; import '../../widgets/textfield_icon_button.dart'; import '../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../address_book_views/address_book_view.dart'; import '../coin_control/coin_control_view.dart'; import 'confirm_transaction_view.dart'; @@ -575,6 +576,28 @@ class _SendViewState extends ConsumerState { throw ArgumentError("custom fee not available for monero"); } + fee = await wallet.estimateFeeFor(amount, BigInt.from(specialMoneroId)); + cachedFees[amount] = ref + .read(pAmountFormatter(coin)) + .format(fee, withUnitName: true, indicatePrecisionLoss: false); + + return cachedFees[amount]!; + } else if (coin is Wownero) { + final int specialMoneroId; + switch (ref.read(feeRateTypeMobileStateProvider.state).state) { + case FeeRateType.fast: + specialMoneroId = csWownero.getTxPriorityHigh(); + break; + case FeeRateType.average: + specialMoneroId = csWownero.getTxPriorityMedium(); + break; + case FeeRateType.slow: + specialMoneroId = csWownero.getTxPriorityNormal(); + break; + default: + throw ArgumentError("custom fee not available for monero"); + } + fee = await wallet.estimateFeeFor(amount, BigInt.from(specialMoneroId)); cachedFees[amount] = ref .read(pAmountFormatter(coin)) diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 8c05fa973..c7112a596 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -29,6 +29,7 @@ import '../../../wallets/wallet/impl/firo_wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../widgets/animated_text.dart'; import '../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; final feeSheetSessionCacheProvider = ChangeNotifierProvider((ref) { @@ -88,12 +89,18 @@ class _TransactionFeeSelectionSheetState if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityHigh()), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityHigh()), + ); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -125,12 +132,18 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityMedium()), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityMedium()), + ); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -161,12 +174,18 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityNormal()), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityNormal()), + ); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart index f6f6b156c..bfa5d0a13 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart @@ -12,6 +12,7 @@ import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../../wallets/wallet/intermediate/lib_wownero_wallet.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -22,6 +23,7 @@ import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; import '../../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../../wl_gen/interfaces/cs_wownero_interface.dart'; class EditRefreshHeightView extends ConsumerStatefulWidget { const EditRefreshHeightView({super.key, required this.walletId}); @@ -55,10 +57,11 @@ class _EditRefreshHeightViewState extends ConsumerState { newRestoreHeight: newHeight, isar: ref.read(mainDBProvider).isar, ); - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet?; - if (wallet?.wallet != null) { - csMonero.setRefreshFromBlockHeight(wallet!.wallet!, newHeight); + final wallet = ref.read(pWallets).getWallet(widget.walletId); + if (wallet is LibMoneroWallet && wallet.wallet != null) { + csMonero.setRefreshFromBlockHeight(wallet.wallet!, newHeight); + } else if (wallet is LibWowneroWallet && wallet.wallet != null) { + csWownero.setRefreshFromBlockHeight(wallet.wallet!, newHeight); } } else { errMessage = "Invalid height: ${_controller.text}"; @@ -96,11 +99,14 @@ class _EditRefreshHeightViewState extends ConsumerState { void initState() { super.initState(); _controller = TextEditingController(); - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet?; - if (wallet?.wallet != null) { + final wallet = ref.read(pWallets).getWallet(widget.walletId); + if (wallet is LibMoneroWallet && wallet.wallet != null) { _controller.text = csMonero - .getRefreshFromBlockHeight(wallet!.wallet!) + .getRefreshFromBlockHeight(wallet.wallet!) + .toString(); + } else if (wallet is LibWowneroWallet && wallet.wallet != null) { + _controller.text = csWownero + .getRefreshFromBlockHeight(wallet.wallet!) .toString(); } else { _controller.text = ref diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart index 462262d9c..7215e6e4f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart @@ -22,6 +22,7 @@ import '../../../../widgets/desktop/desktop_fee_dialog.dart'; import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/fee_slider.dart'; import '../../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../../wl_gen/interfaces/cs_wownero_interface.dart'; class DesktopSendFeeForm extends ConsumerStatefulWidget { const DesktopSendFeeForm({ @@ -171,7 +172,9 @@ class _DesktopSendFeeFormState extends ConsumerState { final fee = await wallet.estimateFeeFor( amount, BigInt.from( - csMonero.getTxPriorityMedium(), + coin is Monero + ? csMonero.getTxPriorityMedium() + : csWownero.getTxPriorityMedium(), ), ); ref diff --git a/lib/wallets/crypto_currency/coins/monero.dart b/lib/wallets/crypto_currency/coins/monero.dart index 3a983cf9f..379c47d70 100644 --- a/lib/wallets/crypto_currency/coins/monero.dart +++ b/lib/wallets/crypto_currency/coins/monero.dart @@ -52,7 +52,7 @@ class Monero extends CryptonoteCurrency { } switch (network) { case CryptoCurrencyNetwork.main: - return csMonero.validateAddress(address, 0, csCoin: CsCoin.monero); + return csMonero.validateAddress(address, 0); default: throw Exception("Unsupported network: $network"); } diff --git a/lib/wallets/crypto_currency/coins/wownero.dart b/lib/wallets/crypto_currency/coins/wownero.dart index 66d27c3e0..7d0fec49f 100644 --- a/lib/wallets/crypto_currency/coins/wownero.dart +++ b/lib/wallets/crypto_currency/coins/wownero.dart @@ -1,7 +1,7 @@ import '../../../models/node_model.dart'; import '../../../utilities/default_nodes.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; -import '../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../crypto_currency.dart'; import '../intermediate/cryptonote_currency.dart'; @@ -52,7 +52,7 @@ class Wownero extends CryptonoteCurrency { } switch (network) { case CryptoCurrencyNetwork.main: - return csMonero.validateAddress(address, 0, csCoin: CsCoin.wownero); + return csWownero.validateAddress(address, 0); default: throw Exception("Unsupported network: $network"); } diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart index 876bec9bf..935d5ad3a 100644 --- a/lib/wallets/wallet/impl/monero_wallet.dart +++ b/lib/wallets/wallet/impl/monero_wallet.dart @@ -35,19 +35,13 @@ class MoneroWallet extends LibMoneroWallet { } @override - bool walletExists(String path) => - csMonero.walletExists(path, csCoin: CsCoin.monero); + bool walletExists(String path) => csMonero.walletExists(path); @override Future loadWallet({ required String path, required String password, - }) => csMonero.loadWallet( - walletId, - path: path, - password: password, - csCoin: CsCoin.monero, - ); + }) => csMonero.loadWallet(walletId, path: path, password: password); @override Future getCreatedWallet({ @@ -56,7 +50,6 @@ class MoneroWallet extends LibMoneroWallet { required int wordCount, required String seedOffset, }) => csMonero.getCreatedWallet( - csCoin: CsCoin.monero, path: path, password: password, wordCount: wordCount, @@ -76,7 +69,6 @@ class MoneroWallet extends LibMoneroWallet { mnemonic: mnemonic, height: height, seedOffset: seedOffset, - csCoin: CsCoin.monero, walletId: walletId, ); @@ -89,7 +81,6 @@ class MoneroWallet extends LibMoneroWallet { int height = 0, }) => csMonero.getRestoredFromViewKeyWallet( walletId: walletId, - csCoin: CsCoin.monero, path: path, password: password, address: address, diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart index 691f60126..86f018381 100644 --- a/lib/wallets/wallet/impl/wownero_wallet.dart +++ b/lib/wallets/wallet/impl/wownero_wallet.dart @@ -5,14 +5,14 @@ import 'package:compat/compat.dart' as lib_monero_compat; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/enums/fee_rate_type_enum.dart'; -import '../../../wl_gen/interfaces/cs_monero_interface.dart'; import '../../../wl_gen/interfaces/cs_salvium_interface.dart' show WrappedWallet; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../models/tx_data.dart'; -import '../intermediate/lib_monero_wallet.dart'; +import '../intermediate/lib_wownero_wallet.dart'; -class WowneroWallet extends LibMoneroWallet { +class WowneroWallet extends LibWowneroWallet { WowneroWallet(CryptoCurrencyNetwork network) : super(Wownero(network), lib_monero_compat.WalletType.wownero); @@ -66,7 +66,7 @@ class WowneroWallet extends LibMoneroWallet { // unsure why this delay? await Future.delayed(const Duration(milliseconds: 500)); } catch (e) { - approximateFee = await csMonero.estimateFee( + approximateFee = await csWownero.estimateFee( feeRate.toInt(), amount.raw, wallet: wallet!, @@ -86,19 +86,13 @@ class WowneroWallet extends LibMoneroWallet { } @override - bool walletExists(String path) => - csMonero.walletExists(path, csCoin: CsCoin.wownero); + bool walletExists(String path) => csWownero.walletExists(path); @override Future loadWallet({ required String path, required String password, - }) => csMonero.loadWallet( - walletId, - path: path, - password: password, - csCoin: CsCoin.wownero, - ); + }) => csWownero.loadWallet(walletId, path: path, password: password); @override Future getCreatedWallet({ @@ -106,8 +100,7 @@ class WowneroWallet extends LibMoneroWallet { required String password, required int wordCount, required String seedOffset, - }) => csMonero.getCreatedWallet( - csCoin: CsCoin.wownero, + }) => csWownero.getCreatedWallet( path: path, password: password, wordCount: wordCount, @@ -121,13 +114,12 @@ class WowneroWallet extends LibMoneroWallet { required String mnemonic, required String seedOffset, int height = 0, - }) => csMonero.getRestoredWallet( + }) => csWownero.getRestoredWallet( path: path, password: password, mnemonic: mnemonic, height: height, seedOffset: seedOffset, - csCoin: CsCoin.wownero, walletId: walletId, ); @@ -138,9 +130,8 @@ class WowneroWallet extends LibMoneroWallet { required String address, required String privateViewKey, int height = 0, - }) => csMonero.getRestoredFromViewKeyWallet( + }) => csWownero.getRestoredFromViewKeyWallet( walletId: walletId, - csCoin: CsCoin.wownero, path: path, password: password, address: address, diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index e5f91c4ff..38ca63ea5 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -338,7 +338,7 @@ abstract class LibMoneroWallet 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!"); + throw Exception("Missing word count for new xmr wallet!"); } try { final password = generatePassword(); @@ -359,7 +359,7 @@ abstract class LibMoneroWallet isar: mainDB.isar, ); - // special case for xmr/wow. Normally mnemonic + passphrase is saved + // special case for xmr. Normally mnemonic + passphrase is saved // before wallet.init() is called await secureStorageInterface.write( key: Wallet.mnemonicKey(walletId: walletId), @@ -830,7 +830,7 @@ abstract class LibMoneroWallet if (wallet == null) { Logging.instance.w( "onUTXOsChanged triggered while cs_monero wallet is null. If this " - "occurs while not in a monero/wownero wallet this warning can be " + "occurs while not in a monero wallet this warning can be " "ignored.", ); return; diff --git a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart new file mode 100644 index 000000000..cf47fb9b5 --- /dev/null +++ b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart @@ -0,0 +1,1509 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:compat/compat.dart' as lib_monero_compat; +import 'package:isar_community/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; + +import '../../../app_config.dart'; +import '../../../db/hive/db.dart'; +import '../../../models/balance.dart'; +import '../../../models/input.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/transaction.dart'; +import '../../../models/isar/models/blockchain_data/utxo.dart'; +import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; +import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import '../../../models/keys/cw_key_data.dart'; +import '../../../models/keys/view_only_wallet_data.dart'; +import '../../../models/node_model.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'; +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/events/global/updated_in_background_event.dart'; +import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; +import '../../../services/tor_service.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/enums/fee_rate_type_enum.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +import '../../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../../wl_gen/interfaces/cs_salvium_interface.dart'; +import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; +import '../../crypto_currency/intermediate/cryptonote_currency.dart'; +import '../../isar/models/wallet_info.dart'; +import '../../models/tx_data.dart'; +import '../wallet.dart'; +import '../wallet_mixin_interfaces/multi_address_interface.dart'; +import '../wallet_mixin_interfaces/view_only_option_interface.dart'; +import 'cryptonote_wallet.dart'; + +abstract class LibWowneroWallet + extends CryptonoteWallet + with ViewOnlyOptionInterface + implements MultiAddressInterface { + @override + int get isarTransactionVersion => 2; + + WrappedWallet? wallet; + + LibWowneroWallet(super.currency, this.compatType) { + final bus = GlobalEventBus.instance; + + // Listen for tor status changes. + _torStatusListener = bus.on().listen(( + event, + ) async { + switch (event.newStatus) { + case TorConnectionStatus.connecting: + if (!_torConnectingLock.isLocked) { + await _torConnectingLock.acquire(); + } + _requireMutex = true; + break; + + case TorConnectionStatus.connected: + case TorConnectionStatus.disconnected: + if (_torConnectingLock.isLocked) { + _torConnectingLock.release(); + } + _requireMutex = false; + break; + } + }); + + // Listen for tor preference changes. + _torPreferenceListener = bus.on().listen(( + event, + ) async { + await updateNode(); + }); + + // Potentially dangerous hack. See comments in _startInit() + _startInit(); + } + // cw based wallet listener to handle synchronization of utxo frozen states + late final StreamSubscription> _streamSub; + Future _startInit() async { + // Delay required as `mainDB` is not initialized in constructor. + // This is a hack and could lead to a race condition. + Future.delayed(const Duration(seconds: 2), () { + _streamSub = mainDB.isar.utxos + .where() + .walletIdEqualTo(walletId) + .watch(fireImmediately: true) + .listen((utxos) async { + try { + await onUTXOsChanged(utxos); + await updateBalance(shouldUpdateUtxos: false); + } catch (e, s) { + Logging.instance.e("_startInit", error: e, stackTrace: s); + } + }); + }); + } + + final lib_monero_compat.WalletType compatType; + + lib_monero_compat.SyncStatus? get syncStatus => _syncStatus; + lib_monero_compat.SyncStatus? _syncStatus; + int _syncedCount = 0; + void _setSyncStatus(lib_monero_compat.SyncStatus status) { + if (status is lib_monero_compat.SyncedSyncStatus) { + if (_syncStatus is lib_monero_compat.SyncedSyncStatus) { + _syncedCount++; + } + } else { + _syncedCount = 0; + } + + if (_syncedCount < 3) { + _syncStatus = status; + syncStatusChanged(); + } + } + + final prepareSendMutex = Mutex(); + final estimateFeeMutex = Mutex(); + + bool _txRefreshLock = false; + int _lastCheckedHeight = -1; + int _txCount = 0; + int currentKnownChainHeight = 0; + double highestPercentCached = 0; + + Future loadWallet({ + required String path, + required String password, + }); + + Future getCreatedWallet({ + required String path, + required String password, + required int wordCount, + required String seedOffset, + }); + + Future getRestoredWallet({ + required String path, + required String password, + required String mnemonic, + required String seedOffset, + int height = 0, + }); + + Future getRestoredFromViewKeyWallet({ + required String path, + required String password, + required String address, + required String privateViewKey, + int height = 0, + }); + + void invalidSeedLengthCheck(int length); + + bool walletExists(String path); + + String getTxKeyFor({required String txid}) { + if (wallet == null) { + throw Exception("Cannot get tx key in uninitialized LibWowneroWallet"); + } + return csWownero.getTxKey(wallet!, txid); + } + + void _setListener() { + if (wallet != null && !csWownero.hasListeners(wallet!)) { + csWownero.addListener( + wallet!, + CsWalletListener( + onSyncingUpdate: onSyncingUpdate, + onNewBlock: onNewBlock, + onBalancesChanged: onBalancesChanged, + onError: (e, s) { + Logging.instance.w("$e\n$s", error: e, stackTrace: s); + }, + ), + ); + } + } + + @override + Future open() async { + bool wasNull = false; + + if (wallet == null) { + wasNull = true; + // LibWowneroWalletT?.close(); + final path = await pathForWallet(name: walletId, type: compatType); + + final String password; + try { + password = (await secureStorageInterface.read( + key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), + ))!; + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + + wallet = await loadWallet(path: path, password: password); + + _setListener(); + + await updateNode(); + } + + Address? currentAddress = await getCurrentReceivingAddress(); + if (currentAddress == null) { + currentAddress = addressFor(index: 0); + await mainDB.updateOrPutAddresses([currentAddress]); + } + if (info.cachedReceivingAddress != currentAddress.value) { + await info.updateReceivingAddress( + newAddress: currentAddress.value, + isar: mainDB.isar, + ); + } + + if (wasNull) { + try { + _setSyncStatus(lib_monero_compat.ConnectingSyncStatus()); + csWownero.startSyncing(wallet!); + } catch (_) { + _setSyncStatus(lib_monero_compat.FailedSyncStatus()); + // TODO log + } + } + _setListener(); + csWownero.startListeners(wallet!); + csWownero.startAutoSaving(wallet!); + + unawaited(refresh()); + } + + @Deprecated("Only used in the case of older wallets") + lib_monero_compat.WalletInfo? getLibWowneroWalletInfo(String walletId) { + try { + return DB.instance.moneroWalletInfoBox.values.firstWhere( + (info) => info.id == lib_monero_compat.hiveIdFor(walletId, compatType), + ); + } catch (_) { + return null; + } + } + + Future save() async { + if (!Platform.isWindows) { + final appRoot = await StackFileSystem.applicationRootDirectory(); + await lib_monero_compat.backupWalletFiles( + name: walletId, + type: compatType, + appRoot: appRoot, + ); + } + await csWownero.save(wallet!); + } + + Address addressFor({required int index, int account = 0}) { + final address = csWownero.getAddress( + wallet!, + accountIndex: account, + addressIndex: index, + ); + + if (address.contains("111")) { + throw Exception("111 address found!"); + } + + final newReceivingAddress = Address( + walletId: walletId, + derivationIndex: index, + derivationPath: null, + value: address, + publicKey: [], + type: AddressType.cryptonote, + subType: AddressSubType.receiving, + ); + + return newReceivingAddress; + } + + Future getKeys() async { + final oldInfo = getLibWowneroWalletInfo(walletId); + if (wallet == null || (oldInfo != null && oldInfo.name != walletId)) { + return null; + } + try { + return CWKeyData( + walletId: walletId, + publicViewKey: csWownero.getPublicViewKey(wallet!), + privateViewKey: csWownero.getPrivateViewKey(wallet!), + publicSpendKey: csWownero.getPublicSpendKey(wallet!), + privateSpendKey: csWownero.getPrivateSpendKey(wallet!), + ); + } catch (e, s) { + Logging.instance.f("getKeys failed: ", error: e, stackTrace: s); + return CWKeyData( + walletId: walletId, + publicViewKey: "ERROR", + privateViewKey: "ERROR", + publicSpendKey: "ERROR", + privateSpendKey: "ERROR", + ); + } + } + + Future<(String, String)> + hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing() async { + final path = await pathForWallet(name: walletId, type: compatType); + final String password; + try { + password = (await secureStorageInterface.read( + key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), + ))!; + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + wallet = await loadWallet(path: path, password: password); + return ( + csWownero.getAddress(wallet!), + csWownero.getPrivateViewKey(wallet!), + ); + } + + @override + 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, + wordCount: wordCount, + seedOffset: "", // default for non restored wallets for now + ); + + await info.updateRestoreHeight( + newRestoreHeight: csWownero.getRefreshFromBlockHeight(wallet), + isar: mainDB.isar, + ); + + // special case for xmr/wow. Normally mnemonic + passphrase is saved + // before wallet.init() is called + await secureStorageInterface.write( + key: Wallet.mnemonicKey(walletId: walletId), + value: csWownero.getSeed(wallet), + ); + await secureStorageInterface.write( + key: Wallet.mnemonicPassphraseKey(walletId: walletId), + value: "", + ); + } catch (e, s) { + Logging.instance.f("", error: e, stackTrace: s); + } + await updateNode(); + } + + return super.init(); + } + + @override + Future recover({required bool isRescan}) async { + if (isRescan) { + await refreshMutex.protect(() async { + // clear blockchain info + await mainDB.deleteWalletBlockchainData(walletId); + + highestPercentCached = 0; + unawaited(csWownero.rescanBlockchain(wallet!)); + csWownero.startSyncing(wallet!); + // unawaited(save()); + }); + unawaited(refresh()); + return; + } + + if (isViewOnly) { + await recoverViewOnly(); + return; + } + + await refreshMutex.protect(() async { + final mnemonic = await getMnemonic(); + final seedOffset = await getMnemonicPassphrase(); + final seedLength = mnemonic.trim().split(" ").length; + + invalidSeedLengthCheck(seedLength); + + try { + final height = max(info.restoreHeight, 0); + + await info.updateRestoreHeight( + newRestoreHeight: height, + isar: mainDB.isar, + ); + + final String name = walletId; + + final path = await pathForWallet(name: name, type: compatType); + + try { + final password = generatePassword(); + await secureStorageInterface.write( + key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), + value: password, + ); + + final wallet = await getRestoredWallet( + path: path, + password: password, + mnemonic: mnemonic, + height: height, + seedOffset: seedOffset, + ); + + if (this.wallet != null) { + await exit(); + } + + this.wallet = wallet; + + _setListener(); + + final newReceivingAddress = + await getCurrentReceivingAddress() ?? + Address( + walletId: walletId, + derivationIndex: 0, + derivationPath: null, + value: csWownero.getAddress(this.wallet!), + publicKey: [], + type: AddressType.cryptonote, + subType: AddressSubType.receiving, + ); + + await mainDB.updateOrPutAddresses([newReceivingAddress]); + await info.updateReceivingAddress( + newAddress: newReceivingAddress.value, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.f("", error: e, stackTrace: s); + rethrow; + } + await updateNode(); + _setListener(); + + // LibWowneroWallet?.setRecoveringFromSeed(isRecovery: true); + unawaited(csWownero.rescanBlockchain(wallet!)); + csWownero.startSyncing(wallet!); + + // await save(); + csWownero.startListeners(wallet!); + csWownero.startAutoSaving(wallet!); + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from recoverFromMnemonic(): ", + error: e, + stackTrace: s, + ); + rethrow; + } + }); + } + + // dumb temporary hack + bool _canPing = false; + + @override + Future pingCheck() { + if (_canPing) { + return csWownero.isConnectedToDaemon(wallet!); + } else { + return Future.value(false); + } + } + + @override + Future updateNode() async { + final node = getCurrentNode(); + + if (_torNodeMismatchGuard(node)) { + throw Exception("TOR – clearnet mismatch"); + } + + final host = node.host.endsWith(".onion") + ? node.host + : Uri.parse(node.host).host; + final ({InternetAddress host, int port})? proxy = + AppConfig.hasFeature(AppFeature.tor) && prefs.useTor && !node.forceNoTor + ? TorService.sharedInstance.getProxyInfo() + : null; + + _setSyncStatus(lib_monero_compat.ConnectingSyncStatus()); + try { + if (_requireMutex) { + await _torConnectingLock.protect(() async { + await csWownero.connect( + wallet!, + daemonAddress: "$host:${node.port}", + daemonUsername: node.loginName, + daemonPassword: await node.getPassword(secureStorageInterface), + trusted: node.trusted ?? false, + useSSL: node.useSSL, + socksProxyAddress: node.forceNoTor + ? null + : proxy == null + ? null + : "${proxy.host.address}:${proxy.port}", + ); + }); + } else { + await csWownero.connect( + wallet!, + daemonAddress: "$host:${node.port}", + daemonUsername: node.loginName, + daemonPassword: await node.getPassword(secureStorageInterface), + trusted: node.trusted ?? false, + useSSL: node.useSSL, + socksProxyAddress: node.forceNoTor + ? null + : proxy == null + ? null + : "${proxy.host.address}:${proxy.port}", + ); + } + csWownero.startSyncing(wallet!); + csWownero.startListeners(wallet!); + csWownero.startAutoSaving(wallet!); + + _setSyncStatus(lib_monero_compat.ConnectedSyncStatus()); + } catch (e, s) { + _setSyncStatus(lib_monero_compat.FailedSyncStatus()); + Logging.instance.e( + "Exception caught in $runtimeType.updateNode(): ", + error: e, + stackTrace: s, + ); + } + + return; + } + + @override + Future updateTransactions() async { + if (wallet == null) { + return; + } + + final localTxids = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .heightGreaterThan(0) + .txidProperty() + .findAll(); + + final allTxids = await csWownero.getAllTxids(wallet!, refresh: true); + + final txidsToFetch = allTxids.toSet().difference(localTxids.toSet()); + + if (txidsToFetch.isEmpty) { + return; + } + + final transactions = await csWownero.getTxs( + wallet!, + txids: txidsToFetch, + refresh: false, + ); + + final allOutputs = await csWownero.getOutputs( + wallet!, + includeSpent: true, + refresh: true, + ); + + // final cachedTransactions = + // DB.instance.get(boxName: walletId, key: 'latest_tx_model') + // as TransactionData?; + // int latestTxnBlockHeight = + // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + // as int? ?? + // 0; + // + // final txidsList = DB.instance + // .get(boxName: walletId, key: "cachedTxids") as List? ?? + // []; + // + // final Set cachedTxids = Set.from(txidsList); + + // TODO: filter to skip cached + confirmed txn processing in next step + // final unconfirmedCachedTransactions = + // cachedTransactions?.getAllTransactions() ?? {}; + // unconfirmedCachedTransactions + // .removeWhere((key, value) => value.confirmedStatus); + // + // if (cachedTransactions != null) { + // for (final tx in allTxHashes.toList(growable: false)) { + // final txHeight = tx["height"] as int; + // if (txHeight > 0 && + // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { + // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { + // allTxHashes.remove(tx); + // } + // } + // } + // } + + final List txns = []; + + for (final tx in transactions) { + final associatedOutputs = allOutputs.where((e) => e.hash == tx.hash); + final List inputs = []; + final List outputs = []; + TransactionType type; + if (!tx.isSpend) { + type = TransactionType.incoming; + for (final output in associatedOutputs) { + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "", + valueStringSats: output.value.toString(), + addresses: [output.address], + walletOwns: true, + ), + ); + } + } else { + type = TransactionType.outgoing; + for (final output in associatedOutputs) { + inputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [output.address], + valueStringSats: output.value.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ), + ); + } + } + + final txn = TransactionV2( + walletId: walletId, + blockHash: null, // not exposed via current cs_monero + hash: tx.hash, + txid: tx.hash, + timestamp: (tx.timeStamp.millisecondsSinceEpoch ~/ 1000), + height: tx.blockHeight, + inputs: inputs, + outputs: outputs, + version: -1, // not exposed via current cs_monero + type: type, + subType: TransactionSubType.none, + otherData: jsonEncode({ + TxV2OdKeys.overrideFee: Amount( + rawValue: tx.fee, + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), + TxV2OdKeys.moneroAmount: Amount( + rawValue: tx.amount, + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), + TxV2OdKeys.moneroAccountIndex: tx.accountIndex, + TxV2OdKeys.isMoneroTransaction: true, + }), + ); + + txns.add(txn); + } + + await mainDB.updateOrPutTransactionV2s(txns); + } + + Future get availableBalance async { + try { + return Amount( + rawValue: csWownero.getUnlockedBalance(wallet!)!, + fractionDigits: cryptoCurrency.fractionDigits, + ); + } catch (_) { + return info.cachedBalance.spendable; + } + } + + Future get totalBalance async { + try { + final full = csWownero.getBalance(wallet!); + if (full != null) { + return Amount( + rawValue: full, + fractionDigits: cryptoCurrency.fractionDigits, + ); + } else { + final transactions = await csWownero.getAllTxs(wallet!, refresh: true); + BigInt transactionBalance = BigInt.zero; + for (final tx in transactions) { + if (!tx.isSpend) { + transactionBalance += tx.amount; + } else { + transactionBalance += -tx.amount - tx.fee; + } + } + + return Amount( + rawValue: transactionBalance, + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + } catch (_) { + return info.cachedBalance.total; + } + } + + @override + Future exit() async { + Logging.instance.i("exit called on $wallet!"); + if (wallet != null) { + csWownero.stopAutoSaving(wallet!); + csWownero.stopListeners(wallet!); + csWownero.stopSyncing(wallet!); + await csWownero.save(wallet!); + } + } + + Future pathForWalletDir({ + required String name, + required lib_monero_compat.WalletType type, + }) async { + final Directory root = await StackFileSystem.applicationRootDirectory(); + return lib_monero_compat.pathForWalletDir( + name: name, + type: type.name.toLowerCase(), + appRoot: root, + ); + } + + Future pathForWallet({ + required String name, + required lib_monero_compat.WalletType type, + }) async => await pathForWalletDir( + name: name, + type: type, + ).then((path) => '$path/$name'); + + void onSyncingUpdate({ + required int syncHeight, + required int nodeHeight, + String? message, + }) { + if (nodeHeight > 0 && syncHeight >= 0) { + currentKnownChainHeight = nodeHeight; + updateChainHeight(); + final blocksLeft = nodeHeight - syncHeight; + final lib_monero_compat.SyncStatus status; + if (blocksLeft < 100) { + status = lib_monero_compat.SyncedSyncStatus(); + + // if (!_hasSyncAfterStartup) { + // _hasSyncAfterStartup = true; + // await save(); + // } + // + // if (walletInfo.isRecovery!) { + // await setAsRecovered(); + // } + } else { + final percent = syncHeight / currentKnownChainHeight; + + status = lib_monero_compat.SyncingSyncStatus( + blocksLeft, + percent, + currentKnownChainHeight, + ); + } + + _setSyncStatus(status); + _refreshTxDataHelper(); + } + } + + void onBalancesChanged({ + required BigInt newBalance, + required BigInt newUnlockedBalance, + }) async { + try { + await updateBalance(); + await updateTransactions(); + } catch (e, s) { + Logging.instance.w("onBalancesChanged(): ", error: e, stackTrace: s); + } + } + + void onNewBlock(int nodeHeight) async { + try { + await updateTransactions(); + } catch (e, s) { + Logging.instance.w("onNewBlock(): ", error: e, stackTrace: s); + } + } + + final _utxosUpdateLock = Mutex(); + Future onUTXOsChanged(List utxos) async { + if (wallet == null) { + Logging.instance.w( + "onUTXOsChanged triggered while cs_monero wallet is null. If this " + "occurs while not in a monero/wownero wallet this warning can be " + "ignored.", + ); + return; + } + + await _utxosUpdateLock.protect(() async { + final cwUtxos = await csWownero.getOutputs(wallet!, refresh: true); + + // bool changed = false; + + for (final cw in cwUtxos) { + final match = utxos.where( + (e) => + e.keyImage != null && + e.keyImage!.isNotEmpty && + e.keyImage == cw.keyImage, + ); + + if (match.isNotEmpty) { + final u = match.first; + + if (u.isBlocked) { + if (!cw.isFrozen) { + await csWownero.freezeOutput(wallet!, cw.keyImage); + // changed = true; + } + } else { + if (cw.isFrozen) { + await csWownero.thawOutput(wallet!, cw.keyImage); + // changed = true; + } + } + } + } + + // if (changed) { + // await LibWowneroWallet?.updateUTXOs(); + // } + }); + } + + void onNewTransaction() { + // TODO: [prio=low] get rid of UpdatedInBackgroundEvent and move to + // adding the v2 tx to the db which would update ui automagically since the + // db is watched by the ui + // call this here? + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId ${info.name} in background!", + walletId, + ), + ); + } + + void syncStatusChanged() async { + final _syncStatus = syncStatus; + + if (_syncStatus != null) { + if (_syncStatus.progress() == 1 && refreshMutex.isLocked) { + refreshMutex.release(); + } + + WalletSyncStatus? status; + xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true); + + if (_syncStatus is lib_monero_compat.SyncingSyncStatus) { + final int blocksLeft = _syncStatus.blocksLeft; + + // ensure at least 1 to prevent math errors + final int height = max(1, _syncStatus.height); + + final nodeHeight = height + blocksLeft; + currentKnownChainHeight = nodeHeight; + + // final percent = height / nodeHeight; + final percent = _syncStatus.ptc; + + final highest = max(highestPercentCached, percent); + + final unchanged = highest == highestPercentCached; + if (unchanged) { + return; + } + + // update cached + if (highestPercentCached < percent) { + highestPercentCached = percent; + } + + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highest, walletId), + ); + GlobalEventBus.instance.fire( + BlocksRemainingEvent(blocksLeft, walletId), + ); + } else if (_syncStatus is lib_monero_compat.SyncedSyncStatus) { + status = WalletSyncStatus.synced; + } else if (_syncStatus is lib_monero_compat.NotConnectedSyncStatus) { + status = WalletSyncStatus.unableToSync; + xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false); + } else if (_syncStatus is lib_monero_compat.StartingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highestPercentCached, walletId), + ); + } else if (_syncStatus is lib_monero_compat.FailedSyncStatus) { + status = WalletSyncStatus.unableToSync; + xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false); + } else if (_syncStatus is lib_monero_compat.ConnectingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highestPercentCached, walletId), + ); + } else if (_syncStatus is lib_monero_compat.ConnectedSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highestPercentCached, walletId), + ); + } else if (_syncStatus is lib_monero_compat.LostConnectionSyncStatus) { + status = WalletSyncStatus.unableToSync; + xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false); + } + + if (status != null) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent(status, walletId, info.coin), + ); + } + } + } + + @override + Future checkSaveInitialReceivingAddress() async { + // this doesn't work without opening the wallet first which takes a while + } + + // ============ Private ====================================================== + Future _refreshTxDataHelper() async { + if (_txRefreshLock) return; + _txRefreshLock = true; + + final _syncStatus = syncStatus; + + if (_syncStatus != null && + _syncStatus is lib_monero_compat.SyncingSyncStatus) { + final int blocksLeft = _syncStatus.blocksLeft; + final tenKChange = blocksLeft ~/ 10000; + + // only refresh transactions periodically during a sync + if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) { + _lastCheckedHeight = tenKChange; + await _refreshTxData(); + } + } else { + await _refreshTxData(); + } + + _txRefreshLock = false; + } + + Future _refreshTxData() async { + await updateTransactions(); + final count = await mainDB.getTransactions(walletId).count(); + + if (count > _txCount) { + _txCount = count; + await updateBalance(); + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New transaction data found in $walletId ${info.name}!", + walletId, + ), + ); + } + } + + bool _torNodeMismatchGuard(NodeModel node) { + _canPing = true; // Reset. + + final bool mismatch = + (prefs.useTor && node.clearnetEnabled && !node.torEnabled) || + (!prefs.useTor && !node.clearnetEnabled && node.torEnabled); + + if (mismatch) { + _canPing = false; + if (wallet != null) { + csWownero.stopAutoSaving(wallet!); + csWownero.stopListeners(wallet!); + csWownero.stopSyncing(wallet!); + } + _setSyncStatus(lib_monero_compat.FailedSyncStatus()); + } + + return mismatch; // Caller decides whether to throw. + } + + // ============ Overrides ==================================================== + + @override + FilterOperation? get changeAddressFilterOperation => null; + + @override + FilterOperation? get receivingAddressFilterOperation => null; + + @override + Future updateUTXOs() async { + final List outputArray = []; + final utxos = wallet == null + ? [] + : await csWownero.getOutputs(wallet!, refresh: true); + for (final rawUTXO in utxos) { + if (!rawUTXO.spent) { + final current = await mainDB.isar.utxos + .where() + .walletIdEqualTo(walletId) + .filter() + .voutEqualTo(rawUTXO.vout) + .and() + .txidEqualTo(rawUTXO.hash) + .findFirst(); + final tx = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .txidEqualTo(rawUTXO.hash) + .findFirst(); + + final otherDataMap = { + UTXOOtherDataKeys.keyImage: rawUTXO.keyImage, + UTXOOtherDataKeys.spent: rawUTXO.spent, + }; + + final utxo = UTXO( + address: rawUTXO.address, + walletId: walletId, + txid: rawUTXO.hash, + vout: rawUTXO.vout, + value: rawUTXO.value.toInt(), + name: current?.name ?? "", + isBlocked: current?.isBlocked ?? rawUTXO.isFrozen, + blockedReason: current?.blockedReason ?? "", + isCoinbase: rawUTXO.coinbase, + blockHash: "", + blockHeight: + tx?.height ?? (rawUTXO.height > 0 ? rawUTXO.height : null), + blockTime: tx?.timestamp, + otherData: jsonEncode(otherDataMap), + ); + + outputArray.add(utxo); + } + } + + await mainDB.updateUTXOs(walletId, outputArray); + + return true; + } + + @override + Future updateBalance({bool shouldUpdateUtxos = true}) async { + if (shouldUpdateUtxos) { + await updateUTXOs(); + } + + final total = await totalBalance; + final available = await availableBalance; + + final balance = Balance( + total: total, + spendable: available, + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: total - available, + ); + + await info.updateBalance(newBalance: balance, isar: mainDB.isar); + } + + @override + Future refresh() async { + // Awaiting this lock could be dangerous. + // Since refresh is periodic (generally) + if (refreshMutex.isLocked) { + return; + } + + final node = getCurrentNode(); + + if (_torNodeMismatchGuard(node)) { + throw Exception("TOR – clearnet mismatch"); + } + + // this acquire should be almost instant due to above check. + // Slight possibility of race but should be irrelevant + await refreshMutex.acquire(); + + csWownero.startSyncing(wallet!); + _setSyncStatus(lib_monero_compat.StartingSyncStatus()); + + await updateTransactions(); + await updateBalance(); + + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } + + if (refreshMutex.isLocked) { + refreshMutex.release(); + } + + final synced = wallet != null && await csWownero.isSynced(wallet!); + + if (synced) { + _setSyncStatus(lib_monero_compat.SyncedSyncStatus()); + } + } + + @override + Future generateNewReceivingAddress() async { + try { + final currentReceiving = await getCurrentReceivingAddress(); + + final newReceivingIndex = currentReceiving == null + ? 0 + : currentReceiving.derivationIndex + 1; + + final newReceivingAddress = addressFor(index: newReceivingIndex); + + // Add that new receiving address + await mainDB.putAddress(newReceivingAddress); + await info.updateReceivingAddress( + newAddress: newReceivingAddress.value, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.e( + "Exception in generateNewAddress(): ", + error: e, + stackTrace: s, + ); + } + } + + @override + Future checkReceivingAddressForTransactions() async { + if (info.otherData[WalletInfoKeys.reuseAddress] == true) { + try { + throw Exception(); + } catch (_, s) { + Logging.instance.e( + "checkReceivingAddressForTransactions called but reuse address flag set: $s", + error: e, + stackTrace: s, + ); + } + } + + try { + int highestIndex = -1; + final entries = await csWownero.getAllTxs(wallet!, refresh: true); + for (final element in entries) { + if (!element.isSpend) { + final int curAddressIndex = element.addressIndexes.isEmpty + ? 0 + : element.addressIndexes.reduce(max); + if (curAddressIndex > highestIndex) { + highestIndex = curAddressIndex; + } + } + } + + // Check the new receiving index + final currentReceiving = await getCurrentReceivingAddress(); + final curIndex = currentReceiving?.derivationIndex ?? -1; + + if (highestIndex >= curIndex) { + // First increment the receiving index + final newReceivingIndex = curIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = addressFor(index: newReceivingIndex); + + final existing = await mainDB + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await mainDB.putAddress(newReceivingAddress); + } else { + // we need to update the address + await mainDB.updateAddress(existing, newReceivingAddress); + } + 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.e( + "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", + error: e, + stackTrace: s, + ); + return; + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from _checkReceivingAddressForTransactions(): ", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + // TODO: this needs some work. Prio's may need to be changed as well as estimated blocks + @override + Future get fees async => FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, + fast: BigInt.from(csWownero.getTxPriorityHigh()), + medium: BigInt.from(csWownero.getTxPriorityMedium()), + slow: BigInt.from(csWownero.getTxPriorityNormal()), + ); + + @override + Future updateChainHeight() async { + await info.updateCachedChainHeight( + newHeight: currentKnownChainHeight, + isar: mainDB.isar, + ); + } + + @override + Future checkChangeAddressForTransactions() async { + // do nothing + } + + @override + Future generateNewChangeAddress() async { + // do nothing + } + + @override + Future prepareSend({required TxData txData}) async { + try { + final feeRate = txData.feeRateType; + if (feeRate is FeeRateType) { + final int feePriority; + switch (feeRate) { + case FeeRateType.fast: + feePriority = csWownero.getTxPriorityHigh(); + break; + case FeeRateType.average: + feePriority = csWownero.getTxPriorityMedium(); + break; + case FeeRateType.slow: + feePriority = csWownero.getTxPriorityNormal(); + break; + default: + throw ArgumentError("Invalid use of custom fee"); + } + + try { + final bool sweep; + + if (txData.utxos == null) { + final balance = await availableBalance; + sweep = txData.amount! == balance; + } else { + final totalInputsValue = txData.utxos! + .map((e) => e.value) + .fold(BigInt.zero, (p, e) => p + e); + sweep = txData.amount!.raw == totalInputsValue; + } + + // TODO: test this one day + // cs_monero may not support this yet properly + if (sweep && txData.recipients!.length > 1) { + throw Exception("Send all not supported with multiple recipients"); + } + + final List outputs = []; + for (final recipient in txData.recipients!) { + final output = CsRecipient(recipient.address, recipient.amount.raw); + + outputs.add(output); + } + + if (outputs.isEmpty) { + throw Exception("No recipients provided"); + } + + final height = await chainHeight; + final inputs = txData.utxos?.whereType().toList(); + + return await prepareSendMutex.protect(() async { + final CsPendingTransaction pendingTransaction; + if (outputs.length == 1) { + pendingTransaction = await csWownero.createTx( + wallet!, + minConfirms: cryptoCurrency.minConfirms, + currentHeight: height, + output: outputs.first, + sweep: sweep, + priority: feePriority, + preferredInputs: inputs, + accountIndex: 0, // sw only uses account 0 at this time + ); + } else { + pendingTransaction = await csWownero.createTxMultiDest( + wallet!, + minConfirms: cryptoCurrency.minConfirms, + currentHeight: height, + outputs: outputs, + priority: feePriority, + preferredInputs: inputs, + sweep: sweep, + accountIndex: 0, // sw only uses account 0 at this time + ); + } + + final realFee = Amount( + rawValue: pendingTransaction.fee, + fractionDigits: cryptoCurrency.fractionDigits, + ); + + return txData.copyWith( + fee: realFee, + pendingTransaction: pendingTransaction, + ); + }); + } catch (e) { + rethrow; + } + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.i( + "Exception rethrown from prepare send(): ", + error: e, + stackTrace: s, + ); + + if (e.toString().contains("Incorrect unlocked balance")) { + throw Exception("Insufficient balance!"); + } else { + throw Exception("Transaction failed with error: $e"); + } + } + } + + @override + Future confirmSend({required TxData txData}) async { + try { + try { + await csWownero.commitTx(wallet!, txData.pendingTransaction!); + + Logging.instance.d( + "transaction ${txData.pendingTransaction!.txid} has been sent", + ); + return txData.copyWith(txid: txData.pendingTransaction!.txid); + } catch (e, s) { + Logging.instance.e( + "${info.name} ${compatType.name.toLowerCase()} confirmSend: ", + error: e, + stackTrace: s, + ); + rethrow; + } + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from confirmSend(): ", + error: e, + stackTrace: s, + ); + rethrow; + } + } + + // ============== View only ================================================== + + @override + Future recoverViewOnly() async { + await refreshMutex.protect(() async { + final data = + await getViewOnlyWalletData() as CryptonoteViewOnlyWalletData; + + try { + final height = max(info.restoreHeight, 0); + + await info.updateRestoreHeight( + newRestoreHeight: height, + isar: mainDB.isar, + ); + + final String name = walletId; + + final path = await pathForWallet(name: name, type: compatType); + + final password = generatePassword(); + await secureStorageInterface.write( + key: lib_monero_compat.libMoneroWalletPasswordKey(walletId), + value: password, + ); + + final wallet = await getRestoredFromViewKeyWallet( + path: path, + password: password, + address: data.address, + privateViewKey: data.privateViewKey, + height: height, + ); + + if (this.wallet == null) { + await exit(); + } + this.wallet = wallet; + + _setListener(); + + final newReceivingAddress = + await getCurrentReceivingAddress() ?? + Address( + walletId: walletId, + derivationIndex: 0, + derivationPath: null, + value: csWownero.getAddress(this.wallet!), + publicKey: [], + type: AddressType.cryptonote, + subType: AddressSubType.receiving, + ); + + await mainDB.updateOrPutAddresses([newReceivingAddress]); + await info.updateReceivingAddress( + newAddress: newReceivingAddress.value, + isar: mainDB.isar, + ); + + await updateNode(); + _setListener(); + + unawaited(csWownero.rescanBlockchain(this.wallet!)); + csWownero.startSyncing(this.wallet!); + + // await save(); + csWownero.startListeners(this.wallet!); + csWownero.startAutoSaving(this.wallet!); + } catch (e, s) { + Logging.instance.e( + "Exception rethrown from recoverViewOnly(): ", + error: e, + stackTrace: s, + ); + rethrow; + } + }); + } + + // ============== Private ==================================================== + + StreamSubscription? _torStatusListener; + StreamSubscription? _torPreferenceListener; + + final Mutex _torConnectingLock = Mutex(); + bool _requireMutex = false; +} diff --git a/lib/widgets/desktop/desktop_fee_dialog.dart b/lib/widgets/desktop/desktop_fee_dialog.dart index 82f92a5a2..5f9d16390 100644 --- a/lib/widgets/desktop/desktop_fee_dialog.dart +++ b/lib/widgets/desktop/desktop_fee_dialog.dart @@ -16,6 +16,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; import '../../wl_gen/interfaces/cs_monero_interface.dart'; +import '../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../animated_text.dart'; import '../conditional_parent.dart'; import 'desktop_dialog.dart'; @@ -60,12 +61,18 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityHigh()), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityHigh()), + ); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -110,12 +117,18 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityMedium()), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityMedium()), + ); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -160,12 +173,18 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero || coin is Wownero) { + if (coin is Monero) { final fee = await wallet.estimateFeeFor( amount, BigInt.from(csMonero.getTxPriorityNormal()), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; + } else if (coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from(csWownero.getTxPriorityNormal()), + ); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { diff --git a/lib/wl_gen/interfaces/cs_monero_interface.dart b/lib/wl_gen/interfaces/cs_monero_interface.dart index a676bd169..898a4a410 100644 --- a/lib/wl_gen/interfaces/cs_monero_interface.dart +++ b/lib/wl_gen/interfaces/cs_monero_interface.dart @@ -13,7 +13,7 @@ abstract class CsMoneroInterface { int getTxPriorityMedium(); int getTxPriorityNormal(); - bool walletExists(String path, {required CsCoin csCoin}); + bool walletExists(String path); Future estimateFee( int rate, @@ -23,7 +23,6 @@ abstract class CsMoneroInterface { Future loadWallet( String walletId, { - required CsCoin csCoin, required String path, required String password, }); @@ -35,7 +34,6 @@ abstract class CsMoneroInterface { }); Future getCreatedWallet({ - required CsCoin csCoin, required String path, required String password, required int wordCount, @@ -44,7 +42,6 @@ abstract class CsMoneroInterface { Future getRestoredWallet({ required String walletId, - required CsCoin csCoin, required String path, required String password, required String mnemonic, @@ -54,7 +51,6 @@ abstract class CsMoneroInterface { Future getRestoredFromViewKeyWallet({ required String walletId, - required CsCoin csCoin, required String path, required String password, required String address, @@ -153,17 +149,14 @@ abstract class CsMoneroInterface { Future thawOutput(WrappedWallet wallet, String keyImage); List getMoneroWordList(String language); - List getWowneroWordList(String language, int seedLength); - int getHeightByDate(DateTime date, {required CsCoin csCoin}); + int getHeightByDate(DateTime date); - bool validateAddress(String address, int network, {required CsCoin csCoin}); + bool validateAddress(String address, int network); String getSeed(WrappedWallet wallet); } -enum CsCoin { monero, wownero } - // forwarding class final class CsWalletListener { CsWalletListener({ diff --git a/lib/wl_gen/interfaces/cs_wownero_interface.dart b/lib/wl_gen/interfaces/cs_wownero_interface.dart new file mode 100644 index 000000000..2e317d932 --- /dev/null +++ b/lib/wl_gen/interfaces/cs_wownero_interface.dart @@ -0,0 +1,159 @@ +import '../../models/input.dart'; +import 'cs_monero_interface.dart'; +import 'cs_salvium_interface.dart' show WrappedWallet; + +export '../generated/cs_wownero_interface_impl.dart'; + +abstract class CsWowneroInterface { + const CsWowneroInterface(); + + void setUseCsWowneroLoggerInternal(bool enable); + + // tx prio forwarding + int getTxPriorityHigh(); + int getTxPriorityMedium(); + int getTxPriorityNormal(); + + bool walletExists(String path); + + Future estimateFee( + int rate, + BigInt amount, { + required WrappedWallet wallet, + }); + + Future loadWallet( + String walletId, { + required String path, + required String password, + }); + + String getAddress( + WrappedWallet wallet, { + int accountIndex = 0, + int addressIndex = 0, + }); + + Future getCreatedWallet({ + required String path, + required String password, + required int wordCount, + required String seedOffset, + }); + + Future getRestoredWallet({ + required String walletId, + required String path, + required String password, + required String mnemonic, + required String seedOffset, + int height = 0, + }); + + Future getRestoredFromViewKeyWallet({ + required String walletId, + required String path, + required String password, + required String address, + required String privateViewKey, + int height = 0, + }); + + String getTxKey(WrappedWallet wallet, String txid); + + Future save(WrappedWallet wallet); + + String getPublicViewKey(WrappedWallet wallet); + String getPrivateViewKey(WrappedWallet wallet); + String getPublicSpendKey(WrappedWallet wallet); + String getPrivateSpendKey(WrappedWallet wallet); + + Future isSynced(WrappedWallet wallet); + void startSyncing(WrappedWallet wallet); + void stopSyncing(WrappedWallet wallet); + + void startAutoSaving(WrappedWallet wallet); + void stopAutoSaving(WrappedWallet wallet); + + bool hasListeners(WrappedWallet wallet); + void addListener(WrappedWallet wallet, CsWalletListener listener); + void startListeners(WrappedWallet wallet); + void stopListeners(WrappedWallet wallet); + + Future rescanBlockchain(WrappedWallet wallet); + Future isConnectedToDaemon(WrappedWallet wallet); + + int getRefreshFromBlockHeight(WrappedWallet wallet); + void setRefreshFromBlockHeight(WrappedWallet wallet, int height); + + Future connect( + WrappedWallet wallet, { + required String daemonAddress, + required bool trusted, + String? daemonUsername, + String? daemonPassword, + bool useSSL = false, + bool isLightWallet = false, + String? socksProxyAddress, + }); + + Future> getAllTxids( + WrappedWallet wallet, { + bool refresh = false, + }); + + BigInt? getBalance(WrappedWallet wallet, {int accountIndex = 0}); + BigInt? getUnlockedBalance(WrappedWallet wallet, {int accountIndex = 0}); + + Future> getAllTxs( + WrappedWallet wallet, { + bool refresh = false, + }); + + Future> getTxs( + WrappedWallet wallet, { + required Set txids, + bool refresh = false, + }); + + Future createTx( + WrappedWallet wallet, { + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }); + + Future createTxMultiDest( + WrappedWallet wallet, { + required List outputs, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }); + + Future commitTx(WrappedWallet wallet, CsPendingTransaction tx); + + Future> getOutputs( + WrappedWallet wallet, { + bool refresh = false, + bool includeSpent = false, + }); + + Future freezeOutput(WrappedWallet wallet, String keyImage); + Future thawOutput(WrappedWallet wallet, String keyImage); + + List getWowneroWordList(String language, int seedLength); + + int getHeightByDate(DateTime date); + + bool validateAddress(String address, int network); + + String getSeed(WrappedWallet wallet); +} diff --git a/pubspec.lock b/pubspec.lock index 67468d4b6..efbd5cec2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -439,74 +439,74 @@ packages: dependency: "direct main" description: name: cs_monero - sha256: "7cfbcd25135a0710ad096678160d7668abed8979838165f06975adbe6bbec215" + sha256: "2bc89f862b4a4bc5312999a35d266db035d2e8760736662e148f07d2ab36e43d" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "2.0.0" cs_monero_flutter_libs: dependency: "direct main" description: name: cs_monero_flutter_libs - sha256: "47d716adc7b668653e359df785702d1213245f2fab6efa930a70b87e4cba23ae" + sha256: "759272ed87908572c0b7bb47edae2c49744bb981a632fc6156526a33a4d17f74" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "2.0.0" cs_monero_flutter_libs_android: dependency: transitive description: name: cs_monero_flutter_libs_android - sha256: "4b9d1117e63352d27bd0cb7115fc20d6212bd02a7e6ec3cd8ab2b37fddfb21eb" + sha256: eafe9b72370f92135e94ba8f25acf8776300d0442e0fdedc31c0ce715aa80daa url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" cs_monero_flutter_libs_android_arm64_v8a: dependency: transitive description: name: cs_monero_flutter_libs_android_arm64_v8a - sha256: cbb8704dcc1d02581a820b99188c97acaa140eaefedee9ce7d17910e24e5530f + sha256: d754cc1effdefdf8d1bf016fe69288f5a9cdde83269b5c153d4ac191dd18fa30 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_monero_flutter_libs_android_armeabi_v7a: dependency: transitive description: name: cs_monero_flutter_libs_android_armeabi_v7a - sha256: dc276544b169553a8a63855beaa6c2cf8180af68fb335ab1b629f2fa9370e123 + sha256: "21c00dcbd506a737750dd228a36395b3f6a9b142ecc9032ee60a746fca80d731" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_monero_flutter_libs_android_x86_64: dependency: transitive description: name: cs_monero_flutter_libs_android_x86_64 - sha256: fb02563c07d3fb4804925ec66446e26389ca2d92659493b72a6cf106765fa321 + sha256: d13fb62be52f44d0fa3aedeaabc33809284c4452728f4dfb9b4147038cae6fa2 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_monero_flutter_libs_ios: dependency: transitive description: name: cs_monero_flutter_libs_ios - sha256: "6fbe1590b0633f42c906dfada1db8e3ce4f8899eae8728a4bb9b696dc7fb5155" + sha256: "04673ca9a46f77ad0493f9dcd1cfe8e93ceacb78a2c46a0e733d0c6925231552" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" cs_monero_flutter_libs_linux: dependency: transitive description: name: cs_monero_flutter_libs_linux - sha256: "394a58f4efefd3857f1f3da03f21e33f1c2ca5141936db7a843e77286ffaa89e" + sha256: "4390b77d529cae362ed14ba4b5906c4e0f9bb4da5f3c8a0963f27c3e9c3197ff" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_monero_flutter_libs_macos: dependency: transitive description: name: cs_monero_flutter_libs_macos - sha256: e00616ab86a0ea18b3360dbae8d862b83fa450b1a83355647e34e8c64696a6c7 + sha256: "1cf88d9d04f327b577d245b579b15e75ad4c43ab7f1996bd369ddc0fa84aec53" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_monero_flutter_libs_platform_interface: dependency: transitive description: @@ -519,10 +519,10 @@ packages: dependency: transitive description: name: cs_monero_flutter_libs_windows - sha256: de265ed544a4edb9e778e88b56ccee098a1ad38cd4c4536a985f05d3dde95a23 + sha256: "3be0fe15bdcb6d1619b70d8dde52eaa02fdd028bd3cc522a4c472742f72a09a7" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" cs_salvium: dependency: "direct main" description: @@ -535,10 +535,10 @@ packages: dependency: "direct main" description: name: cs_salvium_flutter_libs - sha256: "2aea1bbb6e6b69ac0a8e4dace2efc50507a10651ad9bec862f6a5ccd06a76578" + sha256: "05a9f9e3f8cb539a310419d49270492e84d0f89bccb4c31512c854b1fe1f1c5f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" cs_salvium_flutter_libs_android: dependency: transitive description: @@ -575,10 +575,10 @@ packages: dependency: transitive description: name: cs_salvium_flutter_libs_ios - sha256: "4dc2447255f1c8997b6d26e72577e30ceab7f4622620549dc9de9eb8dccac35c" + sha256: aa474e7da65ba36e23afc4936ffbe39328808619fbdac44dacad9aa3aafb1b08 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" cs_salvium_flutter_libs_linux: dependency: transitive description: @@ -591,10 +591,10 @@ packages: dependency: transitive description: name: cs_salvium_flutter_libs_macos - sha256: "428e4eead3d507112cb6f0b70f69bc43430b3db60f0b4d731e0d6a6fab0b69bb" + sha256: "988077e7affc6443a1b665bac6df3b39269cc1352375cb805bd6d26aac82b46f" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" cs_salvium_flutter_libs_platform_interface: dependency: transitive description: @@ -611,6 +611,94 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + cs_wownero: + dependency: "direct main" + description: + name: cs_wownero + sha256: "9ff7a6be0f4524c6b9e5ca1d223df98e9455c7fe3b06f0b519280a175795e925" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + cs_wownero_flutter_libs: + dependency: "direct main" + description: + name: cs_wownero_flutter_libs + sha256: "68b6c682c6cce0915418aa6b01b137ef77652932f8b3fb1bc868bfd20d15c562" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + cs_wownero_flutter_libs_android: + dependency: transitive + description: + name: cs_wownero_flutter_libs_android + sha256: "14fe0666999d078bcd91ca499a9e9395dd270211eedb2250c533cbd036cb328b" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_android_arm64_v8a: + dependency: transitive + description: + name: cs_wownero_flutter_libs_android_arm64_v8a + sha256: "19f7e17ce7adf4615685f92b106c7f588dee80bb4768931c2505d2761a9fa06c" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_android_armeabi_v7a: + dependency: transitive + description: + name: cs_wownero_flutter_libs_android_armeabi_v7a + sha256: "1b7dc845674c938259dcbce6b9d6e6c305c98c2ff9b83803b00ea0f1268dfb28" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_android_x86_64: + dependency: transitive + description: + name: cs_wownero_flutter_libs_android_x86_64 + sha256: c318ce80ef418d53aeef3698c89c0497394269311f8c5b75f160e0f81610f9d9 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_ios: + dependency: transitive + description: + name: cs_wownero_flutter_libs_ios + sha256: f80dd0164902565d4fd0058da5be446a3ea5eaee2ca8651289a35d7fc93f3ca4 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_linux: + dependency: transitive + description: + name: cs_wownero_flutter_libs_linux + sha256: b60a700f0ef676405bfa67793fb7431f7d2ffbaccc1f6ad69001da0c2e0a07b0 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_macos: + dependency: transitive + description: + name: cs_wownero_flutter_libs_macos + sha256: e703975e6a6f698b01e07b238953547391faa4e930f09733d01f1346ee788fc7 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + cs_wownero_flutter_libs_platform_interface: + dependency: transitive + description: + name: cs_wownero_flutter_libs_platform_interface + sha256: "6a3bda9bcf5a904b36cbd0817e7ae8b7a64693e6f532f1783513e93c64436e6f" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + cs_wownero_flutter_libs_windows: + dependency: transitive + description: + name: cs_wownero_flutter_libs_windows + sha256: fe7485863a6e83e31581cef36c62d3507a9ea36c73d56842b4fee059f349cb49 + url: "https://pub.dev" + source: hosted + version: "1.2.0" csslib: dependency: transitive description: diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index cbcead484..6a4ad2841 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -28,6 +28,7 @@ dart "${APP_PROJECT_ROOT_DIR}/tool/process_pubspec_deps.dart" \ MWC \ MWEBD \ XMR \ + WOW \ SAL \ TOR \ EPIC \ @@ -41,6 +42,7 @@ dart "${APP_PROJECT_ROOT_DIR}/tool/gen_interfaces.dart" \ MWC \ MWEBD \ XMR \ + WOW \ SAL \ TOR \ EPIC \ diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml index 5bbaf75d7..e76d533bf 100644 --- a/scripts/app_config/templates/pubspec.template.yaml +++ b/scripts/app_config/templates/pubspec.template.yaml @@ -61,13 +61,18 @@ dependencies: # %%END_ENABLE_TOR%% # %%ENABLE_XMR%% -# cs_monero: 1.1.1 -# cs_monero_flutter_libs: 1.1.1 +# cs_monero: 2.0.0 +# cs_monero_flutter_libs: 2.0.0 # %%END_ENABLE_XMR%% +# %%ENABLE_WOW%% +# cs_wownero: 2.0.0 +# cs_wownero_flutter_libs: 2.0.0 +# %%END_ENABLE_WOW%% + # %%ENABLE_SAL%% # cs_salvium: ^2.0.0 -# cs_salvium_flutter_libs: ^2.0.0 +# cs_salvium_flutter_libs: ^2.0.1 # %%END_ENABLE_SAL%% # %%ENABLE_MWEBD%% diff --git a/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart new file mode 100644 index 000000000..51c5ef094 --- /dev/null +++ b/tool/wl_templates/WOW_cs_wownero_interface_impl.template.dart @@ -0,0 +1,510 @@ +//ON +import 'package:cs_wownero/cs_wownero.dart' as lib_wownero; +import 'package:cs_wownero/src/deprecated/get_height_by_date.dart' + as cs_wownero_deprecated; +import 'package:cs_wownero/src/ffi_bindings/wownero_wallet_bindings.dart' + as wow_wallet_ffi; + +//END_ON +import '../../models/input.dart'; +import '../interfaces/cs_monero_interface.dart'; +import '../interfaces/cs_salvium_interface.dart' show WrappedWallet; +import '../interfaces/cs_wownero_interface.dart'; + +CsWowneroInterface get csWownero => _getInterface(); + +//OFF +CsWowneroInterface _getInterface() => throw Exception("WOW not enabled!"); + +//END_OFF +//ON +CsWowneroInterface _getInterface() => const _CsWowneroInterfaceImpl(); + +class _CsWowneroInterfaceImpl extends CsWowneroInterface { + const _CsWowneroInterfaceImpl(); + + @override + void setUseCsWowneroLoggerInternal(bool enable) => + lib_wownero.Logging.useLogger = enable; + + @override + bool walletExists(String path) => + lib_wownero.WowneroWallet.isWalletExist(path); + + @override + Future estimateFee( + int rate, + BigInt amount, { + required WrappedWallet wallet, + }) { + lib_wownero.TransactionPriority priority; + switch (rate) { + case 1: + priority = lib_wownero.TransactionPriority.low; + break; + case 2: + priority = lib_wownero.TransactionPriority.medium; + break; + case 3: + priority = lib_wownero.TransactionPriority.high; + break; + case 4: + priority = lib_wownero.TransactionPriority.last; + break; + case 0: + default: + priority = lib_wownero.TransactionPriority.normal; + break; + } + + return wallet.get().estimateFee( + priority, + amount.toInt(), + ); + } + + @override + Future loadWallet( + String walletId, { + required String path, + required String password, + }) async { + return WrappedWallet( + await lib_wownero.WowneroWallet.loadWallet( + path: path, + password: password, + ), + ); + } + + @override + int getTxPriorityHigh() => lib_wownero.TransactionPriority.high.value; + + @override + int getTxPriorityMedium() => lib_wownero.TransactionPriority.medium.value; + + @override + int getTxPriorityNormal() => lib_wownero.TransactionPriority.normal.value; + + @override + String getAddress( + WrappedWallet wallet, { + int accountIndex = 0, + int addressIndex = 0, + }) => wallet + .get() + .getAddress(accountIndex: accountIndex, addressIndex: addressIndex) + .value; + + @override + Future getCreatedWallet({ + required String path, + required String password, + required int wordCount, + required String seedOffset, + }) async { + final type = switch (wordCount) { + 16 => lib_wownero.WowneroSeedType.sixteen, + 25 => lib_wownero.WowneroSeedType.twentyFive, + _ => throw Exception("Invalid mnemonic word count: $wordCount"), + }; + + final wallet = await lib_wownero.WowneroWallet.create( + path: path, + password: password, + seedType: type, + seedOffset: seedOffset, + ); + + return WrappedWallet(wallet); + } + + @override + Future getRestoredWallet({ + required String walletId, + + required String path, + required String password, + required String mnemonic, + required String seedOffset, + int height = 0, + }) async { + return WrappedWallet( + await lib_wownero.WowneroWallet.restoreWalletFromSeed( + path: path, + password: password, + seed: mnemonic, + restoreHeight: height, + seedOffset: seedOffset, + ), + ); + } + + @override + Future getRestoredFromViewKeyWallet({ + required String walletId, + + required String path, + required String password, + required String address, + required String privateViewKey, + int height = 0, + }) async { + return WrappedWallet( + await lib_wownero.WowneroWallet.createViewOnlyWallet( + path: path, + password: password, + address: address, + viewKey: privateViewKey, + restoreHeight: height, + ), + ); + } + + @override + String getTxKey(WrappedWallet wallet, String txid) => + wallet.get().getTxKey(txid); + + @override + Future save(WrappedWallet wallet) => + wallet.get().save(); + + @override + String getPublicViewKey(WrappedWallet wallet) => + wallet.get().getPublicViewKey(); + + @override + String getPrivateViewKey(WrappedWallet wallet) => + wallet.get().getPrivateViewKey(); + + @override + String getPublicSpendKey(WrappedWallet wallet) => + wallet.get().getPublicSpendKey(); + + @override + String getPrivateSpendKey(WrappedWallet wallet) => + wallet.get().getPrivateSpendKey(); + + @override + Future isSynced(WrappedWallet wallet) => + wallet.get().isSynced(); + + @override + void startSyncing(WrappedWallet wallet) => + wallet.get().startSyncing(); + + @override + void stopSyncing(WrappedWallet wallet) => + wallet.get().stopSyncing(); + + @override + void startAutoSaving(WrappedWallet wallet) => + wallet.get().startAutoSaving(); + + @override + void stopAutoSaving(WrappedWallet wallet) => + wallet.get().stopAutoSaving(); + + @override + bool hasListeners(WrappedWallet wallet) => + wallet.get().getListeners().isNotEmpty; + + @override + void addListener(WrappedWallet wallet, CsWalletListener listener) => + wallet.get().addListener( + lib_wownero.WalletListener( + onSyncingUpdate: listener.onSyncingUpdate, + onNewBlock: listener.onNewBlock, + onBalancesChanged: listener.onBalancesChanged, + onError: listener.onError, + ), + ); + + @override + void startListeners(WrappedWallet wallet) => + wallet.get().startListeners(); + + @override + void stopListeners(WrappedWallet wallet) => + wallet.get().stopListeners(); + + @override + int getRefreshFromBlockHeight(WrappedWallet wallet) => + wallet.get().getRefreshFromBlockHeight(); + + @override + void setRefreshFromBlockHeight(WrappedWallet wallet, int height) => + wallet.get().setRefreshFromBlockHeight(height); + + @override + Future rescanBlockchain(WrappedWallet wallet) => + wallet.get().rescanBlockchain(); + + @override + Future isConnectedToDaemon(WrappedWallet wallet) => + wallet.get().isConnectedToDaemon(); + + @override + Future connect( + WrappedWallet wallet, { + required String daemonAddress, + required bool trusted, + String? daemonUsername, + String? daemonPassword, + bool useSSL = false, + bool isLightWallet = false, + String? socksProxyAddress, + }) async { + await wallet.get().connect( + daemonAddress: daemonAddress, + trusted: trusted, + daemonUsername: daemonUsername, + daemonPassword: daemonPassword, + useSSL: useSSL, + socksProxyAddress: socksProxyAddress, + isLightWallet: isLightWallet, + ); + } + + @override + Future> getAllTxids( + WrappedWallet wallet, { + bool refresh = false, + }) => wallet.get().getAllTxids(refresh: refresh); + + @override + BigInt? getBalance(WrappedWallet wallet, {int accountIndex = 0}) => + wallet.get().getBalance(accountIndex: accountIndex); + + @override + BigInt? getUnlockedBalance(WrappedWallet wallet, {int accountIndex = 0}) => + wallet.get().getUnlockedBalance( + accountIndex: accountIndex, + ); + + @override + Future> getAllTxs( + WrappedWallet wallet, { + bool refresh = false, + }) async { + final transactions = await wallet.get().getAllTxs( + refresh: refresh, + ); + return transactions + .map( + (e) => CsTransaction( + displayLabel: e.displayLabel, + description: e.description, + fee: e.fee, + confirmations: e.confirmations, + blockHeight: e.blockHeight, + accountIndex: e.accountIndex, + addressIndexes: e.addressIndexes, + paymentId: e.paymentId, + amount: e.amount, + isSpend: e.isSpend, + hash: e.hash, + key: e.key, + timeStamp: e.timeStamp, + minConfirms: e.minConfirms.value, + ), + ) + .toList(); + } + + @override + Future> getTxs( + WrappedWallet wallet, { + required Set txids, + bool refresh = false, + }) async { + final transactions = await wallet.get().getTxs( + txids: txids, + refresh: refresh, + ); + return transactions + .map( + (e) => CsTransaction( + displayLabel: e.displayLabel, + description: e.description, + fee: e.fee, + confirmations: e.confirmations, + blockHeight: e.blockHeight, + accountIndex: e.accountIndex, + addressIndexes: e.addressIndexes, + paymentId: e.paymentId, + amount: e.amount, + isSpend: e.isSpend, + hash: e.hash, + key: e.key, + timeStamp: e.timeStamp, + minConfirms: e.minConfirms.value, + ), + ) + .toList(); + } + + @override + Future createTx( + WrappedWallet wallet, { + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }) async { + final pending = await wallet.get().createTx( + output: lib_wownero.Recipient( + address: output.address, + amount: output.amount, + ), + paymentId: "", + sweep: sweep, + priority: lib_wownero.TransactionPriority.values.firstWhere( + (e) => e.value == priority, + ), + preferredInputs: preferredInputs + ?.map( + (e) => lib_wownero.Output( + address: e.address!, + hash: e.utxo.txid, + keyImage: e.utxo.keyImage!, + value: e.value, + isFrozen: e.utxo.isBlocked, + isUnlocked: + e.utxo.blockHeight != null && + (currentHeight - (e.utxo.blockHeight ?? 0)) >= minConfirms, + height: e.utxo.blockHeight ?? 0, + vout: e.utxo.vout, + spent: e.utxo.used ?? false, + spentHeight: null, // doesn't matter here + coinbase: e.utxo.isCoinbase, + ), + ) + .toList(), + accountIndex: accountIndex, + ); + + return CsPendingTransaction( + pending, + pending.amount, + pending.fee, + pending.txid, + ); + } + + @override + Future createTxMultiDest( + WrappedWallet wallet, { + required List outputs, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }) async { + final pending = await wallet.get().createTxMultiDest( + outputs: outputs + .map( + (e) => lib_wownero.Recipient(address: e.address, amount: e.amount), + ) + .toList(), + paymentId: "", + sweep: sweep, + priority: lib_wownero.TransactionPriority.values.firstWhere( + (e) => e.value == priority, + ), + preferredInputs: preferredInputs + ?.map( + (e) => lib_wownero.Output( + address: e.address!, + hash: e.utxo.txid, + keyImage: e.utxo.keyImage!, + value: e.value, + isFrozen: e.utxo.isBlocked, + isUnlocked: + e.utxo.blockHeight != null && + (currentHeight - (e.utxo.blockHeight ?? 0)) >= minConfirms, + height: e.utxo.blockHeight ?? 0, + vout: e.utxo.vout, + spent: e.utxo.used ?? false, + spentHeight: null, // doesn't matter here + coinbase: e.utxo.isCoinbase, + ), + ) + .toList(), + accountIndex: accountIndex, + ); + + return CsPendingTransaction( + pending, + pending.amount, + pending.fee, + pending.txid, + ); + } + + @override + Future commitTx(WrappedWallet wallet, CsPendingTransaction tx) => wallet + .get() + .commitTx(tx.value as lib_wownero.PendingTransaction); + + @override + Future> getOutputs( + WrappedWallet wallet, { + bool refresh = false, + bool includeSpent = false, + }) async { + final outputs = await wallet.get().getOutputs( + includeSpent: includeSpent, + refresh: refresh, + ); + + return outputs + .map( + (e) => CsOutput( + address: e.address, + hash: e.hash, + keyImage: e.keyImage, + value: e.value, + isFrozen: e.isFrozen, + isUnlocked: e.isUnlocked, + height: e.height, + spentHeight: e.spentHeight, + vout: e.vout, + spent: e.spent, + coinbase: e.coinbase, + ), + ) + .toList(); + } + + @override + Future freezeOutput(WrappedWallet wallet, String keyImage) => + wallet.get().freezeOutput(keyImage); + + @override + Future thawOutput(WrappedWallet wallet, String keyImage) => + wallet.get().thawOutput(keyImage); + + @override + List getWowneroWordList(String language, int seedLength) => + lib_wownero.getWowneroWordList(language, seedWordsLength: seedLength); + + @override + int getHeightByDate(DateTime date) => + cs_wownero_deprecated.getWowneroHeightByDate(date: date); + + @override + bool validateAddress(String address, int network) => + wow_wallet_ffi.validateAddress(address, network); + + @override + String getSeed(WrappedWallet wallet) => + wallet.get().getSeed(); +} + +//END_ON diff --git a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart index af0016e6a..f1d782c1b 100644 --- a/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart +++ b/tool/wl_templates/XMR_cs_monero_interface_impl.template.dart @@ -4,8 +4,6 @@ import 'package:cs_monero/src/deprecated/get_height_by_date.dart' as cs_monero_deprecated; import 'package:cs_monero/src/ffi_bindings/monero_wallet_bindings.dart' as xmr_wallet_ffi; -import 'package:cs_monero/src/ffi_bindings/wownero_wallet_bindings.dart' - as wow_wallet_ffi; //END_ON import '../../models/input.dart'; @@ -15,7 +13,7 @@ import '../interfaces/cs_salvium_interface.dart' show WrappedWallet; CsMoneroInterface get csMonero => _getInterface(); //OFF -CsMoneroInterface _getInterface() => throw Exception("XMR/WOW not enabled!"); +CsMoneroInterface _getInterface() => throw Exception("XMR not enabled!"); //END_OFF //ON @@ -29,10 +27,7 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface { lib_monero.Logging.useLogger = enable; @override - bool walletExists(String path, {required CsCoin csCoin}) => switch (csCoin) { - CsCoin.monero => lib_monero.MoneroWallet.isWalletExist(path), - CsCoin.wownero => lib_monero.WowneroWallet.isWalletExist(path), - }; + bool walletExists(String path) => lib_monero.MoneroWallet.isWalletExist(path); @override Future estimateFee( @@ -69,21 +64,12 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface { @override Future loadWallet( String walletId, { - required CsCoin csCoin, required String path, required String password, }) async { - return WrappedWallet(switch (csCoin) { - CsCoin.monero => await lib_monero.MoneroWallet.loadWallet( - path: path, - password: password, - ), - - CsCoin.wownero => await lib_monero.WowneroWallet.loadWallet( - path: path, - password: password, - ), - }); + return WrappedWallet( + await lib_monero.MoneroWallet.loadWallet(path: path, password: password), + ); } @override @@ -107,45 +93,23 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface { @override Future getCreatedWallet({ - required CsCoin csCoin, required String path, required String password, required int wordCount, required String seedOffset, }) async { - final lib_monero.Wallet wallet; - - switch (csCoin) { - case CsCoin.monero: - final type = switch (wordCount) { - 16 => lib_monero.MoneroSeedType.sixteen, - 25 => lib_monero.MoneroSeedType.twentyFive, - _ => throw Exception("Invalid mnemonic word count: $wordCount"), - }; - - wallet = await lib_monero.MoneroWallet.create( - path: path, - password: password, - seedType: type, - seedOffset: seedOffset, - ); - break; - - case CsCoin.wownero: - final type = switch (wordCount) { - 16 => lib_monero.WowneroSeedType.sixteen, - 25 => lib_monero.WowneroSeedType.twentyFive, - _ => throw Exception("Invalid mnemonic word count: $wordCount"), - }; - - wallet = await lib_monero.WowneroWallet.create( - path: path, - password: password, - seedType: type, - seedOffset: seedOffset, - ); - break; - } + final type = switch (wordCount) { + 16 => lib_monero.MoneroSeedType.sixteen, + 25 => lib_monero.MoneroSeedType.twentyFive, + _ => throw Exception("Invalid mnemonic word count: $wordCount"), + }; + + final wallet = await lib_monero.MoneroWallet.create( + path: path, + password: password, + seedType: type, + seedOffset: seedOffset, + ); return WrappedWallet(wallet); } @@ -153,59 +117,41 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface { @override Future getRestoredWallet({ required String walletId, - required CsCoin csCoin, required String path, required String password, required String mnemonic, required String seedOffset, int height = 0, }) async { - return WrappedWallet(switch (csCoin) { - CsCoin.monero => await lib_monero.MoneroWallet.restoreWalletFromSeed( - path: path, - password: password, - seed: mnemonic, - restoreHeight: height, - seedOffset: seedOffset, - ), - - CsCoin.wownero => await lib_monero.WowneroWallet.restoreWalletFromSeed( + return WrappedWallet( + await lib_monero.MoneroWallet.restoreWalletFromSeed( path: path, password: password, seed: mnemonic, restoreHeight: height, seedOffset: seedOffset, ), - }); + ); } @override Future getRestoredFromViewKeyWallet({ required String walletId, - required CsCoin csCoin, required String path, required String password, required String address, required String privateViewKey, int height = 0, }) async { - return WrappedWallet(switch (csCoin) { - CsCoin.monero => await lib_monero.MoneroWallet.createViewOnlyWallet( - path: path, - password: password, - address: address, - viewKey: privateViewKey, - restoreHeight: height, - ), - - CsCoin.wownero => await lib_monero.WowneroWallet.createViewOnlyWallet( + return WrappedWallet( + await lib_monero.MoneroWallet.createViewOnlyWallet( path: path, password: password, address: address, viewKey: privateViewKey, restoreHeight: height, ), - }); + ); } @override @@ -542,24 +488,12 @@ class _CsMoneroInterfaceImpl extends CsMoneroInterface { lib_monero.getMoneroWordList(language); @override - List getWowneroWordList(String language, int seedLength) => - lib_monero.getWowneroWordList(language, seedWordsLength: seedLength); - - @override - int getHeightByDate(DateTime date, {required CsCoin csCoin}) => - switch (csCoin) { - CsCoin.monero => cs_monero_deprecated.getMoneroHeightByDate(date: date), - CsCoin.wownero => cs_monero_deprecated.getWowneroHeightByDate( - date: date, - ), - }; + int getHeightByDate(DateTime date) => + cs_monero_deprecated.getMoneroHeightByDate(date: date); @override - bool validateAddress(String address, int network, {required CsCoin csCoin}) => - switch (csCoin) { - CsCoin.monero => xmr_wallet_ffi.validateAddress(address, network), - CsCoin.wownero => wow_wallet_ffi.validateAddress(address, network), - }; + bool validateAddress(String address, int network) => + xmr_wallet_ffi.validateAddress(address, network); @override String getSeed(WrappedWallet wallet) => From 025404b63ce5dac9deebdd9b53c7229cd71e90a2 Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 28 Oct 2025 16:43:44 -0600 Subject: [PATCH 5/5] WIP extract some kind of CryptonoteWallet interface --- ...w_wallet_recovery_phrase_warning_view.dart | 582 +++++----- .../verify_recovery_phrase_view.dart | 25 +- lib/pages/send_view/send_view.dart | 34 +- .../transaction_fee_selection_sheet.dart | 34 +- .../add_edit_node_view.dart | 994 +++++++++--------- .../helpers/restore_create_backup.dart | 6 +- .../wallet_network_settings_view.dart | 15 +- .../wallet_settings_view.dart | 213 ++-- .../edit_refresh_height_view.dart | 21 +- .../wallet_settings_wallet_settings_view.dart | 114 +- .../firo_rescan_recovery_error_dialog.dart | 251 ++--- .../tx_v2/transaction_v2_details_view.dart | 5 +- lib/pages/wallet_view/wallet_view.dart | 6 +- .../sub_widgets/desktop_send_fee_form.dart | 11 +- .../sub_widgets/desktop_wallet_features.dart | 5 +- .../unlock_wallet_keys_desktop.dart | 111 +- .../sub_widgets/wallet_options_button.dart | 169 ++- .../churning/churning_service_provider.dart | 4 +- lib/services/churning_service.dart | 21 +- lib/services/wallets.dart | 95 +- .../intermediate/cryptonote_wallet.dart | 59 +- .../intermediate/lib_monero_wallet.dart | 103 +- .../intermediate/lib_salvium_wallet.dart | 103 +- .../intermediate/lib_wownero_wallet.dart | 103 +- lib/widgets/desktop/desktop_fee_dialog.dart | 33 +- lib/widgets/tx_key_widget.dart | 45 +- 26 files changed, 1626 insertions(+), 1536 deletions(-) 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 7e3f57d32..031a8b41d 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 @@ -30,8 +30,7 @@ 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/intermediate/lib_salvium_wallet.dart'; +import '../../../wallets/wallet/intermediate/cryptonote_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'; @@ -81,12 +80,11 @@ class _NewWalletRecoveryPhraseWarningViewState if (mounted) { await showDialog( context: context, - builder: - (_) => StackOkDialog( - title: "Create Wallet Error", - message: ex?.toString() ?? "Unknown error", - maxWidth: 600, - ), + builder: (_) => StackOkDialog( + title: "Create Wallet Error", + message: ex?.toString() ?? "Unknown error", + maxWidth: 600, + ), ); } return; @@ -195,8 +193,10 @@ class _NewWalletRecoveryPhraseWarningViewState } else if (wordCount > 0) { if (ref.read(pNewWalletOptions.state).state != null) { if (coin.hasMnemonicPassphraseSupport) { - mnemonicPassphrase = - ref.read(pNewWalletOptions.state).state!.mnemonicPassphrase; + mnemonicPassphrase = ref + .read(pNewWalletOptions.state) + .state! + .mnemonicPassphrase; } else { // this may not be epiccash and sol specific? if (coin is Epiccash || coin is Solana) { @@ -204,8 +204,10 @@ class _NewWalletRecoveryPhraseWarningViewState } } - wordCount = - ref.read(pNewWalletOptions.state).state!.mnemonicWordsCount; + wordCount = ref + .read(pNewWalletOptions.state) + .state! + .mnemonicWordsCount; } else { mnemonicPassphrase = ""; } @@ -230,9 +232,7 @@ class _NewWalletRecoveryPhraseWarningViewState privateKey: privateKey, ); - if (wallet is LibMoneroWallet) { - await wallet.init(wordCount: wordCount); - } else if (wallet is LibSalviumWallet) { + if (wallet is CryptonoteWallet) { await wallet.init(wordCount: wordCount); } else { await wallet.init(); @@ -241,8 +241,8 @@ class _NewWalletRecoveryPhraseWarningViewState // 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(); + final fetchedMnemonic = await (wallet as MnemonicInterface) + .getMnemonicAsWords(); return (wallet, fetchedMnemonic); } catch (e, s) { @@ -269,46 +269,43 @@ class _NewWalletRecoveryPhraseWarningViewState return MasterScaffold( isDesktop: isDesktop, - appBar: - isDesktop - ? const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - trailing: ExitToMyStackButton(), - ) - : AppBar( - leading: const AppBarBackButton(), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AppBarIconButton( - semanticsLabel: - "Question Button. Opens A Dialog For Recovery Phrase Explanation.", - icon: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 20, - height: 20, - color: - Theme.of( - context, - ).extension()!.accentColorDark, - ), - onPressed: () async { - await showDialog( - context: context, - builder: - (context) => - const RecoveryPhraseExplanationDialog(), - ); - }, + appBar: isDesktop + ? const DesktopAppBar( + isCompactHeight: false, + leading: AppBarBackButton(), + trailing: ExitToMyStackButton(), + ) + : AppBar( + leading: const AppBarBackButton(), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AppBarIconButton( + semanticsLabel: + "Question Button. Opens A Dialog For Recovery Phrase Explanation.", + icon: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.accentColorDark, ), + onPressed: () async { + await showDialog( + context: context, + builder: (context) => + const RecoveryPhraseExplanationDialog(), + ); + }, ), - ], - ), + ), + ], + ), body: SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( @@ -319,10 +316,9 @@ class _NewWalletRecoveryPhraseWarningViewState padding: const EdgeInsets.all(16), child: Center( child: Column( - crossAxisAlignment: - isDesktop - ? CrossAxisAlignment.center - : CrossAxisAlignment.stretch, + crossAxisAlignment: isDesktop + ? CrossAxisAlignment.center + : CrossAxisAlignment.stretch, children: [ /*if (isDesktop) const Spacer( @@ -341,233 +337,206 @@ class _NewWalletRecoveryPhraseWarningViewState Text( "Recovery Phrase", textAlign: TextAlign.center, - style: - isDesktop - ? STextStyles.desktopH2(context) - : STextStyles.pageTitleH1(context), + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), ), SizedBox(height: isDesktop ? 32 : 16), RoundedWhiteContainer( padding: const EdgeInsets.all(32), width: isDesktop ? 480 : null, - child: - isDesktop - ? Text( - "On the next screen you will see " - "$seedCount " - "words that make up your recovery phrase.\n\nPlease " - "write it down. Keep it safe and never share it with " - "anyone. Your recovery phrase is the only way you can" - " access your funds if you forget your PIN, lose your" - " phone, etc.\n\n${AppConfig.appName} does not keep nor is " - "able to restore your recover phrase. Only you have " - "access to your wallet.", - style: - isDesktop - ? STextStyles.desktopTextMediumRegular( - context, - ) - : STextStyles.subtitle( - context, - ).copyWith(fontSize: 12), - ) - : Column( - children: [ - Text( - "Important", + child: isDesktop + ? Text( + "On the next screen you will see " + "$seedCount " + "words that make up your recovery phrase.\n\nPlease " + "write it down. Keep it safe and never share it with " + "anyone. Your recovery phrase is the only way you can" + " access your funds if you forget your PIN, lose your" + " phone, etc.\n\n${AppConfig.appName} does not keep nor is " + "able to restore your recover phrase. Only you have " + "access to your wallet.", + style: isDesktop + ? STextStyles.desktopTextMediumRegular( + context, + ) + : STextStyles.subtitle( + context, + ).copyWith(fontSize: 12), + ) + : Column( + children: [ + Text( + "Important", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorBlue, + ), + ), + const SizedBox(height: 24), + RichText( + textAlign: TextAlign.center, + text: TextSpan( style: STextStyles.desktopH3( context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorBlue, - ), + ).copyWith(fontSize: 18), + children: [ + TextSpan( + text: + "On the next screen you will be given ", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + fontSize: 18, + height: 1.3, + ), + ), + TextSpan( + text: "$seedCount words", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorBlue, + fontSize: 18, + height: 1.3, + ), + ), + TextSpan( + text: ". They are your ", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + fontSize: 18, + height: 1.3, + ), + ), + TextSpan( + text: "recovery phrase", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorBlue, + fontSize: 18, + height: 1.3, + ), + ), + TextSpan( + text: ".", + style: STextStyles.desktopH3(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + fontSize: 18, + height: 1.3, + ), + ), + ], ), - const SizedBox(height: 24), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: STextStyles.desktopH3( - context, - ).copyWith(fontSize: 18), + ), + const SizedBox(height: 40), + Column( + children: [ + Row( children: [ - TextSpan( - text: - "On the next screen you will be given ", - style: STextStyles.desktopH3( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textDark, - fontSize: 18, - height: 1.3, - ), - ), - TextSpan( - text: "$seedCount words", - style: STextStyles.desktopH3( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorBlue, - fontSize: 18, - height: 1.3, + SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + radiusMultiplier: 20, + padding: const EdgeInsets.all(9), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: SvgPicture.asset( + Assets.svg.pencil, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), ), ), - TextSpan( - text: ". They are your ", - style: STextStyles.desktopH3( + const SizedBox(width: 20), + Text( + "Write them down.", + style: STextStyles.navBarTitle( context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textDark, - fontSize: 18, - height: 1.3, ), ), - TextSpan( - text: "recovery phrase", - style: STextStyles.desktopH3( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorBlue, - fontSize: 18, - height: 1.3, + ], + ), + const SizedBox(height: 30), + Row( + children: [ + SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + radiusMultiplier: 20, + padding: const EdgeInsets.all(8), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: SvgPicture.asset( + Assets.svg.lock, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), ), ), - TextSpan( - text: ".", - style: STextStyles.desktopH3( + const SizedBox(width: 20), + Text( + "Keep them safe.", + style: STextStyles.navBarTitle( context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textDark, - fontSize: 18, - height: 1.3, ), ), ], ), - ), - const SizedBox(height: 40), - Column( - children: [ - Row( - children: [ - SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - radiusMultiplier: 20, - padding: const EdgeInsets.all(9), - color: - Theme.of(context) - .extension()! - .buttonBackSecondary, - child: SvgPicture.asset( - Assets.svg.pencil, - color: - Theme.of(context) - .extension< - StackColors - >()! - .accentColorDark, - ), - ), - ), - const SizedBox(width: 20), - Text( - "Write them down.", - style: STextStyles.navBarTitle( - context, - ), - ), - ], - ), - const SizedBox(height: 30), - Row( - children: [ - SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - radiusMultiplier: 20, - padding: const EdgeInsets.all(8), - color: - Theme.of(context) - .extension()! - .buttonBackSecondary, - child: SvgPicture.asset( - Assets.svg.lock, - color: - Theme.of(context) - .extension< - StackColors - >()! - .accentColorDark, - ), + const SizedBox(height: 30), + Row( + children: [ + SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + radiusMultiplier: 20, + padding: const EdgeInsets.all(8), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: SvgPicture.asset( + Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), - const SizedBox(width: 20), - Text( - "Keep them safe.", + ), + const SizedBox(width: 20), + Expanded( + child: Text( + "Do not show them to anyone.", style: STextStyles.navBarTitle( context, ), ), - ], - ), - const SizedBox(height: 30), - Row( - children: [ - SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - radiusMultiplier: 20, - padding: const EdgeInsets.all(8), - color: - Theme.of(context) - .extension()! - .buttonBackSecondary, - child: SvgPicture.asset( - Assets.svg.eyeSlash, - color: - Theme.of(context) - .extension< - StackColors - >()! - .accentColorDark, - ), - ), - ), - const SizedBox(width: 20), - Expanded( - child: Text( - "Do not show them to anyone.", - style: STextStyles.navBarTitle( - context, - ), - ), - ), - ], - ), - ], - ), - ], - ), + ), + ], + ), + ], + ), + ], + ), ), if (!isDesktop) const Spacer(), if (!isDesktop) const SizedBox(height: 16), @@ -584,10 +553,9 @@ class _NewWalletRecoveryPhraseWarningViewState children: [ GestureDetector( onTap: () { - final value = - ref - .read(checkBoxStateProvider.state) - .state; + final value = ref + .read(checkBoxStateProvider.state) + .state; ref.read(checkBoxStateProvider.state).state = !value; }, @@ -603,18 +571,19 @@ class _NewWalletRecoveryPhraseWarningViewState child: Checkbox( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - value: - ref - .watch( - checkBoxStateProvider.state, - ) - .state, + value: ref + .watch( + checkBoxStateProvider.state, + ) + .state, onChanged: (newValue) { ref - .read( - checkBoxStateProvider.state, - ) - .state = newValue!; + .read( + checkBoxStateProvider + .state, + ) + .state = + newValue!; }, ), ), @@ -622,14 +591,13 @@ class _NewWalletRecoveryPhraseWarningViewState Flexible( child: Text( "I understand that ${AppConfig.appName} does not keep and cannot restore my recovery phrase, and If I lose my recovery phrase, I will not be able to access my funds.", - style: - isDesktop - ? STextStyles.desktopTextMedium( - context, - ) - : STextStyles.baseXS( - context, - ).copyWith(height: 1.3), + style: isDesktop + ? STextStyles.desktopTextMedium( + context, + ) + : STextStyles.baseXS( + context, + ).copyWith(height: 1.3), ), ), ], @@ -644,41 +612,39 @@ class _NewWalletRecoveryPhraseWarningViewState child: TextButton( onPressed: ref - .read(checkBoxStateProvider.state) - .state - ? _initNewWallet - : null, + .read(checkBoxStateProvider.state) + .state + ? _initNewWallet + : null, style: ref - .read(checkBoxStateProvider.state) - .state - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle( - context, - ) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonStyle( - context, - ), + .read(checkBoxStateProvider.state) + .state + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle( + context, + ) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonStyle( + context, + ), child: Text( "View recovery phrase", - style: - isDesktop - ? ref - .read( - checkBoxStateProvider - .state, - ) - .state - ? STextStyles.desktopButtonEnabled( + style: isDesktop + ? ref + .read( + checkBoxStateProvider.state, + ) + .state + ? STextStyles.desktopButtonEnabled( context, ) - : STextStyles.desktopButtonDisabled( + : STextStyles.desktopButtonDisabled( context, ) - : STextStyles.button(context), + : STextStyles.button(context), ), ), ), diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index b5063ae83..e19eca141 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -37,8 +37,7 @@ import '../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/impl/xelis_wallet.dart'; -import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -112,8 +111,7 @@ class _VerifyRecoveryPhraseViewState final ViewOnlyWalletType viewOnlyWalletType; if (widget.wallet is ExtendedKeysInterface) { viewOnlyWalletType = ViewOnlyWalletType.xPub; - } else if (widget.wallet is LibMoneroWallet || - widget.wallet is LibSalviumWallet) { + } else if (widget.wallet is CryptonoteWallet) { if (widget.wallet.cryptoCurrency is Monero) { height = csMonero.getHeightByDate( DateTime.now().subtract(const Duration(days: 7)), @@ -174,23 +172,8 @@ class _VerifyRecoveryPhraseViewState walletId: voInfo.walletId, xPubs: [xPub], ); - } else if (widget.wallet is LibMoneroWallet) { - final w = widget.wallet as LibMoneroWallet; - - final info = await w - .hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing(); - final address = info.$1; - final privateViewKey = info.$2; - - await w.exit(); - - viewOnlyData = CryptonoteViewOnlyWalletData( - walletId: voInfo.walletId, - address: address, - privateViewKey: privateViewKey, - ); - } else if (widget.wallet is LibSalviumWallet) { - final w = widget.wallet as LibSalviumWallet; + } else if (widget.wallet is CryptonoteWallet) { + final w = widget.wallet as CryptonoteWallet; final info = await w .hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing(); diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index e56f12c5e..54cdf51eb 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -49,11 +49,13 @@ import '../../utilities/show_loading.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/models/tx_data.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; @@ -74,8 +76,6 @@ import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; import '../../widgets/textfield_icon_button.dart'; -import '../../wl_gen/interfaces/cs_monero_interface.dart'; -import '../../wl_gen/interfaces/cs_wownero_interface.dart'; import '../address_book_views/address_book_view.dart'; import '../coin_control/coin_control_view.dart'; import 'confirm_transaction_view.dart'; @@ -560,39 +560,17 @@ class _SendViewState extends ConsumerState { } Amount fee; - if (coin is Monero) { + if (coin is CryptonoteCurrency) { final int specialMoneroId; switch (ref.read(feeRateTypeMobileStateProvider.state).state) { case FeeRateType.fast: - specialMoneroId = csMonero.getTxPriorityHigh(); + specialMoneroId = (wallet as CryptonoteWallet).getTxPriorityHigh(); break; case FeeRateType.average: - specialMoneroId = csMonero.getTxPriorityMedium(); + specialMoneroId = (wallet as CryptonoteWallet).getTxPriorityMedium(); break; case FeeRateType.slow: - specialMoneroId = csMonero.getTxPriorityNormal(); - break; - default: - throw ArgumentError("custom fee not available for monero"); - } - - fee = await wallet.estimateFeeFor(amount, BigInt.from(specialMoneroId)); - cachedFees[amount] = ref - .read(pAmountFormatter(coin)) - .format(fee, withUnitName: true, indicatePrecisionLoss: false); - - return cachedFees[amount]!; - } else if (coin is Wownero) { - final int specialMoneroId; - switch (ref.read(feeRateTypeMobileStateProvider.state).state) { - case FeeRateType.fast: - specialMoneroId = csWownero.getTxPriorityHigh(); - break; - case FeeRateType.average: - specialMoneroId = csWownero.getTxPriorityMedium(); - break; - case FeeRateType.slow: - specialMoneroId = csWownero.getTxPriorityNormal(); + specialMoneroId = (wallet as CryptonoteWallet).getTxPriorityNormal(); break; default: throw ArgumentError("custom fee not available for monero"); diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index c7112a596..8c165ccdb 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -23,13 +23,13 @@ import '../../../utilities/enums/fee_rate_type_enum.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/text_styles.dart'; import '../../../wallets/crypto_currency/crypto_currency.dart'; +import '../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/firo_wallet.dart'; +import '../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../widgets/animated_text.dart'; -import '../../../wl_gen/interfaces/cs_monero_interface.dart'; -import '../../../wl_gen/interfaces/cs_wownero_interface.dart'; final feeSheetSessionCacheProvider = ChangeNotifierProvider((ref) { @@ -89,16 +89,10 @@ class _TransactionFeeSelectionSheetState if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { + if (coin is CryptonoteCurrency) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csMonero.getTxPriorityHigh()), - ); - ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; - } else if (coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csWownero.getTxPriorityHigh()), + BigInt.from((wallet as CryptonoteWallet).getTxPriorityHigh()), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { @@ -132,16 +126,10 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csMonero.getTxPriorityMedium()), - ); - ref.read(feeSheetSessionCacheProvider).average[amount] = fee; - } else if (coin is Wownero) { + if (coin is CryptonoteCurrency) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csWownero.getTxPriorityMedium()), + BigInt.from((wallet as CryptonoteWallet).getTxPriorityMedium()), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { @@ -174,16 +162,10 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csMonero.getTxPriorityNormal()), - ); - ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; - } else if (coin is Wownero) { + if (coin is CryptonoteCurrency) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csWownero.getTxPriorityNormal()), + BigInt.from((wallet as CryptonoteWallet).getTxPriorityNormal()), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index e0ebd4c53..738ccb8a9 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -35,8 +35,6 @@ import '../../../../utilities/tor_plain_net_option_enum.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -114,107 +112,102 @@ class _AddEditNodeViewState extends ConsumerState { context: context, useSafeArea: true, barrierDismissible: true, - builder: - (_) => - isDesktop - ? DesktopDialog( - maxWidth: 440, - maxHeight: 300, - child: Column( + builder: (_) => isDesktop + ? DesktopDialog( + maxWidth: 440, + maxHeight: 300, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 32), + child: Row( children: [ - Padding( - padding: const EdgeInsets.only(top: 32), - child: Row( - children: [ - const SizedBox(width: 32), - Text( - "Server currently unreachable", - style: STextStyles.desktopH3(context), - ), - ], - ), + const SizedBox(width: 32), + Text( + "Server currently unreachable", + style: STextStyles.desktopH3(context), ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - top: 16, - bottom: 32, - ), - child: Column( - children: [ - const Spacer(), - Text( - "Would you like to save this node anyways?", - style: STextStyles.desktopTextMedium( + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + top: 16, + bottom: 32, + ), + child: Column( + children: [ + const Spacer(), + Text( + "Would you like to save this node anyways?", + style: STextStyles.desktopTextMedium(context), + ), + const Spacer(flex: 2), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: isDesktop + ? ButtonHeight.l + : null, + onPressed: () => Navigator.of( context, - ), + rootNavigator: true, + ).pop(false), ), - const Spacer(flex: 2), - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - buttonHeight: - isDesktop ? ButtonHeight.l : null, - onPressed: - () => Navigator.of( - context, - rootNavigator: true, - ).pop(false), - ), - ), - const SizedBox(width: 16), - Expanded( - child: PrimaryButton( - label: "Save", - buttonHeight: - isDesktop ? ButtonHeight.l : null, - onPressed: - () => Navigator.of( - context, - rootNavigator: true, - ).pop(true), - ), - ), - ], + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Save", + buttonHeight: isDesktop + ? ButtonHeight.l + : null, + onPressed: () => Navigator.of( + context, + rootNavigator: true, + ).pop(true), ), - ], - ), + ), + ], ), - ), - ], - ), - ) - : StackDialog( - title: "Server currently unreachable", - message: "Would you like to save this node anyways?", - leftButton: TextButton( - onPressed: () async { - Navigator.of(context).pop(false); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: - Theme.of( - context, - ).extension()!.accentColorDark, - ), + ], ), ), - rightButton: TextButton( - onPressed: () async { - Navigator.of(context).pop(true); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), - child: Text("Save", style: STextStyles.button(context)), - ), ), + ], + ), + ) + : StackDialog( + title: "Server currently unreachable", + message: "Would you like to save this node anyways?", + leftButton: TextButton( + onPressed: () async { + Navigator.of(context).pop(false); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + rightButton: TextButton( + onPressed: () async { + Navigator.of(context).pop(true); + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text("Save", style: STextStyles.button(context)), + ), + ), ).then((value) { if (value is bool && value) { shouldSave = true; @@ -233,7 +226,7 @@ class _AddEditNodeViewState extends ConsumerState { // strip unused path String address = formData.host!; - if (coin is LibMoneroWallet || coin is LibSalviumWallet) { + if (coin is CryptonoteCurrency) { if (address.startsWith("http")) { final uri = Uri.parse(address); address = "${uri.scheme}://${uri.host}"; @@ -450,8 +443,9 @@ class _AddEditNodeViewState extends ConsumerState { saveEnabled = false; testConnectionEnabled = false; } else { - final node = - ref.read(nodeServiceChangeNotifierProvider).getNodeById(id: nodeId!)!; + final node = ref + .read(nodeServiceChangeNotifierProvider) + .getNodeById(id: nodeId!)!; testConnectionEnabled = node.host.isNotEmpty; saveEnabled = testConnectionEnabled && node.name.isNotEmpty; } @@ -468,205 +462,193 @@ class _AddEditNodeViewState extends ConsumerState { Widget build(BuildContext context) { final NodeModel? node = viewType == AddEditNodeViewType.edit && nodeId != null - ? ref.watch( - nodeServiceChangeNotifierProvider.select( - (value) => value.getNodeById(id: nodeId!), - ), - ) - : null; + ? ref.watch( + nodeServiceChangeNotifierProvider.select( + (value) => value.getNodeById(id: nodeId!), + ), + ) + : null; return ConditionalParent( condition: !isDesktop, - builder: - (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75), - ); - } - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - viewType == AddEditNodeViewType.edit - ? "Edit node" - : "Add node", - style: STextStyles.navBarTitle(context), - ), - actions: [ - if (viewType == AddEditNodeViewType.add && - coin - is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("qrNodeAppBarButtonKey"), - size: 36, - shadows: const [], - color: - Theme.of( - context, - ).extension()!.background, - icon: QrCodeIcon( - width: 20, - height: 20, - color: - Theme.of( - context, - ).extension()!.accentColorDark, - ), - onPressed: _scanQr, - ), + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (context.mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node", + style: STextStyles.navBarTitle(context), + ), + actions: [ + if (viewType == AddEditNodeViewType.add && + coin + is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("qrNodeAppBarButtonKey"), + size: 36, + shadows: const [], + color: Theme.of( + context, + ).extension()!.background, + icon: QrCodeIcon( + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.accentColorDark, ), + onPressed: _scanQr, ), - if (viewType == AddEditNodeViewType.edit && - ref - .watch( - nodeServiceChangeNotifierProvider.select( - (value) => value.getNodesFor(coin), - ), - ) - .length > - 1) - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, + ), + ), + if (viewType == AddEditNodeViewType.edit && + ref + .watch( + nodeServiceChangeNotifierProvider.select( + (value) => value.getNodesFor(coin), + ), + ) + .length > + 1) + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("deleteNodeAppBarButtonKey"), + size: 36, + shadows: const [], + color: Theme.of( + context, + ).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.trash, + color: Theme.of( + context, + ).extension()!.accentColorDark, + width: 20, + height: 20, ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("deleteNodeAppBarButtonKey"), - size: 36, - shadows: const [], - color: - Theme.of( - context, - ).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.trash, - color: - Theme.of( - context, - ).extension()!.accentColorDark, - width: 20, - height: 20, - ), - onPressed: () async { - Navigator.popUntil( - context, - ModalRoute.withName( - widget.routeOnSuccessOrDelete, - ), - ); + onPressed: () async { + Navigator.popUntil( + context, + ModalRoute.withName(widget.routeOnSuccessOrDelete), + ); - await ref - .read(nodeServiceChangeNotifierProvider) - .delete(nodeId!, true); - }, - ), - ), + await ref + .read(nodeServiceChangeNotifierProvider) + .delete(nodeId!, true); + }, ), - ], - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.only( - top: 12, - left: 12, - right: 12, - bottom: 12, - ), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 8, - ), - child: IntrinsicHeight(child: child), - ), - ), - ); - }, ), ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + bottom: 12, + ), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 8, + ), + child: IntrinsicHeight(child: child), + ), + ), + ); + }, ), ), ), + ), + ), child: ConditionalParent( condition: isDesktop, - builder: - (child) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const SizedBox(height: 8), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - const SizedBox(width: 8), - const AppBarBackButton(iconSize: 24, size: 40), - Text( - "Add new node", - style: STextStyles.desktopH3(context), - ), - ], + const SizedBox(width: 8), + const AppBarBackButton(iconSize: 24, size: 40), + Text( + "Add new node", + style: STextStyles.desktopH3(context), ), - if (coin - is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future - Padding( - padding: const EdgeInsets.only(right: 32), - child: AppBarIconButton( - size: 40, - color: - isDesktop - ? Theme.of(context) - .extension()! - .textFieldDefaultBG - : Theme.of( - context, - ).extension()!.background, - icon: const QrCodeIcon(width: 21, height: 21), - onPressed: _scanQr, - ), - ), ], ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - top: 16, - bottom: 32, + if (coin + is CryptonoteCurrency) // TODO: [prio=low] do something other than `coin is CryptonoteCurrency` in the future + Padding( + padding: const EdgeInsets.only(right: 32), + child: AppBarIconButton( + size: 40, + color: isDesktop + ? Theme.of( + context, + ).extension()!.textFieldDefaultBG + : Theme.of( + context, + ).extension()!.background, + icon: const QrCodeIcon(width: 21, height: 21), + onPressed: _scanQr, + ), ), - child: child, - ), ], ), - ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + top: 16, + bottom: 32, + ), + child: child, + ), + ], + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -704,37 +686,36 @@ class _AddEditNodeViewState extends ConsumerState { label: "Test connection", enabled: testConnectionEnabled, buttonHeight: isDesktop ? ButtonHeight.l : null, - onPressed: - testConnectionEnabled - ? () async { - final testPassed = await testNodeConnection( - context: context, - onSuccess: _onTestSuccess, - cryptoCurrency: coin, - nodeFormData: ref.read(nodeFormDataProvider), - ref: ref, - ); - if (context.mounted) { - if (testPassed) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "Server ping success", - context: context, - ), - ); - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Server unreachable", - context: context, - ), - ); - } + onPressed: testConnectionEnabled + ? () async { + final testPassed = await testNodeConnection( + context: context, + onSuccess: _onTestSuccess, + cryptoCurrency: coin, + nodeFormData: ref.read(nodeFormDataProvider), + ref: ref, + ); + if (context.mounted) { + if (testPassed) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Server ping success", + context: context, + ), + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Server unreachable", + context: context, + ), + ); } } - : null, + } + : null, ), ), if (isDesktop) const SizedBox(width: 16), @@ -752,14 +733,13 @@ class _AddEditNodeViewState extends ConsumerState { if (!isDesktop) const SizedBox(height: 16), if (!isDesktop) TextButton( - style: - saveEnabled - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonStyle(context), + style: saveEnabled + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonStyle(context), onPressed: saveEnabled ? attemptSave : null, child: Text("Save", style: STextStyles.button(context)), ), @@ -873,10 +853,12 @@ class _NodeFormState extends ConsumerState { onChanged?.call(canSave, canTestConnection); ref.read(nodeFormDataProvider).name = _nameController.text; ref.read(nodeFormDataProvider).host = _hostController.text; - ref.read(nodeFormDataProvider).login = - _usernameController.text.isEmpty ? null : _usernameController.text; - ref.read(nodeFormDataProvider).password = - _passwordController.text.isEmpty ? null : _passwordController.text; + ref.read(nodeFormDataProvider).login = _usernameController.text.isEmpty + ? null + : _usernameController.text; + ref.read(nodeFormDataProvider).password = _passwordController.text.isEmpty + ? null + : _passwordController.text; ref.read(nodeFormDataProvider).port = port; ref.read(nodeFormDataProvider).useSSL = _useSSL; ref.read(nodeFormDataProvider).isFailover = _isFailover; @@ -985,31 +967,32 @@ class _NodeFormState extends ConsumerState { controller: _nameController, focusNode: _nameFocusNode, style: STextStyles.field(context), - decoration: standardInputDecoration( - "Node name", - _nameFocusNode, - context, - ).copyWith( - suffixIcon: - !shouldBeReadOnly && _nameController.text.isNotEmpty + decoration: + standardInputDecoration( + "Node name", + _nameFocusNode, + context, + ).copyWith( + suffixIcon: + !shouldBeReadOnly && _nameController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _nameController.text = ""; - _updateState(); - }, - ), - ], + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _nameController.text = ""; + _updateState(); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), onChanged: (newValue) { _updateState(); setState(() {}); @@ -1030,31 +1013,32 @@ class _NodeFormState extends ConsumerState { controller: _hostController, focusNode: _hostFocusNode, style: STextStyles.field(context), - decoration: standardInputDecoration( - (widget.coin is! CryptonoteCurrency) ? "IP address" : "Url", - _hostFocusNode, - context, - ).copyWith( - suffixIcon: - !shouldBeReadOnly && _hostController.text.isNotEmpty + decoration: + standardInputDecoration( + (widget.coin is! CryptonoteCurrency) ? "IP address" : "Url", + _hostFocusNode, + context, + ).copyWith( + suffixIcon: + !shouldBeReadOnly && _hostController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _hostController.text = ""; - _updateState(); - }, - ), - ], + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _hostController.text = ""; + _updateState(); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), onChanged: (newValue) { // parse port hack try { @@ -1098,7 +1082,7 @@ class _NodeFormState extends ConsumerState { } else { enableSSLCheckbox = true; } - } else if (widget.coin is LibMoneroWallet || widget.coin is LibSalviumWallet) { + } else if (widget.coin is CryptonoteCurrency) { if (newValue.startsWith("https://")) { _useSSL = true; } else if (newValue.startsWith("http://")) { @@ -1139,31 +1123,28 @@ class _NodeFormState extends ConsumerState { inputFormatters: [FilteringTextInputFormatter.digitsOnly], keyboardType: TextInputType.number, style: STextStyles.field(context), - decoration: standardInputDecoration( - "Port", - _portFocusNode, - context, - ).copyWith( - suffixIcon: - !shouldBeReadOnly && _portController.text.isNotEmpty + decoration: standardInputDecoration("Port", _portFocusNode, context) + .copyWith( + suffixIcon: + !shouldBeReadOnly && _portController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _portController.text = ""; - _updateState(); - }, - ), - ], + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _portController.text = ""; + _updateState(); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), onChanged: (newValue) { _updateState(); setState(() {}); @@ -1184,31 +1165,32 @@ class _NodeFormState extends ConsumerState { enabled: enableField(_usernameController), focusNode: _usernameFocusNode, style: STextStyles.field(context), - decoration: standardInputDecoration( - "Login (optional)", - _usernameFocusNode, - context, - ).copyWith( - suffixIcon: - !shouldBeReadOnly && _usernameController.text.isNotEmpty + decoration: + standardInputDecoration( + "Login (optional)", + _usernameFocusNode, + context, + ).copyWith( + suffixIcon: + !shouldBeReadOnly && _usernameController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _usernameController.text = ""; - _updateState(); - }, - ), - ], + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _usernameController.text = ""; + _updateState(); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), onChanged: (newValue) { _updateState(); setState(() {}); @@ -1230,31 +1212,32 @@ class _NodeFormState extends ConsumerState { obscureText: true, focusNode: _passwordFocusNode, style: STextStyles.field(context), - decoration: standardInputDecoration( - "Password (optional)", - _passwordFocusNode, - context, - ).copyWith( - suffixIcon: - !shouldBeReadOnly && _passwordController.text.isNotEmpty + decoration: + standardInputDecoration( + "Password (optional)", + _passwordFocusNode, + context, + ).copyWith( + suffixIcon: + !shouldBeReadOnly && _passwordController.text.isNotEmpty ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - _passwordController.text = ""; - _updateState(); - }, - ), - ], + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + _passwordController.text = ""; + _updateState(); + }, + ), + ], + ), ), - ), - ) + ) : null, - ), + ), onChanged: (newValue) { _updateState(); setState(() {}); @@ -1266,15 +1249,14 @@ class _NodeFormState extends ConsumerState { Row( children: [ GestureDetector( - onTap: - !shouldBeReadOnly && enableSSLCheckbox - ? () { - setState(() { - _useSSL = !_useSSL; - }); - _updateState(); - } - : null, + onTap: !shouldBeReadOnly && enableSSLCheckbox + ? () { + setState(() { + _useSSL = !_useSSL; + }); + _updateState(); + } + : null, child: Container( color: Colors.transparent, child: Row( @@ -1283,26 +1265,24 @@ class _NodeFormState extends ConsumerState { width: 20, height: 20, child: Checkbox( - fillColor: - !shouldBeReadOnly && enableSSLCheckbox - ? null - : MaterialStateProperty.all( - Theme.of(context) - .extension()! - .checkboxBGDisabled, - ), + fillColor: !shouldBeReadOnly && enableSSLCheckbox + ? null + : MaterialStateProperty.all( + Theme.of(context) + .extension()! + .checkboxBGDisabled, + ), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, value: _useSSL, - onChanged: - !shouldBeReadOnly && enableSSLCheckbox - ? (newValue) { - setState(() { - _useSSL = newValue!; - }); - _updateState(); - } - : null, + onChanged: !shouldBeReadOnly && enableSSLCheckbox + ? (newValue) { + setState(() { + _useSSL = newValue!; + }); + _updateState(); + } + : null, ), ), const SizedBox(width: 12), @@ -1316,19 +1296,18 @@ class _NodeFormState extends ConsumerState { ), ], ), - if (widget.coin is LibMoneroWallet || widget.coin is LibSalviumWallet) + if (widget.coin is CryptonoteCurrency) Row( children: [ GestureDetector( - onTap: - !widget.readOnly /*&& trustedCheckbox*/ - ? () { - setState(() { - _trusted = !_trusted; - }); - _updateState(); - } - : null, + onTap: !widget.readOnly /*&& trustedCheckbox*/ + ? () { + setState(() { + _trusted = !_trusted; + }); + _updateState(); + } + : null, child: Container( color: Colors.transparent, child: Row( @@ -1337,26 +1316,24 @@ class _NodeFormState extends ConsumerState { width: 20, height: 20, child: Checkbox( - fillColor: - !widget.readOnly - ? null - : MaterialStateProperty.all( - Theme.of(context) - .extension()! - .checkboxBGDisabled, - ), + fillColor: !widget.readOnly + ? null + : MaterialStateProperty.all( + Theme.of(context) + .extension()! + .checkboxBGDisabled, + ), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, value: _trusted, - onChanged: - !widget.readOnly - ? (newValue) { - setState(() { - _trusted = newValue!; - }); - _updateState(); - } - : null, + onChanged: !widget.readOnly + ? (newValue) { + setState(() { + _trusted = newValue!; + }); + _updateState(); + } + : null, ), ), const SizedBox(width: 12), @@ -1373,9 +1350,7 @@ class _NodeFormState extends ConsumerState { if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash && widget.coin is! Mimblewimblecoin) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash && widget.coin is! Mimblewimblecoin) @@ -1509,25 +1484,23 @@ class _NodeFormState extends ConsumerState { width: 20, height: 20, child: Checkbox( - fillColor: - !widget.readOnly - ? null - : MaterialStateProperty.all( - Theme.of( - context, - ).extension()!.checkboxBGDisabled, - ), + fillColor: !widget.readOnly + ? null + : MaterialStateProperty.all( + Theme.of( + context, + ).extension()!.checkboxBGDisabled, + ), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, value: _forceNoTor, - onChanged: - !widget.readOnly - ? (newValue) { - setState(() { - _forceNoTor = newValue!; - }); - _updateState(); - } - : null, + onChanged: !widget.readOnly + ? (newValue) { + setState(() { + _forceNoTor = newValue!; + }); + _updateState(); + } + : null, ), ), const SizedBox(width: 12), @@ -1564,9 +1537,8 @@ class RadioTextButton extends StatelessWidget { Widget build(BuildContext context) { return ConditionalParent( condition: Util.isDesktop, - builder: - (child) => - MouseRegion(cursor: SystemMouseCursors.click, child: child), + builder: (child) => + MouseRegion(cursor: SystemMouseCursors.click, child: child), child: GestureDetector( onTap: () { if (value != groupValue) { @@ -1583,20 +1555,18 @@ class RadioTextButton extends StatelessWidget { width: 20, height: 20, child: Radio( - activeColor: - Theme.of( - context, - ).extension()!.radioButtonIconEnabled, + activeColor: Theme.of( + context, + ).extension()!.radioButtonIconEnabled, value: value, groupValue: groupValue, - onChanged: - !enabled - ? null - : (_) { - if (value != groupValue) { - onChanged.call(value); - } - }, + onChanged: !enabled + ? null + : (_) { + if (value != groupValue) { + onChanged.call(value); + } + }, ), ), const SizedBox(width: 14), 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 355fc6643..0bf51bcfa 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 @@ -54,8 +54,7 @@ import '../../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../../wallets/wallet/impl/xelis_wallet.dart'; -import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../../wallets/wallet/wallet.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../../../wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart'; @@ -501,8 +500,7 @@ abstract class SWB { int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0; if (restoreHeight <= 0) { if (wallet is EpiccashWallet || - wallet is LibMoneroWallet || - wallet is LibSalviumWallet || + wallet is CryptonoteWallet || wallet is MimblewimblecoinWallet) { restoreHeight = 0; } else { diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 2e3ffbb5a..b34743125 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -39,11 +39,11 @@ import '../../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../../wallets/crypto_currency/coins/monero.dart'; import '../../../../wallets/crypto_currency/coins/salvium.dart'; import '../../../../wallets/crypto_currency/coins/wownero.dart'; +import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; -import '../../../../wallets/wallet/impl/salvium_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart'; import '../../../../widgets/animated_text.dart'; @@ -333,16 +333,9 @@ class _WalletNetworkSettingsViewState final coin = ref.watch(pWalletCoin(widget.walletId)); - if (coin is Salvium) { + if (coin is CryptonoteCurrency) { final double highestPercent = - (ref.read(pWallets).getWallet(widget.walletId) as SalviumWallet) - .highestPercentCached; - if (_percent < highestPercent) { - _percent = highestPercent.clamp(0.0, 1.0); - } - } else if (coin is Monero || coin is Wownero) { - final double highestPercent = - (ref.read(pWallets).getWallet(widget.walletId) as LibMoneroWallet) + (ref.read(pWallets).getWallet(widget.walletId) as CryptonoteWallet) .highestPercentCached; if (_percent < highestPercent) { _percent = highestPercent.clamp(0.0, 1.0); 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 b1b485451..bae20daa6 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,9 +18,9 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/epicbox_config_model.dart'; -import '../../../models/mwcmqs_config_model.dart'; import '../../../models/keys/key_data_interface.dart'; import '../../../models/keys/view_only_wallet_data.dart'; +import '../../../models/mwcmqs_config_model.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../providers/ui/transaction_filter_provider.dart'; @@ -38,9 +38,8 @@ 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/intermediate/lib_monero_wallet.dart'; -import '../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -115,8 +114,9 @@ class _WalletSettingsViewState extends ConsumerState { _currentSyncStatus = widget.initialSyncStatus; // _currentNodeStatus = widget.initialNodeStatus; - eventBus = - widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance; + eventBus = widget.eventBus != null + ? widget.eventBus! + : GlobalEventBus.instance; _syncStatusSubscription = eventBus .on() @@ -296,12 +296,12 @@ class _WalletSettingsViewState extends ConsumerState { keys: results[0]!, prevGen: results[2] == null || - results[3] == null - ? null - : ( - config: results[3]!, - keys: results[2]!, - ), + results[3] == null + ? null + : ( + config: results[3]!, + keys: results[2]!, + ), ); } } else { @@ -312,30 +312,26 @@ class _WalletSettingsViewState extends ConsumerState { .isViewOnly) { // TODO: is something needed here? } else { - mnemonic = - await wallet - .getMnemonicAsWords(); + mnemonic = await wallet + .getMnemonicAsWords(); } } } - KeyDataInterface? keyData; - if (wallet - is ViewOnlyOptionInterface && - wallet.isViewOnly) { - keyData = - await wallet - .getViewOnlyWalletData(); - } else if (wallet - is ExtendedKeysInterface) { - keyData = await wallet.getXPrivs(); - } else if (wallet - is LibMoneroWallet) { - keyData = await wallet.getKeys(); - } else if (wallet - is LibSalviumWallet) { - keyData = await wallet.getKeys(); - } + KeyDataInterface? keyData; + if (wallet + is ViewOnlyOptionInterface && + wallet.isViewOnly) { + keyData = await wallet + .getViewOnlyWalletData(); + } else if (wallet + is ExtendedKeysInterface) { + keyData = await wallet + .getXPrivs(); + } else if (wallet + is CryptonoteWallet) { + keyData = await wallet.getKeys(); + } if (context.mounted) { if (keyData != null && @@ -348,26 +344,22 @@ class _WalletSettingsViewState extends ConsumerState { shouldUseMaterialRoute: RouteGenerator .useMaterialPageRoute, - builder: - (_) => LockscreenView( - routeOnSuccessArguments: - ( - walletId: - walletId, - keyData: - keyData, - ), - showBackButton: true, - routeOnSuccess: - MobileKeyDataView - .routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to view recovery data", - biometricsAuthenticationTitle: - "View recovery data", - ), + builder: (_) => LockscreenView( + routeOnSuccessArguments: ( + walletId: walletId, + keyData: keyData, + ), + showBackButton: true, + routeOnSuccess: + MobileKeyDataView + .routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to view recovery data", + biometricsAuthenticationTitle: + "View recovery data", + ), settings: const RouteSettings( name: "/viewRecoveryDataLockscreen", @@ -381,27 +373,26 @@ class _WalletSettingsViewState extends ConsumerState { shouldUseMaterialRoute: RouteGenerator .useMaterialPageRoute, - builder: - (_) => LockscreenView( - routeOnSuccessArguments: ( - walletId: walletId, - mnemonic: - mnemonic ?? [], - frostWalletData: - frostWalletData, - keyData: keyData, - ), - showBackButton: true, - routeOnSuccess: - WalletBackupView - .routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to view recovery phrase", - biometricsAuthenticationTitle: - "View recovery phrase", - ), + builder: (_) => LockscreenView( + routeOnSuccessArguments: ( + walletId: walletId, + mnemonic: + mnemonic ?? [], + frostWalletData: + frostWalletData, + keyData: keyData, + ), + showBackButton: true, + routeOnSuccess: + WalletBackupView + .routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to view recovery phrase", + biometricsAuthenticationTitle: + "View recovery phrase", + ), settings: const RouteSettings( name: "/viewRecoverPhraseLockscreen", @@ -489,23 +480,20 @@ class _WalletSettingsViewState extends ConsumerState { useSafeArea: false, barrierDismissible: true, context: context, - builder: - (_) => StackOkDialog( - title: - "Are you sure you want to clear " - "${coin.prettyName} electrumx cache?", - onOkPressed: (value) { - result = value; - }, - leftButton: SecondaryButton( - label: "Cancel", - onPressed: () { - Navigator.of( - context, - ).pop(); - }, - ), - ), + builder: (_) => StackOkDialog( + title: + "Are you sure you want to clear " + "${coin.prettyName} electrumx cache?", + onOkPressed: (value) { + result = value; + }, + leftButton: SecondaryButton( + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), ); if (result == "OK" && @@ -578,8 +566,11 @@ class _WalletSettingsViewState extends ConsumerState { // .getWallet(walletId) // .isActiveWallet = false; ref - .read(transactionFilterProvider.state) - .state = null; + .read( + transactionFilterProvider.state, + ) + .state = + null; Navigator.of(context).popUntil( ModalRoute.withName(HomeView.routeName), @@ -591,10 +582,9 @@ class _WalletSettingsViewState extends ConsumerState { child: Text( "Log out", style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ); @@ -666,8 +656,9 @@ class _EpiBoxInfoFormState extends ConsumerState { enableSuggestions: Util.isDesktop ? false : true, controller: portController, decoration: const InputDecoration(hintText: "Port"), - keyboardType: - Util.isDesktop ? null : const TextInputType.numberWithOptions(), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions(), ), const SizedBox(height: 8), TextButton( @@ -696,8 +687,9 @@ class _EpiBoxInfoFormState extends ConsumerState { child: Text( "Save", style: STextStyles.button(context).copyWith( - color: - Theme.of(context).extension()!.accentColorDark, + color: Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), @@ -708,10 +700,7 @@ class _EpiBoxInfoFormState extends ConsumerState { } class MwcMqsInfoForm extends ConsumerStatefulWidget { - const MwcMqsInfoForm({ - super.key, - required this.walletId, - }); + const MwcMqsInfoForm({super.key, required this.walletId}); final String walletId; @@ -756,20 +745,17 @@ class _MwcmqsInfoFormState extends ConsumerState { controller: hostController, decoration: const InputDecoration(hintText: "Host"), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, controller: portController, decoration: const InputDecoration(hintText: "Port"), - keyboardType: - Util.isDesktop ? null : const TextInputType.numberWithOptions(), - ), - const SizedBox( - height: 8, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions(), ), + const SizedBox(height: 8), TextButton( onPressed: () async { try { @@ -796,8 +782,9 @@ class _MwcmqsInfoFormState extends ConsumerState { child: Text( "Save", style: STextStyles.button(context).copyWith( - color: - Theme.of(context).extension()!.accentColorDark, + color: Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart index bfa5d0a13..70ca0a421 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/edit_refresh_height_view.dart @@ -11,8 +11,7 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_wownero_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -22,8 +21,6 @@ import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; -import '../../../../wl_gen/interfaces/cs_monero_interface.dart'; -import '../../../../wl_gen/interfaces/cs_wownero_interface.dart'; class EditRefreshHeightView extends ConsumerStatefulWidget { const EditRefreshHeightView({super.key, required this.walletId}); @@ -58,10 +55,8 @@ class _EditRefreshHeightViewState extends ConsumerState { isar: ref.read(mainDBProvider).isar, ); final wallet = ref.read(pWallets).getWallet(widget.walletId); - if (wallet is LibMoneroWallet && wallet.wallet != null) { - csMonero.setRefreshFromBlockHeight(wallet.wallet!, newHeight); - } else if (wallet is LibWowneroWallet && wallet.wallet != null) { - csWownero.setRefreshFromBlockHeight(wallet.wallet!, newHeight); + if (wallet is CryptonoteWallet && wallet.wallet != null) { + wallet.setRefreshFromBlockHeight(newHeight); } } else { errMessage = "Invalid height: ${_controller.text}"; @@ -100,14 +95,8 @@ class _EditRefreshHeightViewState extends ConsumerState { super.initState(); _controller = TextEditingController(); final wallet = ref.read(pWallets).getWallet(widget.walletId); - if (wallet is LibMoneroWallet && wallet.wallet != null) { - _controller.text = csMonero - .getRefreshFromBlockHeight(wallet.wallet!) - .toString(); - } else if (wallet is LibWowneroWallet && wallet.wallet != null) { - _controller.text = csWownero - .getRefreshFromBlockHeight(wallet.wallet!) - .toString(); + if (wallet is CryptonoteWallet && wallet.wallet != null) { + _controller.text = wallet.getRefreshFromBlockHeight().toString(); } else { _controller.text = ref .read(pWalletInfo(widget.walletId)) diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index 7792fb2e1..f030856a8 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -22,8 +22,7 @@ import '../../../../utilities/logger.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/isar/models/wallet_info.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; @@ -510,9 +509,8 @@ class _WalletSettingsWalletSettingsViewState ), ), ), - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) - const SizedBox(height: 8), - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) + if (wallet is CryptonoteWallet) const SizedBox(height: 8), + if (wallet is CryptonoteWallet) RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: RawMaterialButton( @@ -560,65 +558,59 @@ class _WalletSettingsWalletSettingsViewState showDialog( barrierDismissible: true, context: context, - builder: - (_) => StackDialog( - title: - "Do you want to delete ${ref.read(pWalletName(widget.walletId))}?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonStyle(context), - onPressed: () { - Navigator.pop(context); - }, - child: Text( - "Cancel", - style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, - ), - ), + builder: (_) => StackDialog( + title: + "Do you want to delete ${ref.read(pWalletName(widget.walletId))}?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), - onPressed: () { - Navigator.pop(context); - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: - (_) => LockscreenView( - routeOnSuccessArguments: - widget.walletId, - showBackButton: true, - routeOnSuccess: - DeleteWalletWarningView - .routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to delete wallet", - biometricsAuthenticationTitle: - "Delete wallet", - ), - settings: const RouteSettings( - name: "/deleteWalletLockscreen", - ), - ), - ); - }, - child: Text( - "Delete", - style: STextStyles.button(context), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => LockscreenView( + routeOnSuccessArguments: widget.walletId, + showBackButton: true, + routeOnSuccess: + DeleteWalletWarningView.routeName, + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to delete wallet", + biometricsAuthenticationTitle: + "Delete wallet", + ), + settings: const RouteSettings( + name: "/deleteWalletLockscreen", + ), ), - ), + ); + }, + child: Text( + "Delete", + style: STextStyles.button(context), ), + ), + ), ); }, child: Padding( diff --git a/lib/pages/special/firo_rescan_recovery_error_dialog.dart b/lib/pages/special/firo_rescan_recovery_error_dialog.dart index 1b8675db6..8e8c21f6a 100644 --- a/lib/pages/special/firo_rescan_recovery_error_dialog.dart +++ b/lib/pages/special/firo_rescan_recovery_error_dialog.dart @@ -12,8 +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/intermediate/lib_monero_wallet.dart'; -import '../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../widgets/background.dart'; @@ -65,21 +64,20 @@ class _FiroRescanRecoveryErrorViewState final result = await showDialog( context: context, barrierDismissible: false, - builder: - (context) => Navigator( - initialRoute: DesktopDeleteWalletDialog.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - RouteGenerator.generateRoute( - RouteSettings( - name: DesktopDeleteWalletDialog.routeName, - arguments: widget.walletId, - ), - ), - ]; - }, - ), + builder: (context) => Navigator( + initialRoute: DesktopDeleteWalletDialog.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: DesktopDeleteWalletDialog.routeName, + arguments: widget.walletId, + ), + ), + ]; + }, + ), ); if (result == true) { @@ -100,8 +98,9 @@ class _FiroRescanRecoveryErrorViewState builder: (child) { return Background( child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, + backgroundColor: Theme.of( + context, + ).extension()!.background, appBar: AppBar( automaticallyImplyLeading: false, actions: [ @@ -120,18 +119,16 @@ class _FiroRescanRecoveryErrorViewState key: const Key("walletViewRadioButton"), size: 36, shadows: const [], - color: - Theme.of( - context, - ).extension()!.background, + color: Theme.of( + context, + ).extension()!.background, icon: SvgPicture.asset( Assets.svg.trash, width: 20, height: 20, - color: - Theme.of( - context, - ).extension()!.topNavIconPrimary, + color: Theme.of( + context, + ).extension()!.topNavIconPrimary, ), onPressed: () async { final walletName = ref.read( @@ -140,71 +137,60 @@ class _FiroRescanRecoveryErrorViewState await showDialog( barrierDismissible: true, context: context, - builder: - (_) => StackDialog( - title: "Do you want to delete $walletName?", - leftButton: TextButton( - style: Theme.of(context) + builder: (_) => StackDialog( + title: "Do you want to delete $walletName?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) .extension()! - .getSecondaryEnabledButtonStyle( - context, - ), - onPressed: () { - Navigator.pop(context); - }, - child: Text( - "Cancel", - style: STextStyles.button( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, - ), - ), + .accentColorDark, ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle( - context, - ), - onPressed: () { - Navigator.pop(context); - Navigator.push( - context, - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: - (_) => LockscreenView( - routeOnSuccessArguments: - widget.walletId, - showBackButton: true, - routeOnSuccess: - DeleteWalletWarningView - .routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to delete wallet", - biometricsAuthenticationTitle: - "Delete wallet", - ), - settings: const RouteSettings( - name: "/deleteWalletLockscreen", - ), - ), - ); - }, - child: Text( - "Delete", - style: STextStyles.button(context), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => LockscreenView( + routeOnSuccessArguments: + widget.walletId, + showBackButton: true, + routeOnSuccess: + DeleteWalletWarningView.routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to delete wallet", + biometricsAuthenticationTitle: + "Delete wallet", + ), + settings: const RouteSettings( + name: "/deleteWalletLockscreen", + ), ), - ), + ); + }, + child: Text( + "Delete", + style: STextStyles.button(context), ), + ), + ), ); }, ), @@ -232,20 +218,18 @@ class _FiroRescanRecoveryErrorViewState Util.isDesktop ? const SizedBox(height: 60) : const Spacer(), BranchedParent( condition: Util.isDesktop, - conditionBranchBuilder: - (children) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children, - ), - otherBranchBuilder: - (children) => Row( - children: [ - Expanded(child: children[0]), - children[1], - Expanded(child: children[2]), - ], - ), + conditionBranchBuilder: (children) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: children, + ), + otherBranchBuilder: (children) => Row( + children: [ + Expanded(child: children[0]), + children[1], + Expanded(child: children[2]), + ], + ), children: [ SecondaryButton( label: "Show mnemonic", @@ -255,21 +239,20 @@ class _FiroRescanRecoveryErrorViewState await showDialog( context: context, barrierDismissible: false, - builder: - (context) => Navigator( - initialRoute: UnlockWalletKeysDesktop.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - RouteGenerator.generateRoute( - RouteSettings( - name: UnlockWalletKeysDesktop.routeName, - arguments: widget.walletId, - ), - ), - ]; - }, - ), + builder: (context) => Navigator( + initialRoute: UnlockWalletKeysDesktop.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: UnlockWalletKeysDesktop.routeName, + arguments: widget.walletId, + ), + ), + ]; + }, + ), ); } else { final wallet = ref @@ -282,9 +265,7 @@ class _FiroRescanRecoveryErrorViewState KeyDataInterface? keyData; if (wallet is ExtendedKeysInterface) { keyData = await wallet.getXPrivs(); - } else if (wallet is LibMoneroWallet) { - keyData = await wallet.getKeys(); - } else if (wallet is LibSalviumWallet) { + } else if (wallet is CryptonoteWallet) { keyData = await wallet.getKeys(); } @@ -294,22 +275,20 @@ class _FiroRescanRecoveryErrorViewState RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: - (_) => LockscreenView( - routeOnSuccessArguments: ( - walletId: widget.walletId, - mnemonic: mnemonic, - keyData: keyData, - ), - showBackButton: true, - routeOnSuccess: - WalletBackupView.routeName, - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to view recovery phrase", - biometricsAuthenticationTitle: - "View recovery phrase", - ), + builder: (_) => LockscreenView( + routeOnSuccessArguments: ( + walletId: widget.walletId, + mnemonic: mnemonic, + keyData: keyData, + ), + showBackButton: true, + routeOnSuccess: WalletBackupView.routeName, + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to view recovery phrase", + biometricsAuthenticationTitle: + "View recovery phrase", + ), settings: const RouteSettings( name: "/viewRecoverPhraseLockscreen", ), 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 21d76faaf..748912e9d 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 @@ -43,8 +43,7 @@ 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/impl/mimblewimblecoin_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -185,7 +184,7 @@ class _TransactionV2DetailsViewState final wallet = ref.read(pWallets).getWallet(walletId); hasTxKeyProbably = - (wallet is LibMoneroWallet || wallet is LibSalviumWallet) && + (wallet is CryptonoteWallet) && (_transaction.type == TransactionType.outgoing || _transaction.type == TransactionType.sentToSelf); diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 417ad227d..b7929f132 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -53,7 +53,7 @@ import '../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../wallets/wallet/impl/namecoin_wallet.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; @@ -1272,9 +1272,7 @@ class _WalletViewState extends ConsumerState { ); }, ), - if ((wallet is LibMoneroWallet || - wallet is LibSalviumWallet) && - !viewOnly) + if ((wallet is CryptonoteWallet) && !viewOnly) WalletNavigationBarItemData( label: "Churn", icon: const ChurnNavIcon(), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart index 7215e6e4f..e0dfd7fc1 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart @@ -12,17 +12,17 @@ import '../../../../utilities/eth_commons.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart'; +import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/firo_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../widgets/animated_text.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../../widgets/desktop/desktop_fee_dialog.dart'; import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/fee_slider.dart'; -import '../../../../wl_gen/interfaces/cs_monero_interface.dart'; -import '../../../../wl_gen/interfaces/cs_wownero_interface.dart'; class DesktopSendFeeForm extends ConsumerStatefulWidget { const DesktopSendFeeForm({ @@ -168,13 +168,12 @@ class _DesktopSendFeeFormState extends ConsumerState { .read(pWallets) .getWallet(widget.walletId); - if (coin is Monero || coin is Wownero) { + if (coin is CryptonoteCurrency) { final fee = await wallet.estimateFeeFor( amount, BigInt.from( - coin is Monero - ? csMonero.getTxPriorityMedium() - : csWownero.getTxPriorityMedium(), + (wallet as CryptonoteWallet) + .getTxPriorityMedium(), ), ); ref diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart index 8d14c789d..052569af2 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -41,7 +41,7 @@ import '../../../../wallets/crypto_currency/coins/banano.dart'; import '../../../../wallets/crypto_currency/coins/firo.dart'; import '../../../../wallets/wallet/impl/firo_wallet.dart'; import '../../../../wallets/wallet/impl/namecoin_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../../../../wallets/wallet/wallet.dart' show Wallet; import '../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart'; @@ -490,8 +490,7 @@ class _DesktopWalletFeaturesState extends ConsumerState { wallet is CashFusionInterface) (WalletFeature.fusion, Assets.svg.cashFusion, _onFusionPressed), - if (!isViewOnly && - (wallet is LibMoneroWallet || wallet is LibSalviumWallet)) + if (!isViewOnly && (wallet is CryptonoteWallet)) (WalletFeature.churn, Assets.svg.churn, _onChurnPressed), if (wallet is NamecoinWallet) 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 4f1db4504..dc1577183 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 @@ -23,8 +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/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -61,12 +60,11 @@ class _UnlockWalletKeysDesktopState unawaited( showDialog( context: context, - builder: - (context) => const Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [LoadingIndicator(width: 200, height: 200)], - ), + builder: (context) => const Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [LoadingIndicator(width: 200, height: 200)], + ), ), ); @@ -108,10 +106,9 @@ class _UnlockWalletKeysDesktopState myName: wallet.frostInfo.myName, config: results[1]!, keys: results[0]!, - prevGen: - results[2] == null || results[3] == null - ? null - : (config: results[3]!, keys: results[2]!), + prevGen: results[2] == null || results[3] == null + ? null + : (config: results[3]!, keys: results[2]!), ); } } else { @@ -131,9 +128,7 @@ class _UnlockWalletKeysDesktopState keyData = await wallet.getViewOnlyWalletData(); } else if (wallet is ExtendedKeysInterface) { keyData = await wallet.getXPrivs(); - } else if (wallet is LibMoneroWallet) { - keyData = await wallet.getKeys(); - } else if (wallet is LibSalviumWallet) { + } else if (wallet is CryptonoteWallet) { keyData = await wallet.getKeys(); } @@ -191,8 +186,10 @@ class _UnlockWalletKeysDesktopState mainAxisAlignment: MainAxisAlignment.end, children: [ DesktopDialogCloseButton( - onPressedOverride: - Navigator.of(context, rootNavigator: true).pop, + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, ), ], ), @@ -230,53 +227,53 @@ class _UnlockWalletKeysDesktopState enterPassphrase(); } }, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: SizedBox( - height: 70, - child: Row( - children: [ - GestureDetector( - key: const Key( - "enterUnlockWalletKeysDesktopFieldShowPasswordButtonKey", - ), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(1000), - ), - height: 32, - width: 32, - child: Center( - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: - Theme.of( + decoration: + standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + GestureDetector( + key: const Key( + "enterUnlockWalletKeysDesktopFieldShowPasswordButtonKey", + ), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(1000), + ), + height: 32, + width: 32, + child: Center( + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of( context, ).extension()!.textDark3, - width: 24, - height: 19, + width: 24, + height: 19, + ), + ), ), ), - ), + const SizedBox(width: 10), + ], ), - const SizedBox(width: 10), - ], + ), ), ), - ), - ), onChanged: (newValue) { setState(() { continueEnabled = newValue.isNotEmpty; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index 8136133ca..9a933e511 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -29,8 +29,7 @@ import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; +import '../../../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../addresses/desktop_wallet_addresses_view.dart'; @@ -63,23 +62,15 @@ enum _WalletOptions { } class WalletOptionsButton extends ConsumerWidget { - const WalletOptionsButton({ - super.key, - required this.walletId, - }); + const WalletOptionsButton({super.key, required this.walletId}); final String walletId; @override Widget build(BuildContext context, WidgetRef ref) { return RawMaterialButton( - constraints: const BoxConstraints( - minHeight: 32, - minWidth: 32, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(1000), - ), + constraints: const BoxConstraints(minHeight: 32, minWidth: 32), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(1000)), onPressed: () async { final func = await showDialog<_WalletOptions?>( context: context, @@ -148,9 +139,10 @@ class WalletOptionsButton extends ConsumerWidget { case _WalletOptions.showXpub: final xpubData = await showLoading( delay: const Duration(milliseconds: 800), - whileFuture: (ref.read(pWallets).getWallet(walletId) - as ExtendedKeysInterface) - .getXPubs(), + whileFuture: + (ref.read(pWallets).getWallet(walletId) + as ExtendedKeysInterface) + .getXPubs(), context: context, message: "Loading xpubs", rootNavigator: Util.isDesktop, @@ -224,9 +216,8 @@ class WalletOptionsButton extends ConsumerWidget { unawaited( showDialog( context: context, - builder: (context) => EditRefreshHeightView( - walletId: walletId, - ), + builder: (context) => + EditRefreshHeightView(walletId: walletId), ), ); } else { @@ -242,19 +233,16 @@ class WalletOptionsButton extends ConsumerWidget { } }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 19, - horizontal: 32, - ), + padding: const EdgeInsets.symmetric(vertical: 19, horizontal: 32), child: Row( children: [ SvgPicture.asset( Assets.svg.ellipsis, width: 20, height: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, ), ], ), @@ -297,7 +285,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { final bool canChangeRep = coin is NanoCurrency; final bool isFrost = coin is FrostCurrency; - final bool isMoneroWow = wallet is LibMoneroWallet || wallet is LibSalviumWallet; + final bool isCN = wallet is CryptonoteWallet; return Stack( children: [ @@ -339,23 +327,21 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.addressList.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], ), ), ), - if (canChangeRep) - const SizedBox( - height: 8, - ), + if (canChangeRep) const SizedBox(height: 8), if (canChangeRep) TransparentButton( onPressed: onChangeRepPressed, @@ -376,23 +362,21 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.changeRepresentative.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], ), ), ), - if (isFrost) - const SizedBox( - height: 8, - ), + if (isFrost) const SizedBox(height: 8), if (isFrost) TransparentButton( onPressed: onFrostMSWalletOptionsPressed, @@ -413,24 +397,22 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.frostOptions.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], ), ), ), - if (isMoneroWow) - const SizedBox( - height: 8, - ), - if (isMoneroWow) + if (isCN) const SizedBox(height: 8), + if (isCN) TransparentButton( onPressed: onRefreshHeightPressed, child: Padding( @@ -450,23 +432,21 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.refreshFromHeight.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], ), ), ), - if (xpubEnabled) - const SizedBox( - height: 8, - ), + if (xpubEnabled) const SizedBox(height: 8), if (xpubEnabled) TransparentButton( onPressed: onShowXpubPressed, @@ -487,22 +467,21 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.showXpub.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], ), ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), TransparentButton( onPressed: onDeletePressed, child: Padding( @@ -522,13 +501,14 @@ class WalletOptionsPopupMenu extends ConsumerWidget { Expanded( child: Text( _WalletOptions.deleteWallet.prettyName, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), ), ), ], @@ -546,11 +526,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget { } class TransparentButton extends StatelessWidget { - const TransparentButton({ - super.key, - required this.child, - this.onPressed, - }); + const TransparentButton({super.key, required this.child, this.onPressed}); final Widget child; final VoidCallback? onPressed; @@ -558,10 +534,7 @@ class TransparentButton extends StatelessWidget { @override Widget build(BuildContext context) { return RawMaterialButton( - constraints: const BoxConstraints( - minHeight: 32, - minWidth: 32, - ), + constraints: const BoxConstraints(minHeight: 32, minWidth: 32), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( diff --git a/lib/providers/churning/churning_service_provider.dart b/lib/providers/churning/churning_service_provider.dart index 642d1103f..2da0fa5b5 100644 --- a/lib/providers/churning/churning_service_provider.dart +++ b/lib/providers/churning/churning_service_provider.dart @@ -1,12 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../services/churning_service.dart'; -import '../../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../global/wallets_provider.dart'; final pChurningService = ChangeNotifierProvider.family( (ref, walletId) { final wallet = ref.watch(pWallets.select((s) => s.getWallet(walletId))); - return ChurningService(wallet: wallet as LibMoneroWallet); + return ChurningService(wallet: wallet as CryptonoteWallet); }, ); diff --git a/lib/services/churning_service.dart b/lib/services/churning_service.dart index b1449fd68..a7f08197c 100644 --- a/lib/services/churning_service.dart +++ b/lib/services/churning_service.dart @@ -5,8 +5,9 @@ import 'package:flutter/cupertino.dart'; import 'package:mutex/mutex.dart'; import '../utilities/logger.dart'; -import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; -import '../wl_gen/interfaces/cs_monero_interface.dart'; +import '../wallets/wallet/intermediate/cryptonote_wallet.dart'; +import '../wl_gen/interfaces/cs_monero_interface.dart' + show CsRecipient, CsOutput; enum ChurnStatus { waiting, running, failed, success } @@ -16,7 +17,7 @@ class ChurningService extends ChangeNotifier { ChurningService({required this.wallet}); - final LibMoneroWallet wallet; + final CryptonoteWallet wallet; String get walletId => wallet.walletId; int rounds = 1; // default @@ -33,7 +34,7 @@ class ChurningService extends ChangeNotifier { bool _canChurn() { if (wallet.wallet != null && - csMonero.getUnlockedBalance(wallet.wallet!, accountIndex: kAccount)! > + wallet.internalGetUnlockedBalance(accountIndex: kAccount)! > BigInt.zero) { return true; } else { @@ -50,7 +51,7 @@ class ChurningService extends ChangeNotifier { final outputs = wallet.wallet == null ? [] - : await csMonero.getOutputs(wallet.wallet!, refresh: true); + : await wallet.internalGetOutputs(refresh: true); final required = wallet.cryptoCurrency.minConfirms; int lowestNumberOfConfirms = required; @@ -185,27 +186,25 @@ class ChurningService extends ChangeNotifier { } Future _churnTxSimple() async { - final address = csMonero.getAddress( - wallet.wallet!, + final address = wallet.internalGetAddress( accountIndex: kAccount, addressIndex: 0, ); final height = await wallet.chainHeight; - final pending = await csMonero.createTx( - wallet.wallet!, + final pending = await wallet.internalCreateTx( output: CsRecipient( address, BigInt.zero, // Doesn't matter if `sweep` is true ), - priority: csMonero.getTxPriorityNormal(), + priority: wallet.getTxPriorityNormal(), accountIndex: kAccount, sweep: true, minConfirms: wallet.cryptoCurrency.minConfirms, currentHeight: height, ); - await csMonero.commitTx(wallet.wallet!, pending); + await wallet.internalCommitTx(pending); } } diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index ffdfbdc0a..d8028421a 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -27,7 +27,7 @@ import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/wallet/impl/epiccash_wallet.dart'; import '../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; -import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'event_bus/events/wallet_added_event.dart'; @@ -201,11 +201,10 @@ class Wallets { boxName: DB.boxNameWalletsToDeleteOnStart, )) { await mainDB.isar.writeTxn( - () async => - await mainDB.isar.walletInfo - .where() - .walletIdEqualTo(walletId) - .deleteAll(), + () async => await mainDB.isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .deleteAll(), ); } // clear list @@ -262,7 +261,7 @@ class Wallets { shouldAutoSyncAll || walletIdsToEnableAutoSync.contains(walletInfo.walletId); - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) { + if (wallet is CryptonoteWallet) { // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); } else { walletInitFutures.add( @@ -310,11 +309,10 @@ class Wallets { boxName: DB.boxNameWalletsToDeleteOnStart, )) { await mainDB.isar.writeTxn( - () async => - await mainDB.isar.walletInfo - .where() - .walletIdEqualTo(walletId) - .deleteAll(), + () async => await mainDB.isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .deleteAll(), ); } // clear list @@ -371,7 +369,7 @@ class Wallets { nodeService: nodeService, prefs: prefs, ).then((wallet) { - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) { + if (wallet is CryptonoteWallet) { // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); walletIdCompleter.complete("dummy_ignore"); @@ -394,17 +392,15 @@ class Wallets { final asyncWalletIds = await Future.wait(walletIDInitFutures); asyncWalletIds.removeWhere((e) => e == "dummy_ignore"); - final List> walletInitFutures = - asyncWalletIds - .map( - (id) => _wallets[id]!.init().then((_) { - if (shouldAutoSyncAll || - walletIdsToEnableAutoSync.contains(id)) { - _wallets[id]!.shouldAutoSync = true; - } - }), - ) - .toList(); + final List> walletInitFutures = asyncWalletIds + .map( + (id) => _wallets[id]!.init().then((_) { + if (shouldAutoSyncAll || walletIdsToEnableAutoSync.contains(id)) { + _wallets[id]!.shouldAutoSync = true; + } + }), + ) + .toList(); if (walletInitFutures.isNotEmpty && walletsToInitLinearly.isNotEmpty) { unawaited( @@ -435,11 +431,10 @@ class Wallets { boxName: DB.boxNameWalletsToDeleteOnStart, )) { await mainDB.isar.writeTxn( - () async => - await mainDB.isar.walletInfo - .where() - .walletIdEqualTo(walletId) - .deleteAll(), + () async => await mainDB.isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .deleteAll(), ); } // clear list @@ -447,15 +442,14 @@ class Wallets { boxName: DB.boxNameWalletsToDeleteOnStart, ); - final walletInfoList = - await mainDB.isar.walletInfo - .where() - .filter() - .anyOf( - AppConfig.coins.map((e) => e.identifier), - (q, element) => q.coinNameMatches(element), - ) - .findAll(); + final walletInfoList = await mainDB.isar.walletInfo + .where() + .filter() + .anyOf( + AppConfig.coins.map((e) => e.identifier), + (q, element) => q.coinNameMatches(element), + ) + .findAll(); if (isDuress) { walletInfoList.retainWhere((e) => e.isDuressVisible); @@ -509,7 +503,7 @@ class Wallets { nodeService: nodeService, prefs: prefs, ).then((wallet) { - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) { + if (wallet is CryptonoteWallet) { // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); walletIdCompleter.complete("dummy_ignore"); @@ -533,17 +527,16 @@ class Wallets { asyncWalletIds.removeWhere((e) => e == "dummy_ignore"); final List idsToRefresh = []; - final List> walletInitFutures = - asyncWalletIds - .map( - (id) => _wallets[id]!.init().then((_) { - if (shouldSyncAllOnceOnStartup || - walletIdsToSyncOnceOnStartup.contains(id)) { - idsToRefresh.add(id); - } - }), - ) - .toList(); + final List> walletInitFutures = asyncWalletIds + .map( + (id) => _wallets[id]!.init().then((_) { + if (shouldSyncAllOnceOnStartup || + walletIdsToSyncOnceOnStartup.contains(id)) { + idsToRefresh.add(id); + } + }), + ) + .toList(); Future _refreshFutures(List idsToRefresh) async { final start = DateTime.now(); @@ -620,7 +613,7 @@ class Wallets { walletIdsToEnableAutoSync.contains(wallet.walletId); if (isDesktop) { - if (wallet is LibMoneroWallet || wallet is LibSalviumWallet) { + if (wallet is CryptonoteWallet) { // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); } else { walletInitFutures.add( diff --git a/lib/wallets/wallet/intermediate/cryptonote_wallet.dart b/lib/wallets/wallet/intermediate/cryptonote_wallet.dart index 0d32e07a3..602343427 100644 --- a/lib/wallets/wallet/intermediate/cryptonote_wallet.dart +++ b/lib/wallets/wallet/intermediate/cryptonote_wallet.dart @@ -1,10 +1,65 @@ +import 'package:meta/meta.dart'; + +import '../../../models/input.dart'; +import '../../../models/keys/cw_key_data.dart'; +import '../../../wl_gen/interfaces/cs_monero_interface.dart' + show CsOutput, CsPendingTransaction, CsRecipient; +import '../../../wl_gen/interfaces/cs_salvium_interface.dart'; import '../../crypto_currency/intermediate/cryptonote_currency.dart'; -import '../wallet.dart'; import '../wallet_mixin_interfaces/coin_control_interface.dart'; import '../wallet_mixin_interfaces/mnemonic_interface.dart'; import 'external_wallet.dart'; -abstract class CryptonoteWallet extends ExternalWallet +abstract class CryptonoteWallet + extends ExternalWallet with MnemonicInterface, CoinControlInterface { CryptonoteWallet(super.currency); + + WrappedWallet? wallet; + + double highestPercentCached = 0; + int currentKnownChainHeight = 0; + + @mustCallSuper + @override + Future init({bool? isRestore, int? wordCount}); + + Future getKeys(); + + String getTxKeyFor({required String txid}); + + Future<(String, String)> + hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing(); + + void setRefreshFromBlockHeight(int newHeight); + + int getRefreshFromBlockHeight(); + + String internalGetAddress({ + required int accountIndex, + required int addressIndex, + }); + + BigInt? internalGetUnlockedBalance({int accountIndex = 0}); + Future> internalGetOutputs({ + bool refresh = false, + bool includeSpent = false, + }); + + Future internalCreateTx({ + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }); + + Future internalCommitTx(CsPendingTransaction tx); + + // tx prio forwarding + int getTxPriorityHigh(); + int getTxPriorityMedium(); + int getTxPriorityNormal(); } diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index 38ca63ea5..bea4547dc 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -51,8 +51,6 @@ abstract class LibMoneroWallet @override int get isarTransactionVersion => 2; - WrappedWallet? wallet; - LibMoneroWallet(super.currency, this.compatType) { final bus = GlobalEventBus.instance; @@ -135,8 +133,6 @@ abstract class LibMoneroWallet bool _txRefreshLock = false; int _lastCheckedHeight = -1; int _txCount = 0; - int currentKnownChainHeight = 0; - double highestPercentCached = 0; Future loadWallet({ required String path, @@ -170,6 +166,7 @@ abstract class LibMoneroWallet bool walletExists(String path); + @override String getTxKeyFor({required String txid}) { if (wallet == null) { throw Exception("Cannot get tx key in uninitialized libMoneroWallet"); @@ -293,6 +290,7 @@ abstract class LibMoneroWallet return newReceivingAddress; } + @override Future getKeys() async { final oldInfo = getLibMoneroWalletInfo(walletId); if (wallet == null || (oldInfo != null && oldInfo.name != walletId)) { @@ -318,6 +316,7 @@ abstract class LibMoneroWallet } } + @override Future<(String, String)> hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing() async { final path = await pathForWallet(name: walletId, type: compatType); @@ -1416,6 +1415,102 @@ abstract class LibMoneroWallet } } + @override + int getRefreshFromBlockHeight() => wallet == null + ? throw Exception( + "Cannot getRefreshFromBlockHeight when wallet is not open", + ) + : csMonero.getRefreshFromBlockHeight(wallet!); + + @override + int getTxPriorityHigh() => csMonero.getTxPriorityHigh(); + + @override + int getTxPriorityMedium() => csMonero.getTxPriorityMedium(); + + @override + int getTxPriorityNormal() => csMonero.getTxPriorityNormal(); + + @override + Future internalCommitTx(CsPendingTransaction tx) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + + return csMonero.commitTx(wallet!, tx); + } + + @override + Future internalCreateTx({ + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csMonero.createTx( + wallet!, + output: output, + priority: priority, + sweep: sweep, + accountIndex: accountIndex, + minConfirms: minConfirms, + currentHeight: currentHeight, + preferredInputs: preferredInputs, + ); + } + + @override + String internalGetAddress({ + required int accountIndex, + required int addressIndex, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csMonero.getAddress( + wallet!, + accountIndex: accountIndex, + addressIndex: addressIndex, + ); + } + + @override + Future> internalGetOutputs({ + bool refresh = false, + bool includeSpent = false, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csMonero.getOutputs( + wallet!, + refresh: refresh, + includeSpent: includeSpent, + ); + } + + @override + BigInt? internalGetUnlockedBalance({int accountIndex = 0}) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csMonero.getUnlockedBalance(wallet!, accountIndex: accountIndex); + } + + @override + void setRefreshFromBlockHeight(int newHeight) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + csMonero.setRefreshFromBlockHeight(wallet!, newHeight); + } + // ============== View only ================================================== @override diff --git a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart index 4081c9012..020fe1a6f 100644 --- a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart @@ -49,8 +49,6 @@ abstract class LibSalviumWallet @override int get isarTransactionVersion => 2; - WrappedWallet? wallet; - LibSalviumWallet(super.currency) { final bus = GlobalEventBus.instance; @@ -131,8 +129,6 @@ abstract class LibSalviumWallet bool _txRefreshLock = false; int _lastCheckedHeight = -1; int _txCount = 0; - int currentKnownChainHeight = 0; - double highestPercentCached = 0; Future loadWallet({ required String path, @@ -166,6 +162,7 @@ abstract class LibSalviumWallet bool walletExists(String path); + @override String getTxKeyFor({required String txid}) { if (wallet == null) { throw Exception("Cannot get tx key in uninitialized libSalviumWallet"); @@ -274,6 +271,7 @@ abstract class LibSalviumWallet return newReceivingAddress; } + @override Future getKeys() async { if (wallet == null) { return null; @@ -298,6 +296,7 @@ abstract class LibSalviumWallet } } + @override Future<(String, String)> hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing() async { final path = await pathForWallet(name: walletId); @@ -1409,6 +1408,102 @@ abstract class LibSalviumWallet } } + @override + int getRefreshFromBlockHeight() => wallet == null + ? throw Exception( + "Cannot getRefreshFromBlockHeight when wallet is not open", + ) + : csSalvium.getRefreshFromBlockHeight(wallet!); + + @override + int getTxPriorityHigh() => csSalvium.getTxPriorityHigh(); + + @override + int getTxPriorityMedium() => csSalvium.getTxPriorityMedium(); + + @override + int getTxPriorityNormal() => csSalvium.getTxPriorityNormal(); + + @override + Future internalCommitTx(CsPendingTransaction tx) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + + return csSalvium.commitTx(wallet!, tx); + } + + @override + Future internalCreateTx({ + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csSalvium.createTx( + wallet!, + output: output, + priority: priority, + sweep: sweep, + accountIndex: accountIndex, + minConfirms: minConfirms, + currentHeight: currentHeight, + preferredInputs: preferredInputs, + ); + } + + @override + String internalGetAddress({ + required int accountIndex, + required int addressIndex, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csSalvium.getAddress( + wallet!, + accountIndex: accountIndex, + addressIndex: addressIndex, + ); + } + + @override + Future> internalGetOutputs({ + bool refresh = false, + bool includeSpent = false, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csSalvium.getOutputs( + wallet!, + refresh: refresh, + includeSpent: includeSpent, + ); + } + + @override + BigInt? internalGetUnlockedBalance({int accountIndex = 0}) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csSalvium.getUnlockedBalance(wallet!, accountIndex: accountIndex); + } + + @override + void setRefreshFromBlockHeight(int newHeight) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + csSalvium.setRefreshFromBlockHeight(wallet!, newHeight); + } + // ============== View only ================================================== @override diff --git a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart index cf47fb9b5..b6ad2ef0b 100644 --- a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart @@ -52,8 +52,6 @@ abstract class LibWowneroWallet @override int get isarTransactionVersion => 2; - WrappedWallet? wallet; - LibWowneroWallet(super.currency, this.compatType) { final bus = GlobalEventBus.instance; @@ -136,8 +134,6 @@ abstract class LibWowneroWallet bool _txRefreshLock = false; int _lastCheckedHeight = -1; int _txCount = 0; - int currentKnownChainHeight = 0; - double highestPercentCached = 0; Future loadWallet({ required String path, @@ -171,6 +167,7 @@ abstract class LibWowneroWallet bool walletExists(String path); + @override String getTxKeyFor({required String txid}) { if (wallet == null) { throw Exception("Cannot get tx key in uninitialized LibWowneroWallet"); @@ -294,6 +291,7 @@ abstract class LibWowneroWallet return newReceivingAddress; } + @override Future getKeys() async { final oldInfo = getLibWowneroWalletInfo(walletId); if (wallet == null || (oldInfo != null && oldInfo.name != walletId)) { @@ -319,6 +317,7 @@ abstract class LibWowneroWallet } } + @override Future<(String, String)> hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing() async { final path = await pathForWallet(name: walletId, type: compatType); @@ -1420,6 +1419,102 @@ abstract class LibWowneroWallet } } + @override + int getRefreshFromBlockHeight() => wallet == null + ? throw Exception( + "Cannot getRefreshFromBlockHeight when wallet is not open", + ) + : csWownero.getRefreshFromBlockHeight(wallet!); + + @override + int getTxPriorityHigh() => csWownero.getTxPriorityHigh(); + + @override + int getTxPriorityMedium() => csWownero.getTxPriorityMedium(); + + @override + int getTxPriorityNormal() => csWownero.getTxPriorityNormal(); + + @override + Future internalCommitTx(CsPendingTransaction tx) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + + return csWownero.commitTx(wallet!, tx); + } + + @override + Future internalCreateTx({ + required CsRecipient output, + required int priority, + required bool sweep, + List? preferredInputs, + required int accountIndex, + required int minConfirms, + required int currentHeight, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csWownero.createTx( + wallet!, + output: output, + priority: priority, + sweep: sweep, + accountIndex: accountIndex, + minConfirms: minConfirms, + currentHeight: currentHeight, + preferredInputs: preferredInputs, + ); + } + + @override + String internalGetAddress({ + required int accountIndex, + required int addressIndex, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csWownero.getAddress( + wallet!, + accountIndex: accountIndex, + addressIndex: addressIndex, + ); + } + + @override + Future> internalGetOutputs({ + bool refresh = false, + bool includeSpent = false, + }) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csWownero.getOutputs( + wallet!, + refresh: refresh, + includeSpent: includeSpent, + ); + } + + @override + BigInt? internalGetUnlockedBalance({int accountIndex = 0}) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + return csWownero.getUnlockedBalance(wallet!, accountIndex: accountIndex); + } + + @override + void setRefreshFromBlockHeight(int newHeight) { + if (wallet == null) { + throw Exception("Cannot internalCommitTx when wallet is not open"); + } + csWownero.setRefreshFromBlockHeight(wallet!, newHeight); + } + // ============== View only ================================================== @override diff --git a/lib/widgets/desktop/desktop_fee_dialog.dart b/lib/widgets/desktop/desktop_fee_dialog.dart index 5f9d16390..3ec5124bd 100644 --- a/lib/widgets/desktop/desktop_fee_dialog.dart +++ b/lib/widgets/desktop/desktop_fee_dialog.dart @@ -15,8 +15,7 @@ import '../../utilities/text_styles.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/wallet/impl/firo_wallet.dart'; -import '../../wl_gen/interfaces/cs_monero_interface.dart'; -import '../../wl_gen/interfaces/cs_wownero_interface.dart'; +import '../../wallets/wallet/intermediate/cryptonote_wallet.dart'; import '../animated_text.dart'; import '../conditional_parent.dart'; import 'desktop_dialog.dart'; @@ -61,16 +60,10 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { + if (wallet is CryptonoteWallet) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csMonero.getTxPriorityHigh()), - ); - ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; - } else if (coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csWownero.getTxPriorityHigh()), + BigInt.from(wallet.getTxPriorityHigh()), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { @@ -117,16 +110,10 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csMonero.getTxPriorityMedium()), - ); - ref.read(feeSheetSessionCacheProvider).average[amount] = fee; - } else if (coin is Wownero) { + if (wallet is CryptonoteWallet) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csWownero.getTxPriorityMedium()), + BigInt.from(wallet.getTxPriorityMedium()), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { @@ -173,16 +160,10 @@ class _DesktopFeeDialogState extends ConsumerState { if (widget.isToken == false) { final wallet = ref.read(pWallets).getWallet(walletId); - if (coin is Monero) { - final fee = await wallet.estimateFeeFor( - amount, - BigInt.from(csMonero.getTxPriorityNormal()), - ); - ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; - } else if (coin is Wownero) { + if (wallet is CryptonoteWallet) { final fee = await wallet.estimateFeeFor( amount, - BigInt.from(csWownero.getTxPriorityNormal()), + BigInt.from(wallet.getTxPriorityNormal()), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { diff --git a/lib/widgets/tx_key_widget.dart b/lib/widgets/tx_key_widget.dart index f9e1fb4bc..8222fdbab 100644 --- a/lib/widgets/tx_key_widget.dart +++ b/lib/widgets/tx_key_widget.dart @@ -8,13 +8,13 @@ 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 '../wallets/wallet/intermediate/cryptonote_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]! + /// The [walletId] MUST be the id of a [CryptonoteWallet]! const TxKeyWidget({super.key, required this.walletId, required this.txid}); final String walletId; @@ -38,24 +38,20 @@ class _TxKeyWidgetState extends ConsumerState { 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", - ), + 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; + ref.read(pWallets).getWallet(widget.walletId) as CryptonoteWallet; _private = wallet.getTxKeyFor(txid: widget.txid); if (_private!.isEmpty) { @@ -76,16 +72,15 @@ class _TxKeyWidgetState extends ConsumerState { @override Widget build(BuildContext context) { return DetailItemBase( - button: - _private == null - ? CustomTextButton( - text: "Show", - onTap: _loadTxKey, - enabled: _private == null, - ) - : Util.isDesktop - ? tvd.IconCopyButton(data: _private!) - : SimpleCopyButton(data: _private!), + button: _private == null + ? CustomTextButton( + text: "Show", + onTap: _loadTxKey, + enabled: _private == null, + ) + : Util.isDesktop + ? tvd.IconCopyButton(data: _private!) + : SimpleCopyButton(data: _private!), title: Text("Private view key", style: STextStyles.itemSubtitle(context)), detail: SelectableText( // TODO