From 470a8ac21222b42d9a18b741525aed16dcbcfc5c Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 26 Oct 2024 16:31:05 +0400 Subject: [PATCH 01/91] init mwc --- .gitmodules | 3 + README.md | 1 + crypto_plugins/flutter_libmwc | 1 + lib/db/db_version_migration.dart | 75 +- lib/db/migrate_wallets_to_isar.dart | 16 + .../blockchain_data/v2/transaction_v2.dart | 37 + lib/models/isar/stack_theme.dart | 9 + .../restore_wallet_view.dart | 27 +- .../send_view/confirm_transaction_view.dart | 24 +- lib/pages/send_view/send_view.dart | 44 +- .../global_settings_view/about_view.dart | 48 + .../advanced_views/debug_view.dart | 6 + .../add_edit_node_view.dart | 40 +- .../helpers/restore_create_backup.dart | 3 +- .../wallet_network_settings_view.dart | 34 +- .../wallet_settings_view.dart | 103 ++ .../all_transactions_view.dart | 17 + .../tx_v2/transaction_v2_details_view.dart | 131 +- .../wallet_view/sub_widgets/desktop_send.dart | 64 +- .../delete_password_warning_view.dart | 12 + .../settings_menu/desktop_about_view.dart | 171 ++- lib/services/price.dart | 1 + lib/services/wallets.dart | 10 + lib/themes/coin_icon_provider.dart | 2 + lib/utilities/address_utils.dart | 27 + lib/utilities/amount/amount_unit.dart | 1 + lib/utilities/assets.dart | 1 + lib/utilities/default_mwcmqs.dart | 53 + lib/utilities/git_status.dart | 74 +- lib/utilities/test_node_connection.dart | 1 - .../coins/mimblewimblecoin.dart | 128 ++ .../crypto_currency/crypto_currency.dart | 3 +- lib/wallets/isar/models/wallet_info.dart | 1 + .../wallet/impl/mimblewimblecoin_wallet.dart | 1197 +++++++++++++++++ ...imblewimblecoin_wallet_info_extension.dart | 111 ++ lib/wallets/wallet/wallet.dart | 4 + .../tor_has_been_add_dialog.dart | 2 +- lib/widgets/transaction_card.dart | 19 + linux/flutter/generated_plugin_registrant.cc | 5 +- linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 7 + scripts/app_config/configure_stack_wallet.sh | 1 + .../app_config/templates/linux/CMakeLists.txt | 5 +- scripts/app_config/templates/pubspec.template | 3 + .../templates/windows/CMakeLists.txt | 3 + scripts/ios/build_all.sh | 2 + scripts/ios/build_all_campfire.sh | 2 + scripts/ios/build_all_duo.sh | 2 + scripts/linux/build_all.sh | 2 + scripts/linux/build_all_campfire.sh | 2 + scripts/linux/build_all_duo.sh | 2 + scripts/macos/build_all.sh | 2 + scripts/macos/build_all_campfire.sh | 2 + scripts/macos/build_all_duo.sh | 2 + scripts/rust_version.sh | 9 + scripts/windows/build_all.sh | 2 + scripts/windows/build_all_campfire.sh | 2 + scripts/windows/build_all_duo.sh | 2 + scripts/windows/deps.sh | 7 +- test/price_test.dart | 2 + test/sample_data/theme_json.dart | 4 + test/sample_data/theme_json_v2.dart | 4 + test/services/node_service_test.dart | 13 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 66 files changed, 2464 insertions(+), 131 deletions(-) create mode 160000 crypto_plugins/flutter_libmwc create mode 100644 lib/utilities/default_mwcmqs.dart create mode 100644 lib/wallets/crypto_currency/coins/mimblewimblecoin.dart create mode 100644 lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart create mode 100644 lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart diff --git a/.gitmodules b/.gitmodules index 2186826df3..f1b4eb8746 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "crypto_plugins/frostdart"] path = crypto_plugins/frostdart url = https://github.com/cypherstack/frostdart +[submodule "crypto_plugins/flutter_libmwc"] + path = crypto_plugins/flutter_libmwc + url = https://github.com/vekamo/flutter_libmwc diff --git a/README.md b/README.md index 4c18c3181a..286bb6f544 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Highlights include: - [Bitcoin Cash](https://bch.info/en/) - [Dogecoin](https://dogecoin.com/) - [Epic Cash](https://linktr.ee/epiccash) + - [MimbleWimbleCoin](https://mwc.mw) - [Ethereum](https://ethereum.org/en/) - [Firo](https://firo.org/) - [Litecoin](https://litecoin.org/) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc new file mode 160000 index 0000000000..ff25b65406 --- /dev/null +++ b/crypto_plugins/flutter_libmwc @@ -0,0 +1 @@ +Subproject commit ff25b6540698e6fe3e89dda5f0480552a882369d diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index 1415803e4f..f350af42e7 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -528,6 +528,71 @@ class DbVersionMigrator with WalletDB { await MainDB.instance.addNewTransactionData(transactionsData, walletId); } + // we need to manually migrate mimblewimblecoin transactions as they are not + // stored on the mimblewimblecoin blockchain + final mimblewimblecoin = Mimblewimblecoin(CryptoCurrencyNetwork.main); + if (info.coinIdentifier == mimblewimblecoin.identifier) { + final txnData = walletBox.get("latest_tx_model") as TransactionData?; + + // we ever only used index 0 in the past + const rcvIndex = 0; + + final List> + transactionsData = []; + if (txnData != null) { + final txns = txnData.getAllTransactions(); + + for (final tx in txns.values) { + final bool isIncoming = tx.txType == "Received"; + + final iTx = isar_models.Transaction( + walletId: walletId, + txid: tx.txid, + timestamp: tx.timestamp, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: tx.amount, + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: mimblewimblecoin.fractionDigits, + ).toJsonString(), + fee: tx.fees, + height: tx.height, + isCancelled: tx.isCancelled, + isLelantus: false, + slateId: tx.slateId, + otherData: tx.otherData, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: tx.numberOfMessages, + ); + + if (tx.address.isEmpty) { + transactionsData.add(Tuple2(iTx, null)); + } else { + final address = isar_models.Address( + walletId: walletId, + value: tx.address, + publicKey: [], + derivationIndex: isIncoming ? rcvIndex : -1, + derivationPath: null, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, + ); + transactionsData.add(Tuple2(iTx, address)); + } + } + } + await MainDB.instance.addNewTransactionData(transactionsData, walletId); + } + // delete data from hive await walletBox.delete(receiveAddressesPrefix); await walletBox.delete("${receiveAddressesPrefix}P2PKH"); @@ -549,9 +614,13 @@ class DbVersionMigrator with WalletDB { ); } - // doing this for epic cash will delete transaction history as it is not - // stored on the epic cash blockchain - if (info.coinIdentifier != epic.identifier) { + // doing this for epiccash/mimblewimblecoin will delete transaction history as it is not + // stored on the epiccash/mimblewimblecoin blockchain + final excludedIdentifiers = [ + epic.identifier, + mimblewimblecoin.identifier + ]; + if ((!excludedIdentifiers.contains(info.coinIdentifier))) { // set flag to initiate full rescan on opening wallet await DB.instance.put( boxName: DB.boxNameDBInfo, diff --git a/lib/db/migrate_wallets_to_isar.dart b/lib/db/migrate_wallets_to_isar.dart index cd54a4063f..d0f75a2e37 100644 --- a/lib/db/migrate_wallets_to_isar.dart +++ b/lib/db/migrate_wallets_to_isar.dart @@ -11,6 +11,7 @@ import '../wallets/isar/models/token_wallet_info.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/isar/models/wallet_info_meta.dart'; import '../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import 'hive/db.dart'; import 'isar/main_db.dart'; @@ -146,6 +147,21 @@ Future migrateWalletsToIsar({ otherData[WalletInfoKeys.epiccashData] = jsonEncode( epicWalletInfo.toMap(), ); + } else if (old.coinIdentifier == + Mimblewimblecoin(CryptoCurrencyNetwork.main)) { + final mimblewimblecoinWalletInfo = + ExtraMimblewimblecoinWalletInfo.fromMap({ + "receivingIndex": walletBox.get("receivingIndex") as int? ?? 0, + "changeIndex": walletBox.get("changeIndex") as int? ?? 0, + "slatesToAddresses": walletBox.get("slate_to_address") as Map? ?? {}, + "slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {}, + "lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0, + "restoreHeight": walletBox.get("restoreHeight") as int? ?? 0, + "creationHeight": walletBox.get("creationHeight") as int? ?? 0, + }); + otherData[WalletInfoKeys.mimblewimblecoinData] = jsonEncode( + mimblewimblecoinWalletInfo.toMap(), + ); } else if (old.coinIdentifier == Firo(CryptoCurrencyNetwork.main).identifier || old.coinIdentifier == Firo(CryptoCurrencyNetwork.test).identifier) { diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 4582ef47d2..cde1c965f5 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -95,6 +95,8 @@ class TransactionV2 { bool get isEpiccashTransaction => _getFromOtherData(key: TxV2OdKeys.isEpiccashTransaction) == true; + bool get isMimblewimblecoinTransaction => + _getFromOtherData(key: TxV2OdKeys.isMimblewimblecoinTransaction) == true; int? get numberOfMessages => _getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?; String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?; @@ -274,6 +276,40 @@ class TransactionV2 { } } + if (isMimblewimblecoinTransaction) { + if (slateId == null) { + return "Restored Funds"; + } + + if (isCancelled) { + return "Cancelled"; + } else if (type == TransactionType.incoming) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Received"; + } else { + if (numberOfMessages == 1) { + return "Receiving (waiting for sender)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no) + } else { + return "Receiving ${prettyConfirms()}"; + } + } + } else if (type == TransactionType.outgoing) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Sent (confirmed)"; + } else { + if (numberOfMessages == 1) { + return "Sending (waiting for receiver)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Sending (waiting for confirmations)"; + } else { + return "Sending ${prettyConfirms()}"; + } + } + } + } + if (type == TransactionType.incoming) { // if (_transaction.isMinting) { // return "Minting"; @@ -331,6 +367,7 @@ abstract final class TxV2OdKeys { static const size = "size"; static const vSize = "vSize"; static const isEpiccashTransaction = "isEpiccashTransaction"; + static const isMimblewimblecoinTransaction = "isMimblewimblecoinTransaction"; static const numberOfMessages = "numberOfMessages"; static const slateId = "slateId"; static const onChainNote = "onChainNote"; diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index ce5002a1d5..4f831732f0 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -1939,6 +1939,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; + late final String mimblewimblecoin; late final String ethereum; late final String firo; late final String monero; @@ -1949,6 +1950,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; + late final String mimblewimblecoinImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -1960,6 +1962,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; + late final String mimblewimblecoinImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2006,6 +2009,8 @@ class ThemeAssets implements IThemeAssets { ..bitcoincash = "$themeId/assets/${json["bitcoincash"] as String}" ..dogecoin = "$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$themeId/assets/${json["epicCash"] as String}" + ..mimblewimblecoin = + "$themeId/assets/${json["mimblewimblecoin"] as String}" ..ethereum = "$themeId/assets/${json["ethereum"] as String}" ..firo = "$themeId/assets/${json["firo"] as String}" ..monero = "$themeId/assets/${json["monero"] as String}" @@ -2017,6 +2022,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["bitcoincash_image"] as String}" ..dogecoinImage = "$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$themeId/assets/${json["epicCash_image"] as String}" + ..mimblewimblecoinImage = + "$themeId/assets/${json["mimblewimblecoin_image"] as String}" ..ethereumImage = "$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = "$themeId/assets/${json["firo_image"] as String}" ..litecoinImage = "$themeId/assets/${json["litecoin_image"] as String}" @@ -2032,6 +2039,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$themeId/assets/${json["epicCash_image_secondary"] as String}" + ..mimblewimblecoinImageSecondary = + "$themeId/assets/${json["mimblewimblecoin_image_secondary"] as String}" ..ethereumImageSecondary = "$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = 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 2fae2352cc..20bf9fd8b1 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 @@ -46,9 +46,11 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_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/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../../../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/desktop_app_bar.dart'; @@ -253,10 +255,27 @@ class _RestoreWalletViewState extends ConsumerState { height = 0; } + // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index + if (widget.coin is Mimblewimblecoin) { + final int secondsSinceEpoch = + widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = + chosenSeconds ~/ overestimateSecondsPerBlock; + //todo: check if print needed + // debugPrint( + // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); + height = approximateHeight; + if (height < 0) { + height = 0; + } + otherDataJsonString = jsonEncode( { - WalletInfoKeys.epiccashData: jsonEncode( - ExtraEpiccashWalletInfo( + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( receivingIndex: 0, changeIndex: 0, slatesToAddresses: {}, @@ -354,6 +373,10 @@ class _RestoreWalletViewState extends ConsumerState { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 5090645e30..ce08150f35 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -14,6 +14,7 @@ import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/lib.dart'; +import 'package:flutter_libmwc/lib.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -33,6 +34,7 @@ import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; @@ -159,7 +161,7 @@ class _ConfirmTransactionViewState break; } } else { - if (coin is Epiccash) { + if (coin is Epiccash || coin is Mimblewimblecoin) { txDataFuture = wallet.confirmSend( txData: widget.txData.copyWith( noteOnChain: onChainNoteController.text, @@ -578,11 +580,13 @@ class _ConfirmTransactionViewState ], ), ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) const SizedBox( height: 12, ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -611,7 +615,9 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - (coin is Epiccash) ? "Local Note" : "Note", + (coin is Epiccash || coin is Mimblewimblecoin) + ? "Local Note" + : "Note", style: STextStyles.smallMed12(context), ), const SizedBox( @@ -913,17 +919,17 @@ class _ConfirmTransactionViewState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -963,12 +969,12 @@ class _ConfirmTransactionViewState ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), SelectableText( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 584d868d28..63c6b267cf 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -311,7 +311,9 @@ class _SendViewState extends ConsumerState { _cryptoAmountChangedFeeUpdateTimer?.cancel(); _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_baseFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_baseFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( amount == null @@ -332,7 +334,9 @@ class _SendViewState extends ConsumerState { void _baseAmountChanged() { _baseAmountChangedFeeUpdateTimer?.cancel(); _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_cryptoFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_cryptoFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( ref.read(pSendAmount) == null @@ -1116,6 +1120,21 @@ class _SendViewState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text.trim(); + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = AddressUtils().formatAddress(_address!); + } + }); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -1447,6 +1466,14 @@ class _SendViewState extends ConsumerState { content, ); } + if (coin + is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils() + .formatAddress( + content, + ); + } sendToController.text = content.trim(); _address = content.trim(); @@ -2030,17 +2057,17 @@ class _SendViewState extends ConsumerState { const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -2084,12 +2111,12 @@ class _SendViewState extends ConsumerState { ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), Text( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: STextStyles.smallMed12(context), @@ -2141,6 +2168,7 @@ class _SendViewState extends ConsumerState { height: 12, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Text( @@ -2149,12 +2177,14 @@ class _SendViewState extends ConsumerState { textAlign: TextAlign.left, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) const SizedBox( height: 8, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Stack( diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index b72c3222cc..d29aae7dc3 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -268,6 +268,54 @@ class AboutView extends ConsumerWidget { ); }, ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + const SizedBox( + height: 12, + ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus.getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + stateOfCommit = snapshot.data!; + } + + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + GitStatus.mimblewimblecoinCommit, + style: GitStatus.styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ), + ); + }, + ), if (AppConfig.coins.whereType().isNotEmpty) const SizedBox( height: 12, diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index 92e5cc256d..8315302df7 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; // import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libmwc/git_versions.dart' as MIMBLEWIMBLECOIN_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; @@ -315,6 +316,9 @@ class _DebugViewState extends ConsumerState { EPIC_VERSIONS.getPluginVersion(); // final String moneroCommit = // MONERO_VERSIONS.getPluginVersion(); + final String mimblewimblecoinCommit = + MIMBLEWIMBLECOIN_VERSIONS + .getPluginVersion(); final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfo = @@ -348,6 +352,8 @@ class _DebugViewState extends ConsumerState { "firoCommit": firoCommit, "epicCashCommit": epicCashCommit, // "moneroCommit": moneroCommit, + "mimblewimblecoinCommit": + mimblewimblecoinCommit, "deviceInfoMap": deviceInfoMap, "errorLogs": errorLogs, }; 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 7888635b76..e9b15cc307 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 @@ -78,6 +78,17 @@ class _AddEditNodeViewState extends ConsumerState { ref.read(nodeFormDataProvider).host = data.host; ref.read(nodeFormDataProvider).port = data.port; ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is CryptonoteCurrency) { + ref.read(nodeFormDataProvider).host = data.host; + } + if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; } else if (coin is CryptonoteCurrency) { ref.read(nodeFormDataProvider).host = data.host; } @@ -706,6 +717,8 @@ class _NodeFormState extends ConsumerState { _trusted = node.trusted ?? false; if (widget.coin is Epiccash) { enableSSLCheckbox = !node.host.startsWith("http"); + } else if (widget.coin is Mimblewimblecoin) { + enableSSLCheckbox = !node.host.startsWith("http"); } else { enableSSLCheckbox = true; } @@ -846,6 +859,25 @@ class _NodeFormState extends ConsumerState { _useSSL = true; } } + if (widget.coin is Mimblewimblecoin) { + if (newValue.startsWith("https://")) { + _useSSL = true; + enableSSLCheckbox = false; + } else if (newValue.startsWith("http://")) { + _useSSL = false; + enableSSLCheckbox = false; + } else { + enableSSLCheckbox = true; + } + } else if (widget.coin is CwBasedInterface) { + if (newValue.startsWith("https://")) { + _useSSL = true; + } else if (newValue.startsWith("http://")) { + _useSSL = false; + } else { + _useSSL = true; + } + } _updateState(); setState(() {}); }, @@ -1105,11 +1137,15 @@ class _NodeFormState extends ConsumerState { ), ], ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) Row( children: [ GestureDetector( 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 32430d228a..2dcc604568 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 @@ -52,6 +52,7 @@ import '../../../../../wallets/isar/models/frost_wallet_info.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; @@ -503,7 +504,7 @@ abstract class SWB { Future? restoringFuture; - if (!(wallet is LibMoneroWallet || wallet is EpiccashWallet)) { + if (!(wallet is LibMoneroWallet || wallet is EpiccashWallet || wallet is MimblewimblecoinWallet)) { if (wallet is BitcoinFrostWallet) { restoringFuture = wallet.recover( isRescan: false, 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 1be577f1d4..b88e9ce92f 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 @@ -33,10 +33,12 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../../wallets/crypto_currency/coins/monero.dart'; import '../../../../wallets/crypto_currency/coins/wownero.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/monero_wallet.dart'; import '../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../widgets/animated_text.dart'; @@ -263,7 +265,10 @@ class _WalletNetworkSettingsViewState final coin = ref.read(pWalletCoin(widget.walletId)); - if (coin is Monero || coin is Wownero || coin is Epiccash) { + if (coin is Monero || + coin is Wownero || + coin is Epiccash || + coin is Mimblewimblecoin) { _blocksRemainingSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { @@ -343,6 +348,14 @@ class _WalletNetworkSettingsViewState if (_percent < highestPercent) { _percent = highestPercent.clamp(0.0, 1.0); } + } else if (coin is Mimblewimblecoin) { + final double highestPercent = (ref + .watch(pWallets) + .getWallet(widget.walletId) as MimblewimblecoinWallet) + .highestPercent; + if (_percent < highestPercent) { + _percent = highestPercent.clamp(0.0, 1.0); + } } return ConditionalParent( @@ -363,7 +376,11 @@ class _WalletNetworkSettingsViewState style: STextStyles.navBarTitle(context), ), actions: [ - if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin || + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( top: 10, @@ -627,6 +644,7 @@ class _WalletNetworkSettingsViewState ), if (coin is Monero || coin is Wownero || + coin is Mimblewimblecoin || coin is Epiccash) Text( " (Blocks to go: ${_blocksRemaining == -1 ? "?" : _blocksRemaining})", @@ -950,11 +968,15 @@ class _WalletNetworkSettingsViewState coin: ref.watch(pWalletCoin(widget.walletId)), popBackToRoute: WalletNetworkSettingsView.routeName, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) const SizedBox( height: 32, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( bottom: 12, @@ -970,7 +992,9 @@ class _WalletNetworkSettingsViewState ], ), ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) RoundedWhiteContainer( borderColor: isDesktop ? Theme.of(context).extension()!.background 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 494834b029..0135d5cac0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/epicbox_config_model.dart'; +import '../../../models/mwcmqs_config_model.dart'; import '../../../models/keys/key_data_interface.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; @@ -37,6 +38,8 @@ 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/impl/mimblewimblecoin_wallet.dart'; +import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -694,3 +697,103 @@ class _EpiBoxInfoFormState extends ConsumerState { ); } } + +class MwcMqsInfoForm extends ConsumerStatefulWidget { + const MwcMqsInfoForm({ + super.key, + required this.walletId, + }); + + final String walletId; + + @override + ConsumerState createState() => _MwcMqsInfoFormState(); +} + +class _MwcmqsInfoFormState extends ConsumerState { + final hostController = TextEditingController(); + final portController = TextEditingController(); + + late MimblewimblecoinWallet wallet; + + @override + void initState() { + wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; + + wallet.getMwcMqsConfig().then((MwcMqsConfigModel mwcmqsConfig) { + hostController.text = mwcmqsConfig.host; + portController.text = "${mwcmqsConfig.port ?? 443}"; + }); + super.initState(); + } + + @override + void dispose() { + hostController.dispose(); + portController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: hostController, + decoration: const InputDecoration(hintText: "Host"), + ), + 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, + ), + TextButton( + onPressed: () async { + try { + await wallet.updateMwcmqsConfig( + hostController.text, + int.parse(portController.text), + ); + if (mounted) { + await showFloatingFlushBar( + context: context, + message: "Mwcmqs info saved!", + type: FlushBarType.success, + ); + } + unawaited(wallet.refresh()); + } catch (e) { + await showFloatingFlushBar( + context: context, + message: "Failed to save mwcmqs info: $e", + type: FlushBarType.warning, + ); + } + }, + child: Text( + "Save", + style: STextStyles.button(context).copyWith( + color: + Theme.of(context).extension()!.accentColorDark, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 5afd82597f..385bb8a2e0 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -873,6 +873,10 @@ class _DesktopTransactionCardRowState return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + if (_transaction.subType == TransactionSubType.mint) { if (_transaction.isConfirmed(height, minConfirms)) { return "Anonymized"; @@ -967,6 +971,19 @@ class _DesktopTransactionCardRowState ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } if (Util.isDesktop) { await showDialog( context: context, 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 666d8d041f..03316599f6 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/models/spark_coin.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -241,6 +242,33 @@ class _TransactionV2DetailsViewState ), ) .toList(); + } else if (_transaction.isMimblewimblecoinTransaction) { + switch (_transaction.type) { + case TransactionType.outgoing: + case TransactionType.unknown: + amount = _transaction.getAmountSentFromThisWallet( + fractionDigits: fractionDigits, + ); + break; + + case TransactionType.incoming: + case TransactionType.sentToSelf: + amount = _transaction.getAmountReceivedInThisWallet( + fractionDigits: fractionDigits, + ); + break; + } + data = _transaction.outputs + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ) + ), + ) + .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( fractionDigits: fractionDigits, @@ -1053,13 +1081,13 @@ class _TransactionV2DetailsViewState ], ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1131,7 +1159,8 @@ class _TransactionV2DetailsViewState MainAxisAlignment.spaceBetween, children: [ Text( - (coin is Epiccash) + (coin is Epiccash || + coin is Mimblewimblecoin) ? "Local Note" : "Note ", style: isDesktop @@ -1202,7 +1231,9 @@ class _TransactionV2DetailsViewState .watch( pTransactionNote( ( - txid: (coin is Epiccash) + txid: (coin is Epiccash || + coin + is Mimblewimblecoin) ? _transaction.slateId .toString() : _transaction.txid, @@ -1478,8 +1509,9 @@ class _TransactionV2DetailsViewState ? "${_transaction.height!}" : "Pending"; confirmations = confirms.toString(); - } else if (widget.coin is Epiccash && - _transaction.slateId == null) { + } else if (widget.coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.slateId == null) { confirmations = "Unknown"; height = "Unknown"; } else { @@ -1487,7 +1519,9 @@ class _TransactionV2DetailsViewState currentHeight, minConfirms, ); - if (widget.coin is! Epiccash && confirmed) { + if (widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin && + confirmed) { height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { @@ -1748,11 +1782,13 @@ class _TransactionV2DetailsViewState context, ), ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) CustomTextButton( text: "Open in block explorer", onTap: () async { @@ -1909,13 +1945,13 @@ class _TransactionV2DetailsViewState // ], // ), // ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1999,9 +2035,10 @@ class _TransactionV2DetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) + floatingActionButton: (coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) ? ConditionalParent( condition: isDesktop, builder: (child) => Padding( @@ -2085,6 +2122,72 @@ class _TransactionV2DetailsViewState ); return; } + + if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Could not find Mimblewimblecoin transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "ERROR: Wallet type is not Mimblewimblecoin", + context: context, + ), + ); + return; + } }, child: Text( "Cancel Transaction", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 80dea67954..a09f60b104 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -482,6 +482,11 @@ class _DesktopSendState extends ConsumerState { noteOnChain: _onChainNote ?? "", ); } + if (coin is Mimblewimblecoin) { + txData = txData.copyWith( + noteOnChain: _onChainNote ?? "", + ); + } } // pop building dialog Navigator.of( @@ -840,6 +845,9 @@ class _DesktopSendState extends ConsumerState { if (coin is Epiccash) { content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -855,6 +863,10 @@ class _DesktopSendState extends ConsumerState { // strip http:// and https:// if content contains @ content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -1092,6 +1104,21 @@ class _DesktopSendState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text; + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = formatAddressMwc(_address!); + } + }); + } + final firoType = ref.watch(publicPrivateBalanceStateProvider); final isExchangeAddress = ref.watch(pIsExchangeAddress); @@ -1765,7 +1792,10 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 20, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) ConditionalParent( condition: ref.watch(pWallets).getWallet(walletId) is ElectrumXInterface && @@ -1821,11 +1851,17 @@ class _DesktopSendState extends ConsumerState { textAlign: TextAlign.left, ), ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) const SizedBox( height: 10, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) if (!isCustomFee) Padding( padding: const EdgeInsets.all(10), @@ -2030,3 +2066,25 @@ String formatAddress(String epicAddress) { } return epicAddress; } + +String formatAddressMwc(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; +} diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index e8a4c5256b..2aae2e1fc6 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -63,6 +63,12 @@ class _ForgotPasswordDesktopViewState await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await Isar.getInstance("desktopStore")?.close(deleteFromDisk: true); await (await StackFileSystem.applicationHiveDirectory()) @@ -79,6 +85,12 @@ class _ForgotPasswordDesktopViewState if (epicDir.existsSync()) { await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await (await StackFileSystem.applicationHiveDirectory()) .delete(recursive: true); await (await StackFileSystem.applicationIsarDirectory()) diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 98a7dec344..7d48217bc0 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -434,63 +434,122 @@ class DesktopAboutView extends ConsumerWidget { ); }, ), - // if (AppConfig.coins - // .whereType() - // .isNotEmpty) - // FutureBuilder( - // future: GitStatus - // .getMoneroCommitStatus(), - // builder: ( - // context, - // AsyncSnapshot - // snapshot, - // ) { - // CommitStatus stateOfCommit = - // CommitStatus.notLoaded; + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus + .getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot + snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + stateOfCommit = + snapshot.data!; + } + + return Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ) + .extension< + StackColors>()! + .textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + GitStatus + .mimblewimblecoinCommit, + style: GitStatus + .styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ); + }, + ), + //if (AppConfig.coins + // .whereType() + // .isNotEmpty) + // FutureBuilder( + // future: GitStatus + // .getMoneroCommitStatus(), + // builder: ( + // context, + // AsyncSnapshot + // snapshot, + // ) { + // CommitStatus stateOfCommit = + // CommitStatus.notLoaded; // - // if (snapshot.connectionState == - // ConnectionState - // .done && - // snapshot.hasData) { - // stateOfCommit = - // snapshot.data!; - // } - // return Column( - // mainAxisSize: - // MainAxisSize.min, - // crossAxisAlignment: - // CrossAxisAlignment - // .start, - // children: [ - // Text( - // "Monero Build Commit", - // style: STextStyles - // .desktopTextExtraExtraSmall( - // context, - // ).copyWith( - // color: Theme.of( - // context, - // ) - // .extension< - // StackColors>()! - // .textDark, - // ), - // ), - // const SizedBox( - // height: 2, - // ), - // SelectableText( - // GitStatus.moneroCommit, - // style: GitStatus - // .styleForStatus( - // stateOfCommit, - // context, - // ), - // ), - // ], - // ); - // }, - // ), + // if (snapshot.connectionState == + // ConnectionState + // .done && + // snapshot.hasData) { + // stateOfCommit = + // snapshot.data!; + // } + // return Column( + // mainAxisSize: + // MainAxisSize.min, + // crossAxisAlignment: + // CrossAxisAlignment + // .start, + // children: [ + // Text( + // "Monero Build Commit", + // style: STextStyles + // .desktopTextExtraExtraSmall( + // context, + // ).copyWith( + // color: Theme.of( + // context, + // ) + // .extension< + // StackColors>()! + // .textDark, + // ), + // ), + // const SizedBox( + // height: 2, + // ), + // SelectableText( + // GitStatus.moneroCommit, + // style: GitStatus + // .styleForStatus( + // stateOfCommit, + // context, + // ), + // ), + // ], + // ); + // }, + // ), ], ), const SizedBox(height: 35), diff --git a/lib/services/price.dart b/lib/services/price.dart index 5a97e43cbc..071ce59804 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -34,6 +34,7 @@ class PriceAPI { Dash: "dash", Dogecoin: "dogecoin", Epiccash: "epic-cash", + Mimblewimblecoin: "mimblewimblecoin", Ecash: "ecash", Ethereum: "ethereum", Firo: "zcoin", diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index d7b93cf33f..1e6c250b79 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -25,6 +25,7 @@ import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/wallet/impl/epiccash_wallet.dart'; import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'event_bus/events/wallet_added_event.dart'; import 'event_bus/global_event_bus.dart'; @@ -110,6 +111,15 @@ class Wallets { "epic wallet: $walletId deleted with result: $deleteResult", level: LogLevel.Info, ); + } else if (info.coin is Mimblewimblecoin) { + final deleteResult = await deleteMimblewimblecoinWallet( + walletId: walletId, + secureStore: secureStorage, + ); + Logging.instance.log( + "Mimblewimblecoin wallet: $walletId deleted with result: $deleteResult", + level: LogLevel.Info, + ); } // delete wallet data in main db diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 4a789ad76a..1deb22b4e1 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -28,6 +28,8 @@ final coinIconProvider = Provider.family((ref, coin) { return assets.dogecoin; case const (Epiccash): return assets.epicCash; + case const (Mimblewimblecoin): + return assets.mimblewimblecoin; case const (Firo): return assets.firo; case const (Monero): diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 869aa58956..9cd952b3e8 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -46,6 +46,8 @@ class AddressUtils { // return Dogecoin(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.epicCash: // return Epiccash(CryptoCurrencyNetwork.main).validateAddress(address); + // case Coin.mimblewimblecoin: + // return Mimblewimblecoin(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.ethereum: // return Ethereum(CryptoCurrencyNetwork.main).validateAddress(address); // case Coin.firo: @@ -372,6 +374,31 @@ class AddressUtils { } return epicAddress; } + + /// Formats an address string to remove any unnecessary prefixes or suffixes. + String formatMimblewimblecoinAddress(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; + } } class PaymentUriData { diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index a8cbfa701e..79e45232b3 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -63,6 +63,7 @@ enum AmountUnit { // case Coin.dogecoin: // case Coin.eCash: // case Coin.epicCash: + // case Coin.mimblewimblecoin: // case Coin.stellar: // TODO: check if this is correct // case Coin.stellarTestnet: // case Coin.tezos: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index e0245131a9..3a1001ad8f 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -245,6 +245,7 @@ class _SVG { String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; + String get mimblewimblecoin => "assets/svg/coin_icons/Mimblewimblecoin.svg"; String get ethereum => "assets/svg/coin_icons/Ethereum.svg"; String get firo => "assets/svg/coin_icons/Firo.svg"; String get monero => "assets/svg/coin_icons/Monero.svg"; diff --git a/lib/utilities/default_mwcmqs.dart b/lib/utilities/default_mwcmqs.dart new file mode 100644 index 0000000000..75be1d247b --- /dev/null +++ b/lib/utilities/default_mwcmqs.dart @@ -0,0 +1,53 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import '../models/mwcmqs_server_model.dart'; + +abstract class DefaultMwcMqs { + static const String defaultName = "Default"; + + static List get all => [americas, asia, europe]; + static List get defaultIds => ['americas', 'asia', 'europe']; + + static MwcMqsServerModel get americas => MwcMqsServerModel( + host: 'MwcMqs.stackwallet.com', + port: 443, + name: 'Americas', + id: 'americas', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get asia => MwcMqsServerModel( + host: 'MwcMqs.hyperbig.com', + port: 443, + name: 'Asia', + id: 'asia', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get europe => MwcMqsServerModel( + host: 'MwcMqs.fastepic.eu', + port: 443, + name: 'Europe', + id: 'europe', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static final defaultMwcMqsServer = americas; +} diff --git a/lib/utilities/git_status.dart b/lib/utilities/git_status.dart index 093bc39af3..17617c0fa7 100644 --- a/lib/utilities/git_status.dart +++ b/lib/utilities/git_status.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; // import 'package:flutter_libmonero/git_versions.dart' as monero_versions; +import 'package:flutter_libmwc/git_versions.dart' as mimblewimblecoin_versions; import 'package:http/http.dart'; import 'package:lelantus/git_versions.dart' as firo_versions; @@ -21,6 +22,8 @@ abstract class GitStatus { static String get firoCommit => firo_versions.getPluginVersion(); static String get epicCashCommit => epic_versions.getPluginVersion(); // static String get moneroCommit => monero_versions.getPluginVersion(); + static String get mimblewimblecoinCommit => + mimblewimblecoin_versions.getPluginVersion(); static String get appCommitHash => AppConfig.commitHash; @@ -78,31 +81,60 @@ abstract class GitStatus { return _cachedEpicStatus!; } + + static CommitStatus? _cachedMimblewimblecoinStatus; + static Future getMimblewimblecoinCommitStatus() async { + if (_cachedMimblewimblecoinStatus != null) { + return _cachedMimblewimblecoinStatus!; + } + final List results = await Future.wait([ + _doesCommitExist("cypherstack", "flutter_libmwc", mimblewimblecoinCommit), + _isHeadCommit( + "cypherstack", + "flutter_libmwc", + "main", + mimblewimblecoinCommit, + ), + ]); + + final commitExists = results[0]; + final commitIsHead = results[1]; + + if (commitExists && commitIsHead) { + _cachedMimblewimblecoinStatus = CommitStatus.isHead; + } else if (commitExists) { + _cachedMimblewimblecoinStatus = CommitStatus.isOldCommit; + } else { + _cachedMimblewimblecoinStatus = CommitStatus.notACommit; + } + + return _cachedMimblewimblecoinStatus!; + } + + //static CommitStatus? _cachedMoneroStatus; + //static Future getMoneroCommitStatus() async { + // if (_cachedMoneroStatus != null) { + // return _cachedMoneroStatus!; + // } // - // static CommitStatus? _cachedMoneroStatus; - // static Future getMoneroCommitStatus() async { - // if (_cachedMoneroStatus != null) { - // return _cachedMoneroStatus!; - // } - // - // final List results = await Future.wait([ - // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), - // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), - // ]); + // final List results = await Future.wait([ + // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), + // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), + // ]); // - // final commitExists = results[0]; - // final commitIsHead = results[1]; + // final commitExists = results[0]; + // final commitIsHead = results[1]; // - // if (commitExists && commitIsHead) { - // _cachedMoneroStatus = CommitStatus.isHead; - // } else if (commitExists) { - // _cachedMoneroStatus = CommitStatus.isOldCommit; - // } else { - // _cachedMoneroStatus = CommitStatus.notACommit; - // } + // if (commitExists && commitIsHead) { + // _cachedMoneroStatus = CommitStatus.isHead; + // } else if (commitExists) { + // _cachedMoneroStatus = CommitStatus.isOldCommit; + // } else { + // _cachedMoneroStatus = CommitStatus.notACommit; + // } // - // return _cachedMoneroStatus!; - // } + // return _cachedMoneroStatus!; + //} static TextStyle styleForStatus(CommitStatus status, BuildContext context) { final Color color; diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 458e04e4d9..c83bf39e19 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -45,7 +45,6 @@ Future _xmrHelper( final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path"; - if (proxyInfo == null && uri.host.endsWith(".onion")) { return false; } diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart new file mode 100644 index 0000000000..b06ec1f1d4 --- /dev/null +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -0,0 +1,128 @@ +import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; + +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/node_model.dart'; +import '../../../utilities/default_nodes.dart'; +import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../crypto_currency.dart'; +import '../intermediate/bip39_currency.dart'; + +class Mimblewimblecoin extends Bip39Currency { + Mimblewimblecoin(super.network) { + _idMain = "mimblewimblecoin"; + _uriScheme = "mimblewimblecoin"; // ? + switch (network) { + case CryptoCurrencyNetwork.main: + _id = _idMain; + _name = "Mimblewimblecoin"; + _ticker = "MWC"; + default: + throw Exception("Unsupported network: $network"); + } + } + + late final String _id; + @override + String get identifier => _id; + + late final String _idMain; + @override + String get mainNetId => _idMain; + + late final String _name; + @override + String get prettyName => _name; + + late final String _uriScheme; + @override + String get uriScheme => _uriScheme; + + late final String _ticker; + @override + String get ticker => _ticker; + + @override + String get genesisHash { + return "not used in mimblewimblecoin"; + } + + @override + // change this to change the number of confirms a tx needs in order to show as confirmed + int get minConfirms => 3; + + @override + bool validateAddress(String address) { + // Invalid address that contains HTTP and mwcmqs domain + if ((address.startsWith("http://") || address.startsWith("https://")) && + address.contains("@")) { + return false; + } + if (address.startsWith("http://") || address.startsWith("https://")) { + if (Uri.tryParse(address) != null) { + return true; + } + } + + return mimblewimblecoin.LibMwc.validateSendAddress(address: address); + } + + @override + NodeModel get defaultNode { + switch (network) { + case CryptoCurrencyNetwork.main: + return NodeModel( + host: "http://epiccash.stackwallet.com", + port: 3413, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: false, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false, + ); + + default: + throw UnimplementedError(); + } + } + + @override + int get defaultSeedPhraseLength => 24; + + @override + int get fractionDigits => 8; + + @override + bool get hasBuySupport => false; + + @override + bool get hasMnemonicPassphraseSupport => false; + + @override + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 12]; + + @override + AddressType get defaultAddressType => AddressType.mimbleWimble; + + @override + BigInt get satsPerCoin => BigInt.from(100000000); + + @override + int get targetBlockTimeSeconds => 60; + + @override + DerivePathType get defaultDerivePathType => throw UnsupportedError( + "$runtimeType does not use bitcoin style derivation paths", + ); + + @override + Uri defaultBlockExplorer(String txid) { + switch (network) { + default: + throw Exception( + "Unsupported network for defaultBlockExplorer(): $network", + ); + } + } +} diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 074536be65..4d2acc4015 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -11,6 +11,7 @@ export 'coins/dash.dart'; export 'coins/dogecoin.dart'; export 'coins/ecash.dart'; export 'coins/epiccash.dart'; +export 'coins/mimblewimblecoin.dart'; export 'coins/ethereum.dart'; export 'coins/firo.dart'; export 'coins/litecoin.dart'; @@ -61,7 +62,7 @@ abstract class CryptoCurrency { int get minConfirms; - // TODO: [prio=low] could be handled differently as (at least) epiccash does not use this + // TODO: [prio=low] could be handled differently as (at least) epiccash/mimblewimblecoin does not use this String get genesisHash; bool validateAddress(String address); diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index 3e296a3a0b..ec805e6aca 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -507,6 +507,7 @@ class WalletInfo implements IsarId { abstract class WalletInfoKeys { static const String tokenContractAddresses = "tokenContractAddressesKey"; static const String epiccashData = "epiccashDataKey"; + static const String mimblewimblecoinData = "mimblewimblecoinDataKey"; static const String bananoMonkeyImageBytes = "monkeyImageBytesKey"; static const String tezosDerivationPath = "tezosDerivationPathKey"; static const String lelantusCoinIsarRescanRequired = diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart new file mode 100644 index 0000000000..ed77e34bc7 --- /dev/null +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -0,0 +1,1197 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; +import 'package:flutter_libmwc/models/transaction.dart' + as mimblewimblecoin_models; +import 'package:isar/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../../../models/balance.dart'; +import '../../../models/mwcmqs_config_model.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/transaction.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/node_model.dart'; +import '../../../models/paymint/fee_object_model.dart'; +import '../../../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; +import '../../../services/event_bus/events/global/node_connection_status_changed_event.dart'; +import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; +import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/flutter_secure_storage_interface.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +//import '../../../utilities/test_epic_box_connection.dart'; +import '../../crypto_currency/crypto_currency.dart'; +import '../../models/tx_data.dart'; +import '../intermediate/bip39_wallet.dart'; +import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; + +// +// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart +// +class MimblewimblecoinWallet extends Bip39Wallet { + MimblewimblecoinWallet(CryptoCurrencyNetwork network) + : super(Mimblewimblecoin(network)); + + final syncMutex = Mutex(); + NodeModel? _mimblewimblecoinNode; + Timer? timer; + + double highestPercent = 0; + Future get getSyncPercent async { + final int lastScannedBlock = + info.mimblewimblecoinData?.lastScannedBlock ?? 0; + final _chainHeight = await chainHeight; + final double restorePercent = lastScannedBlock / _chainHeight; + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(highestPercent, walletId)); + if (restorePercent > highestPercent) { + highestPercent = restorePercent; + } + + final int blocksRemaining = _chainHeight - lastScannedBlock; + GlobalEventBus.instance + .fire(BlocksRemainingEvent(blocksRemaining, walletId)); + + return restorePercent < 0 ? 0.0 : restorePercent; + } + + /*Future updateEpicboxConfig(String host, int port) async { + final String stringConfig = jsonEncode({ + "epicbox_domain": host, + "epicbox_port": port, + "epicbox_protocol_unsecure": false, + "epicbox_address_index": 0, + }); + await secureStorageInterface.write( + key: '${walletId}_epicboxConfig', + value: stringConfig, + ); + // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed + } + */ + /// returns an empty String on success, error message on failure + Future cancelPendingTransactionAndPost(String txSlateId) async { + try { + final String wallet = (await secureStorageInterface.read( + key: '${walletId}_wallet', + ))!; + + final result = await mimblewimblecoin.LibMwc.cancelTransaction( + wallet: wallet, + transactionId: txSlateId, + ); + Logging.instance.log( + "cancel $txSlateId result: $result", + level: LogLevel.Info, + ); + return result; + } catch (e, s) { + Logging.instance.log("$e, $s", level: LogLevel.Error); + return e.toString(); + } + } + + Future getMwcMqsConfig() async { + final MwcMqsConfigModel _mwcMqsConfig = MwcMqsConfigModel.fromServer( + DefaultMwcMqs.defaultMwcMqsServer, + ); + + //Get the default Epicbox server and check if it's conected + // bool isEpicboxConnected = await _testEpicboxServer( + // DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); + + // if (isEpicboxConnected) { + //Use default server for as Epicbox config + + // } + // else { + // //Use Europe config + // _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + // } + // // example of selecting another random server from the default list + // // alternative servers: copy list of all default EB servers but remove the default default + // // List alternativeServers = DefaultEpicBoxes.all; + // // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // // alternativeServers.shuffle(); // randomize which server is used + // // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + // + // // TODO test this connection before returning it + // } + + return _mwcMqsConfig; + } + + // ================= Private ================================================= + + Future _getConfig() async { + if (_mimblewimblecoinNode == null) { + await updateNode(); + } + final NodeModel node = _mimblewimblecoinNode!; + final String nodeAddress = node.host; + final int port = node.port; + + final uri = Uri.parse(nodeAddress).replace(port: port); + + final String nodeApiAddress = uri.toString(); + + final walletDir = await _currentWalletDirPath(); + + final Map config = {}; + config["wallet_dir"] = walletDir; + config["check_node_api_http_addr"] = nodeApiAddress; + config["chain"] = "mainnet"; + config["account"] = "default"; + config["api_listen_port"] = port; + config["api_listen_interface"] = + nodeApiAddress.replaceFirst(uri.scheme, ""); + final String stringConfig = jsonEncode(config); + return stringConfig; + } + + Future _currentWalletDirPath() async { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + return '$path/$name'; + } + + Future _nativeFee( + int satoshiAmount, { + bool ifErrorEstimateFee = false, + }) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + try { + final available = info.cachedBalance.spendable.raw.toInt(); + + final transactionFees = await mimblewimblecoin.LibMwc.getTransactionFees( + wallet: wallet!, + amount: satoshiAmount, + minimumConfirmations: cryptoCurrency.minConfirms, + available: available, + ); + + int realFee = 0; + try { + realFee = + (Decimal.parse(transactionFees.fee.toString())).toBigInt().toInt(); + } catch (e, s) { + //todo: come back to this + debugPrint("$e $s"); + } + return realFee; + } catch (e, s) { + Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + rethrow; + } + } + + Future _startSync() async { + Logging.instance.log("request start sync", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const int refreshFromNode = 1; + if (!syncMutex.isLocked) { + await syncMutex.protect(() async { + // How does getWalletBalances start syncing???? + await mimblewimblecoin.LibMwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: 10, + ); + }); + } else { + Logging.instance.log("request start sync denied", level: LogLevel.Info); + } + } + + Future< + ({ + double awaitingFinalization, + double pending, + double spendable, + double total + })> _allWalletBalances() async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 0; + return await mimblewimblecoin.LibMwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: cryptoCurrency.minConfirms, + ); + } + + /*Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { + final host = epicboxConfig.host; + final port = epicboxConfig.port ?? 443; + WebSocketChannel? channel; + try { + final uri = Uri.parse('wss://$host:$port'); + + channel = WebSocketChannel.connect( + uri, + ); + + await channel.ready; + + final response = await channel.stream.first.timeout( + const Duration(seconds: 2), + ); + + return response is String && response.contains("Challenge"); + } catch (_) { + Logging.instance.log( + "_testEpicBoxConnection failed on \"$host:$port\"", + level: LogLevel.Info, + ); + return false; + } finally { + await channel?.sink.close(); + } + }*/ + + Future _putSendToAddresses( + ({String slateId, String commitId}) slateData, + Map txAddressInfo, + ) async { + try { + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; + slatesToCommits[slateData.slateId] = { + "commitId": slateData.commitId, + "from": from, + "to": to, + }; + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + slatesToCommits: slatesToCommits, + ), + isar: mainDB.isar, + ); + return true; + } catch (e, s) { + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + return false; + } + } + + Future _getCurrentIndex() async { + try { + final int receivingIndex = info.mimblewimblecoinData!.receivingIndex; + // TODO: go through pendingarray and processed array and choose the index + // of the last one that has not been processed, or the index after the one most recently processed; + return receivingIndex; + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return 0; + } + } + + Future
_generateAndStoreReceivingAddressForIndex( + int index, + ) async { + Address? address = await getCurrentReceivingAddress(); + + if (address != null) { + final splitted = address.value.split('@'); + //Check if the address is the same as the current epicbox domain + //Since we're only using one epicbpox now this doesn't apply but will be + // useful in the future + final mwcmqsConfig = await getMwcMqsConfig(); + if (splitted[1] != mwcmqsConfig.host) { + //Update the address + address = await thisWalletAddress(index, mwcmqsConfig); + } + } else { + final mwcmqsConfig = await getMwcMqsConfig(); + address = await thisWalletAddress(index, mwcmqsConfig); + } + + if (info.cachedReceivingAddress != address.value) { + await info.updateReceivingAddress( + newAddress: address.value, + isar: mainDB.isar, + ); + } + return address; + } + + Future
thisWalletAddress( + int index, + MwcMqsConfigModel mwcmqsConfig, + ) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + + final walletAddress = await mimblewimblecoin.LibMwc.getAddressInfo( + wallet: wallet!, + index: index, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + + Logging.instance.log( + "WALLET_ADDRESS_IS $walletAddress", + level: LogLevel.Info, + ); + + final address = Address( + walletId: walletId, + value: walletAddress, + derivationIndex: index, + derivationPath: null, + type: AddressType.mimbleWimble, + subType: AddressSubType.receiving, + publicKey: [], // ?? + ); + await mainDB.updateOrPutAddresses([address]); + return address; + } + + Future _startScans() async { + try { + //First stop the current listener + mimblewimblecoin.LibMwc.stopMwcmqsListener(); + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + + // max number of blocks to scan per loop iteration + const scanChunkSize = 10000; + + // force firing of scan progress event + await getSyncPercent; + + // fetch current chain height and last scanned block (should be the + // restore height if full rescan or a wallet restore) + int chainHeight = await this.chainHeight; + int lastScannedBlock = info.mimblewimblecoinData!.lastScannedBlock; + + // loop while scanning in chain in chunks (of blocks?) + while (lastScannedBlock < chainHeight) { + Logging.instance.log( + "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", + level: LogLevel.Info, + ); + + final int nextScannedBlock = await mimblewimblecoin.LibMwc.scanOutputs( + wallet: wallet!, + startHeight: lastScannedBlock, + numberOfBlocks: scanChunkSize, + ); + + // update local cache + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: nextScannedBlock, + ), + isar: mainDB.isar, + ); + + // force firing of scan progress event + await getSyncPercent; + + // update while loop condition variables + chainHeight = await this.chainHeight; + lastScannedBlock = nextScannedBlock; + } + + Logging.instance.log( + "_startScans successfully at the tip", + level: LogLevel.Info, + ); + //Once scanner completes restart listener + await _listenToMwcmqs(); + } catch (e, s) { + Logging.instance.log( + "_startScans failed: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future _listenToMwcmqs() async { + Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + mimblewimblecoin.LibMwc.startMwcMqsListener( + wallet: wallet!, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + } + + // As opposed to fake config? + Future _getRealConfig() async { + String? config = await secureStorageInterface.read( + key: '${walletId}_config', + ); + if (Platform.isIOS) { + final walletDir = await _currentWalletDirPath(); + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + return config!; + } + + // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index + int _calculateRestoreHeightFrom({required DateTime date}) { + final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + int height = approximateHeight; + if (height < 0) { + height = 0; + } + return height; + } + + // ============== Overrides ================================================== + + @override + int get isarTransactionVersion => 2; + + @override + FilterOperation? get changeAddressFilterOperation => + FilterGroup.and(standardChangeAddressFilters); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + @override + Future checkSaveInitialReceivingAddress() async { + // epiccash seems ok with nothing here? + } + + @override + Future init({bool? isRestore}) async { + if (isRestore != true) { + String? encodedWallet = + await secureStorageInterface.read(key: "${walletId}_wallet"); + + // check if should create a new wallet + if (encodedWallet == null) { + await updateNode(); + final mnemonicString = await getMnemonic(); + + final String password = generatePassword(); + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + final String name = walletId; + + await mimblewimblecoin.LibMwc.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); + + //Open wallet + encodedWallet = await mimblewimblecoin.LibMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: encodedWallet, + ); + + //Store MwcMqs address info + await _generateAndStoreReceivingAddressForIndex(0); + + // subtract a couple days to ensure we have a buffer for SWB + final bufferedCreateHeight = _calculateRestoreHeightFrom( + date: DateTime.now().subtract(const Duration(days: 2)), + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: bufferedCreateHeight, + restoreHeight: bufferedCreateHeight, + creationHeight: bufferedCreateHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + } else { + try { + Logging.instance.log( + "initializeExisting() ${cryptoCurrency.prettyName} wallet", + level: LogLevel.Info, + ); + + final config = await _getRealConfig(); + final password = + await secureStorageInterface.read(key: '${walletId}_password'); + + final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + config: config, + password: password!, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await updateNode(); + } catch (e, s) { + // do nothing, still allow user into wallet + Logging.instance.log( + "$runtimeType init() failed: $e\n$s", + level: LogLevel.Error, + ); + } + } + } + + return await super.init(); + } + + @override + Future confirmSend({required TxData txData}) async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + // TODO determine whether it is worth sending change to a change address. + + final String receiverAddress = txData.recipients!.first.address; + + if (!receiverAddress.startsWith("http://") || + !receiverAddress.startsWith("https://")) { + final bool isMwcmqsConnected = await _testMwcmqsServer( + mwcmqsConfig, + ); + if (!isMwcmqsConnected) { + throw Exception( + "Failed to send TX : Unable to reach mimblewimblecoin server"); + } + } + + ({String commitId, String slateId}) transaction; + + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { + transaction = await mimblewimblecoin.LibMwc.txHttpSend( + wallet: wallet!, + selectionStrategyIsAll: 0, + minimumConfirmations: cryptoCurrency.minConfirms, + message: txData.noteOnChain ?? "", + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + ); + } else { + transaction = await mimblewimblecoin.LibMwc.createTransaction( + wallet: wallet!, + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + secretKeyIndex: 0, + mwcmqsConfig: mwsmqsConfig.toString(), + minimumConfirmations: cryptoCurrency.minConfirms, + note: txData.noteOnChain!, + ); + } + + final Map txAddressInfo = {}; + txAddressInfo['from'] = (await getCurrentReceivingAddress())!.value; + txAddressInfo['to'] = txData.recipients!.first.address; + await _putSendToAddresses(transaction, txAddressInfo); + + return txData.copyWith( + txid: transaction.slateId, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin confirmSend: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + @override + Future prepareSend({required TxData txData}) async { + try { + if (txData.recipients?.length != 1) { + throw Exception( + "Mimblewimblecoin prepare send requires a single recipient!"); + } + + ({String address, Amount amount, bool isChange}) recipient = + txData.recipients!.first; + + final int realFee = await _nativeFee(recipient.amount.raw.toInt()); + final feeAmount = Amount( + rawValue: BigInt.from(realFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + if (feeAmount > info.cachedBalance.spendable) { + throw Exception( + "Mimblewimblecoin prepare send fee is greater than available balance!", + ); + } + + if (info.cachedBalance.spendable == recipient.amount) { + recipient = ( + address: recipient.address, + amount: recipient.amount - feeAmount, + isChange: recipient.isChange, + ); + } + + return txData.copyWith( + recipients: [recipient], + fee: feeAmount, + ); + } catch (e, s) { + Logging.instance + .log("Mimblewimblecoin prepareSend: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future recover({required bool isRescan}) async { + try { + await refreshMutex.protect(() async { + if (isRescan) { + // clear blockchain info + await mainDB.deleteWalletBlockchainData(walletId); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: info.mimblewimblecoinData!.restoreHeight, + ), + isar: mainDB.isar, + ); + + unawaited(_startScans()); + } else { + await updateNode(); + final String password = generatePassword(); + + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + await mimblewimblecoin.LibMwc.recoverWallet( + config: stringConfig, + password: password, + mnemonic: await getMnemonic(), + name: info.walletId, + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: info.restoreHeight, + restoreHeight: info.restoreHeight, + creationHeight: + info.mimblewimblecoinData?.creationHeight ?? info.restoreHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + + //Open Wallet + final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await _generateAndStoreReceivingAddressForIndex( + mimblewimblecoinData.receivingIndex, + ); + } + }); + + unawaited(refresh()); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from electrumx_mixin recover(): $e\n$s", + level: LogLevel.Info, + ); + + rethrow; + } + } + + @override + Future refresh() async { + // Awaiting this lock could be dangerous. + // Since refresh is periodic (generally) + if (refreshMutex.isLocked) { + return; + } + + try { + // this acquire should be almost instant due to above check. + // Slight possibility of race but should be irrelevant + await refreshMutex.acquire(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + cryptoCurrency, + ), + ); + + // if (info.epicData?.creationHeight == null) { + // await info.updateExtraEpiccashWalletInfo(epicData: inf, isar: isar) + // await epicUpdateCreationHeight(await chainHeight); + // } + + // this will always be zero???? + final int curAdd = await _getCurrentIndex(); + await _generateAndStoreReceivingAddressForIndex(curAdd); + + await _startScans(); + + unawaited(_startSync()); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + await updateChainHeight(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkReceivingAddressForTransactions(); + // } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + + // // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkChangeAddressForTransactions(); + // } + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); + final fetchFuture = updateTransactions(); + // if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); + + await fetchFuture; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); + + // await getAllTxsToWatch(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); + + await updateBalance(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + cryptoCurrency, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + + // TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call + // if (await refreshIfThereIsNewData()) { + unawaited(refresh()); + + // } + // } + }); + } + } catch (error, strace) { + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + cryptoCurrency, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error, + ); + } finally { + refreshMutex.release(); + } + } + + @override + Future updateBalance() async { + try { + final balances = await _allWalletBalances(); + final balance = Balance( + total: Amount.fromDecimal( + Decimal.parse(balances.total.toString()) + + Decimal.parse(balances.awaitingFinalization.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + spendable: Amount.fromDecimal( + Decimal.parse(balances.spendable.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + blockedTotal: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: Amount.fromDecimal( + Decimal.parse(balances.pending.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + ); + + await info.updateBalance( + newBalance: balance, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin wallet failed to update balance: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateTransactions() async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 1; + + final myAddresses = await mainDB + .getAddresses(walletId) + .filter() + .typeEqualTo(AddressType.mimbleWimble) + .and() + .subTypeEqualTo(AddressSubType.receiving) + .and() + .valueIsNotEmpty() + .valueProperty() + .findAll(); + final myAddressesSet = myAddresses.toSet(); + + final transactions = await mimblewimblecoin.LibMwc.getTransactions( + wallet: wallet!, + refreshFromNode: refreshFromNode, + ); + + final List txns = []; + + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + + for (final tx in transactions) { + Logging.instance.log("tx: $tx", level: LogLevel.Info); + + final isIncoming = + tx.txType == mimblewimblecoin_models.TransactionType.TxReceived || + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled; + final slateId = tx.txSlateId; + final commitId = slatesToCommits[slateId]?['commitId'] as String?; + final numberOfMessages = tx.messages?.messages.length; + final onChainNote = tx.messages?.messages[0].message; + final addressFrom = slatesToCommits[slateId]?["from"] as String?; + final addressTo = slatesToCommits[slateId]?["to"] as String?; + + final credit = int.parse(tx.amountCredited); + final debit = int.parse(tx.amountDebited); + final fee = int.tryParse(tx.fee ?? "0") ?? 0; + + // hack Mimblewimblecoin tx data into inputs and outputs + final List outputs = []; + final List inputs = []; + final addressFromIsMine = myAddressesSet.contains(addressFrom); + final addressToIsMine = myAddressesSet.contains(addressTo); + + OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: credit.toString(), + addresses: [ + if (addressFrom != null) addressFrom, + ], + walletOwns: true, + ); + final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [if (addressTo != null) addressTo], + valueStringSats: debit.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ); + + final TransactionType txType; + if (isIncoming) { + if (addressToIsMine && addressFromIsMine) { + txType = TransactionType.sentToSelf; + } else { + txType = TransactionType.incoming; + } + output = output.copyWith( + addresses: [ + myAddressesSet + .first, // Must be changed if we ever do more than a single wallet address!!! + ], + walletOwns: true, + ); + } else { + txType = TransactionType.outgoing; + } + + outputs.add(output); + inputs.add(input); + + final otherData = { + "isMimblewimblecoinTransaction": true, + "numberOfMessages": numberOfMessages, + "slateId": slateId, + "onChainNote": onChainNote, + "isCancelled": tx.txType == + mimblewimblecoin_models.TransactionType.TxSentCancelled || + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled, + "overrideFee": Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), + }; + + final txn = TransactionV2( + walletId: walletId, + blockHash: null, + hash: commitId ?? tx.id.toString(), + txid: commitId ?? tx.id.toString(), + timestamp: + DateTime.parse(tx.creationTs).millisecondsSinceEpoch ~/ 1000, + height: tx.confirmed ? tx.kernelLookupMinHeight ?? 1 : null, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: 0, + type: txType, + subType: TransactionSubType.none, + otherData: jsonEncode(otherData), + ); + + txns.add(txn); + } + + await mainDB.isar.writeTxn(() async { + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .deleteAll(); + await mainDB.isar.transactionV2s.putAll(txns); + }); + } catch (e, s) { + Logging.instance.log( + "${cryptoCurrency.runtimeType} ${cryptoCurrency.network} net wallet" + " \"${info.name}\"_${info.walletId} updateTransactions() failed: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateUTXOs() async { + // not used for mimblewimblecoin + return false; + } + + @override + Future updateNode() async { + _mimblewimblecoinNode = getCurrentNode(); + + // TODO: [prio=low] move this out of secure storage if secure storage not needed + final String stringConfig = await _getConfig(); + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + + // unawaited(refresh()); + } + + @override + Future pingCheck() async { + try { + final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); + + // force unwrap optional as we want connection test to fail if wallet + // wasn't initialized or mwcmqs node was set to null + return await testMimblewimblecoinNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + return false; + } + } + + @override + Future updateChainHeight() async { + final config = await _getRealConfig(); + final latestHeight = + await mimblewimblecoin.LibMwc.getChainHeight(config: config); + await info.updateCachedChainHeight( + newHeight: latestHeight, + isar: mainDB.isar, + ); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? + final int currentFee = await _nativeFee( + amount.raw.toInt(), + ifErrorEstimateFee: true, + ); + return Amount( + rawValue: BigInt.from(currentFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + + @override + Future get fees async { + // this wasn't done before the refactor either so... + // TODO: implement _getFees + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: 1, + medium: 1, + slow: 1, + ); + } + + @override + Future updateSentCachedTxData({required TxData txData}) async { + // TODO: [prio=low] Was not used before refactor so maybe not required(?) + return txData; + } + + @override + Future exit() async { + timer?.cancel(); + timer = null; + await super.exit(); + Logging.instance + .log("Mimblewimblecoin_wallet exit finished", level: LogLevel.Info); + } +} + +Future deleteMimblewimblecoinWallet({ + required String walletId, + required SecureStorageInterface secureStore, +}) async { + final wallet = await secureStore.read(key: '${walletId}_wallet'); + String? config = await secureStore.read(key: '${walletId}_config'); + if (Platform.isIOS) { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + final walletDir = '$path/$name'; + + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + + if (wallet == null) { + return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; + } else { + try { + return mimblewimblecoin.LibMwc.deleteWallet( + wallet: wallet, + config: config!, + ); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + return "deleteMimblewimblecoinWallet($walletId) failed..."; + } + } +} diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart new file mode 100644 index 0000000000..724657079e --- /dev/null +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -0,0 +1,111 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; +import '../../../utilities/logger.dart'; +import '../../isar/models/wallet_info.dart'; + +extension MimblewimblecoinWalletInfoExtension on WalletInfo { + ExtraMimblewimblecoinWalletInfo? get epicData { + final String? data = + otherData[WalletInfoKeys.mimblewimblecoinData] as String?; + if (data == null) { + return null; + } + try { + return ExtraMimblewimblecoinWalletInfo.fromMap( + Map.from( + jsonDecode(data) as Map, + ), + ); + } catch (e, s) { + Logging.instance.log( + "ExtraMimblewimblecoinWalletInfo.fromMap failed: $e\n$s", + level: LogLevel.Error, + ); + return null; + } + } + + Future updateExtraMimblewimblecoinWalletInfo({ + required ExtraMimblewimblecoinWalletInfo epicData, + required Isar isar, + }) async { + await updateOtherData( + newEntries: { + WalletInfoKeys.mimblewimblecoinData: jsonEncode(epicData.toMap()), + }, + isar: isar, + ); + } +} + +/// Holds data previously stored in hive +class ExtraMimblewimblecoinWalletInfo { + final int receivingIndex; + final int changeIndex; + + // TODO [prio=low] strongly type these maps at some point + final Map slatesToAddresses; + final Map slatesToCommits; + + final int lastScannedBlock; + final int restoreHeight; + final int creationHeight; + + ExtraMimblewimblecoinWalletInfo({ + required this.receivingIndex, + required this.changeIndex, + required this.slatesToAddresses, + required this.slatesToCommits, + required this.lastScannedBlock, + required this.restoreHeight, + required this.creationHeight, + }); + + // Convert the object to JSON + Map toMap() { + return { + 'receivingIndex': receivingIndex, + 'changeIndex': changeIndex, + 'slatesToAddresses': slatesToAddresses, + 'slatesToCommits': slatesToCommits, + 'lastScannedBlock': lastScannedBlock, + 'restoreHeight': restoreHeight, + 'creationHeight': creationHeight, + }; + } + + ExtraMimblewimblecoinWalletInfo.fromMap(Map json) + : receivingIndex = json['receivingIndex'] as int, + changeIndex = json['changeIndex'] as int, + slatesToAddresses = json['slatesToAddresses'] as Map, + slatesToCommits = json['slatesToCommits'] as Map, + lastScannedBlock = json['lastScannedBlock'] as int, + restoreHeight = json['restoreHeight'] as int, + creationHeight = json['creationHeight'] as int; + + ExtraMimblewimblecoinWalletInfo copyWith({ + int? receivingIndex, + int? changeIndex, + Map? slatesToAddresses, + Map? slatesToCommits, + int? lastScannedBlock, + int? restoreHeight, + int? creationHeight, + }) { + return ExtraMimblewimblecoinWalletInfo( + receivingIndex: receivingIndex ?? this.receivingIndex, + changeIndex: changeIndex ?? this.changeIndex, + slatesToAddresses: slatesToAddresses ?? this.slatesToAddresses, + slatesToCommits: slatesToCommits ?? this.slatesToCommits, + lastScannedBlock: lastScannedBlock ?? this.lastScannedBlock, + restoreHeight: restoreHeight ?? this.restoreHeight, + creationHeight: creationHeight ?? this.creationHeight, + ); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 4f69b6056d..1372d0f3c0 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -33,6 +33,7 @@ import 'impl/dash_wallet.dart'; import 'impl/dogecoin_wallet.dart'; import 'impl/ecash_wallet.dart'; import 'impl/epiccash_wallet.dart'; +import 'impl/mimblewimblecoin_wallet.dart'; import 'impl/ethereum_wallet.dart'; import 'impl/firo_wallet.dart'; import 'impl/litecoin_wallet.dart'; @@ -358,6 +359,9 @@ abstract class Wallet { case const (Epiccash): return EpiccashWallet(net); + case const (Mimblewimblecoin): + return Mimblewimblecoin(net); + case const (Ethereum): return EthereumWallet(net); diff --git a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart index 9709c46b3d..05dd4311da 100644 --- a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart +++ b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart @@ -153,7 +153,7 @@ class _TorHasBeenAddedDialogState extends State<_TorHasBeenAddedDialog> { height: Util.isDesktop ? 24 : 16, ), Text( - "Note: Tor does NOT yet work for Monero or Epic Cash wallets. " + "Note: Tor does NOT yet work for Monero, Mimblewimblecoin or Epic Cash wallets. " "Opening one of these will leak your IP address.", style: Util.isDesktop ? STextStyles.desktopTextMedium(context) diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index cab5043d66..f842858e9f 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -27,6 +27,7 @@ import '../utilities/constants.dart'; import '../utilities/format.dart'; import '../utilities/text_styles.dart'; import '../utilities/util.dart'; +import '../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; import 'desktop/desktop_dialog.dart'; @@ -63,6 +64,10 @@ class _TransactionCardState extends ConsumerState { return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + final confirmedStatus = _transaction.isConfirmed( currentHeight, minConfirms, @@ -188,6 +193,20 @@ class _TransactionCardState extends ConsumerState { ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } + if (Util.isDesktop) { await showDialog( context: context, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 88c196c5ed..8faaf8a515 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -30,6 +30,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_libepiccash_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibepiccashPlugin"); flutter_libepiccash_plugin_register_with_registrar(flutter_libepiccash_registrar); + g_autoptr(FlPluginRegistrar) flutter_libmwc_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibmwcPlugin"); + flutter_libmwc_plugin_register_with_registrar(flutter_libmwc_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 72a81025cb..52a5ce0a5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop devicelocale flutter_libepiccash + flutter_libmwc flutter_secure_storage_linux isar_flutter_libs sqlite3_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 66db749d95..6c71495b0f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import desktop_drop import device_info_plus import devicelocale import flutter_libepiccash +import flutter_libmwc import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs @@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) FlutterLibepiccashPlugin.register(with: registry.registrar(forPlugin: "FlutterLibepiccashPlugin")) + FlutterLibmwcPlugin.register(with: registry.registrar(forPlugin: "FlutterLibmwcPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 96a16a3ae2..48728fb53c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -810,6 +810,13 @@ packages: relative: true source: path version: "0.0.1" + flutter_libmwc: + dependency: "direct main" + description: + path: "crypto_plugins/flutter_libmwc" + relative: true + source: path + version: "0.0.1" flutter_libsparkmobile: dependency: "direct main" description: diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 0fd8e5e8a0..1139cbfc8d 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -62,6 +62,7 @@ final List _supportedCoins = List.unmodifiable([ Dogecoin(CryptoCurrencyNetwork.main), Ecash(CryptoCurrencyNetwork.main), Epiccash(CryptoCurrencyNetwork.main), + Mimblewimblecoin(CryptoCurrencyNetwork.main), Ethereum(CryptoCurrencyNetwork.main), Firo(CryptoCurrencyNetwork.main), Litecoin(CryptoCurrencyNetwork.main), diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index 25750ef4f6..d6ac3f66d3 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -137,7 +137,10 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libemwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1.7.4" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e54f270b7e..59279398d1 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -45,6 +45,9 @@ dependencies: flutter_libepiccash: path: ./crypto_plugins/flutter_libepiccash + flutter_libmwc: + path: ./crypto_plugins/flutter_libmwc + bitcoindart: git: url: https://github.com/cypherstack/bitcoindart.git diff --git a/scripts/app_config/templates/windows/CMakeLists.txt b/scripts/app_config/templates/windows/CMakeLists.txt index b9add856d7..dd039195e2 100644 --- a/scripts/app_config/templates/windows/CMakeLists.txt +++ b/scripts/app_config/templates/windows/CMakeLists.txt @@ -83,6 +83,9 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/windows/build/libmwc_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index bcb03e9912..aa9d52aeef 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_campfire.sh b/scripts/ios/build_all_campfire.sh index bcb03e9912..aa9d52aeef 100755 --- a/scripts/ios/build_all_campfire.sh +++ b/scripts/ios/build_all_campfire.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_duo.sh b/scripts/ios/build_all_duo.sh index 89e6f46417..c6c11add7a 100755 --- a/scripts/ios/build_all_duo.sh +++ b/scripts/ios/build_all_duo.sh @@ -18,6 +18,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh index 423646185e..4cc1ad8a09 100755 --- a/scripts/linux/build_all.sh +++ b/scripts/linux/build_all.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_campfire.sh b/scripts/linux/build_all_campfire.sh index 423646185e..4cc1ad8a09 100755 --- a/scripts/linux/build_all_campfire.sh +++ b/scripts/linux/build_all_campfire.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_duo.sh b/scripts/linux/build_all_duo.sh index 78067b4786..ace233fd8e 100755 --- a/scripts/linux/build_all_duo.sh +++ b/scripts/linux/build_all_duo.sh @@ -16,6 +16,8 @@ mkdir -p build ./build_secure_storage_deps.sh & (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index af608846fe..2e28e2605d 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_campfire.sh b/scripts/macos/build_all_campfire.sh index af608846fe..2e28e2605d 100755 --- a/scripts/macos/build_all_campfire.sh +++ b/scripts/macos/build_all_campfire.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_duo.sh b/scripts/macos/build_all_duo.sh index 8a53e58015..ac3788cc86 100755 --- a/scripts/macos/build_all_duo.sh +++ b/scripts/macos/build_all_duo.sh @@ -10,6 +10,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index 8cda1229ba..a95729573d 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -16,4 +16,13 @@ set_rust_to_1720() { echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2 exit 1 fi +} + +set_rust_to_1810() { + if rustup toolchain list | grep -q "1.72.0"; then + rustup default 1.81.0 + else + echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 + exit 1 + fi } \ No newline at end of file diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index 191a46cc07..53c035a3da 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_campfire.sh b/scripts/windows/build_all_campfire.sh index 191a46cc07..53c035a3da 100755 --- a/scripts/windows/build_all_campfire.sh +++ b/scripts/windows/build_all_campfire.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_duo.sh b/scripts/windows/build_all_duo.sh index 3e27eff02a..e5667b188d 100755 --- a/scripts/windows/build_all_duo.sh +++ b/scripts/windows/build_all_duo.sh @@ -11,6 +11,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/deps.sh b/scripts/windows/deps.sh index 4914d074f1..513a8ce7f2 100644 --- a/scripts/windows/deps.sh +++ b/scripts/windows/deps.sh @@ -1,8 +1,9 @@ #!/bin/bash -cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh -cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh -# cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh +(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh ) +(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh ) +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./deps.sh) +# (cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh) & wait echo "Done building" diff --git a/test/price_test.dart b/test/price_test.dart index dc7aaeb9e1..e15a7b95d7 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'dart:convert'; import 'dart:io'; diff --git a/test/sample_data/theme_json.dart b/test/sample_data/theme_json.dart index 6f7601b42b..660e680e68 100644 --- a/test/sample_data/theme_json.dart +++ b/test/sample_data/theme_json.dart @@ -10,6 +10,7 @@ const Map lightThemeJsonMap = { "firo": "0xFFFF897A", "dogecoin": "0xFFFFE079", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -186,6 +187,7 @@ const Map lightThemeJsonMap = { "bitcoincash": "dummy.svg", "dogecoin": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -197,6 +199,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image": "dummy.svg", "dogecoin_image": "dummy.svg", "epicCash_image": "dummy.svg.svg", + "mimblewimblecoin_image": "dummy.svg", "ethereum_image": "dummy.svg", "firo_image": "dummy.svg", "monero_image": "dummy.svg", @@ -208,6 +211,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image_secondary": "dummy.svg", "dogecoin_image_secondary": "dummy.svg", "epicCash_image_secondary": "dummy.svg.svg", + "mimblewimblecoin_image_secondary": "dummy.svg", "ethereum_image_secondary": "dummy.svg", "firo_image_secondary": "dummy.svg", "monero_image_secondary": "dummy.svg", diff --git a/test/sample_data/theme_json_v2.dart b/test/sample_data/theme_json_v2.dart index 3f9fbfb7f3..1327789c02 100644 --- a/test/sample_data/theme_json_v2.dart +++ b/test/sample_data/theme_json_v2.dart @@ -13,6 +13,7 @@ const Map lightThemeJsonMap = { "dogecoin": "0xFFFFE079", "eCash": "0xFFC5C7CB", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -194,6 +195,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -208,6 +210,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -222,6 +225,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index 5b4f1e6358..b44ebf3231 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; @@ -152,6 +154,17 @@ void main() { isFailover: true, isDown: false, ); + final nodeD = NodeModel( + host: "host3", + port: 423, + name: "btcnode", + id: "pnodeID3", + useSSL: true, + enabled: true, + coinName: "mimblewimblecoin", + isFailover: true, + isDown: false, + ); setUp(() async { await NodeService(secureStorageInterface: FakeSecureStorage()) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 74ef33c4e5..bb0e286970 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopDropPlugin")); FlutterLibepiccashPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLibepiccashPluginCApi")); + FlutterLibmwcPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLibmwcPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); IsarFlutterLibsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2f370e6855..0cd9af7943 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST cs_monero_flutter_libs_windows desktop_drop flutter_libepiccash + flutter_libmwc flutter_secure_storage_windows isar_flutter_libs local_auth_windows From 9d04e479e74d087f46b847c32e7d3790576f0bc8 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 27 Oct 2024 15:54:52 +0400 Subject: [PATCH 02/91] continuous integration mwc v2 --- .../blockchain_data/v2/transaction_v2.g.dart | 142 +++-- lib/models/isar/stack_theme.g.dart | 603 +++++++++++++++--- lib/models/mwcmqs_config_model.dart | 118 ++++ lib/models/mwcmqs_server_model.dart | 93 +++ .../type_adaptors/mwcmqs_config_model.g.dart | 50 ++ .../type_adaptors/mwcmqs_server_model.g.dart | 62 ++ .../restore_wallet_view.dart | 28 +- .../wallet_settings_view.dart | 2 +- lib/utilities/address_utils.dart | 2 +- lib/utilities/test_mwcmqs_connection.dart | 81 +++ .../coins/mimblewimblecoin.dart | 6 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 95 ++- ...imblewimblecoin_wallet_info_extension.dart | 6 +- lib/wallets/wallet/wallet.dart | 2 +- pubspec.lock | 2 +- .../app_config/templates/linux/CMakeLists.txt | 2 +- scripts/linux/build_secp256k1.sh | 2 +- test/cached_electrumx_test.mocks.dart | 13 + .../pages/send_view/send_view_test.mocks.dart | 13 + .../exchange/exchange_view_test.mocks.dart | 13 + .../managed_favorite_test.mocks.dart | 13 + .../node_options_sheet_test.mocks.dart | 13 + .../transaction_card_test.mocks.dart | 13 + 23 files changed, 1181 insertions(+), 193 deletions(-) create mode 100644 lib/models/mwcmqs_config_model.dart create mode 100644 lib/models/mwcmqs_server_model.dart create mode 100644 lib/models/type_adaptors/mwcmqs_config_model.g.dart create mode 100644 lib/models/type_adaptors/mwcmqs_server_model.g.dart create mode 100644 lib/utilities/test_mwcmqs_connection.dart diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart index 68d8a18c57..3bb48c3508 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart @@ -53,66 +53,71 @@ const TransactionV2Schema = CollectionSchema( name: r'isEpiccashTransaction', type: IsarType.bool, ), - r'nonce': PropertySchema( + r'isMimblewimblecoinTransaction': PropertySchema( id: 7, + name: r'isMimblewimblecoinTransaction', + type: IsarType.bool, + ), + r'nonce': PropertySchema( + id: 8, name: r'nonce', type: IsarType.long, ), r'numberOfMessages': PropertySchema( - id: 8, + id: 9, name: r'numberOfMessages', type: IsarType.long, ), r'onChainNote': PropertySchema( - id: 9, + id: 10, name: r'onChainNote', type: IsarType.string, ), r'otherData': PropertySchema( - id: 10, + id: 11, name: r'otherData', type: IsarType.string, ), r'outputs': PropertySchema( - id: 11, + id: 12, name: r'outputs', type: IsarType.objectList, target: r'OutputV2', ), r'slateId': PropertySchema( - id: 12, + id: 13, name: r'slateId', type: IsarType.string, ), r'subType': PropertySchema( - id: 13, + id: 14, name: r'subType', type: IsarType.byte, enumMap: _TransactionV2subTypeEnumValueMap, ), r'timestamp': PropertySchema( - id: 14, + id: 15, name: r'timestamp', type: IsarType.long, ), r'txid': PropertySchema( - id: 15, + id: 16, name: r'txid', type: IsarType.string, ), r'type': PropertySchema( - id: 16, + id: 17, name: r'type', type: IsarType.byte, enumMap: _TransactionV2typeEnumValueMap, ), r'version': PropertySchema( - id: 17, + id: 18, name: r'version', type: IsarType.long, ), r'walletId': PropertySchema( - id: 18, + id: 19, name: r'walletId', type: IsarType.string, ) @@ -256,23 +261,24 @@ void _transactionV2Serialize( ); writer.writeBool(offsets[5], object.isCancelled); writer.writeBool(offsets[6], object.isEpiccashTransaction); - writer.writeLong(offsets[7], object.nonce); - writer.writeLong(offsets[8], object.numberOfMessages); - writer.writeString(offsets[9], object.onChainNote); - writer.writeString(offsets[10], object.otherData); + writer.writeBool(offsets[7], object.isMimblewimblecoinTransaction); + writer.writeLong(offsets[8], object.nonce); + writer.writeLong(offsets[9], object.numberOfMessages); + writer.writeString(offsets[10], object.onChainNote); + writer.writeString(offsets[11], object.otherData); writer.writeObjectList( - offsets[11], + offsets[12], allOffsets, OutputV2Schema.serialize, object.outputs, ); - writer.writeString(offsets[12], object.slateId); - writer.writeByte(offsets[13], object.subType.index); - writer.writeLong(offsets[14], object.timestamp); - writer.writeString(offsets[15], object.txid); - writer.writeByte(offsets[16], object.type.index); - writer.writeLong(offsets[17], object.version); - writer.writeString(offsets[18], object.walletId); + writer.writeString(offsets[13], object.slateId); + writer.writeByte(offsets[14], object.subType.index); + writer.writeLong(offsets[15], object.timestamp); + writer.writeString(offsets[16], object.txid); + writer.writeByte(offsets[17], object.type.index); + writer.writeLong(offsets[18], object.version); + writer.writeString(offsets[19], object.walletId); } TransactionV2 _transactionV2Deserialize( @@ -292,23 +298,23 @@ TransactionV2 _transactionV2Deserialize( InputV2(), ) ?? [], - otherData: reader.readStringOrNull(offsets[10]), + otherData: reader.readStringOrNull(offsets[11]), outputs: reader.readObjectList( - offsets[11], + offsets[12], OutputV2Schema.deserialize, allOffsets, OutputV2(), ) ?? [], subType: - _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ?? + _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? TransactionSubType.none, - timestamp: reader.readLong(offsets[14]), - txid: reader.readString(offsets[15]), - type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ?? + timestamp: reader.readLong(offsets[15]), + txid: reader.readString(offsets[16]), + type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[17])] ?? TransactionType.outgoing, - version: reader.readLong(offsets[17]), - walletId: reader.readString(offsets[18]), + version: reader.readLong(offsets[18]), + walletId: reader.readString(offsets[19]), ); object.id = id; return object; @@ -342,14 +348,16 @@ P _transactionV2DeserializeProp

( case 6: return (reader.readBool(offset)) as P; case 7: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBool(offset)) as P; case 8: return (reader.readLongOrNull(offset)) as P; case 9: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 10: return (reader.readStringOrNull(offset)) as P; case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: return (reader.readObjectList( offset, OutputV2Schema.deserialize, @@ -357,22 +365,22 @@ P _transactionV2DeserializeProp

( OutputV2(), ) ?? []) as P; - case 12: - return (reader.readStringOrNull(offset)) as P; case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: return (_TransactionV2subTypeValueEnumMap[ reader.readByteOrNull(offset)] ?? TransactionSubType.none) as P; - case 14: - return (reader.readLong(offset)) as P; case 15: - return (reader.readString(offset)) as P; + return (reader.readLong(offset)) as P; case 16: + return (reader.readString(offset)) as P; + case 17: return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? TransactionType.outgoing) as P; - case 17: - return (reader.readLong(offset)) as P; case 18: + return (reader.readLong(offset)) as P; + case 19: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -1513,6 +1521,16 @@ extension TransactionV2QueryFilter }); } + QueryBuilder + isMimblewimblecoinTransactionEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isMimblewimblecoinTransaction', + value: value, + )); + }); + } + QueryBuilder nonceIsNull() { return QueryBuilder.apply(this, (query) { @@ -2807,6 +2825,20 @@ extension TransactionV2QuerySortBy }); } + QueryBuilder + sortByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + sortByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder sortByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3038,6 +3070,20 @@ extension TransactionV2QuerySortThenBy }); } + QueryBuilder + thenByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + thenByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder thenByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3221,6 +3267,13 @@ extension TransactionV2QueryWhereDistinct }); } + QueryBuilder + distinctByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder distinctByNonce() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'nonce'); @@ -3347,6 +3400,13 @@ extension TransactionV2QueryProperty }); } + QueryBuilder + isMimblewimblecoinTransactionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder nonceProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'nonce'); diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart index c77d979f54..0117ef7b9b 100644 --- a/lib/models/isar/stack_theme.g.dart +++ b/lib/models/isar/stack_theme.g.dart @@ -18148,138 +18148,153 @@ const ThemeAssetsSchema = Schema( name: r'loadingGif', type: IsarType.string, ), - r'monero': PropertySchema( + r'mimblewimblecoin': PropertySchema( id: 26, + name: r'mimblewimblecoin', + type: IsarType.string, + ), + r'mimblewimblecoinImage': PropertySchema( + id: 27, + name: r'mimblewimblecoinImage', + type: IsarType.string, + ), + r'mimblewimblecoinImageSecondary': PropertySchema( + id: 28, + name: r'mimblewimblecoinImageSecondary', + type: IsarType.string, + ), + r'monero': PropertySchema( + id: 29, name: r'monero', type: IsarType.string, ), r'moneroImage': PropertySchema( - id: 27, + id: 30, name: r'moneroImage', type: IsarType.string, ), r'moneroImageSecondary': PropertySchema( - id: 28, + id: 31, name: r'moneroImageSecondary', type: IsarType.string, ), r'namecoin': PropertySchema( - id: 29, + id: 32, name: r'namecoin', type: IsarType.string, ), r'namecoinImage': PropertySchema( - id: 30, + id: 33, name: r'namecoinImage', type: IsarType.string, ), r'namecoinImageSecondary': PropertySchema( - id: 31, + id: 34, name: r'namecoinImageSecondary', type: IsarType.string, ), r'particl': PropertySchema( - id: 32, + id: 35, name: r'particl', type: IsarType.string, ), r'particlImage': PropertySchema( - id: 33, + id: 36, name: r'particlImage', type: IsarType.string, ), r'particlImageSecondary': PropertySchema( - id: 34, + id: 37, name: r'particlImageSecondary', type: IsarType.string, ), r'personaEasy': PropertySchema( - id: 35, + id: 38, name: r'personaEasy', type: IsarType.string, ), r'personaIncognito': PropertySchema( - id: 36, + id: 39, name: r'personaIncognito', type: IsarType.string, ), r'receive': PropertySchema( - id: 37, + id: 40, name: r'receive', type: IsarType.string, ), r'receiveCancelled': PropertySchema( - id: 38, + id: 41, name: r'receiveCancelled', type: IsarType.string, ), r'receivePending': PropertySchema( - id: 39, + id: 42, name: r'receivePending', type: IsarType.string, ), r'send': PropertySchema( - id: 40, + id: 43, name: r'send', type: IsarType.string, ), r'sendCancelled': PropertySchema( - id: 41, + id: 44, name: r'sendCancelled', type: IsarType.string, ), r'sendPending': PropertySchema( - id: 42, + id: 45, name: r'sendPending', type: IsarType.string, ), r'stack': PropertySchema( - id: 43, + id: 46, name: r'stack', type: IsarType.string, ), r'stackIcon': PropertySchema( - id: 44, + id: 47, name: r'stackIcon', type: IsarType.string, ), r'themePreview': PropertySchema( - id: 45, + id: 48, name: r'themePreview', type: IsarType.string, ), r'themeSelector': PropertySchema( - id: 46, + id: 49, name: r'themeSelector', type: IsarType.string, ), r'txExchange': PropertySchema( - id: 47, + id: 50, name: r'txExchange', type: IsarType.string, ), r'txExchangeFailed': PropertySchema( - id: 48, + id: 51, name: r'txExchangeFailed', type: IsarType.string, ), r'txExchangePending': PropertySchema( - id: 49, + id: 52, name: r'txExchangePending', type: IsarType.string, ), r'wownero': PropertySchema( - id: 50, + id: 53, name: r'wownero', type: IsarType.string, ), r'wowneroImage': PropertySchema( - id: 51, + id: 54, name: r'wowneroImage', type: IsarType.string, ), r'wowneroImageSecondary': PropertySchema( - id: 52, + id: 55, name: r'wowneroImageSecondary', type: IsarType.string, ) @@ -18332,6 +18347,9 @@ int _themeAssetsEstimateSize( bytesCount += 3 + value.length * 3; } } + bytesCount += 3 + object.mimblewimblecoin.length * 3; + bytesCount += 3 + object.mimblewimblecoinImage.length * 3; + bytesCount += 3 + object.mimblewimblecoinImageSecondary.length * 3; bytesCount += 3 + object.monero.length * 3; bytesCount += 3 + object.moneroImage.length * 3; bytesCount += 3 + object.moneroImageSecondary.length * 3; @@ -18394,33 +18412,36 @@ void _themeAssetsSerialize( writer.writeString(offsets[23], object.litecoinImage); writer.writeString(offsets[24], object.litecoinImageSecondary); writer.writeString(offsets[25], object.loadingGif); - writer.writeString(offsets[26], object.monero); - writer.writeString(offsets[27], object.moneroImage); - writer.writeString(offsets[28], object.moneroImageSecondary); - writer.writeString(offsets[29], object.namecoin); - writer.writeString(offsets[30], object.namecoinImage); - writer.writeString(offsets[31], object.namecoinImageSecondary); - writer.writeString(offsets[32], object.particl); - writer.writeString(offsets[33], object.particlImage); - writer.writeString(offsets[34], object.particlImageSecondary); - writer.writeString(offsets[35], object.personaEasy); - writer.writeString(offsets[36], object.personaIncognito); - writer.writeString(offsets[37], object.receive); - writer.writeString(offsets[38], object.receiveCancelled); - writer.writeString(offsets[39], object.receivePending); - writer.writeString(offsets[40], object.send); - writer.writeString(offsets[41], object.sendCancelled); - writer.writeString(offsets[42], object.sendPending); - writer.writeString(offsets[43], object.stack); - writer.writeString(offsets[44], object.stackIcon); - writer.writeString(offsets[45], object.themePreview); - writer.writeString(offsets[46], object.themeSelector); - writer.writeString(offsets[47], object.txExchange); - writer.writeString(offsets[48], object.txExchangeFailed); - writer.writeString(offsets[49], object.txExchangePending); - writer.writeString(offsets[50], object.wownero); - writer.writeString(offsets[51], object.wowneroImage); - writer.writeString(offsets[52], object.wowneroImageSecondary); + writer.writeString(offsets[26], object.mimblewimblecoin); + writer.writeString(offsets[27], object.mimblewimblecoinImage); + writer.writeString(offsets[28], object.mimblewimblecoinImageSecondary); + writer.writeString(offsets[29], object.monero); + writer.writeString(offsets[30], object.moneroImage); + writer.writeString(offsets[31], object.moneroImageSecondary); + writer.writeString(offsets[32], object.namecoin); + writer.writeString(offsets[33], object.namecoinImage); + writer.writeString(offsets[34], object.namecoinImageSecondary); + writer.writeString(offsets[35], object.particl); + writer.writeString(offsets[36], object.particlImage); + writer.writeString(offsets[37], object.particlImageSecondary); + writer.writeString(offsets[38], object.personaEasy); + writer.writeString(offsets[39], object.personaIncognito); + writer.writeString(offsets[40], object.receive); + writer.writeString(offsets[41], object.receiveCancelled); + writer.writeString(offsets[42], object.receivePending); + writer.writeString(offsets[43], object.send); + writer.writeString(offsets[44], object.sendCancelled); + writer.writeString(offsets[45], object.sendPending); + writer.writeString(offsets[46], object.stack); + writer.writeString(offsets[47], object.stackIcon); + writer.writeString(offsets[48], object.themePreview); + writer.writeString(offsets[49], object.themeSelector); + writer.writeString(offsets[50], object.txExchange); + writer.writeString(offsets[51], object.txExchangeFailed); + writer.writeString(offsets[52], object.txExchangePending); + writer.writeString(offsets[53], object.wownero); + writer.writeString(offsets[54], object.wowneroImage); + writer.writeString(offsets[55], object.wowneroImageSecondary); } ThemeAssets _themeAssetsDeserialize( @@ -18456,33 +18477,36 @@ ThemeAssets _themeAssetsDeserialize( object.litecoinImage = reader.readString(offsets[23]); object.litecoinImageSecondary = reader.readString(offsets[24]); object.loadingGif = reader.readStringOrNull(offsets[25]); - object.monero = reader.readString(offsets[26]); - object.moneroImage = reader.readString(offsets[27]); - object.moneroImageSecondary = reader.readString(offsets[28]); - object.namecoin = reader.readString(offsets[29]); - object.namecoinImage = reader.readString(offsets[30]); - object.namecoinImageSecondary = reader.readString(offsets[31]); - object.particl = reader.readString(offsets[32]); - object.particlImage = reader.readString(offsets[33]); - object.particlImageSecondary = reader.readString(offsets[34]); - object.personaEasy = reader.readString(offsets[35]); - object.personaIncognito = reader.readString(offsets[36]); - object.receive = reader.readString(offsets[37]); - object.receiveCancelled = reader.readString(offsets[38]); - object.receivePending = reader.readString(offsets[39]); - object.send = reader.readString(offsets[40]); - object.sendCancelled = reader.readString(offsets[41]); - object.sendPending = reader.readString(offsets[42]); - object.stack = reader.readString(offsets[43]); - object.stackIcon = reader.readString(offsets[44]); - object.themePreview = reader.readString(offsets[45]); - object.themeSelector = reader.readString(offsets[46]); - object.txExchange = reader.readString(offsets[47]); - object.txExchangeFailed = reader.readString(offsets[48]); - object.txExchangePending = reader.readString(offsets[49]); - object.wownero = reader.readString(offsets[50]); - object.wowneroImage = reader.readString(offsets[51]); - object.wowneroImageSecondary = reader.readString(offsets[52]); + object.mimblewimblecoin = reader.readString(offsets[26]); + object.mimblewimblecoinImage = reader.readString(offsets[27]); + object.mimblewimblecoinImageSecondary = reader.readString(offsets[28]); + object.monero = reader.readString(offsets[29]); + object.moneroImage = reader.readString(offsets[30]); + object.moneroImageSecondary = reader.readString(offsets[31]); + object.namecoin = reader.readString(offsets[32]); + object.namecoinImage = reader.readString(offsets[33]); + object.namecoinImageSecondary = reader.readString(offsets[34]); + object.particl = reader.readString(offsets[35]); + object.particlImage = reader.readString(offsets[36]); + object.particlImageSecondary = reader.readString(offsets[37]); + object.personaEasy = reader.readString(offsets[38]); + object.personaIncognito = reader.readString(offsets[39]); + object.receive = reader.readString(offsets[40]); + object.receiveCancelled = reader.readString(offsets[41]); + object.receivePending = reader.readString(offsets[42]); + object.send = reader.readString(offsets[43]); + object.sendCancelled = reader.readString(offsets[44]); + object.sendPending = reader.readString(offsets[45]); + object.stack = reader.readString(offsets[46]); + object.stackIcon = reader.readString(offsets[47]); + object.themePreview = reader.readString(offsets[48]); + object.themeSelector = reader.readString(offsets[49]); + object.txExchange = reader.readString(offsets[50]); + object.txExchangeFailed = reader.readString(offsets[51]); + object.txExchangePending = reader.readString(offsets[52]); + object.wownero = reader.readString(offsets[53]); + object.wowneroImage = reader.readString(offsets[54]); + object.wowneroImageSecondary = reader.readString(offsets[55]); return object; } @@ -18599,6 +18623,12 @@ P _themeAssetsDeserializeProp

( return (reader.readString(offset)) as P; case 52: return (reader.readString(offset)) as P; + case 53: + return (reader.readString(offset)) as P; + case 54: + return (reader.readString(offset)) as P; + case 55: + return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); } @@ -22170,6 +22200,417 @@ extension ThemeAssetsQueryFilter }); } + QueryBuilder + mimblewimblecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + QueryBuilder moneroEqualTo( String value, { bool caseSensitive = true, diff --git a/lib/models/mwcmqs_config_model.dart b/lib/models/mwcmqs_config_model.dart new file mode 100644 index 0000000000..6970905106 --- /dev/null +++ b/lib/models/mwcmqs_config_model.dart @@ -0,0 +1,118 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:convert'; + +import 'package:hive/hive.dart'; + +import 'mwcmqs_server_model.dart'; + +part 'type_adaptors/mwcmqs_config_model.g.dart'; + +@HiveType(typeId: 82) +class MwcMqsConfigModel { + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + @HiveField(3) + final bool? protocolInsecure; + @HiveField(4) + final int? addressIndex; + + MwcMqsConfigModel({ + required this.host, + this.port, + this.protocolInsecure, + this.addressIndex, + }); + + MwcMqsConfigModel copyWith({ + int? port, + bool? protocolInsecure, + int? addressIndex, + String? id, + String? name, + }) { + return MwcMqsConfigModel( + host: host, + port: this.port ?? 443, + protocolInsecure: this.protocolInsecure ?? false, + addressIndex: this.addressIndex ?? 0, + ); + } + + Map toMap() { + final Map map = {}; + map['mwcmqs_domain'] = host; + map['mwcmqs_port'] = port; + map['mwcmqs_protocol_insecure'] = protocolInsecure; + map['mwcmqs_address_index'] = addressIndex; + return map; + } + + Map toJson() { + return { + 'mwcmqs_domain': host, + 'mwcmqs_port': port, + 'mwcmqs_protocol_insecure': protocolInsecure, + 'mwcmqs_address_index': addressIndex, + }; + } + + @override + String toString() { + return json.encode(toJson()); + } + + static MwcMqsConfigModel fromString(String MwcMqsConfigString) { + final dynamic _mwcmqs = json.decode(MwcMqsConfigString); + + final oldDomain = _mwcmqs["domain"] ?? "empty"; + if (oldDomain != "empty") { + _mwcmqs['mwcmqs_domain'] = _mwcmqs['domain']; + } + final oldPort = _mwcmqs["port"] ?? "empty"; + if (oldPort != "empty") { + _mwcmqs['mwcmqs_port'] = _mwcmqs['port']; + } + final oldProtocolInsecure = _mwcmqs["protocol_insecure"] ?? "empty"; + if (oldProtocolInsecure != "empty") { + _mwcmqs['mwcmqs_protocol_insecure'] = _mwcmqs['protocol_insecure']; + } + final oldAddressIndex = _mwcmqs["address_index"] ?? "empty"; + if (oldAddressIndex != "empty") { + _mwcmqs['mwcmqs_address_index'] = _mwcmqs['address_index']; + } + + _mwcmqs['mwcmqs_protocol_insecure'] ??= false; + _mwcmqs['mwcmqs_address_index'] ??= 0; + + return MwcMqsConfigModel( + host: _mwcmqs['mwcmqs_domain'] as String, + port: _mwcmqs['mwcmqs_port'] as int, + protocolInsecure: _mwcmqs['mwcmqs_protocol_insecure'] as bool, + addressIndex: _mwcmqs['mwcmqs_address_index'] as int, + ); + } + + static MwcMqsConfigModel fromServer( + MwcMqsServerModel server, { + bool? protocolInsecure, + int? addressIndex, + }) { + return MwcMqsConfigModel( + host: server.host, + port: server.port ?? 443, + protocolInsecure: protocolInsecure ?? false, + addressIndex: addressIndex ?? 0, + ); + } +} diff --git a/lib/models/mwcmqs_server_model.dart b/lib/models/mwcmqs_server_model.dart new file mode 100644 index 0000000000..c6ffd757f9 --- /dev/null +++ b/lib/models/mwcmqs_server_model.dart @@ -0,0 +1,93 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:hive/hive.dart'; + +part 'type_adaptors/mwcmqs_server_model.g.dart'; + +@HiveType(typeId: 81) +class MwcMqsServerModel { + @HiveField(0) + final String id; + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + @HiveField(3) + final String name; + @HiveField(4) + final bool? useSSL; + @HiveField(5) + final bool? enabled; + @HiveField(6) + final bool? isFailover; + @HiveField(7) + final bool? isDown; + + MwcMqsServerModel({ + required this.id, + required this.host, + this.port, + required this.name, + this.useSSL, + this.enabled, + this.isFailover, + this.isDown, + }); + + MwcMqsServerModel copyWith({ + String? host, + int? port, + String? name, + bool? useSSL, + bool? enabled, + bool? isFailover, + bool? isDown, + }) { + return MwcMqsServerModel( + id: id, + host: host ?? this.host, + port: port ?? this.port, + name: name ?? this.name, + useSSL: useSSL ?? this.useSSL, + enabled: enabled ?? this.enabled, + isFailover: isFailover ?? this.isFailover, + isDown: isDown ?? this.isDown, + ); + } + + Map toMap() { + final Map map = {}; + map['id'] = id; + map['host'] = host; + map['port'] = port; + map['name'] = name; + map['useSSL'] = useSSL; + map['enabled'] = enabled; + map['isFailover'] = isFailover; + map['isDown'] = isDown; + return map; + } + + bool get isDefault => id.startsWith("default_"); + + Map toJson() { + return { + 'id': id, + 'host': host, + 'port': port, + 'name': name, + 'useSSL': useSSL, + 'enabled': enabled, + 'isFailover': isFailover, + 'isDown': isDown, + }; + } +} diff --git a/lib/models/type_adaptors/mwcmqs_config_model.g.dart b/lib/models/type_adaptors/mwcmqs_config_model.g.dart new file mode 100644 index 0000000000..e64fc54082 --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_config_model.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_config_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsConfigModelAdapter extends TypeAdapter { + @override + final int typeId = 82; + + @override + MwcMqsConfigModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsConfigModel( + host: fields[1] as String, + port: fields[2] as int?, + protocolInsecure: fields[3] as bool?, + addressIndex: fields[4] as int?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsConfigModel obj) { + writer + ..writeByte(4) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.protocolInsecure) + ..writeByte(4) + ..write(obj.addressIndex); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsConfigModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/mwcmqs_server_model.g.dart b/lib/models/type_adaptors/mwcmqs_server_model.g.dart new file mode 100644 index 0000000000..dabdd9464d --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_server_model.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_server_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsServerModelAdapter extends TypeAdapter { + @override + final int typeId = 81; + + @override + MwcMqsServerModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsServerModel( + id: fields[0] as String, + host: fields[1] as String, + port: fields[2] as int?, + name: fields[3] as String, + useSSL: fields[4] as bool?, + enabled: fields[5] as bool?, + isFailover: fields[6] as bool?, + isDown: fields[7] as bool?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsServerModel obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.name) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.enabled) + ..writeByte(6) + ..write(obj.isFailover) + ..writeByte(7) + ..write(obj.isDown); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsServerModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} 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 20bf9fd8b1..e56694fed0 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 @@ -254,24 +254,32 @@ class _RestoreWalletViewState extends ConsumerState { if (height < 0) { height = 0; } - - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index - if (widget.coin is Mimblewimblecoin) { - final int secondsSinceEpoch = - widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; - const int mimblewimblecoinFirstBlock = 1565370278; + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: height, + restoreHeight: height, + creationHeight: height, + ).toMap(), + ), + }, + ); + } else if (widget.coin is Mimblewimblecoin) { + final int secondsSinceEpoch = widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1573462801; const double overestimateSecondsPerBlock = 61; final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; - //todo: check if print needed - // debugPrint( - // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); height = approximateHeight; if (height < 0) { height = 0; } - otherDataJsonString = jsonEncode( { WalletInfoKeys.mimblewimblecoinData: jsonEncode( 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 0135d5cac0..e8a485be87 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 @@ -707,7 +707,7 @@ class MwcMqsInfoForm extends ConsumerStatefulWidget { final String walletId; @override - ConsumerState createState() => _MwcMqsInfoFormState(); + ConsumerState createState() => _MwcmqsInfoFormState(); } class _MwcmqsInfoFormState extends ConsumerState { diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 9cd952b3e8..b3c2404f07 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -376,7 +376,7 @@ class AddressUtils { } /// Formats an address string to remove any unnecessary prefixes or suffixes. - String formatMimblewimblecoinAddress(String mimblewimblecoinAddress) { + String formatAddressMwc(String mimblewimblecoinAddress) { // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) if ((mimblewimblecoinAddress.startsWith("http://") || mimblewimblecoinAddress.startsWith("https://")) && diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart new file mode 100644 index 0000000000..44433a1036 --- /dev/null +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -0,0 +1,81 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:convert'; + +import '../networking/http.dart'; +import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../services/tor_service.dart'; +import 'logger.dart'; +import 'prefs.dart'; + +Future _testMwcMqsNodeConnection(Uri uri) async { + final HTTP client = HTTP(); + try { + final response = await client + .get( + url: uri, + headers: {'Content-Type': 'application/json'}, + proxyInfo: Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ) + .timeout( + const Duration(milliseconds: 2000), + onTimeout: () async => Response(utf8.encode('Error'), 408), + ); + + final json = jsonDecode(response.body); + + if (response.code == 200 && json["node_version"] != null) { + return true; + } else { + return false; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return false; + } +} + +// returns node data with properly formatted host/url if successful, otherwise null +Future testMwcNodeConnection(NodeFormData data) async { + if (data.host == null || data.port == null || data.useSSL == null) { + return null; + } + const String path_postfix = "/v1/version"; + + if (data.host!.startsWith("https://")) { + data.useSSL = true; + } else if (data.host!.startsWith("http://")) { + data.useSSL = false; + } else { + if (data.useSSL!) { + data.host = "https://${data.host!}"; + } else { + data.host = "http://${data.host!}"; + } + } + + Uri uri = Uri.parse(data.host! + path_postfix); + + uri = uri.replace(port: data.port); + + try { + if (await _testMwcMqsNodeConnection(uri)) { + return data; + } else { + return null; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return null; + } +} diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index b06ec1f1d4..9ea295dd4e 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -14,7 +14,7 @@ class Mimblewimblecoin extends Bip39Currency { switch (network) { case CryptoCurrencyNetwork.main: _id = _idMain; - _name = "Mimblewimblecoin"; + _name = "MimbleWimbleCoin"; _ticker = "MWC"; default: throw Exception("Unsupported network: $network"); @@ -63,7 +63,7 @@ class Mimblewimblecoin extends Bip39Currency { } } - return mimblewimblecoin.LibMwc.validateSendAddress(address: address); + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } @override @@ -71,7 +71,7 @@ class Mimblewimblecoin extends Bip39Currency { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( - host: "http://epiccash.stackwallet.com", + host: "http://mwc713.mwc.mw", port: 3413, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(this), diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index ed77e34bc7..9edce8a5a1 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -31,7 +31,8 @@ import '../../../utilities/amount/amount.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/stack_file_system.dart'; -//import '../../../utilities/test_epic_box_connection.dart'; +import '../../../utilities/default_mwcmqs.dart'; +import '../../../utilities/test_mwcmqs_connection.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; @@ -67,20 +68,20 @@ class MimblewimblecoinWallet extends Bip39Wallet { return restorePercent < 0 ? 0.0 : restorePercent; } - /*Future updateEpicboxConfig(String host, int port) async { + Future updateMwcmqsConfig(String host, int port) async { final String stringConfig = jsonEncode({ - "epicbox_domain": host, - "epicbox_port": port, - "epicbox_protocol_unsecure": false, - "epicbox_address_index": 0, + "mwcmqs_domain": host, + "mwcmqs_port": port, + "mwcmqs_protocol_unsecure": false, + "mwcmqs_address_index": 0, }); await secureStorageInterface.write( - key: '${walletId}_epicboxConfig', + key: '${walletId}_mwcmqsConfig', value: stringConfig, ); - // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed + // TODO: refresh anything that needs to be refreshed/updated due to mwcmqs info changed } - */ + /// returns an empty String on success, error message on failure Future cancelPendingTransactionAndPost(String txSlateId) async { try { @@ -88,7 +89,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', ))!; - final result = await mimblewimblecoin.LibMwc.cancelTransaction( + final result = await mimblewimblecoin.Libmwc.cancelTransaction( wallet: wallet, transactionId: txSlateId, ); @@ -108,24 +109,24 @@ class MimblewimblecoinWallet extends Bip39Wallet { DefaultMwcMqs.defaultMwcMqsServer, ); - //Get the default Epicbox server and check if it's conected - // bool isEpicboxConnected = await _testEpicboxServer( - // DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); + //Get the default mwcmqs server and check if it's conected + // bool ismwcmqsConnected = await _testmwcmqsServer( + // Defaultmwcmqses.defaultmwcmqsServer.host, Defaultmwcmqses.defaultmwcmqsServer.port ?? 443); - // if (isEpicboxConnected) { - //Use default server for as Epicbox config + // if (ismwcmqsConnected) { + //Use default server for as mwcmqs config // } // else { // //Use Europe config - // _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + // _mwcmqsConfig = mwcmqsConfigModel.fromServer(Defaultmwcmqses.europe); // } // // example of selecting another random server from the default list // // alternative servers: copy list of all default EB servers but remove the default default - // // List alternativeServers = DefaultEpicBoxes.all; - // // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // // List alternativeServers = Defaultmwcmqses.all; + // // alternativeServers.removeWhere((opt) => opt.name == Defaultmwcmqses.defaultmwcmqsServer.name); // // alternativeServers.shuffle(); // randomize which server is used - // // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + // // _mwcmqsConfig = mwcmqsConfigModel.fromServer(alternativeServers.first); // // // TODO test this connection before returning it // } @@ -154,9 +155,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { config["check_node_api_http_addr"] = nodeApiAddress; config["chain"] = "mainnet"; config["account"] = "default"; - config["api_listen_port"] = port; - config["api_listen_interface"] = - nodeApiAddress.replaceFirst(uri.scheme, ""); final String stringConfig = jsonEncode(config); return stringConfig; } @@ -177,7 +175,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await mimblewimblecoin.LibMwc.getTransactionFees( + final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees( wallet: wallet!, amount: satoshiAmount, minimumConfirmations: cryptoCurrency.minConfirms, @@ -206,7 +204,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (!syncMutex.isLocked) { await syncMutex.protect(() async { // How does getWalletBalances start syncing???? - await mimblewimblecoin.LibMwc.getWalletBalances( + await mimblewimblecoin.Libmwc.getWalletBalances( wallet: wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: 10, @@ -226,16 +224,16 @@ class MimblewimblecoinWallet extends Bip39Wallet { })> _allWalletBalances() async { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const refreshFromNode = 0; - return await mimblewimblecoin.LibMwc.getWalletBalances( + return await mimblewimblecoin.Libmwc.getWalletBalances( wallet: wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: cryptoCurrency.minConfirms, ); } - /*Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { - final host = epicboxConfig.host; - final port = epicboxConfig.port ?? 443; + Future _testMwcmqsServer(MwcMqsConfigModel mwcmqsConfig) async { + final host = mwcmqsConfig.host; + final port = mwcmqsConfig.port ?? 443; WebSocketChannel? channel; try { final uri = Uri.parse('wss://$host:$port'); @@ -253,14 +251,14 @@ class MimblewimblecoinWallet extends Bip39Wallet { return response is String && response.contains("Challenge"); } catch (_) { Logging.instance.log( - "_testEpicBoxConnection failed on \"$host:$port\"", + "_testMwcmqsConnection failed on \"$host:$port\"", level: LogLevel.Info, ); return false; } finally { await channel?.sink.close(); } - }*/ + } Future _putSendToAddresses( ({String slateId, String commitId}) slateData, @@ -308,7 +306,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (address != null) { final splitted = address.value.split('@'); - //Check if the address is the same as the current epicbox domain + //Check if the address is the same as the current mwcmqs domain //Since we're only using one epicbpox now this doesn't apply but will be // useful in the future final mwcmqsConfig = await getMwcMqsConfig(); @@ -336,10 +334,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { ) async { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); - final walletAddress = await mimblewimblecoin.LibMwc.getAddressInfo( + final walletAddress = await mimblewimblecoin.Libmwc.getAddressInfo( wallet: wallet!, - index: index, - mwcmqsConfig: mwcmqsConfig.toString(), + index: index ); Logging.instance.log( @@ -363,7 +360,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _startScans() async { try { //First stop the current listener - mimblewimblecoin.LibMwc.stopMwcmqsListener(); + mimblewimblecoin.Libmwc.stopMwcMqsListener(); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); @@ -385,7 +382,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { level: LogLevel.Info, ); - final int nextScannedBlock = await mimblewimblecoin.LibMwc.scanOutputs( + final int nextScannedBlock = await mimblewimblecoin.Libmwc.scanOutputs( wallet: wallet!, startHeight: lastScannedBlock, numberOfBlocks: scanChunkSize, @@ -426,7 +423,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); - mimblewimblecoin.LibMwc.startMwcMqsListener( + mimblewimblecoin.Libmwc.startMwcMqsListener( wallet: wallet!, mwcmqsConfig: mwcmqsConfig.toString(), ); @@ -509,7 +506,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String name = walletId; - await mimblewimblecoin.LibMwc.initializeNewWallet( + await mimblewimblecoin.Libmwc.initializeNewWallet( config: stringConfig, mnemonic: mnemonicString, password: password, @@ -517,7 +514,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await mimblewimblecoin.LibMwc.openWallet( + encodedWallet = await mimblewimblecoin.Libmwc.openWallet( config: stringConfig, password: password, ); @@ -559,7 +556,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final password = await secureStorageInterface.read(key: '${walletId}_password'); - final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + final walletOpen = await mimblewimblecoin.Libmwc.openWallet( config: config, password: password!, ); @@ -608,7 +605,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { - transaction = await mimblewimblecoin.LibMwc.txHttpSend( + transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, minimumConfirmations: cryptoCurrency.minConfirms, @@ -617,12 +614,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { address: txData.recipients!.first.address, ); } else { - transaction = await mimblewimblecoin.LibMwc.createTransaction( + transaction = await mimblewimblecoin.Libmwc.createTransaction( wallet: wallet!, amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, secretKeyIndex: 0, - mwcmqsConfig: mwsmqsConfig.toString(), + mwcmqsConfig: mwcmqsConfig.toString(), minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, ); @@ -724,7 +721,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: mwcmqsConfig.toString(), ); - await mimblewimblecoin.LibMwc.recoverWallet( + await mimblewimblecoin.Libmwc.recoverWallet( config: stringConfig, password: password, mnemonic: await getMnemonic(), @@ -748,7 +745,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open Wallet - final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + final walletOpen = await mimblewimblecoin.Libmwc.openWallet( config: stringConfig, password: password, ); @@ -943,7 +940,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { .findAll(); final myAddressesSet = myAddresses.toSet(); - final transactions = await mimblewimblecoin.LibMwc.getTransactions( + final transactions = await mimblewimblecoin.Libmwc.getTransactions( wallet: wallet!, refreshFromNode: refreshFromNode, ); @@ -1095,7 +1092,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // force unwrap optional as we want connection test to fail if wallet // wasn't initialized or mwcmqs node was set to null - return await testMimblewimblecoinNodeConnection( + return await testMwcNodeConnection( NodeFormData() ..host = node!.host ..useSSL = node.useSSL @@ -1112,7 +1109,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future updateChainHeight() async { final config = await _getRealConfig(); final latestHeight = - await mimblewimblecoin.LibMwc.getChainHeight(config: config); + await mimblewimblecoin.Libmwc.getChainHeight(config: config); await info.updateCachedChainHeight( newHeight: latestHeight, isar: mainDB.isar, @@ -1185,7 +1182,7 @@ Future deleteMimblewimblecoinWallet({ return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; } else { try { - return mimblewimblecoin.LibMwc.deleteWallet( + return mimblewimblecoin.Libmwc.deleteWallet( wallet: wallet, config: config!, ); diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart index 724657079e..0c876d5c57 100644 --- a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -5,7 +5,7 @@ import '../../../utilities/logger.dart'; import '../../isar/models/wallet_info.dart'; extension MimblewimblecoinWalletInfoExtension on WalletInfo { - ExtraMimblewimblecoinWalletInfo? get epicData { + ExtraMimblewimblecoinWalletInfo? get mimblewimblecoinData { final String? data = otherData[WalletInfoKeys.mimblewimblecoinData] as String?; if (data == null) { @@ -27,12 +27,12 @@ extension MimblewimblecoinWalletInfoExtension on WalletInfo { } Future updateExtraMimblewimblecoinWalletInfo({ - required ExtraMimblewimblecoinWalletInfo epicData, + required ExtraMimblewimblecoinWalletInfo mimblewimblecoinData, required Isar isar, }) async { await updateOtherData( newEntries: { - WalletInfoKeys.mimblewimblecoinData: jsonEncode(epicData.toMap()), + WalletInfoKeys.mimblewimblecoinData: jsonEncode(mimblewimblecoinData.toMap()), }, isar: isar, ); diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 1372d0f3c0..3a4ccd3d34 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -360,7 +360,7 @@ abstract class Wallet { return EpiccashWallet(net); case const (Mimblewimblecoin): - return Mimblewimblecoin(net); + return MimblewimblecoinWallet(net); case const (Ethereum): return EthereumWallet(net); diff --git a/pubspec.lock b/pubspec.lock index 48728fb53c..1346803686 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -957,7 +957,7 @@ packages: source: hosted version: "2.4.4" frontend_server_client: - dependency: transitive + dependency: "direct dev" description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index d6ac3f66d3..3707ec1b4e 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -137,7 +137,7 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libemwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/scripts/linux/build_secp256k1.sh b/scripts/linux/build_secp256k1.sh index f00b6b82bf..cf7650ffe1 100755 --- a/scripts/linux/build_secp256k1.sh +++ b/scripts/linux/build_secp256k1.sh @@ -10,5 +10,5 @@ mkdir -p build && cd build cmake .. cmake --build . mkdir -p ../../../../../build -cp lib/libsecp256k1.so.2.2.2 "../../../../../build/libsecp256k1.so" +cp lib/libsecp256k1.so.2.*.* "../../../../../build/libsecp256k1.so" cd ../../../ diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 993f73f694..e37380e0b1 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -1113,6 +1113,19 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index fee6a0d7f5..c20fd4e293 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -1129,6 +1129,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index b7a8d49d51..3f04c3fc27 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -558,6 +558,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index c2bc00d2c0..ee2de45f91 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -834,6 +834,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 5e878015ff..26f7b6cf34 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -709,6 +709,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index a5816bdcbb..d9234ff9bf 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -818,6 +818,19 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, From 68a6769a239fba117f9ee80c265b24455c7b8639 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 27 Oct 2024 17:07:52 +0400 Subject: [PATCH 03/91] disable PingCheck --- crypto_plugins/flutter_libmwc | 2 +- lib/utilities/default_mwcmqs.dart | 6 +-- .../wallet/impl/mimblewimblecoin_wallet.dart | 43 ++++++++++--------- pubspec.lock | 2 +- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index ff25b65406..19764f5c8a 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit ff25b6540698e6fe3e89dda5f0480552a882369d +Subproject commit 19764f5c8adbf93567962ea94e9ff146205aaf45 diff --git a/lib/utilities/default_mwcmqs.dart b/lib/utilities/default_mwcmqs.dart index 75be1d247b..84e7356d84 100644 --- a/lib/utilities/default_mwcmqs.dart +++ b/lib/utilities/default_mwcmqs.dart @@ -17,7 +17,7 @@ abstract class DefaultMwcMqs { static List get defaultIds => ['americas', 'asia', 'europe']; static MwcMqsServerModel get americas => MwcMqsServerModel( - host: 'MwcMqs.stackwallet.com', + host: 'mqs.mwc.mw', port: 443, name: 'Americas', id: 'americas', @@ -28,7 +28,7 @@ abstract class DefaultMwcMqs { ); static MwcMqsServerModel get asia => MwcMqsServerModel( - host: 'MwcMqs.hyperbig.com', + host: 'mqs.mwc.mw', port: 443, name: 'Asia', id: 'asia', @@ -39,7 +39,7 @@ abstract class DefaultMwcMqs { ); static MwcMqsServerModel get europe => MwcMqsServerModel( - host: 'MwcMqs.fastepic.eu', + host: 'mqs.mwc.mw', port: 443, name: 'Europe', id: 'europe', diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 9edce8a5a1..83d038e9dd 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -524,7 +524,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Store MwcMqs address info - await _generateAndStoreReceivingAddressForIndex(0); + // await _generateAndStoreReceivingAddressForIndex(0); // subtract a couple days to ensure we have a buffer for SWB final bufferedCreateHeight = _calculateRestoreHeightFrom( @@ -754,9 +754,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: walletOpen, ); - await _generateAndStoreReceivingAddressForIndex( - mimblewimblecoinData.receivingIndex, - ); + // await _generateAndStoreReceivingAddressForIndex( + // mimblewimblecoinData.receivingIndex, + // ); } }); @@ -799,7 +799,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // this will always be zero???? final int curAdd = await _getCurrentIndex(); - await _generateAndStoreReceivingAddressForIndex(curAdd); + // await _generateAndStoreReceivingAddressForIndex(curAdd); await _startScans(); @@ -1087,22 +1087,23 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future pingCheck() async { - try { - final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); - - // force unwrap optional as we want connection test to fail if wallet - // wasn't initialized or mwcmqs node was set to null - return await testMwcNodeConnection( - NodeFormData() - ..host = node!.host - ..useSSL = node.useSSL - ..port = node.port, - ) != - null; - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); - return false; - } + return true; + //try { + // final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); +// + // // force unwrap optional as we want connection test to fail if wallet + // // wasn't initialized or mwcmqs node was set to null + // return await testMwcNodeConnection( + // NodeFormData() + // ..host = node!.host + // ..useSSL = node.useSSL + // ..port = node.port, + // ) != + // null; + //} catch (e, s) { + // Logging.instance.log("$e\n$s", level: LogLevel.Info); + // return false; + //} } @override diff --git a/pubspec.lock b/pubspec.lock index 1346803686..48728fb53c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -957,7 +957,7 @@ packages: source: hosted version: "2.4.4" frontend_server_client: - dependency: "direct dev" + dependency: transitive description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 From 753710561f544cfc2f2408afd89de3c8acbe6699 Mon Sep 17 00:00:00 2001 From: vekamo Date: Tue, 12 Nov 2024 22:00:58 +0400 Subject: [PATCH 04/91] few fixes and remove mwc-wallet logs --- crypto_plugins/flutter_libmwc | 2 +- .../transaction_details_view.dart | 58 ++++++++++- .../tx_v2/transaction_v2_details_view.dart | 16 +-- lib/utilities/test_mwcmqs_connection.dart | 16 ++- lib/utilities/test_node_connection.dart | 14 +++ .../coins/mimblewimblecoin.dart | 41 ++++---- .../wallet/impl/mimblewimblecoin_wallet.dart | 99 +++++++++---------- 7 files changed, 149 insertions(+), 97 deletions(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 19764f5c8a..fe78fd4528 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 19764f5c8adbf93567962ea94e9ff146205aaf45 +Subproject commit fe78fd45284f99f26a40bba15e8d1a53c718be1a diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1071ffdaec..777f2b75ee 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -1730,7 +1731,7 @@ class _TransactionDetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && + floatingActionButton: ((coin is Epiccash || coin is Mimblewimblecoin) && _transaction.getConfirmations(currentHeight) < 1 && _transaction.isCancelled == false) ? ConditionalParent( @@ -1775,6 +1776,59 @@ class _TransactionDetailsViewState ), ); + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find MWC transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + final result = await wallet.cancelPendingTransactionAndPost(id); if (mounted) { @@ -1810,7 +1864,7 @@ class _TransactionDetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); 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 03316599f6..737b5e6605 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 @@ -2112,18 +2112,7 @@ class _TransactionV2DetailsViewState ); } } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - ), - ); - return; - } - - if (wallet is MimblewimblecoinWallet) { + } else if (wallet is MimblewimblecoinWallet) { final String? id = _transaction.slateId; if (id == null) { unawaited( @@ -2181,8 +2170,7 @@ class _TransactionV2DetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: - "ERROR: Wallet type is not Mimblewimblecoin", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart index 44433a1036..cfcd4a098f 100644 --- a/lib/utilities/test_mwcmqs_connection.dart +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -18,11 +18,21 @@ import 'prefs.dart'; Future _testMwcMqsNodeConnection(Uri uri) async { final HTTP client = HTTP(); - try { + try { + final headers = { + 'Content-Type': 'application/json', + }; + + if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { + const username = 'mwcmain'; + const password = '11ne3EAUtOXVKwhxm84U'; + final credentials = base64Encode(utf8.encode('$username:$password')); + headers['Authorization'] = 'Basic $credentials'; + } final response = await client .get( url: uri, - headers: {'Content-Type': 'application/json'}, + headers: headers, proxyInfo: Prefs.instance.useTor ? TorService.sharedInstance.getProxyInfo() : null, @@ -67,7 +77,7 @@ Future testMwcNodeConnection(NodeFormData data) async { Uri uri = Uri.parse(data.host! + path_postfix); uri = uri.replace(port: data.port); - + try { if (await _testMwcMqsNodeConnection(uri)) { return data; diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index c83bf39e19..a66352453c 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -22,6 +22,7 @@ import '../wallets/wallet/impl/solana_wallet.dart'; import 'connection_check/electrum_connection_check.dart'; import 'logger.dart'; import 'test_epic_box_connection.dart'; +import 'test_mwcmqs_connection.dart'; import 'test_eth_node_connection.dart'; import 'test_monero_node_connection.dart'; import 'test_stellar_node_connection.dart'; @@ -108,6 +109,19 @@ Future testNodeConnection({ } break; + case Mimblewimblecoin(): + try { + final data = await testMwcNodeConnection(formData); + + if (data != null) { + testPassed = true; + onSuccess?.call(data); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + break; + case CryptonoteCurrency(): try { final proxyInfo = ref diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 9ea295dd4e..3127ef035b 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -50,36 +50,33 @@ class Mimblewimblecoin extends Bip39Currency { // change this to change the number of confirms a tx needs in order to show as confirmed int get minConfirms => 3; - @override - bool validateAddress(String address) { - // Invalid address that contains HTTP and mwcmqs domain - if ((address.startsWith("http://") || address.startsWith("https://")) && - address.contains("@")) { - return false; - } - if (address.startsWith("http://") || address.startsWith("https://")) { - if (Uri.tryParse(address) != null) { - return true; - } - } - - return mimblewimblecoin.Libmwc.validateSendAddress(address: address); + @override +bool validateAddress(String address) { + Uri? uri = Uri.tryParse(address); + if (uri != null && + (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "mwcmqs") && + uri.host.isNotEmpty && + !uri.host.endsWith(".onion")) { + return true; } + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); +} + @override NodeModel get defaultNode { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( - host: "http://mwc713.mwc.mw", - port: 3413, + host: "https://mwc713.mwc.mw", + port: 443, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(this), - useSSL: false, + useSSL: true, enabled: true, coinName: identifier, isFailover: true, - isDown: false, + isDown: false ); default: @@ -88,10 +85,10 @@ class Mimblewimblecoin extends Bip39Currency { } @override - int get defaultSeedPhraseLength => 24; + int get defaultSeedPhraseLength => 12; @override - int get fractionDigits => 8; + int get fractionDigits => 9; @override bool get hasBuySupport => false; @@ -100,13 +97,13 @@ class Mimblewimblecoin extends Bip39Currency { bool get hasMnemonicPassphraseSupport => false; @override - List get possibleMnemonicLengths => [defaultSeedPhraseLength, 12]; + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 24]; @override AddressType get defaultAddressType => AddressType.mimbleWimble; @override - BigInt get satsPerCoin => BigInt.from(100000000); + BigInt get satsPerCoin => BigInt.from(1000000000); @override int get targetBlockTimeSeconds => 60; diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 83d038e9dd..5ce8a6b0ea 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -38,6 +38,7 @@ import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; + // // refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart // @@ -48,7 +49,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { final syncMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; - + bool _logsInitialized = false; + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = @@ -147,7 +149,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { final uri = Uri.parse(nodeAddress).replace(port: port); final String nodeApiAddress = uri.toString(); - final walletDir = await _currentWalletDirPath(); final Map config = {}; @@ -174,7 +175,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); try { final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees( wallet: wallet!, amount: satoshiAmount, @@ -304,17 +304,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ) async { Address? address = await getCurrentReceivingAddress(); - if (address != null) { - final splitted = address.value.split('@'); - //Check if the address is the same as the current mwcmqs domain - //Since we're only using one epicbpox now this doesn't apply but will be - // useful in the future - final mwcmqsConfig = await getMwcMqsConfig(); - if (splitted[1] != mwcmqsConfig.host) { - //Update the address - address = await thisWalletAddress(index, mwcmqsConfig); - } - } else { + if (address == null) { final mwcmqsConfig = await getMwcMqsConfig(); address = await thisWalletAddress(index, mwcmqsConfig); } @@ -478,6 +468,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { + if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read(key: "${walletId}_wallet"); @@ -490,7 +481,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String password = generatePassword(); final String stringConfig = await _getConfig(); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); - + //if (!_logsInitialized) { + // await mimblewimblecoin.Libmwc.initLogs(config: stringConfig); + // _logsInitialized = true; // Set flag to true after initializing + // } await secureStorageInterface.write( key: '${walletId}_config', value: stringConfig, @@ -522,9 +516,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: encodedWallet, ); - //Store MwcMqs address info - // await _generateAndStoreReceivingAddressForIndex(0); + await _generateAndStoreReceivingAddressForIndex(0); // subtract a couple days to ensure we have a buffer for SWB final bufferedCreateHeight = _calculateRestoreHeightFrom( @@ -547,12 +540,11 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } else { try { - Logging.instance.log( - "initializeExisting() ${cryptoCurrency.prettyName} wallet", - level: LogLevel.Info, - ); - final config = await _getRealConfig(); + //if (!_logsInitialized) { + // await mimblewimblecoin.Libmwc.initLogs(config: config); + // _logsInitialized = true; // Set flag to true after initializing + //} final password = await secureStorageInterface.read(key: '${walletId}_password'); @@ -590,21 +582,22 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String receiverAddress = txData.recipients!.first.address; - if (!receiverAddress.startsWith("http://") || - !receiverAddress.startsWith("https://")) { - final bool isMwcmqsConnected = await _testMwcmqsServer( - mwcmqsConfig, - ); - if (!isMwcmqsConnected) { - throw Exception( - "Failed to send TX : Unable to reach mimblewimblecoin server"); - } - } + //if (!receiverAddress.startsWith("http://") || + // !receiverAddress.startsWith("https://")) { + // final bool isMwcmqsConnected = await _testMwcmqsServer( + // mwcmqsConfig, + // ); + // if (!isMwcmqsConnected) { + // throw Exception( + // "Failed to send TX : Unable to reach mimblewimblecoin server"); + // } + //} ({String commitId, String slateId}) transaction; if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://")) { + receiverAddress.startsWith("https://") || + receiverAddress.startsWith("mwcmqcs://")) { transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, @@ -753,10 +746,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: walletOpen, ); - - // await _generateAndStoreReceivingAddressForIndex( - // mimblewimblecoinData.receivingIndex, - // ); + + await _generateAndStoreReceivingAddressForIndex( + mimblewimblecoinData.receivingIndex, + ); } }); @@ -799,7 +792,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // this will always be zero???? final int curAdd = await _getCurrentIndex(); - // await _generateAndStoreReceivingAddressForIndex(curAdd); + await _generateAndStoreReceivingAddressForIndex(curAdd); await _startScans(); @@ -1087,23 +1080,19 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future pingCheck() async { - return true; - //try { - // final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); -// - // // force unwrap optional as we want connection test to fail if wallet - // // wasn't initialized or mwcmqs node was set to null - // return await testMwcNodeConnection( - // NodeFormData() - // ..host = node!.host - // ..useSSL = node.useSSL - // ..port = node.port, - // ) != - // null; - //} catch (e, s) { - // Logging.instance.log("$e\n$s", level: LogLevel.Info); - // return false; - //} + try { + final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); + return await testMwcNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + return false; + } } @override From 0accfffbab5a1eede900096329a196110a288a9d Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 17 Nov 2024 14:59:54 +0400 Subject: [PATCH 05/91] last fixes mwc and clean up --- crypto_plugins/flutter_libmwc | 2 +- lib/models/mwcmqs_config_model.dart | 35 ++----------------- .../type_adaptors/mwcmqs_config_model.g.dart | 10 ++---- .../restore_options_view.dart | 12 +++---- .../restore_view_only_wallet_view.dart | 5 +++ .../restore_wallet_view.dart | 2 +- .../add_edit_node_view.dart | 8 ----- .../wallet_settings_view.dart | 1 - .../wallet/impl/mimblewimblecoin_wallet.dart | 19 +++++----- 9 files changed, 27 insertions(+), 67 deletions(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index fe78fd4528..2008080fd8 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit fe78fd45284f99f26a40bba15e8d1a53c718be1a +Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 diff --git a/lib/models/mwcmqs_config_model.dart b/lib/models/mwcmqs_config_model.dart index 6970905106..067d1a8ee5 100644 --- a/lib/models/mwcmqs_config_model.dart +++ b/lib/models/mwcmqs_config_model.dart @@ -22,30 +22,19 @@ class MwcMqsConfigModel { final String host; @HiveField(2) final int? port; - @HiveField(3) - final bool? protocolInsecure; - @HiveField(4) - final int? addressIndex; MwcMqsConfigModel({ required this.host, - this.port, - this.protocolInsecure, - this.addressIndex, + this.port }); MwcMqsConfigModel copyWith({ int? port, bool? protocolInsecure, - int? addressIndex, - String? id, - String? name, }) { return MwcMqsConfigModel( host: host, port: this.port ?? 443, - protocolInsecure: this.protocolInsecure ?? false, - addressIndex: this.addressIndex ?? 0, ); } @@ -53,8 +42,6 @@ class MwcMqsConfigModel { final Map map = {}; map['mwcmqs_domain'] = host; map['mwcmqs_port'] = port; - map['mwcmqs_protocol_insecure'] = protocolInsecure; - map['mwcmqs_address_index'] = addressIndex; return map; } @@ -62,8 +49,6 @@ class MwcMqsConfigModel { return { 'mwcmqs_domain': host, 'mwcmqs_port': port, - 'mwcmqs_protocol_insecure': protocolInsecure, - 'mwcmqs_address_index': addressIndex, }; } @@ -83,23 +68,11 @@ class MwcMqsConfigModel { if (oldPort != "empty") { _mwcmqs['mwcmqs_port'] = _mwcmqs['port']; } - final oldProtocolInsecure = _mwcmqs["protocol_insecure"] ?? "empty"; - if (oldProtocolInsecure != "empty") { - _mwcmqs['mwcmqs_protocol_insecure'] = _mwcmqs['protocol_insecure']; - } - final oldAddressIndex = _mwcmqs["address_index"] ?? "empty"; - if (oldAddressIndex != "empty") { - _mwcmqs['mwcmqs_address_index'] = _mwcmqs['address_index']; - } - _mwcmqs['mwcmqs_protocol_insecure'] ??= false; - _mwcmqs['mwcmqs_address_index'] ??= 0; return MwcMqsConfigModel( host: _mwcmqs['mwcmqs_domain'] as String, - port: _mwcmqs['mwcmqs_port'] as int, - protocolInsecure: _mwcmqs['mwcmqs_protocol_insecure'] as bool, - addressIndex: _mwcmqs['mwcmqs_address_index'] as int, + port: _mwcmqs['mwcmqs_port'] as int ); } @@ -110,9 +83,7 @@ class MwcMqsConfigModel { }) { return MwcMqsConfigModel( host: server.host, - port: server.port ?? 443, - protocolInsecure: protocolInsecure ?? false, - addressIndex: addressIndex ?? 0, + port: server.port ?? 443 ); } } diff --git a/lib/models/type_adaptors/mwcmqs_config_model.g.dart b/lib/models/type_adaptors/mwcmqs_config_model.g.dart index e64fc54082..f66fbdb56b 100644 --- a/lib/models/type_adaptors/mwcmqs_config_model.g.dart +++ b/lib/models/type_adaptors/mwcmqs_config_model.g.dart @@ -19,23 +19,17 @@ class MwcMqsConfigModelAdapter extends TypeAdapter { return MwcMqsConfigModel( host: fields[1] as String, port: fields[2] as int?, - protocolInsecure: fields[3] as bool?, - addressIndex: fields[4] as int?, ); } @override void write(BinaryWriter writer, MwcMqsConfigModel obj) { writer - ..writeByte(4) + ..writeByte(2) ..writeByte(1) ..write(obj.host) ..writeByte(2) - ..write(obj.port) - ..writeByte(3) - ..write(obj.protocolInsecure) - ..writeByte(4) - ..write(obj.addressIndex); + ..write(obj.port); } @override 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 9d2ac8c1d0..f95440e8d7 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 @@ -361,7 +361,7 @@ class _SeedRestoreOptionState extends ConsumerState { return Column( children: [ - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) Text( "Choose start date", style: Util.isDesktop @@ -372,20 +372,20 @@ class _SeedRestoreOptionState extends ConsumerState { : STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 16 : 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RestoreFromDatePicker( onTap: widget.dateChooserFunction, controller: widget.dateController, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) const SizedBox( height: 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RoundedWhiteContainer( child: Center( child: Text( @@ -402,7 +402,7 @@ class _SeedRestoreOptionState extends ConsumerState { ), ), ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 24 : 16, ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart index 5f2bd08b06..8878b305c2 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart @@ -21,6 +21,7 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_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/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; @@ -196,6 +197,10 @@ class _RestoreViewOnlyWalletViewState case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); 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 e56694fed0..9c9f4ec7e0 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 @@ -270,7 +270,7 @@ class _RestoreWalletViewState extends ConsumerState { }, ); } else if (widget.coin is Mimblewimblecoin) { - final int secondsSinceEpoch = widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + final int secondsSinceEpoch = widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; const int mimblewimblecoinFirstBlock = 1573462801; const double overestimateSecondsPerBlock = 61; final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; 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 e9b15cc307..7b5f4f25ef 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 @@ -869,14 +869,6 @@ class _NodeFormState extends ConsumerState { } else { enableSSLCheckbox = true; } - } else if (widget.coin is CwBasedInterface) { - if (newValue.startsWith("https://")) { - _useSSL = true; - } else if (newValue.startsWith("http://")) { - _useSSL = false; - } else { - _useSSL = true; - } } _updateState(); setState(() {}); 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 e8a485be87..7fcfa9be9b 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 @@ -39,7 +39,6 @@ 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/impl/mimblewimblecoin_wallet.dart'; -import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 5ce8a6b0ea..31339e2fcb 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -39,9 +39,6 @@ import '../intermediate/bip39_wallet.dart'; import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; -// -// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart -// class MimblewimblecoinWallet extends Bip39Wallet { MimblewimblecoinWallet(CryptoCurrencyNetwork network) : super(Mimblewimblecoin(network)); @@ -73,9 +70,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future updateMwcmqsConfig(String host, int port) async { final String stringConfig = jsonEncode({ "mwcmqs_domain": host, - "mwcmqs_port": port, - "mwcmqs_protocol_unsecure": false, - "mwcmqs_address_index": 0, + "mwcmqs_port": port }); await secureStorageInterface.write( key: '${walletId}_mwcmqsConfig', @@ -434,7 +429,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { return config!; } - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index int _calculateRestoreHeightFrom({required DateTime date}) { final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; const int mimblewimblecoinFirstBlock = 1565370278; @@ -596,8 +590,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ({String commitId, String slateId}) transaction; if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://") || - receiverAddress.startsWith("mwcmqcs://")) { + receiverAddress.startsWith("https://")) { transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, @@ -606,7 +599,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, ); - } else { + } else if (receiverAddress.startsWith("mwcmqs://")) { transaction = await mimblewimblecoin.Libmwc.createTransaction( wallet: wallet!, amount: txData.recipients!.first.amount.raw.toInt(), @@ -616,6 +609,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, ); + + } else { + throw Exception( + "Unsupported address format: $receiverAddress. Please use a valid address.", + ); + } final Map txAddressInfo = {}; From 356c640d82a069a4a157e226157c19cf2289977d Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 26 Oct 2024 16:31:05 +0400 Subject: [PATCH 06/91] init mwc --- .gitmodules | 3 + README.md | 1 + crypto_plugins/flutter_libmwc | 1 + lib/db/db_version_migration.dart | 75 +- lib/db/migrate_wallets_to_isar.dart | 16 + .../blockchain_data/v2/transaction_v2.dart | 37 + lib/models/isar/stack_theme.dart | 9 + .../restore_wallet_view.dart | 27 +- .../send_view/confirm_transaction_view.dart | 24 +- lib/pages/send_view/send_view.dart | 44 +- .../global_settings_view/about_view.dart | 48 + .../advanced_views/debug_view.dart | 6 + .../add_edit_node_view.dart | 40 +- .../helpers/restore_create_backup.dart | 7 +- .../wallet_network_settings_view.dart | 34 +- .../wallet_settings_view.dart | 103 ++ .../all_transactions_view.dart | 17 + .../tx_v2/transaction_v2_details_view.dart | 131 +- .../wallet_view/sub_widgets/desktop_send.dart | 64 +- .../delete_password_warning_view.dart | 12 + .../settings_menu/desktop_about_view.dart | 171 ++- lib/services/price.dart | 1 + lib/services/wallets.dart | 10 + lib/themes/coin_icon_provider.dart | 2 + lib/utilities/address_utils.dart | 25 + lib/utilities/amount/amount_unit.dart | 1 + lib/utilities/assets.dart | 1 + lib/utilities/default_mwcmqs.dart | 53 + lib/utilities/git_status.dart | 74 +- .../coins/mimblewimblecoin.dart | 128 ++ .../crypto_currency/crypto_currency.dart | 3 +- lib/wallets/isar/models/wallet_info.dart | 1 + .../wallet/impl/mimblewimblecoin_wallet.dart | 1197 +++++++++++++++++ ...imblewimblecoin_wallet_info_extension.dart | 111 ++ lib/wallets/wallet/wallet.dart | 4 + .../tor_has_been_add_dialog.dart | 2 +- lib/widgets/transaction_card.dart | 19 + linux/flutter/generated_plugin_registrant.cc | 5 +- linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 7 + scripts/app_config/configure_stack_wallet.sh | 1 + .../app_config/templates/linux/CMakeLists.txt | 5 +- scripts/app_config/templates/pubspec.template | 3 + .../templates/windows/CMakeLists.txt | 3 + scripts/ios/build_all.sh | 2 + scripts/ios/build_all_campfire.sh | 2 + scripts/ios/build_all_duo.sh | 2 + scripts/linux/build_all.sh | 2 + scripts/linux/build_all_campfire.sh | 2 + scripts/linux/build_all_duo.sh | 2 + scripts/macos/build_all.sh | 2 + scripts/macos/build_all_campfire.sh | 2 + scripts/macos/build_all_duo.sh | 2 + scripts/rust_version.sh | 9 + scripts/windows/build_all.sh | 2 + scripts/windows/build_all_campfire.sh | 2 + scripts/windows/build_all_duo.sh | 2 + scripts/windows/deps.sh | 8 +- test/price_test.dart | 2 + test/sample_data/theme_json.dart | 4 + test/sample_data/theme_json_v2.dart | 4 + test/services/node_service_test.dart | 13 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 65 files changed, 2466 insertions(+), 131 deletions(-) create mode 160000 crypto_plugins/flutter_libmwc create mode 100644 lib/utilities/default_mwcmqs.dart create mode 100644 lib/wallets/crypto_currency/coins/mimblewimblecoin.dart create mode 100644 lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart create mode 100644 lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart diff --git a/.gitmodules b/.gitmodules index 2186826df3..f1b4eb8746 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "crypto_plugins/frostdart"] path = crypto_plugins/frostdart url = https://github.com/cypherstack/frostdart +[submodule "crypto_plugins/flutter_libmwc"] + path = crypto_plugins/flutter_libmwc + url = https://github.com/vekamo/flutter_libmwc diff --git a/README.md b/README.md index 4c18c3181a..286bb6f544 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Highlights include: - [Bitcoin Cash](https://bch.info/en/) - [Dogecoin](https://dogecoin.com/) - [Epic Cash](https://linktr.ee/epiccash) + - [MimbleWimbleCoin](https://mwc.mw) - [Ethereum](https://ethereum.org/en/) - [Firo](https://firo.org/) - [Litecoin](https://litecoin.org/) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc new file mode 160000 index 0000000000..2008080fd8 --- /dev/null +++ b/crypto_plugins/flutter_libmwc @@ -0,0 +1 @@ +Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index fcf632669c..81aec9b255 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -532,6 +532,71 @@ class DbVersionMigrator with WalletDB { await MainDB.instance.addNewTransactionData(transactionsData, walletId); } + // we need to manually migrate mimblewimblecoin transactions as they are not + // stored on the mimblewimblecoin blockchain + final mimblewimblecoin = Mimblewimblecoin(CryptoCurrencyNetwork.main); + if (info.coinIdentifier == mimblewimblecoin.identifier) { + final txnData = walletBox.get("latest_tx_model") as TransactionData?; + + // we ever only used index 0 in the past + const rcvIndex = 0; + + final List> + transactionsData = []; + if (txnData != null) { + final txns = txnData.getAllTransactions(); + + for (final tx in txns.values) { + final bool isIncoming = tx.txType == "Received"; + + final iTx = isar_models.Transaction( + walletId: walletId, + txid: tx.txid, + timestamp: tx.timestamp, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: tx.amount, + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: mimblewimblecoin.fractionDigits, + ).toJsonString(), + fee: tx.fees, + height: tx.height, + isCancelled: tx.isCancelled, + isLelantus: false, + slateId: tx.slateId, + otherData: tx.otherData, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: tx.numberOfMessages, + ); + + if (tx.address.isEmpty) { + transactionsData.add(Tuple2(iTx, null)); + } else { + final address = isar_models.Address( + walletId: walletId, + value: tx.address, + publicKey: [], + derivationIndex: isIncoming ? rcvIndex : -1, + derivationPath: null, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, + ); + transactionsData.add(Tuple2(iTx, address)); + } + } + } + await MainDB.instance.addNewTransactionData(transactionsData, walletId); + } + // delete data from hive await walletBox.delete(receiveAddressesPrefix); await walletBox.delete("${receiveAddressesPrefix}P2PKH"); @@ -553,9 +618,13 @@ class DbVersionMigrator with WalletDB { ); } - // doing this for epic cash will delete transaction history as it is not - // stored on the epic cash blockchain - if (info.coinIdentifier != epic.identifier) { + // doing this for epiccash/mimblewimblecoin will delete transaction history as it is not + // stored on the epiccash/mimblewimblecoin blockchain + final excludedIdentifiers = [ + epic.identifier, + mimblewimblecoin.identifier + ]; + if ((!excludedIdentifiers.contains(info.coinIdentifier))) { // set flag to initiate full rescan on opening wallet await DB.instance.put( boxName: DB.boxNameDBInfo, diff --git a/lib/db/migrate_wallets_to_isar.dart b/lib/db/migrate_wallets_to_isar.dart index cd54a4063f..d0f75a2e37 100644 --- a/lib/db/migrate_wallets_to_isar.dart +++ b/lib/db/migrate_wallets_to_isar.dart @@ -11,6 +11,7 @@ import '../wallets/isar/models/token_wallet_info.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/isar/models/wallet_info_meta.dart'; import '../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import 'hive/db.dart'; import 'isar/main_db.dart'; @@ -146,6 +147,21 @@ Future migrateWalletsToIsar({ otherData[WalletInfoKeys.epiccashData] = jsonEncode( epicWalletInfo.toMap(), ); + } else if (old.coinIdentifier == + Mimblewimblecoin(CryptoCurrencyNetwork.main)) { + final mimblewimblecoinWalletInfo = + ExtraMimblewimblecoinWalletInfo.fromMap({ + "receivingIndex": walletBox.get("receivingIndex") as int? ?? 0, + "changeIndex": walletBox.get("changeIndex") as int? ?? 0, + "slatesToAddresses": walletBox.get("slate_to_address") as Map? ?? {}, + "slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {}, + "lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0, + "restoreHeight": walletBox.get("restoreHeight") as int? ?? 0, + "creationHeight": walletBox.get("creationHeight") as int? ?? 0, + }); + otherData[WalletInfoKeys.mimblewimblecoinData] = jsonEncode( + mimblewimblecoinWalletInfo.toMap(), + ); } else if (old.coinIdentifier == Firo(CryptoCurrencyNetwork.main).identifier || old.coinIdentifier == Firo(CryptoCurrencyNetwork.test).identifier) { diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 3d89033916..f05cce21e0 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -95,6 +95,8 @@ class TransactionV2 { bool get isEpiccashTransaction => _getFromOtherData(key: TxV2OdKeys.isEpiccashTransaction) == true; + bool get isMimblewimblecoinTransaction => + _getFromOtherData(key: TxV2OdKeys.isMimblewimblecoinTransaction) == true; int? get numberOfMessages => _getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?; String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?; @@ -287,6 +289,40 @@ class TransactionV2 { } } + if (isMimblewimblecoinTransaction) { + if (slateId == null) { + return "Restored Funds"; + } + + if (isCancelled) { + return "Cancelled"; + } else if (type == TransactionType.incoming) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Received"; + } else { + if (numberOfMessages == 1) { + return "Receiving (waiting for sender)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no) + } else { + return "Receiving ${prettyConfirms()}"; + } + } + } else if (type == TransactionType.outgoing) { + if (isConfirmed(currentChainHeight, minConfirms)) { + return "Sent (confirmed)"; + } else { + if (numberOfMessages == 1) { + return "Sending (waiting for receiver)"; + } else if ((numberOfMessages ?? 0) > 1) { + return "Sending (waiting for confirmations)"; + } else { + return "Sending ${prettyConfirms()}"; + } + } + } + } + if (type == TransactionType.incoming) { // if (_transaction.isMinting) { // return "Minting"; @@ -347,6 +383,7 @@ abstract final class TxV2OdKeys { static const size = "size"; static const vSize = "vSize"; static const isEpiccashTransaction = "isEpiccashTransaction"; + static const isMimblewimblecoinTransaction = "isMimblewimblecoinTransaction"; static const numberOfMessages = "numberOfMessages"; static const slateId = "slateId"; static const onChainNote = "onChainNote"; diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index ce5002a1d5..4f831732f0 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -1939,6 +1939,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; + late final String mimblewimblecoin; late final String ethereum; late final String firo; late final String monero; @@ -1949,6 +1950,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; + late final String mimblewimblecoinImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -1960,6 +1962,7 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; + late final String mimblewimblecoinImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2006,6 +2009,8 @@ class ThemeAssets implements IThemeAssets { ..bitcoincash = "$themeId/assets/${json["bitcoincash"] as String}" ..dogecoin = "$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$themeId/assets/${json["epicCash"] as String}" + ..mimblewimblecoin = + "$themeId/assets/${json["mimblewimblecoin"] as String}" ..ethereum = "$themeId/assets/${json["ethereum"] as String}" ..firo = "$themeId/assets/${json["firo"] as String}" ..monero = "$themeId/assets/${json["monero"] as String}" @@ -2017,6 +2022,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["bitcoincash_image"] as String}" ..dogecoinImage = "$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$themeId/assets/${json["epicCash_image"] as String}" + ..mimblewimblecoinImage = + "$themeId/assets/${json["mimblewimblecoin_image"] as String}" ..ethereumImage = "$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = "$themeId/assets/${json["firo_image"] as String}" ..litecoinImage = "$themeId/assets/${json["litecoin_image"] as String}" @@ -2032,6 +2039,8 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$themeId/assets/${json["epicCash_image_secondary"] as String}" + ..mimblewimblecoinImageSecondary = + "$themeId/assets/${json["mimblewimblecoin_image_secondary"] as String}" ..ethereumImageSecondary = "$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = 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 58e51b34e7..44a0c2157d 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 @@ -46,10 +46,12 @@ import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/crypto_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/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import '../../../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import '../../../wallets/wallet/wallet.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/desktop/desktop_app_bar.dart'; @@ -254,10 +256,27 @@ class _RestoreWalletViewState extends ConsumerState { height = 0; } + // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index + if (widget.coin is Mimblewimblecoin) { + final int secondsSinceEpoch = + widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = + chosenSeconds ~/ overestimateSecondsPerBlock; + //todo: check if print needed + // debugPrint( + // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); + height = approximateHeight; + if (height < 0) { + height = 0; + } + otherDataJsonString = jsonEncode( { - WalletInfoKeys.epiccashData: jsonEncode( - ExtraEpiccashWalletInfo( + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( receivingIndex: 0, changeIndex: 0, slatesToAddresses: {}, @@ -355,6 +374,10 @@ class _RestoreWalletViewState extends ConsumerState { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 5090645e30..ce08150f35 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -14,6 +14,7 @@ import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/lib.dart'; +import 'package:flutter_libmwc/lib.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -33,6 +34,7 @@ import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; @@ -159,7 +161,7 @@ class _ConfirmTransactionViewState break; } } else { - if (coin is Epiccash) { + if (coin is Epiccash || coin is Mimblewimblecoin) { txDataFuture = wallet.confirmSend( txData: widget.txData.copyWith( noteOnChain: onChainNoteController.text, @@ -578,11 +580,13 @@ class _ConfirmTransactionViewState ], ), ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) const SizedBox( height: 12, ), - if (coin is Epiccash && widget.txData.noteOnChain!.isNotEmpty) + if ((coin is Epiccash || coin is Mimblewimblecoin) && + widget.txData.noteOnChain!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -611,7 +615,9 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - (coin is Epiccash) ? "Local Note" : "Note", + (coin is Epiccash || coin is Mimblewimblecoin) + ? "Local Note" + : "Note", style: STextStyles.smallMed12(context), ), const SizedBox( @@ -913,17 +919,17 @@ class _ConfirmTransactionViewState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -963,12 +969,12 @@ class _ConfirmTransactionViewState ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), SelectableText( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 1dd9b68efa..f101130c2d 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -311,7 +311,9 @@ class _SendViewState extends ConsumerState { _cryptoAmountChangedFeeUpdateTimer?.cancel(); _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_baseFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_baseFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( amount == null @@ -332,7 +334,9 @@ class _SendViewState extends ConsumerState { void _baseAmountChanged() { _baseAmountChangedFeeUpdateTimer?.cancel(); _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { - if (coin is! Epiccash && !_cryptoFocus.hasFocus) { + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + !_cryptoFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( ref.read(pSendAmount) == null @@ -1116,6 +1120,21 @@ class _SendViewState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text.trim(); + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = AddressUtils().formatAddress(_address!); + } + }); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -1447,6 +1466,14 @@ class _SendViewState extends ConsumerState { content, ); } + if (coin + is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils() + .formatAddress( + content, + ); + } sendToController.text = content.trim(); _address = content.trim(); @@ -2030,17 +2057,17 @@ class _SendViewState extends ConsumerState { const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) Text( "On chain Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -2084,12 +2111,12 @@ class _SendViewState extends ConsumerState { ), ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) const SizedBox( height: 12, ), Text( - (coin is Epiccash) + (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" : "Note (optional)", style: STextStyles.smallMed12(context), @@ -2141,6 +2168,7 @@ class _SendViewState extends ConsumerState { height: 12, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Text( @@ -2149,12 +2177,14 @@ class _SendViewState extends ConsumerState { textAlign: TextAlign.left, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) const SizedBox( height: 8, ), if (coin is! Epiccash && + coin is! Mimblewimblecoin && coin is! NanoCurrency && coin is! Tezos) Stack( diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index b72c3222cc..d29aae7dc3 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -268,6 +268,54 @@ class AboutView extends ConsumerWidget { ); }, ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + const SizedBox( + height: 12, + ), + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus.getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + stateOfCommit = snapshot.data!; + } + + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 4, + ), + SelectableText( + GitStatus.mimblewimblecoinCommit, + style: GitStatus.styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ), + ); + }, + ), if (AppConfig.coins.whereType().isNotEmpty) const SizedBox( height: 12, diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index 92e5cc256d..8315302df7 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; // import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libmwc/git_versions.dart' as MIMBLEWIMBLECOIN_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; @@ -315,6 +316,9 @@ class _DebugViewState extends ConsumerState { EPIC_VERSIONS.getPluginVersion(); // final String moneroCommit = // MONERO_VERSIONS.getPluginVersion(); + final String mimblewimblecoinCommit = + MIMBLEWIMBLECOIN_VERSIONS + .getPluginVersion(); final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfo = @@ -348,6 +352,8 @@ class _DebugViewState extends ConsumerState { "firoCommit": firoCommit, "epicCashCommit": epicCashCommit, // "moneroCommit": moneroCommit, + "mimblewimblecoinCommit": + mimblewimblecoinCommit, "deviceInfoMap": deviceInfoMap, "errorLogs": errorLogs, }; 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 fb18a4716d..e7f0824e40 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 @@ -88,6 +88,17 @@ class _AddEditNodeViewState extends ConsumerState { ref.read(nodeFormDataProvider).host = data.host; ref.read(nodeFormDataProvider).port = data.port; ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; + } else if (coin is CryptonoteCurrency) { + ref.read(nodeFormDataProvider).host = data.host; + } + if (coin is Mimblewimblecoin) { + ref.read(nodeFormDataProvider).host = data.host; + ref.read(nodeFormDataProvider).port = data.port; + ref.read(nodeFormDataProvider).useSSL = data.useSSL; } else if (coin is CryptonoteCurrency) { ref.read(nodeFormDataProvider).host = data.host; } @@ -917,6 +928,8 @@ class _NodeFormState extends ConsumerState { if (widget.coin is Epiccash) { enableSSLCheckbox = !node.host.startsWith("http"); + } else if (widget.coin is Mimblewimblecoin) { + enableSSLCheckbox = !node.host.startsWith("http"); } else { enableSSLCheckbox = true; } @@ -1058,6 +1071,25 @@ class _NodeFormState extends ConsumerState { _useSSL = true; } } + if (widget.coin is Mimblewimblecoin) { + if (newValue.startsWith("https://")) { + _useSSL = true; + enableSSLCheckbox = false; + } else if (newValue.startsWith("http://")) { + _useSSL = false; + enableSSLCheckbox = false; + } else { + enableSSLCheckbox = true; + } + } else if (widget.coin is CwBasedInterface) { + if (newValue.startsWith("https://")) { + _useSSL = true; + } else if (newValue.startsWith("http://")) { + _useSSL = false; + } else { + _useSSL = true; + } + } _updateState(); setState(() {}); }, @@ -1317,11 +1349,15 @@ class _NodeFormState extends ConsumerState { ), ], ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (widget.coin is! CryptonoteCurrency && widget.coin is! Epiccash) + if (widget.coin is! CryptonoteCurrency && + widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin) Row( children: [ GestureDetector( 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 0ffa6bcf91..c83d927e80 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 @@ -53,6 +53,7 @@ import '../../../../../wallets/isar/models/frost_wallet_info.dart'; import '../../../../../wallets/isar/models/wallet_info.dart'; import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; import '../../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../../wallets/wallet/impl/monero_wallet.dart'; import '../../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; @@ -507,6 +508,10 @@ abstract class SWB { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); @@ -522,7 +527,7 @@ abstract class SWB { int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0; if (restoreHeight <= 0) { - if (wallet is EpiccashWallet || wallet is LibMoneroWallet) { + if (wallet is EpiccashWallet || wallet is MimblewimblecoinWallet || wallet is LibMoneroWallet) { restoreHeight = 0; } else { restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0; 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 1be577f1d4..b88e9ce92f 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 @@ -33,10 +33,12 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../../wallets/crypto_currency/coins/monero.dart'; import '../../../../wallets/crypto_currency/coins/wownero.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/monero_wallet.dart'; import '../../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../../widgets/animated_text.dart'; @@ -263,7 +265,10 @@ class _WalletNetworkSettingsViewState final coin = ref.read(pWalletCoin(widget.walletId)); - if (coin is Monero || coin is Wownero || coin is Epiccash) { + if (coin is Monero || + coin is Wownero || + coin is Epiccash || + coin is Mimblewimblecoin) { _blocksRemainingSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { @@ -343,6 +348,14 @@ class _WalletNetworkSettingsViewState if (_percent < highestPercent) { _percent = highestPercent.clamp(0.0, 1.0); } + } else if (coin is Mimblewimblecoin) { + final double highestPercent = (ref + .watch(pWallets) + .getWallet(widget.walletId) as MimblewimblecoinWallet) + .highestPercent; + if (_percent < highestPercent) { + _percent = highestPercent.clamp(0.0, 1.0); + } } return ConditionalParent( @@ -363,7 +376,11 @@ class _WalletNetworkSettingsViewState style: STextStyles.navBarTitle(context), ), actions: [ - if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin || + ref.watch(pWalletCoin(widget.walletId)) + is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( top: 10, @@ -627,6 +644,7 @@ class _WalletNetworkSettingsViewState ), if (coin is Monero || coin is Wownero || + coin is Mimblewimblecoin || coin is Epiccash) Text( " (Blocks to go: ${_blocksRemaining == -1 ? "?" : _blocksRemaining})", @@ -950,11 +968,15 @@ class _WalletNetworkSettingsViewState coin: ref.watch(pWalletCoin(widget.walletId)), popBackToRoute: WalletNetworkSettingsView.routeName, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) const SizedBox( height: 32, ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) Padding( padding: const EdgeInsets.only( bottom: 12, @@ -970,7 +992,9 @@ class _WalletNetworkSettingsViewState ], ), ), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) + if (isDesktop && + ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && + ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) RoundedWhiteContainer( borderColor: isDesktop ? Theme.of(context).extension()!.background 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 8e889ed7c5..2a217dcb92 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -18,6 +18,7 @@ import 'package:tuple/tuple.dart'; import '../../../db/hive/db.dart'; import '../../../db/sqlite/firo_cache.dart'; import '../../../models/epicbox_config_model.dart'; +import '../../../models/mwcmqs_config_model.dart'; import '../../../models/keys/key_data_interface.dart'; import '../../../models/keys/view_only_wallet_data.dart'; import '../../../notifications/show_flush_bar.dart'; @@ -38,6 +39,8 @@ 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/impl/mimblewimblecoin_wallet.dart'; +import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; @@ -719,3 +722,103 @@ class _EpiBoxInfoFormState extends ConsumerState { ); } } + +class MwcMqsInfoForm extends ConsumerStatefulWidget { + const MwcMqsInfoForm({ + super.key, + required this.walletId, + }); + + final String walletId; + + @override + ConsumerState createState() => _MwcMqsInfoFormState(); +} + +class _MwcmqsInfoFormState extends ConsumerState { + final hostController = TextEditingController(); + final portController = TextEditingController(); + + late MimblewimblecoinWallet wallet; + + @override + void initState() { + wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; + + wallet.getMwcMqsConfig().then((MwcMqsConfigModel mwcmqsConfig) { + hostController.text = mwcmqsConfig.host; + portController.text = "${mwcmqsConfig.port ?? 443}"; + }); + super.initState(); + } + + @override + void dispose() { + hostController.dispose(); + portController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: hostController, + decoration: const InputDecoration(hintText: "Host"), + ), + 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, + ), + TextButton( + onPressed: () async { + try { + await wallet.updateMwcmqsConfig( + hostController.text, + int.parse(portController.text), + ); + if (mounted) { + await showFloatingFlushBar( + context: context, + message: "Mwcmqs info saved!", + type: FlushBarType.success, + ); + } + unawaited(wallet.refresh()); + } catch (e) { + await showFloatingFlushBar( + context: context, + message: "Failed to save mwcmqs info: $e", + type: FlushBarType.warning, + ); + } + }, + child: Text( + "Save", + style: STextStyles.button(context).copyWith( + color: + Theme.of(context).extension()!.accentColorDark, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 5afd82597f..385bb8a2e0 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -873,6 +873,10 @@ class _DesktopTransactionCardRowState return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + if (_transaction.subType == TransactionSubType.mint) { if (_transaction.isConfirmed(height, minConfirms)) { return "Anonymized"; @@ -967,6 +971,19 @@ class _DesktopTransactionCardRowState ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } if (Util.isDesktop) { await showDialog( context: context, 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 7081f59290..53c54ae22a 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,6 +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/intermediate/lib_monero_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -251,6 +252,33 @@ class _TransactionV2DetailsViewState ), ) .toList(); + } else if (_transaction.isMimblewimblecoinTransaction) { + switch (_transaction.type) { + case TransactionType.outgoing: + case TransactionType.unknown: + amount = _transaction.getAmountSentFromThisWallet( + fractionDigits: fractionDigits, + ); + break; + + case TransactionType.incoming: + case TransactionType.sentToSelf: + amount = _transaction.getAmountReceivedInThisWallet( + fractionDigits: fractionDigits, + ); + break; + } + data = _transaction.outputs + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ) + ), + ) + .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( fractionDigits: fractionDigits, @@ -1069,13 +1097,13 @@ class _TransactionV2DetailsViewState ], ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -1147,7 +1175,8 @@ class _TransactionV2DetailsViewState MainAxisAlignment.spaceBetween, children: [ Text( - (coin is Epiccash) + (coin is Epiccash || + coin is Mimblewimblecoin) ? "Local Note" : "Note ", style: isDesktop @@ -1218,7 +1247,9 @@ class _TransactionV2DetailsViewState .watch( pTransactionNote( ( - txid: (coin is Epiccash) + txid: (coin is Epiccash || + coin + is Mimblewimblecoin) ? _transaction.slateId .toString() : _transaction.txid, @@ -1495,8 +1526,9 @@ class _TransactionV2DetailsViewState ? "${_transaction.height!}" : "Pending"; confirmations = confirms.toString(); - } else if (widget.coin is Epiccash && - _transaction.slateId == null) { + } else if (widget.coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.slateId == null) { confirmations = "Unknown"; height = "Unknown"; } else { @@ -1504,7 +1536,9 @@ class _TransactionV2DetailsViewState currentHeight, minConfirms, coin.minCoinbaseConfirms); - if (widget.coin is! Epiccash && confirmed) { + if (widget.coin is! Epiccash && + widget.coin is! Mimblewimblecoin && + confirmed) { height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { @@ -1787,11 +1821,13 @@ class _TransactionV2DetailsViewState context, ), ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) const SizedBox( height: 8, ), - if (coin is! Epiccash) + if (coin is! Epiccash && + coin is! Mimblewimblecoin) CustomTextButton( text: "Open in block explorer", onTap: () async { @@ -1948,13 +1984,13 @@ class _TransactionV2DetailsViewState // ], // ), // ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox( height: 12, ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -2038,9 +2074,10 @@ class _TransactionV2DetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) + floatingActionButton: (coin is Epiccash || + coin is Mimblewimblecoin && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) ? ConditionalParent( condition: isDesktop, builder: (child) => Padding( @@ -2124,6 +2161,72 @@ class _TransactionV2DetailsViewState ); return; } + + if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Could not find Mimblewimblecoin transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "ERROR: Wallet type is not Mimblewimblecoin", + context: context, + ), + ); + return; + } }, child: Text( "Cancel Transaction", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 0fb65c6353..0721bbb832 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -488,6 +488,11 @@ class _DesktopSendState extends ConsumerState { noteOnChain: _onChainNote ?? "", ); } + if (coin is Mimblewimblecoin) { + txData = txData.copyWith( + noteOnChain: _onChainNote ?? "", + ); + } } // pop building dialog Navigator.of( @@ -790,6 +795,9 @@ class _DesktopSendState extends ConsumerState { if (coin is Epiccash) { content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -805,6 +813,10 @@ class _DesktopSendState extends ConsumerState { // strip http:// and https:// if content contains @ content = AddressUtils().formatAddress(content); } + if (coin is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddressMwc(content); + } sendToController.text = content; _address = content; @@ -1042,6 +1054,21 @@ class _DesktopSendState extends ConsumerState { }); } + if (coin is Mimblewimblecoin) { + sendToController.addListener(() { + _address = sendToController.text; + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = formatAddressMwc(_address!); + } + }); + } + final firoType = ref.watch(publicPrivateBalanceStateProvider); final isExchangeAddress = ref.watch(pIsExchangeAddress); @@ -1715,7 +1742,10 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 20, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) ConditionalParent( condition: ref.watch(pWallets).getWallet(walletId) is ElectrumXInterface && @@ -1771,11 +1801,17 @@ class _DesktopSendState extends ConsumerState { textAlign: TextAlign.left, ), ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) const SizedBox( height: 10, ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! Tezos) if (!isCustomFee) Padding( padding: const EdgeInsets.all(10), @@ -1980,3 +2016,25 @@ String formatAddress(String epicAddress) { } return epicAddress; } + +String formatAddressMwc(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; +} diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index e8a4c5256b..2aae2e1fc6 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -63,6 +63,12 @@ class _ForgotPasswordDesktopViewState await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await Isar.getInstance("desktopStore")?.close(deleteFromDisk: true); await (await StackFileSystem.applicationHiveDirectory()) @@ -79,6 +85,12 @@ class _ForgotPasswordDesktopViewState if (epicDir.existsSync()) { await epicDir.delete(recursive: true); } + final mimblewimblecoinDir = + Directory("${appRoot.path}/mimblewimblecoin"); + if (mimblewimblecoinDir.existsSync()) { + await mimblewimblecoinDir.delete(recursive: true); + } + await (await StackFileSystem.applicationHiveDirectory()) .delete(recursive: true); await (await StackFileSystem.applicationIsarDirectory()) diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 98a7dec344..7d48217bc0 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -434,63 +434,122 @@ class DesktopAboutView extends ConsumerWidget { ); }, ), - // if (AppConfig.coins - // .whereType() - // .isNotEmpty) - // FutureBuilder( - // future: GitStatus - // .getMoneroCommitStatus(), - // builder: ( - // context, - // AsyncSnapshot - // snapshot, - // ) { - // CommitStatus stateOfCommit = - // CommitStatus.notLoaded; + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: GitStatus + .getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot + snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; + + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + stateOfCommit = + snapshot.data!; + } + + return Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles + .desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ) + .extension< + StackColors>()! + .textDark, + ), + ), + const SizedBox( + height: 2, + ), + SelectableText( + GitStatus + .mimblewimblecoinCommit, + style: GitStatus + .styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ); + }, + ), + //if (AppConfig.coins + // .whereType() + // .isNotEmpty) + // FutureBuilder( + // future: GitStatus + // .getMoneroCommitStatus(), + // builder: ( + // context, + // AsyncSnapshot + // snapshot, + // ) { + // CommitStatus stateOfCommit = + // CommitStatus.notLoaded; // - // if (snapshot.connectionState == - // ConnectionState - // .done && - // snapshot.hasData) { - // stateOfCommit = - // snapshot.data!; - // } - // return Column( - // mainAxisSize: - // MainAxisSize.min, - // crossAxisAlignment: - // CrossAxisAlignment - // .start, - // children: [ - // Text( - // "Monero Build Commit", - // style: STextStyles - // .desktopTextExtraExtraSmall( - // context, - // ).copyWith( - // color: Theme.of( - // context, - // ) - // .extension< - // StackColors>()! - // .textDark, - // ), - // ), - // const SizedBox( - // height: 2, - // ), - // SelectableText( - // GitStatus.moneroCommit, - // style: GitStatus - // .styleForStatus( - // stateOfCommit, - // context, - // ), - // ), - // ], - // ); - // }, - // ), + // if (snapshot.connectionState == + // ConnectionState + // .done && + // snapshot.hasData) { + // stateOfCommit = + // snapshot.data!; + // } + // return Column( + // mainAxisSize: + // MainAxisSize.min, + // crossAxisAlignment: + // CrossAxisAlignment + // .start, + // children: [ + // Text( + // "Monero Build Commit", + // style: STextStyles + // .desktopTextExtraExtraSmall( + // context, + // ).copyWith( + // color: Theme.of( + // context, + // ) + // .extension< + // StackColors>()! + // .textDark, + // ), + // ), + // const SizedBox( + // height: 2, + // ), + // SelectableText( + // GitStatus.moneroCommit, + // style: GitStatus + // .styleForStatus( + // stateOfCommit, + // context, + // ), + // ), + // ], + // ); + // }, + // ), ], ), const SizedBox(height: 35), diff --git a/lib/services/price.dart b/lib/services/price.dart index 5a97e43cbc..071ce59804 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -34,6 +34,7 @@ class PriceAPI { Dash: "dash", Dogecoin: "dogecoin", Epiccash: "epic-cash", + Mimblewimblecoin: "mimblewimblecoin", Ecash: "ecash", Ethereum: "ethereum", Firo: "zcoin", diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index c606a75cfa..b2b445ddd3 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -25,6 +25,7 @@ import '../wallets/crypto_currency/crypto_currency.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/wallet/impl/epiccash_wallet.dart'; import '../wallets/wallet/intermediate/lib_monero_wallet.dart'; +import '../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'event_bus/events/wallet_added_event.dart'; import 'event_bus/global_event_bus.dart'; @@ -113,6 +114,15 @@ class Wallets { "epic wallet: $walletId deleted with result: $deleteResult", level: LogLevel.Info, ); + } else if (info.coin is Mimblewimblecoin) { + final deleteResult = await deleteMimblewimblecoinWallet( + walletId: walletId, + secureStore: secureStorage, + ); + Logging.instance.log( + "Mimblewimblecoin wallet: $walletId deleted with result: $deleteResult", + level: LogLevel.Info, + ); } // delete wallet data in main db diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart index 4a789ad76a..1deb22b4e1 100644 --- a/lib/themes/coin_icon_provider.dart +++ b/lib/themes/coin_icon_provider.dart @@ -28,6 +28,8 @@ final coinIconProvider = Provider.family((ref, coin) { return assets.dogecoin; case const (Epiccash): return assets.epicCash; + case const (Mimblewimblecoin): + return assets.mimblewimblecoin; case const (Firo): return assets.firo; case const (Monero): diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 2e6b241b88..aa9bb6c3c9 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -232,6 +232,31 @@ class AddressUtils { } return epicAddress; } + + /// Formats an address string to remove any unnecessary prefixes or suffixes. + String formatMimblewimblecoinAddress(String mimblewimblecoinAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) + if ((mimblewimblecoinAddress.startsWith("http://") || + mimblewimblecoinAddress.startsWith("https://")) && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("http://", ""); + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (mimblewimblecoinAddress.startsWith("mailto:")) { + mimblewimblecoinAddress = + mimblewimblecoinAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an mwcmqs address) + if (mimblewimblecoinAddress.endsWith("/") && + mimblewimblecoinAddress.contains("@")) { + mimblewimblecoinAddress = mimblewimblecoinAddress.substring( + 0, mimblewimblecoinAddress.length - 1); + } + return mimblewimblecoinAddress; + } } class PaymentUriData { diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index a8cbfa701e..79e45232b3 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -63,6 +63,7 @@ enum AmountUnit { // case Coin.dogecoin: // case Coin.eCash: // case Coin.epicCash: + // case Coin.mimblewimblecoin: // case Coin.stellar: // TODO: check if this is correct // case Coin.stellarTestnet: // case Coin.tezos: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 73c5204100..9d62c75f3b 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -245,6 +245,7 @@ class _SVG { String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; + String get mimblewimblecoin => "assets/svg/coin_icons/Mimblewimblecoin.svg"; String get ethereum => "assets/svg/coin_icons/Ethereum.svg"; String get firo => "assets/svg/coin_icons/Firo.svg"; String get monero => "assets/svg/coin_icons/Monero.svg"; diff --git a/lib/utilities/default_mwcmqs.dart b/lib/utilities/default_mwcmqs.dart new file mode 100644 index 0000000000..75be1d247b --- /dev/null +++ b/lib/utilities/default_mwcmqs.dart @@ -0,0 +1,53 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import '../models/mwcmqs_server_model.dart'; + +abstract class DefaultMwcMqs { + static const String defaultName = "Default"; + + static List get all => [americas, asia, europe]; + static List get defaultIds => ['americas', 'asia', 'europe']; + + static MwcMqsServerModel get americas => MwcMqsServerModel( + host: 'MwcMqs.stackwallet.com', + port: 443, + name: 'Americas', + id: 'americas', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get asia => MwcMqsServerModel( + host: 'MwcMqs.hyperbig.com', + port: 443, + name: 'Asia', + id: 'asia', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static MwcMqsServerModel get europe => MwcMqsServerModel( + host: 'MwcMqs.fastepic.eu', + port: 443, + name: 'Europe', + id: 'europe', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static final defaultMwcMqsServer = americas; +} diff --git a/lib/utilities/git_status.dart b/lib/utilities/git_status.dart index 093bc39af3..17617c0fa7 100644 --- a/lib/utilities/git_status.dart +++ b/lib/utilities/git_status.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/git_versions.dart' as epic_versions; // import 'package:flutter_libmonero/git_versions.dart' as monero_versions; +import 'package:flutter_libmwc/git_versions.dart' as mimblewimblecoin_versions; import 'package:http/http.dart'; import 'package:lelantus/git_versions.dart' as firo_versions; @@ -21,6 +22,8 @@ abstract class GitStatus { static String get firoCommit => firo_versions.getPluginVersion(); static String get epicCashCommit => epic_versions.getPluginVersion(); // static String get moneroCommit => monero_versions.getPluginVersion(); + static String get mimblewimblecoinCommit => + mimblewimblecoin_versions.getPluginVersion(); static String get appCommitHash => AppConfig.commitHash; @@ -78,31 +81,60 @@ abstract class GitStatus { return _cachedEpicStatus!; } + + static CommitStatus? _cachedMimblewimblecoinStatus; + static Future getMimblewimblecoinCommitStatus() async { + if (_cachedMimblewimblecoinStatus != null) { + return _cachedMimblewimblecoinStatus!; + } + final List results = await Future.wait([ + _doesCommitExist("cypherstack", "flutter_libmwc", mimblewimblecoinCommit), + _isHeadCommit( + "cypherstack", + "flutter_libmwc", + "main", + mimblewimblecoinCommit, + ), + ]); + + final commitExists = results[0]; + final commitIsHead = results[1]; + + if (commitExists && commitIsHead) { + _cachedMimblewimblecoinStatus = CommitStatus.isHead; + } else if (commitExists) { + _cachedMimblewimblecoinStatus = CommitStatus.isOldCommit; + } else { + _cachedMimblewimblecoinStatus = CommitStatus.notACommit; + } + + return _cachedMimblewimblecoinStatus!; + } + + //static CommitStatus? _cachedMoneroStatus; + //static Future getMoneroCommitStatus() async { + // if (_cachedMoneroStatus != null) { + // return _cachedMoneroStatus!; + // } // - // static CommitStatus? _cachedMoneroStatus; - // static Future getMoneroCommitStatus() async { - // if (_cachedMoneroStatus != null) { - // return _cachedMoneroStatus!; - // } - // - // final List results = await Future.wait([ - // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), - // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), - // ]); + // final List results = await Future.wait([ + // _doesCommitExist("cypherstack", "flutter_libmonero", moneroCommit), + // _isHeadCommit("cypherstack", "flutter_libmonero", "main", moneroCommit), + // ]); // - // final commitExists = results[0]; - // final commitIsHead = results[1]; + // final commitExists = results[0]; + // final commitIsHead = results[1]; // - // if (commitExists && commitIsHead) { - // _cachedMoneroStatus = CommitStatus.isHead; - // } else if (commitExists) { - // _cachedMoneroStatus = CommitStatus.isOldCommit; - // } else { - // _cachedMoneroStatus = CommitStatus.notACommit; - // } + // if (commitExists && commitIsHead) { + // _cachedMoneroStatus = CommitStatus.isHead; + // } else if (commitExists) { + // _cachedMoneroStatus = CommitStatus.isOldCommit; + // } else { + // _cachedMoneroStatus = CommitStatus.notACommit; + // } // - // return _cachedMoneroStatus!; - // } + // return _cachedMoneroStatus!; + //} static TextStyle styleForStatus(CommitStatus status, BuildContext context) { final Color color; diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart new file mode 100644 index 0000000000..b06ec1f1d4 --- /dev/null +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -0,0 +1,128 @@ +import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; + +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/node_model.dart'; +import '../../../utilities/default_nodes.dart'; +import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../crypto_currency.dart'; +import '../intermediate/bip39_currency.dart'; + +class Mimblewimblecoin extends Bip39Currency { + Mimblewimblecoin(super.network) { + _idMain = "mimblewimblecoin"; + _uriScheme = "mimblewimblecoin"; // ? + switch (network) { + case CryptoCurrencyNetwork.main: + _id = _idMain; + _name = "Mimblewimblecoin"; + _ticker = "MWC"; + default: + throw Exception("Unsupported network: $network"); + } + } + + late final String _id; + @override + String get identifier => _id; + + late final String _idMain; + @override + String get mainNetId => _idMain; + + late final String _name; + @override + String get prettyName => _name; + + late final String _uriScheme; + @override + String get uriScheme => _uriScheme; + + late final String _ticker; + @override + String get ticker => _ticker; + + @override + String get genesisHash { + return "not used in mimblewimblecoin"; + } + + @override + // change this to change the number of confirms a tx needs in order to show as confirmed + int get minConfirms => 3; + + @override + bool validateAddress(String address) { + // Invalid address that contains HTTP and mwcmqs domain + if ((address.startsWith("http://") || address.startsWith("https://")) && + address.contains("@")) { + return false; + } + if (address.startsWith("http://") || address.startsWith("https://")) { + if (Uri.tryParse(address) != null) { + return true; + } + } + + return mimblewimblecoin.LibMwc.validateSendAddress(address: address); + } + + @override + NodeModel get defaultNode { + switch (network) { + case CryptoCurrencyNetwork.main: + return NodeModel( + host: "http://epiccash.stackwallet.com", + port: 3413, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: false, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false, + ); + + default: + throw UnimplementedError(); + } + } + + @override + int get defaultSeedPhraseLength => 24; + + @override + int get fractionDigits => 8; + + @override + bool get hasBuySupport => false; + + @override + bool get hasMnemonicPassphraseSupport => false; + + @override + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 12]; + + @override + AddressType get defaultAddressType => AddressType.mimbleWimble; + + @override + BigInt get satsPerCoin => BigInt.from(100000000); + + @override + int get targetBlockTimeSeconds => 60; + + @override + DerivePathType get defaultDerivePathType => throw UnsupportedError( + "$runtimeType does not use bitcoin style derivation paths", + ); + + @override + Uri defaultBlockExplorer(String txid) { + switch (network) { + default: + throw Exception( + "Unsupported network for defaultBlockExplorer(): $network", + ); + } + } +} diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 066675d288..58d7e010f9 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -11,6 +11,7 @@ export 'coins/dash.dart'; export 'coins/dogecoin.dart'; export 'coins/ecash.dart'; export 'coins/epiccash.dart'; +export 'coins/mimblewimblecoin.dart'; export 'coins/ethereum.dart'; export 'coins/firo.dart'; export 'coins/litecoin.dart'; @@ -62,7 +63,7 @@ abstract class CryptoCurrency { int get minConfirms; int get minCoinbaseConfirms => minConfirms; - // TODO: [prio=low] could be handled differently as (at least) epiccash does not use this + // TODO: [prio=low] could be handled differently as (at least) epiccash/mimblewimblecoin does not use this String get genesisHash; bool validateAddress(String address); diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index eb6a4e9ea2..dd7dcba413 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -515,6 +515,7 @@ class WalletInfo implements IsarId { abstract class WalletInfoKeys { static const String tokenContractAddresses = "tokenContractAddressesKey"; static const String epiccashData = "epiccashDataKey"; + static const String mimblewimblecoinData = "mimblewimblecoinDataKey"; static const String bananoMonkeyImageBytes = "monkeyImageBytesKey"; static const String tezosDerivationPath = "tezosDerivationPathKey"; static const String lelantusCoinIsarRescanRequired = diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart new file mode 100644 index 0000000000..ed77e34bc7 --- /dev/null +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -0,0 +1,1197 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; +import 'package:flutter_libmwc/models/transaction.dart' + as mimblewimblecoin_models; +import 'package:isar/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../../../models/balance.dart'; +import '../../../models/mwcmqs_config_model.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/isar/models/blockchain_data/transaction.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/node_model.dart'; +import '../../../models/paymint/fee_object_model.dart'; +import '../../../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; +import '../../../services/event_bus/events/global/node_connection_status_changed_event.dart'; +import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; +import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import '../../../services/event_bus/global_event_bus.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/flutter_secure_storage_interface.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/stack_file_system.dart'; +//import '../../../utilities/test_epic_box_connection.dart'; +import '../../crypto_currency/crypto_currency.dart'; +import '../../models/tx_data.dart'; +import '../intermediate/bip39_wallet.dart'; +import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; + +// +// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart +// +class MimblewimblecoinWallet extends Bip39Wallet { + MimblewimblecoinWallet(CryptoCurrencyNetwork network) + : super(Mimblewimblecoin(network)); + + final syncMutex = Mutex(); + NodeModel? _mimblewimblecoinNode; + Timer? timer; + + double highestPercent = 0; + Future get getSyncPercent async { + final int lastScannedBlock = + info.mimblewimblecoinData?.lastScannedBlock ?? 0; + final _chainHeight = await chainHeight; + final double restorePercent = lastScannedBlock / _chainHeight; + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(highestPercent, walletId)); + if (restorePercent > highestPercent) { + highestPercent = restorePercent; + } + + final int blocksRemaining = _chainHeight - lastScannedBlock; + GlobalEventBus.instance + .fire(BlocksRemainingEvent(blocksRemaining, walletId)); + + return restorePercent < 0 ? 0.0 : restorePercent; + } + + /*Future updateEpicboxConfig(String host, int port) async { + final String stringConfig = jsonEncode({ + "epicbox_domain": host, + "epicbox_port": port, + "epicbox_protocol_unsecure": false, + "epicbox_address_index": 0, + }); + await secureStorageInterface.write( + key: '${walletId}_epicboxConfig', + value: stringConfig, + ); + // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed + } + */ + /// returns an empty String on success, error message on failure + Future cancelPendingTransactionAndPost(String txSlateId) async { + try { + final String wallet = (await secureStorageInterface.read( + key: '${walletId}_wallet', + ))!; + + final result = await mimblewimblecoin.LibMwc.cancelTransaction( + wallet: wallet, + transactionId: txSlateId, + ); + Logging.instance.log( + "cancel $txSlateId result: $result", + level: LogLevel.Info, + ); + return result; + } catch (e, s) { + Logging.instance.log("$e, $s", level: LogLevel.Error); + return e.toString(); + } + } + + Future getMwcMqsConfig() async { + final MwcMqsConfigModel _mwcMqsConfig = MwcMqsConfigModel.fromServer( + DefaultMwcMqs.defaultMwcMqsServer, + ); + + //Get the default Epicbox server and check if it's conected + // bool isEpicboxConnected = await _testEpicboxServer( + // DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); + + // if (isEpicboxConnected) { + //Use default server for as Epicbox config + + // } + // else { + // //Use Europe config + // _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + // } + // // example of selecting another random server from the default list + // // alternative servers: copy list of all default EB servers but remove the default default + // // List alternativeServers = DefaultEpicBoxes.all; + // // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // // alternativeServers.shuffle(); // randomize which server is used + // // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + // + // // TODO test this connection before returning it + // } + + return _mwcMqsConfig; + } + + // ================= Private ================================================= + + Future _getConfig() async { + if (_mimblewimblecoinNode == null) { + await updateNode(); + } + final NodeModel node = _mimblewimblecoinNode!; + final String nodeAddress = node.host; + final int port = node.port; + + final uri = Uri.parse(nodeAddress).replace(port: port); + + final String nodeApiAddress = uri.toString(); + + final walletDir = await _currentWalletDirPath(); + + final Map config = {}; + config["wallet_dir"] = walletDir; + config["check_node_api_http_addr"] = nodeApiAddress; + config["chain"] = "mainnet"; + config["account"] = "default"; + config["api_listen_port"] = port; + config["api_listen_interface"] = + nodeApiAddress.replaceFirst(uri.scheme, ""); + final String stringConfig = jsonEncode(config); + return stringConfig; + } + + Future _currentWalletDirPath() async { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + return '$path/$name'; + } + + Future _nativeFee( + int satoshiAmount, { + bool ifErrorEstimateFee = false, + }) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + try { + final available = info.cachedBalance.spendable.raw.toInt(); + + final transactionFees = await mimblewimblecoin.LibMwc.getTransactionFees( + wallet: wallet!, + amount: satoshiAmount, + minimumConfirmations: cryptoCurrency.minConfirms, + available: available, + ); + + int realFee = 0; + try { + realFee = + (Decimal.parse(transactionFees.fee.toString())).toBigInt().toInt(); + } catch (e, s) { + //todo: come back to this + debugPrint("$e $s"); + } + return realFee; + } catch (e, s) { + Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + rethrow; + } + } + + Future _startSync() async { + Logging.instance.log("request start sync", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const int refreshFromNode = 1; + if (!syncMutex.isLocked) { + await syncMutex.protect(() async { + // How does getWalletBalances start syncing???? + await mimblewimblecoin.LibMwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: 10, + ); + }); + } else { + Logging.instance.log("request start sync denied", level: LogLevel.Info); + } + } + + Future< + ({ + double awaitingFinalization, + double pending, + double spendable, + double total + })> _allWalletBalances() async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 0; + return await mimblewimblecoin.LibMwc.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: cryptoCurrency.minConfirms, + ); + } + + /*Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { + final host = epicboxConfig.host; + final port = epicboxConfig.port ?? 443; + WebSocketChannel? channel; + try { + final uri = Uri.parse('wss://$host:$port'); + + channel = WebSocketChannel.connect( + uri, + ); + + await channel.ready; + + final response = await channel.stream.first.timeout( + const Duration(seconds: 2), + ); + + return response is String && response.contains("Challenge"); + } catch (_) { + Logging.instance.log( + "_testEpicBoxConnection failed on \"$host:$port\"", + level: LogLevel.Info, + ); + return false; + } finally { + await channel?.sink.close(); + } + }*/ + + Future _putSendToAddresses( + ({String slateId, String commitId}) slateData, + Map txAddressInfo, + ) async { + try { + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; + slatesToCommits[slateData.slateId] = { + "commitId": slateData.commitId, + "from": from, + "to": to, + }; + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + slatesToCommits: slatesToCommits, + ), + isar: mainDB.isar, + ); + return true; + } catch (e, s) { + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + return false; + } + } + + Future _getCurrentIndex() async { + try { + final int receivingIndex = info.mimblewimblecoinData!.receivingIndex; + // TODO: go through pendingarray and processed array and choose the index + // of the last one that has not been processed, or the index after the one most recently processed; + return receivingIndex; + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return 0; + } + } + + Future

_generateAndStoreReceivingAddressForIndex( + int index, + ) async { + Address? address = await getCurrentReceivingAddress(); + + if (address != null) { + final splitted = address.value.split('@'); + //Check if the address is the same as the current epicbox domain + //Since we're only using one epicbpox now this doesn't apply but will be + // useful in the future + final mwcmqsConfig = await getMwcMqsConfig(); + if (splitted[1] != mwcmqsConfig.host) { + //Update the address + address = await thisWalletAddress(index, mwcmqsConfig); + } + } else { + final mwcmqsConfig = await getMwcMqsConfig(); + address = await thisWalletAddress(index, mwcmqsConfig); + } + + if (info.cachedReceivingAddress != address.value) { + await info.updateReceivingAddress( + newAddress: address.value, + isar: mainDB.isar, + ); + } + return address; + } + + Future
thisWalletAddress( + int index, + MwcMqsConfigModel mwcmqsConfig, + ) async { + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + + final walletAddress = await mimblewimblecoin.LibMwc.getAddressInfo( + wallet: wallet!, + index: index, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + + Logging.instance.log( + "WALLET_ADDRESS_IS $walletAddress", + level: LogLevel.Info, + ); + + final address = Address( + walletId: walletId, + value: walletAddress, + derivationIndex: index, + derivationPath: null, + type: AddressType.mimbleWimble, + subType: AddressSubType.receiving, + publicKey: [], // ?? + ); + await mainDB.updateOrPutAddresses([address]); + return address; + } + + Future _startScans() async { + try { + //First stop the current listener + mimblewimblecoin.LibMwc.stopMwcmqsListener(); + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + + // max number of blocks to scan per loop iteration + const scanChunkSize = 10000; + + // force firing of scan progress event + await getSyncPercent; + + // fetch current chain height and last scanned block (should be the + // restore height if full rescan or a wallet restore) + int chainHeight = await this.chainHeight; + int lastScannedBlock = info.mimblewimblecoinData!.lastScannedBlock; + + // loop while scanning in chain in chunks (of blocks?) + while (lastScannedBlock < chainHeight) { + Logging.instance.log( + "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", + level: LogLevel.Info, + ); + + final int nextScannedBlock = await mimblewimblecoin.LibMwc.scanOutputs( + wallet: wallet!, + startHeight: lastScannedBlock, + numberOfBlocks: scanChunkSize, + ); + + // update local cache + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: nextScannedBlock, + ), + isar: mainDB.isar, + ); + + // force firing of scan progress event + await getSyncPercent; + + // update while loop condition variables + chainHeight = await this.chainHeight; + lastScannedBlock = nextScannedBlock; + } + + Logging.instance.log( + "_startScans successfully at the tip", + level: LogLevel.Info, + ); + //Once scanner completes restart listener + await _listenToMwcmqs(); + } catch (e, s) { + Logging.instance.log( + "_startScans failed: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future _listenToMwcmqs() async { + Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + mimblewimblecoin.LibMwc.startMwcMqsListener( + wallet: wallet!, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + } + + // As opposed to fake config? + Future _getRealConfig() async { + String? config = await secureStorageInterface.read( + key: '${walletId}_config', + ); + if (Platform.isIOS) { + final walletDir = await _currentWalletDirPath(); + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + return config!; + } + + // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index + int _calculateRestoreHeightFrom({required DateTime date}) { + final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + int height = approximateHeight; + if (height < 0) { + height = 0; + } + return height; + } + + // ============== Overrides ================================================== + + @override + int get isarTransactionVersion => 2; + + @override + FilterOperation? get changeAddressFilterOperation => + FilterGroup.and(standardChangeAddressFilters); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + @override + Future checkSaveInitialReceivingAddress() async { + // epiccash seems ok with nothing here? + } + + @override + Future init({bool? isRestore}) async { + if (isRestore != true) { + String? encodedWallet = + await secureStorageInterface.read(key: "${walletId}_wallet"); + + // check if should create a new wallet + if (encodedWallet == null) { + await updateNode(); + final mnemonicString = await getMnemonic(); + + final String password = generatePassword(); + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + final String name = walletId; + + await mimblewimblecoin.LibMwc.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); + + //Open wallet + encodedWallet = await mimblewimblecoin.LibMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: encodedWallet, + ); + + //Store MwcMqs address info + await _generateAndStoreReceivingAddressForIndex(0); + + // subtract a couple days to ensure we have a buffer for SWB + final bufferedCreateHeight = _calculateRestoreHeightFrom( + date: DateTime.now().subtract(const Duration(days: 2)), + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: bufferedCreateHeight, + restoreHeight: bufferedCreateHeight, + creationHeight: bufferedCreateHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + } else { + try { + Logging.instance.log( + "initializeExisting() ${cryptoCurrency.prettyName} wallet", + level: LogLevel.Info, + ); + + final config = await _getRealConfig(); + final password = + await secureStorageInterface.read(key: '${walletId}_password'); + + final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + config: config, + password: password!, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await updateNode(); + } catch (e, s) { + // do nothing, still allow user into wallet + Logging.instance.log( + "$runtimeType init() failed: $e\n$s", + level: LogLevel.Error, + ); + } + } + } + + return await super.init(); + } + + @override + Future confirmSend({required TxData txData}) async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + // TODO determine whether it is worth sending change to a change address. + + final String receiverAddress = txData.recipients!.first.address; + + if (!receiverAddress.startsWith("http://") || + !receiverAddress.startsWith("https://")) { + final bool isMwcmqsConnected = await _testMwcmqsServer( + mwcmqsConfig, + ); + if (!isMwcmqsConnected) { + throw Exception( + "Failed to send TX : Unable to reach mimblewimblecoin server"); + } + } + + ({String commitId, String slateId}) transaction; + + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { + transaction = await mimblewimblecoin.LibMwc.txHttpSend( + wallet: wallet!, + selectionStrategyIsAll: 0, + minimumConfirmations: cryptoCurrency.minConfirms, + message: txData.noteOnChain ?? "", + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + ); + } else { + transaction = await mimblewimblecoin.LibMwc.createTransaction( + wallet: wallet!, + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + secretKeyIndex: 0, + mwcmqsConfig: mwsmqsConfig.toString(), + minimumConfirmations: cryptoCurrency.minConfirms, + note: txData.noteOnChain!, + ); + } + + final Map txAddressInfo = {}; + txAddressInfo['from'] = (await getCurrentReceivingAddress())!.value; + txAddressInfo['to'] = txData.recipients!.first.address; + await _putSendToAddresses(transaction, txAddressInfo); + + return txData.copyWith( + txid: transaction.slateId, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin confirmSend: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + @override + Future prepareSend({required TxData txData}) async { + try { + if (txData.recipients?.length != 1) { + throw Exception( + "Mimblewimblecoin prepare send requires a single recipient!"); + } + + ({String address, Amount amount, bool isChange}) recipient = + txData.recipients!.first; + + final int realFee = await _nativeFee(recipient.amount.raw.toInt()); + final feeAmount = Amount( + rawValue: BigInt.from(realFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + if (feeAmount > info.cachedBalance.spendable) { + throw Exception( + "Mimblewimblecoin prepare send fee is greater than available balance!", + ); + } + + if (info.cachedBalance.spendable == recipient.amount) { + recipient = ( + address: recipient.address, + amount: recipient.amount - feeAmount, + isChange: recipient.isChange, + ); + } + + return txData.copyWith( + recipients: [recipient], + fee: feeAmount, + ); + } catch (e, s) { + Logging.instance + .log("Mimblewimblecoin prepareSend: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future recover({required bool isRescan}) async { + try { + await refreshMutex.protect(() async { + if (isRescan) { + // clear blockchain info + await mainDB.deleteWalletBlockchainData(walletId); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: info.mimblewimblecoinData!.copyWith( + lastScannedBlock: info.mimblewimblecoinData!.restoreHeight, + ), + isar: mainDB.isar, + ); + + unawaited(_startScans()); + } else { + await updateNode(); + final String password = generatePassword(); + + final String stringConfig = await _getConfig(); + final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + + await secureStorageInterface.write( + key: '${walletId}_mwcmqsConfig', + value: mwcmqsConfig.toString(), + ); + + await mimblewimblecoin.LibMwc.recoverWallet( + config: stringConfig, + password: password, + mnemonic: await getMnemonic(), + name: info.walletId, + ); + + final mimblewimblecoinData = ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: info.restoreHeight, + restoreHeight: info.restoreHeight, + creationHeight: + info.mimblewimblecoinData?.creationHeight ?? info.restoreHeight, + ); + + await info.updateExtraMimblewimblecoinWalletInfo( + mimblewimblecoinData: mimblewimblecoinData, + isar: mainDB.isar, + ); + + //Open Wallet + final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await _generateAndStoreReceivingAddressForIndex( + mimblewimblecoinData.receivingIndex, + ); + } + }); + + unawaited(refresh()); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from electrumx_mixin recover(): $e\n$s", + level: LogLevel.Info, + ); + + rethrow; + } + } + + @override + Future refresh() async { + // Awaiting this lock could be dangerous. + // Since refresh is periodic (generally) + if (refreshMutex.isLocked) { + return; + } + + try { + // this acquire should be almost instant due to above check. + // Slight possibility of race but should be irrelevant + await refreshMutex.acquire(); + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + cryptoCurrency, + ), + ); + + // if (info.epicData?.creationHeight == null) { + // await info.updateExtraEpiccashWalletInfo(epicData: inf, isar: isar) + // await epicUpdateCreationHeight(await chainHeight); + // } + + // this will always be zero???? + final int curAdd = await _getCurrentIndex(); + await _generateAndStoreReceivingAddressForIndex(curAdd); + + await _startScans(); + + unawaited(_startSync()); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + await updateChainHeight(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkReceivingAddressForTransactions(); + // } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + + // // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. + // if (this is MultiAddressInterface) { + // await (this as MultiAddressInterface) + // .checkChangeAddressForTransactions(); + // } + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); + final fetchFuture = updateTransactions(); + // if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); + + await fetchFuture; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); + + // await getAllTxsToWatch(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); + + await updateBalance(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + cryptoCurrency, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + + // TODO: [prio=med] some kind of quick check if wallet needs to refresh to replace the old refreshIfThereIsNewData call + // if (await refreshIfThereIsNewData()) { + unawaited(refresh()); + + // } + // } + }); + } + } catch (error, strace) { + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + cryptoCurrency, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error, + ); + } finally { + refreshMutex.release(); + } + } + + @override + Future updateBalance() async { + try { + final balances = await _allWalletBalances(); + final balance = Balance( + total: Amount.fromDecimal( + Decimal.parse(balances.total.toString()) + + Decimal.parse(balances.awaitingFinalization.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + spendable: Amount.fromDecimal( + Decimal.parse(balances.spendable.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + blockedTotal: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: Amount.fromDecimal( + Decimal.parse(balances.pending.toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ), + ); + + await info.updateBalance( + newBalance: balance, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.log( + "Mimblewimblecoin wallet failed to update balance: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateTransactions() async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + const refreshFromNode = 1; + + final myAddresses = await mainDB + .getAddresses(walletId) + .filter() + .typeEqualTo(AddressType.mimbleWimble) + .and() + .subTypeEqualTo(AddressSubType.receiving) + .and() + .valueIsNotEmpty() + .valueProperty() + .findAll(); + final myAddressesSet = myAddresses.toSet(); + + final transactions = await mimblewimblecoin.LibMwc.getTransactions( + wallet: wallet!, + refreshFromNode: refreshFromNode, + ); + + final List txns = []; + + final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; + + for (final tx in transactions) { + Logging.instance.log("tx: $tx", level: LogLevel.Info); + + final isIncoming = + tx.txType == mimblewimblecoin_models.TransactionType.TxReceived || + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled; + final slateId = tx.txSlateId; + final commitId = slatesToCommits[slateId]?['commitId'] as String?; + final numberOfMessages = tx.messages?.messages.length; + final onChainNote = tx.messages?.messages[0].message; + final addressFrom = slatesToCommits[slateId]?["from"] as String?; + final addressTo = slatesToCommits[slateId]?["to"] as String?; + + final credit = int.parse(tx.amountCredited); + final debit = int.parse(tx.amountDebited); + final fee = int.tryParse(tx.fee ?? "0") ?? 0; + + // hack Mimblewimblecoin tx data into inputs and outputs + final List outputs = []; + final List inputs = []; + final addressFromIsMine = myAddressesSet.contains(addressFrom); + final addressToIsMine = myAddressesSet.contains(addressTo); + + OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: credit.toString(), + addresses: [ + if (addressFrom != null) addressFrom, + ], + walletOwns: true, + ); + final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [if (addressTo != null) addressTo], + valueStringSats: debit.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: true, + ); + + final TransactionType txType; + if (isIncoming) { + if (addressToIsMine && addressFromIsMine) { + txType = TransactionType.sentToSelf; + } else { + txType = TransactionType.incoming; + } + output = output.copyWith( + addresses: [ + myAddressesSet + .first, // Must be changed if we ever do more than a single wallet address!!! + ], + walletOwns: true, + ); + } else { + txType = TransactionType.outgoing; + } + + outputs.add(output); + inputs.add(input); + + final otherData = { + "isMimblewimblecoinTransaction": true, + "numberOfMessages": numberOfMessages, + "slateId": slateId, + "onChainNote": onChainNote, + "isCancelled": tx.txType == + mimblewimblecoin_models.TransactionType.TxSentCancelled || + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled, + "overrideFee": Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), + }; + + final txn = TransactionV2( + walletId: walletId, + blockHash: null, + hash: commitId ?? tx.id.toString(), + txid: commitId ?? tx.id.toString(), + timestamp: + DateTime.parse(tx.creationTs).millisecondsSinceEpoch ~/ 1000, + height: tx.confirmed ? tx.kernelLookupMinHeight ?? 1 : null, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: 0, + type: txType, + subType: TransactionSubType.none, + otherData: jsonEncode(otherData), + ); + + txns.add(txn); + } + + await mainDB.isar.writeTxn(() async { + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .deleteAll(); + await mainDB.isar.transactionV2s.putAll(txns); + }); + } catch (e, s) { + Logging.instance.log( + "${cryptoCurrency.runtimeType} ${cryptoCurrency.network} net wallet" + " \"${info.name}\"_${info.walletId} updateTransactions() failed: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateUTXOs() async { + // not used for mimblewimblecoin + return false; + } + + @override + Future updateNode() async { + _mimblewimblecoinNode = getCurrentNode(); + + // TODO: [prio=low] move this out of secure storage if secure storage not needed + final String stringConfig = await _getConfig(); + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + + // unawaited(refresh()); + } + + @override + Future pingCheck() async { + try { + final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); + + // force unwrap optional as we want connection test to fail if wallet + // wasn't initialized or mwcmqs node was set to null + return await testMimblewimblecoinNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + return false; + } + } + + @override + Future updateChainHeight() async { + final config = await _getRealConfig(); + final latestHeight = + await mimblewimblecoin.LibMwc.getChainHeight(config: config); + await info.updateCachedChainHeight( + newHeight: latestHeight, + isar: mainDB.isar, + ); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? + final int currentFee = await _nativeFee( + amount.raw.toInt(), + ifErrorEstimateFee: true, + ); + return Amount( + rawValue: BigInt.from(currentFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); + } + + @override + Future get fees async { + // this wasn't done before the refactor either so... + // TODO: implement _getFees + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 10, + numberOfBlocksSlow: 10, + fast: 1, + medium: 1, + slow: 1, + ); + } + + @override + Future updateSentCachedTxData({required TxData txData}) async { + // TODO: [prio=low] Was not used before refactor so maybe not required(?) + return txData; + } + + @override + Future exit() async { + timer?.cancel(); + timer = null; + await super.exit(); + Logging.instance + .log("Mimblewimblecoin_wallet exit finished", level: LogLevel.Info); + } +} + +Future deleteMimblewimblecoinWallet({ + required String walletId, + required SecureStorageInterface secureStore, +}) async { + final wallet = await secureStore.read(key: '${walletId}_wallet'); + String? config = await secureStore.read(key: '${walletId}_config'); + if (Platform.isIOS) { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + final walletDir = '$path/$name'; + + final editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + + if (wallet == null) { + return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; + } else { + try { + return mimblewimblecoin.LibMwc.deleteWallet( + wallet: wallet, + config: config!, + ); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + return "deleteMimblewimblecoinWallet($walletId) failed..."; + } + } +} diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart new file mode 100644 index 0000000000..724657079e --- /dev/null +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -0,0 +1,111 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; +import '../../../utilities/logger.dart'; +import '../../isar/models/wallet_info.dart'; + +extension MimblewimblecoinWalletInfoExtension on WalletInfo { + ExtraMimblewimblecoinWalletInfo? get epicData { + final String? data = + otherData[WalletInfoKeys.mimblewimblecoinData] as String?; + if (data == null) { + return null; + } + try { + return ExtraMimblewimblecoinWalletInfo.fromMap( + Map.from( + jsonDecode(data) as Map, + ), + ); + } catch (e, s) { + Logging.instance.log( + "ExtraMimblewimblecoinWalletInfo.fromMap failed: $e\n$s", + level: LogLevel.Error, + ); + return null; + } + } + + Future updateExtraMimblewimblecoinWalletInfo({ + required ExtraMimblewimblecoinWalletInfo epicData, + required Isar isar, + }) async { + await updateOtherData( + newEntries: { + WalletInfoKeys.mimblewimblecoinData: jsonEncode(epicData.toMap()), + }, + isar: isar, + ); + } +} + +/// Holds data previously stored in hive +class ExtraMimblewimblecoinWalletInfo { + final int receivingIndex; + final int changeIndex; + + // TODO [prio=low] strongly type these maps at some point + final Map slatesToAddresses; + final Map slatesToCommits; + + final int lastScannedBlock; + final int restoreHeight; + final int creationHeight; + + ExtraMimblewimblecoinWalletInfo({ + required this.receivingIndex, + required this.changeIndex, + required this.slatesToAddresses, + required this.slatesToCommits, + required this.lastScannedBlock, + required this.restoreHeight, + required this.creationHeight, + }); + + // Convert the object to JSON + Map toMap() { + return { + 'receivingIndex': receivingIndex, + 'changeIndex': changeIndex, + 'slatesToAddresses': slatesToAddresses, + 'slatesToCommits': slatesToCommits, + 'lastScannedBlock': lastScannedBlock, + 'restoreHeight': restoreHeight, + 'creationHeight': creationHeight, + }; + } + + ExtraMimblewimblecoinWalletInfo.fromMap(Map json) + : receivingIndex = json['receivingIndex'] as int, + changeIndex = json['changeIndex'] as int, + slatesToAddresses = json['slatesToAddresses'] as Map, + slatesToCommits = json['slatesToCommits'] as Map, + lastScannedBlock = json['lastScannedBlock'] as int, + restoreHeight = json['restoreHeight'] as int, + creationHeight = json['creationHeight'] as int; + + ExtraMimblewimblecoinWalletInfo copyWith({ + int? receivingIndex, + int? changeIndex, + Map? slatesToAddresses, + Map? slatesToCommits, + int? lastScannedBlock, + int? restoreHeight, + int? creationHeight, + }) { + return ExtraMimblewimblecoinWalletInfo( + receivingIndex: receivingIndex ?? this.receivingIndex, + changeIndex: changeIndex ?? this.changeIndex, + slatesToAddresses: slatesToAddresses ?? this.slatesToAddresses, + slatesToCommits: slatesToCommits ?? this.slatesToCommits, + lastScannedBlock: lastScannedBlock ?? this.lastScannedBlock, + restoreHeight: restoreHeight ?? this.restoreHeight, + creationHeight: creationHeight ?? this.creationHeight, + ); + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 0beaf2a809..78079da970 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -34,6 +34,7 @@ import 'impl/dash_wallet.dart'; import 'impl/dogecoin_wallet.dart'; import 'impl/ecash_wallet.dart'; import 'impl/epiccash_wallet.dart'; +import 'impl/mimblewimblecoin_wallet.dart'; import 'impl/ethereum_wallet.dart'; import 'impl/firo_wallet.dart'; import 'impl/litecoin_wallet.dart'; @@ -359,6 +360,9 @@ abstract class Wallet { case const (Epiccash): return EpiccashWallet(net); + case const (Mimblewimblecoin): + return Mimblewimblecoin(net); + case const (Ethereum): return EthereumWallet(net); diff --git a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart index 9709c46b3d..05dd4311da 100644 --- a/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart +++ b/lib/widgets/onetime_popups/tor_has_been_add_dialog.dart @@ -153,7 +153,7 @@ class _TorHasBeenAddedDialogState extends State<_TorHasBeenAddedDialog> { height: Util.isDesktop ? 24 : 16, ), Text( - "Note: Tor does NOT yet work for Monero or Epic Cash wallets. " + "Note: Tor does NOT yet work for Monero, Mimblewimblecoin or Epic Cash wallets. " "Opening one of these will leak your IP address.", style: Util.isDesktop ? STextStyles.desktopTextMedium(context) diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index cab5043d66..f842858e9f 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -27,6 +27,7 @@ import '../utilities/constants.dart'; import '../utilities/format.dart'; import '../utilities/text_styles.dart'; import '../utilities/util.dart'; +import '../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; import 'desktop/desktop_dialog.dart'; @@ -63,6 +64,10 @@ class _TransactionCardState extends ConsumerState { return "Restored Funds"; } + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + return "Restored Funds"; + } + final confirmedStatus = _transaction.isConfirmed( currentHeight, minConfirms, @@ -188,6 +193,20 @@ class _TransactionCardState extends ConsumerState { ); return; } + + if (coin is Mimblewimblecoin && _transaction.slateId == null) { + unawaited( + showFloatingFlushBar( + context: context, + message: + "Restored Mimblewimblecoin funds from your Seed have no Data.", + type: FlushBarType.warning, + duration: const Duration(seconds: 5), + ), + ); + return; + } + if (Util.isDesktop) { await showDialog( context: context, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 88c196c5ed..8faaf8a515 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -30,6 +30,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_libepiccash_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibepiccashPlugin"); flutter_libepiccash_plugin_register_with_registrar(flutter_libepiccash_registrar); + g_autoptr(FlPluginRegistrar) flutter_libmwc_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLibmwcPlugin"); + flutter_libmwc_plugin_register_with_registrar(flutter_libmwc_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 72a81025cb..52a5ce0a5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_drop devicelocale flutter_libepiccash + flutter_libmwc flutter_secure_storage_linux isar_flutter_libs sqlite3_flutter_libs diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 66db749d95..6c71495b0f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import desktop_drop import device_info_plus import devicelocale import flutter_libepiccash +import flutter_libmwc import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs @@ -34,6 +35,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) FlutterLibepiccashPlugin.register(with: registry.registrar(forPlugin: "FlutterLibepiccashPlugin")) + FlutterLibmwcPlugin.register(with: registry.registrar(forPlugin: "FlutterLibmwcPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 3ccce4760b..285d841128 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -812,6 +812,13 @@ packages: relative: true source: path version: "0.0.1" + flutter_libmwc: + dependency: "direct main" + description: + path: "crypto_plugins/flutter_libmwc" + relative: true + source: path + version: "0.0.1" flutter_libsparkmobile: dependency: "direct main" description: diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 0fd8e5e8a0..1139cbfc8d 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -62,6 +62,7 @@ final List _supportedCoins = List.unmodifiable([ Dogecoin(CryptoCurrencyNetwork.main), Ecash(CryptoCurrencyNetwork.main), Epiccash(CryptoCurrencyNetwork.main), + Mimblewimblecoin(CryptoCurrencyNetwork.main), Ethereum(CryptoCurrencyNetwork.main), Firo(CryptoCurrencyNetwork.main), Litecoin(CryptoCurrencyNetwork.main), diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index 25750ef4f6..d6ac3f66d3 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -137,7 +137,10 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libemwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1.7.4" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/scripts/app_config/templates/pubspec.template b/scripts/app_config/templates/pubspec.template index e44cdcab45..a0f43767b9 100644 --- a/scripts/app_config/templates/pubspec.template +++ b/scripts/app_config/templates/pubspec.template @@ -45,6 +45,9 @@ dependencies: flutter_libepiccash: path: ./crypto_plugins/flutter_libepiccash + flutter_libmwc: + path: ./crypto_plugins/flutter_libmwc + bitcoindart: git: url: https://github.com/cypherstack/bitcoindart.git diff --git a/scripts/app_config/templates/windows/CMakeLists.txt b/scripts/app_config/templates/windows/CMakeLists.txt index b9add856d7..dd039195e2 100644 --- a/scripts/app_config/templates/windows/CMakeLists.txt +++ b/scripts/app_config/templates/windows/CMakeLists.txt @@ -83,6 +83,9 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/windows/build/libepic_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/windows/build/libmwc_cash_wallet.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/windows/build/libmobileliblelantus.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index bcb03e9912..aa9d52aeef 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_campfire.sh b/scripts/ios/build_all_campfire.sh index bcb03e9912..aa9d52aeef 100755 --- a/scripts/ios/build_all_campfire.sh +++ b/scripts/ios/build_all_campfire.sh @@ -16,6 +16,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/ios/build_all_duo.sh b/scripts/ios/build_all_duo.sh index 89e6f46417..c6c11add7a 100755 --- a/scripts/ios/build_all_duo.sh +++ b/scripts/ios/build_all_duo.sh @@ -18,6 +18,8 @@ rustup target add x86_64-apple-ios (cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh index 423646185e..4cc1ad8a09 100755 --- a/scripts/linux/build_all.sh +++ b/scripts/linux/build_all.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_campfire.sh b/scripts/linux/build_all_campfire.sh index 423646185e..4cc1ad8a09 100755 --- a/scripts/linux/build_all_campfire.sh +++ b/scripts/linux/build_all_campfire.sh @@ -14,6 +14,8 @@ mkdir -p build ./build_secure_storage_deps.sh (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/linux/build_all_duo.sh b/scripts/linux/build_all_duo.sh index 78067b4786..ace233fd8e 100755 --- a/scripts/linux/build_all_duo.sh +++ b/scripts/linux/build_all_duo.sh @@ -16,6 +16,8 @@ mkdir -p build ./build_secure_storage_deps.sh & (cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index af608846fe..2e28e2605d 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_campfire.sh b/scripts/macos/build_all_campfire.sh index af608846fe..2e28e2605d 100755 --- a/scripts/macos/build_all_campfire.sh +++ b/scripts/macos/build_all_campfire.sh @@ -8,6 +8,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/macos/build_all_duo.sh b/scripts/macos/build_all_duo.sh index 8a53e58015..ac3788cc86 100755 --- a/scripts/macos/build_all_duo.sh +++ b/scripts/macos/build_all_duo.sh @@ -10,6 +10,8 @@ set_rust_to_1671 (cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index 8cda1229ba..a95729573d 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -16,4 +16,13 @@ set_rust_to_1720() { echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2 exit 1 fi +} + +set_rust_to_1810() { + if rustup toolchain list | grep -q "1.72.0"; then + rustup default 1.81.0 + else + echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 + exit 1 + fi } \ No newline at end of file diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index 191a46cc07..53c035a3da 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_campfire.sh b/scripts/windows/build_all_campfire.sh index 191a46cc07..53c035a3da 100755 --- a/scripts/windows/build_all_campfire.sh +++ b/scripts/windows/build_all_campfire.sh @@ -9,6 +9,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/build_all_duo.sh b/scripts/windows/build_all_duo.sh index 3e27eff02a..e5667b188d 100755 --- a/scripts/windows/build_all_duo.sh +++ b/scripts/windows/build_all_duo.sh @@ -11,6 +11,8 @@ set_rust_to_1671 mkdir -p build (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) (cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) +set_rust_to_1810 +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) set_rust_to_1720 (cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) diff --git a/scripts/windows/deps.sh b/scripts/windows/deps.sh index c9975b4572..513a8ce7f2 100644 --- a/scripts/windows/deps.sh +++ b/scripts/windows/deps.sh @@ -1,9 +1,9 @@ #!/bin/bash -cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh -cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh -# cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh -sudo apt install libgtk2.0-dev +(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./deps.sh ) +(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./mxedeps.sh ) +(cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./deps.sh) +# (cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./monerodeps.sh && ./mxedeps.sh) & wait echo "Done building" diff --git a/test/price_test.dart b/test/price_test.dart index dc7aaeb9e1..e15a7b95d7 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'dart:convert'; import 'dart:io'; diff --git a/test/sample_data/theme_json.dart b/test/sample_data/theme_json.dart index 6f7601b42b..660e680e68 100644 --- a/test/sample_data/theme_json.dart +++ b/test/sample_data/theme_json.dart @@ -10,6 +10,7 @@ const Map lightThemeJsonMap = { "firo": "0xFFFF897A", "dogecoin": "0xFFFFE079", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -186,6 +187,7 @@ const Map lightThemeJsonMap = { "bitcoincash": "dummy.svg", "dogecoin": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -197,6 +199,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image": "dummy.svg", "dogecoin_image": "dummy.svg", "epicCash_image": "dummy.svg.svg", + "mimblewimblecoin_image": "dummy.svg", "ethereum_image": "dummy.svg", "firo_image": "dummy.svg", "monero_image": "dummy.svg", @@ -208,6 +211,7 @@ const Map lightThemeJsonMap = { "bitcoincash_image_secondary": "dummy.svg", "dogecoin_image_secondary": "dummy.svg", "epicCash_image_secondary": "dummy.svg.svg", + "mimblewimblecoin_image_secondary": "dummy.svg", "ethereum_image_secondary": "dummy.svg", "firo_image_secondary": "dummy.svg", "monero_image_secondary": "dummy.svg", diff --git a/test/sample_data/theme_json_v2.dart b/test/sample_data/theme_json_v2.dart index 3f9fbfb7f3..1327789c02 100644 --- a/test/sample_data/theme_json_v2.dart +++ b/test/sample_data/theme_json_v2.dart @@ -13,6 +13,7 @@ const Map lightThemeJsonMap = { "dogecoin": "0xFFFFE079", "eCash": "0xFFC5C7CB", "epicCash": "0xFFC5C7CB", + "mimblewimblecoin": "0xFFC5C7CB", "ethereum": "0xFFA7ADE9", "monero": "0xFFFF9E6B", "namecoin": "0xFF91B1E1", @@ -194,6 +195,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -208,6 +210,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", @@ -222,6 +225,7 @@ const Map lightThemeJsonMap = { "dogecoin": "dummy.svg", "eCash": "dummy.svg", "epicCash": "dummy.svg", + "mimblewimblecoin": "dummy.svg", "ethereum": "dummy.svg", "firo": "dummy.svg", "monero": "dummy.svg", diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index cb402deedd..fed0f3eecd 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -1,3 +1,5 @@ +// TODO MWC + import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; @@ -160,6 +162,17 @@ void main() { torEnabled: true, clearnetEnabled: true, ); + final nodeD = NodeModel( + host: "host3", + port: 423, + name: "btcnode", + id: "pnodeID3", + useSSL: true, + enabled: true, + coinName: "mimblewimblecoin", + isFailover: true, + isDown: false, + ); setUp(() async { await NodeService(secureStorageInterface: FakeSecureStorage()) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 74ef33c4e5..bb0e286970 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopDropPlugin")); FlutterLibepiccashPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLibepiccashPluginCApi")); + FlutterLibmwcPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLibmwcPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); IsarFlutterLibsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 2f370e6855..0cd9af7943 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST cs_monero_flutter_libs_windows desktop_drop flutter_libepiccash + flutter_libmwc flutter_secure_storage_windows isar_flutter_libs local_auth_windows From 652c1412c833037bb199905110a97c9b277ef82f Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 27 Oct 2024 15:54:52 +0400 Subject: [PATCH 07/91] continuous integration mwc v2 --- .../blockchain_data/v2/transaction_v2.g.dart | 142 +++-- lib/models/isar/stack_theme.g.dart | 603 +++++++++++++++--- lib/models/mwcmqs_config_model.dart | 118 ++++ lib/models/mwcmqs_server_model.dart | 93 +++ .../type_adaptors/mwcmqs_config_model.g.dart | 50 ++ .../type_adaptors/mwcmqs_server_model.g.dart | 62 ++ .../restore_wallet_view.dart | 28 +- .../wallet_settings_view.dart | 2 +- lib/utilities/address_utils.dart | 2 +- lib/utilities/test_mwcmqs_connection.dart | 81 +++ .../coins/mimblewimblecoin.dart | 6 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 95 ++- ...imblewimblecoin_wallet_info_extension.dart | 6 +- lib/wallets/wallet/wallet.dart | 2 +- pubspec.lock | 2 +- .../app_config/templates/linux/CMakeLists.txt | 2 +- scripts/linux/build_secp256k1.sh | 2 +- test/cached_electrumx_test.mocks.dart | 13 + .../pages/send_view/send_view_test.mocks.dart | 13 + .../exchange/exchange_view_test.mocks.dart | 13 + .../managed_favorite_test.mocks.dart | 13 + .../node_options_sheet_test.mocks.dart | 13 + .../transaction_card_test.mocks.dart | 13 + 23 files changed, 1181 insertions(+), 193 deletions(-) create mode 100644 lib/models/mwcmqs_config_model.dart create mode 100644 lib/models/mwcmqs_server_model.dart create mode 100644 lib/models/type_adaptors/mwcmqs_config_model.g.dart create mode 100644 lib/models/type_adaptors/mwcmqs_server_model.g.dart create mode 100644 lib/utilities/test_mwcmqs_connection.dart diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart index 68d8a18c57..3bb48c3508 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart @@ -53,66 +53,71 @@ const TransactionV2Schema = CollectionSchema( name: r'isEpiccashTransaction', type: IsarType.bool, ), - r'nonce': PropertySchema( + r'isMimblewimblecoinTransaction': PropertySchema( id: 7, + name: r'isMimblewimblecoinTransaction', + type: IsarType.bool, + ), + r'nonce': PropertySchema( + id: 8, name: r'nonce', type: IsarType.long, ), r'numberOfMessages': PropertySchema( - id: 8, + id: 9, name: r'numberOfMessages', type: IsarType.long, ), r'onChainNote': PropertySchema( - id: 9, + id: 10, name: r'onChainNote', type: IsarType.string, ), r'otherData': PropertySchema( - id: 10, + id: 11, name: r'otherData', type: IsarType.string, ), r'outputs': PropertySchema( - id: 11, + id: 12, name: r'outputs', type: IsarType.objectList, target: r'OutputV2', ), r'slateId': PropertySchema( - id: 12, + id: 13, name: r'slateId', type: IsarType.string, ), r'subType': PropertySchema( - id: 13, + id: 14, name: r'subType', type: IsarType.byte, enumMap: _TransactionV2subTypeEnumValueMap, ), r'timestamp': PropertySchema( - id: 14, + id: 15, name: r'timestamp', type: IsarType.long, ), r'txid': PropertySchema( - id: 15, + id: 16, name: r'txid', type: IsarType.string, ), r'type': PropertySchema( - id: 16, + id: 17, name: r'type', type: IsarType.byte, enumMap: _TransactionV2typeEnumValueMap, ), r'version': PropertySchema( - id: 17, + id: 18, name: r'version', type: IsarType.long, ), r'walletId': PropertySchema( - id: 18, + id: 19, name: r'walletId', type: IsarType.string, ) @@ -256,23 +261,24 @@ void _transactionV2Serialize( ); writer.writeBool(offsets[5], object.isCancelled); writer.writeBool(offsets[6], object.isEpiccashTransaction); - writer.writeLong(offsets[7], object.nonce); - writer.writeLong(offsets[8], object.numberOfMessages); - writer.writeString(offsets[9], object.onChainNote); - writer.writeString(offsets[10], object.otherData); + writer.writeBool(offsets[7], object.isMimblewimblecoinTransaction); + writer.writeLong(offsets[8], object.nonce); + writer.writeLong(offsets[9], object.numberOfMessages); + writer.writeString(offsets[10], object.onChainNote); + writer.writeString(offsets[11], object.otherData); writer.writeObjectList( - offsets[11], + offsets[12], allOffsets, OutputV2Schema.serialize, object.outputs, ); - writer.writeString(offsets[12], object.slateId); - writer.writeByte(offsets[13], object.subType.index); - writer.writeLong(offsets[14], object.timestamp); - writer.writeString(offsets[15], object.txid); - writer.writeByte(offsets[16], object.type.index); - writer.writeLong(offsets[17], object.version); - writer.writeString(offsets[18], object.walletId); + writer.writeString(offsets[13], object.slateId); + writer.writeByte(offsets[14], object.subType.index); + writer.writeLong(offsets[15], object.timestamp); + writer.writeString(offsets[16], object.txid); + writer.writeByte(offsets[17], object.type.index); + writer.writeLong(offsets[18], object.version); + writer.writeString(offsets[19], object.walletId); } TransactionV2 _transactionV2Deserialize( @@ -292,23 +298,23 @@ TransactionV2 _transactionV2Deserialize( InputV2(), ) ?? [], - otherData: reader.readStringOrNull(offsets[10]), + otherData: reader.readStringOrNull(offsets[11]), outputs: reader.readObjectList( - offsets[11], + offsets[12], OutputV2Schema.deserialize, allOffsets, OutputV2(), ) ?? [], subType: - _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ?? + _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? TransactionSubType.none, - timestamp: reader.readLong(offsets[14]), - txid: reader.readString(offsets[15]), - type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ?? + timestamp: reader.readLong(offsets[15]), + txid: reader.readString(offsets[16]), + type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[17])] ?? TransactionType.outgoing, - version: reader.readLong(offsets[17]), - walletId: reader.readString(offsets[18]), + version: reader.readLong(offsets[18]), + walletId: reader.readString(offsets[19]), ); object.id = id; return object; @@ -342,14 +348,16 @@ P _transactionV2DeserializeProp

( case 6: return (reader.readBool(offset)) as P; case 7: - return (reader.readLongOrNull(offset)) as P; + return (reader.readBool(offset)) as P; case 8: return (reader.readLongOrNull(offset)) as P; case 9: - return (reader.readStringOrNull(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 10: return (reader.readStringOrNull(offset)) as P; case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: return (reader.readObjectList( offset, OutputV2Schema.deserialize, @@ -357,22 +365,22 @@ P _transactionV2DeserializeProp

( OutputV2(), ) ?? []) as P; - case 12: - return (reader.readStringOrNull(offset)) as P; case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: return (_TransactionV2subTypeValueEnumMap[ reader.readByteOrNull(offset)] ?? TransactionSubType.none) as P; - case 14: - return (reader.readLong(offset)) as P; case 15: - return (reader.readString(offset)) as P; + return (reader.readLong(offset)) as P; case 16: + return (reader.readString(offset)) as P; + case 17: return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? TransactionType.outgoing) as P; - case 17: - return (reader.readLong(offset)) as P; case 18: + return (reader.readLong(offset)) as P; + case 19: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -1513,6 +1521,16 @@ extension TransactionV2QueryFilter }); } + QueryBuilder + isMimblewimblecoinTransactionEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isMimblewimblecoinTransaction', + value: value, + )); + }); + } + QueryBuilder nonceIsNull() { return QueryBuilder.apply(this, (query) { @@ -2807,6 +2825,20 @@ extension TransactionV2QuerySortBy }); } + QueryBuilder + sortByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + sortByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder sortByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3038,6 +3070,20 @@ extension TransactionV2QuerySortThenBy }); } + QueryBuilder + thenByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); + }); + } + + QueryBuilder + thenByIsMimblewimblecoinTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); + }); + } + QueryBuilder thenByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3221,6 +3267,13 @@ extension TransactionV2QueryWhereDistinct }); } + QueryBuilder + distinctByIsMimblewimblecoinTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder distinctByNonce() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'nonce'); @@ -3347,6 +3400,13 @@ extension TransactionV2QueryProperty }); } + QueryBuilder + isMimblewimblecoinTransactionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isMimblewimblecoinTransaction'); + }); + } + QueryBuilder nonceProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'nonce'); diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart index c77d979f54..0117ef7b9b 100644 --- a/lib/models/isar/stack_theme.g.dart +++ b/lib/models/isar/stack_theme.g.dart @@ -18148,138 +18148,153 @@ const ThemeAssetsSchema = Schema( name: r'loadingGif', type: IsarType.string, ), - r'monero': PropertySchema( + r'mimblewimblecoin': PropertySchema( id: 26, + name: r'mimblewimblecoin', + type: IsarType.string, + ), + r'mimblewimblecoinImage': PropertySchema( + id: 27, + name: r'mimblewimblecoinImage', + type: IsarType.string, + ), + r'mimblewimblecoinImageSecondary': PropertySchema( + id: 28, + name: r'mimblewimblecoinImageSecondary', + type: IsarType.string, + ), + r'monero': PropertySchema( + id: 29, name: r'monero', type: IsarType.string, ), r'moneroImage': PropertySchema( - id: 27, + id: 30, name: r'moneroImage', type: IsarType.string, ), r'moneroImageSecondary': PropertySchema( - id: 28, + id: 31, name: r'moneroImageSecondary', type: IsarType.string, ), r'namecoin': PropertySchema( - id: 29, + id: 32, name: r'namecoin', type: IsarType.string, ), r'namecoinImage': PropertySchema( - id: 30, + id: 33, name: r'namecoinImage', type: IsarType.string, ), r'namecoinImageSecondary': PropertySchema( - id: 31, + id: 34, name: r'namecoinImageSecondary', type: IsarType.string, ), r'particl': PropertySchema( - id: 32, + id: 35, name: r'particl', type: IsarType.string, ), r'particlImage': PropertySchema( - id: 33, + id: 36, name: r'particlImage', type: IsarType.string, ), r'particlImageSecondary': PropertySchema( - id: 34, + id: 37, name: r'particlImageSecondary', type: IsarType.string, ), r'personaEasy': PropertySchema( - id: 35, + id: 38, name: r'personaEasy', type: IsarType.string, ), r'personaIncognito': PropertySchema( - id: 36, + id: 39, name: r'personaIncognito', type: IsarType.string, ), r'receive': PropertySchema( - id: 37, + id: 40, name: r'receive', type: IsarType.string, ), r'receiveCancelled': PropertySchema( - id: 38, + id: 41, name: r'receiveCancelled', type: IsarType.string, ), r'receivePending': PropertySchema( - id: 39, + id: 42, name: r'receivePending', type: IsarType.string, ), r'send': PropertySchema( - id: 40, + id: 43, name: r'send', type: IsarType.string, ), r'sendCancelled': PropertySchema( - id: 41, + id: 44, name: r'sendCancelled', type: IsarType.string, ), r'sendPending': PropertySchema( - id: 42, + id: 45, name: r'sendPending', type: IsarType.string, ), r'stack': PropertySchema( - id: 43, + id: 46, name: r'stack', type: IsarType.string, ), r'stackIcon': PropertySchema( - id: 44, + id: 47, name: r'stackIcon', type: IsarType.string, ), r'themePreview': PropertySchema( - id: 45, + id: 48, name: r'themePreview', type: IsarType.string, ), r'themeSelector': PropertySchema( - id: 46, + id: 49, name: r'themeSelector', type: IsarType.string, ), r'txExchange': PropertySchema( - id: 47, + id: 50, name: r'txExchange', type: IsarType.string, ), r'txExchangeFailed': PropertySchema( - id: 48, + id: 51, name: r'txExchangeFailed', type: IsarType.string, ), r'txExchangePending': PropertySchema( - id: 49, + id: 52, name: r'txExchangePending', type: IsarType.string, ), r'wownero': PropertySchema( - id: 50, + id: 53, name: r'wownero', type: IsarType.string, ), r'wowneroImage': PropertySchema( - id: 51, + id: 54, name: r'wowneroImage', type: IsarType.string, ), r'wowneroImageSecondary': PropertySchema( - id: 52, + id: 55, name: r'wowneroImageSecondary', type: IsarType.string, ) @@ -18332,6 +18347,9 @@ int _themeAssetsEstimateSize( bytesCount += 3 + value.length * 3; } } + bytesCount += 3 + object.mimblewimblecoin.length * 3; + bytesCount += 3 + object.mimblewimblecoinImage.length * 3; + bytesCount += 3 + object.mimblewimblecoinImageSecondary.length * 3; bytesCount += 3 + object.monero.length * 3; bytesCount += 3 + object.moneroImage.length * 3; bytesCount += 3 + object.moneroImageSecondary.length * 3; @@ -18394,33 +18412,36 @@ void _themeAssetsSerialize( writer.writeString(offsets[23], object.litecoinImage); writer.writeString(offsets[24], object.litecoinImageSecondary); writer.writeString(offsets[25], object.loadingGif); - writer.writeString(offsets[26], object.monero); - writer.writeString(offsets[27], object.moneroImage); - writer.writeString(offsets[28], object.moneroImageSecondary); - writer.writeString(offsets[29], object.namecoin); - writer.writeString(offsets[30], object.namecoinImage); - writer.writeString(offsets[31], object.namecoinImageSecondary); - writer.writeString(offsets[32], object.particl); - writer.writeString(offsets[33], object.particlImage); - writer.writeString(offsets[34], object.particlImageSecondary); - writer.writeString(offsets[35], object.personaEasy); - writer.writeString(offsets[36], object.personaIncognito); - writer.writeString(offsets[37], object.receive); - writer.writeString(offsets[38], object.receiveCancelled); - writer.writeString(offsets[39], object.receivePending); - writer.writeString(offsets[40], object.send); - writer.writeString(offsets[41], object.sendCancelled); - writer.writeString(offsets[42], object.sendPending); - writer.writeString(offsets[43], object.stack); - writer.writeString(offsets[44], object.stackIcon); - writer.writeString(offsets[45], object.themePreview); - writer.writeString(offsets[46], object.themeSelector); - writer.writeString(offsets[47], object.txExchange); - writer.writeString(offsets[48], object.txExchangeFailed); - writer.writeString(offsets[49], object.txExchangePending); - writer.writeString(offsets[50], object.wownero); - writer.writeString(offsets[51], object.wowneroImage); - writer.writeString(offsets[52], object.wowneroImageSecondary); + writer.writeString(offsets[26], object.mimblewimblecoin); + writer.writeString(offsets[27], object.mimblewimblecoinImage); + writer.writeString(offsets[28], object.mimblewimblecoinImageSecondary); + writer.writeString(offsets[29], object.monero); + writer.writeString(offsets[30], object.moneroImage); + writer.writeString(offsets[31], object.moneroImageSecondary); + writer.writeString(offsets[32], object.namecoin); + writer.writeString(offsets[33], object.namecoinImage); + writer.writeString(offsets[34], object.namecoinImageSecondary); + writer.writeString(offsets[35], object.particl); + writer.writeString(offsets[36], object.particlImage); + writer.writeString(offsets[37], object.particlImageSecondary); + writer.writeString(offsets[38], object.personaEasy); + writer.writeString(offsets[39], object.personaIncognito); + writer.writeString(offsets[40], object.receive); + writer.writeString(offsets[41], object.receiveCancelled); + writer.writeString(offsets[42], object.receivePending); + writer.writeString(offsets[43], object.send); + writer.writeString(offsets[44], object.sendCancelled); + writer.writeString(offsets[45], object.sendPending); + writer.writeString(offsets[46], object.stack); + writer.writeString(offsets[47], object.stackIcon); + writer.writeString(offsets[48], object.themePreview); + writer.writeString(offsets[49], object.themeSelector); + writer.writeString(offsets[50], object.txExchange); + writer.writeString(offsets[51], object.txExchangeFailed); + writer.writeString(offsets[52], object.txExchangePending); + writer.writeString(offsets[53], object.wownero); + writer.writeString(offsets[54], object.wowneroImage); + writer.writeString(offsets[55], object.wowneroImageSecondary); } ThemeAssets _themeAssetsDeserialize( @@ -18456,33 +18477,36 @@ ThemeAssets _themeAssetsDeserialize( object.litecoinImage = reader.readString(offsets[23]); object.litecoinImageSecondary = reader.readString(offsets[24]); object.loadingGif = reader.readStringOrNull(offsets[25]); - object.monero = reader.readString(offsets[26]); - object.moneroImage = reader.readString(offsets[27]); - object.moneroImageSecondary = reader.readString(offsets[28]); - object.namecoin = reader.readString(offsets[29]); - object.namecoinImage = reader.readString(offsets[30]); - object.namecoinImageSecondary = reader.readString(offsets[31]); - object.particl = reader.readString(offsets[32]); - object.particlImage = reader.readString(offsets[33]); - object.particlImageSecondary = reader.readString(offsets[34]); - object.personaEasy = reader.readString(offsets[35]); - object.personaIncognito = reader.readString(offsets[36]); - object.receive = reader.readString(offsets[37]); - object.receiveCancelled = reader.readString(offsets[38]); - object.receivePending = reader.readString(offsets[39]); - object.send = reader.readString(offsets[40]); - object.sendCancelled = reader.readString(offsets[41]); - object.sendPending = reader.readString(offsets[42]); - object.stack = reader.readString(offsets[43]); - object.stackIcon = reader.readString(offsets[44]); - object.themePreview = reader.readString(offsets[45]); - object.themeSelector = reader.readString(offsets[46]); - object.txExchange = reader.readString(offsets[47]); - object.txExchangeFailed = reader.readString(offsets[48]); - object.txExchangePending = reader.readString(offsets[49]); - object.wownero = reader.readString(offsets[50]); - object.wowneroImage = reader.readString(offsets[51]); - object.wowneroImageSecondary = reader.readString(offsets[52]); + object.mimblewimblecoin = reader.readString(offsets[26]); + object.mimblewimblecoinImage = reader.readString(offsets[27]); + object.mimblewimblecoinImageSecondary = reader.readString(offsets[28]); + object.monero = reader.readString(offsets[29]); + object.moneroImage = reader.readString(offsets[30]); + object.moneroImageSecondary = reader.readString(offsets[31]); + object.namecoin = reader.readString(offsets[32]); + object.namecoinImage = reader.readString(offsets[33]); + object.namecoinImageSecondary = reader.readString(offsets[34]); + object.particl = reader.readString(offsets[35]); + object.particlImage = reader.readString(offsets[36]); + object.particlImageSecondary = reader.readString(offsets[37]); + object.personaEasy = reader.readString(offsets[38]); + object.personaIncognito = reader.readString(offsets[39]); + object.receive = reader.readString(offsets[40]); + object.receiveCancelled = reader.readString(offsets[41]); + object.receivePending = reader.readString(offsets[42]); + object.send = reader.readString(offsets[43]); + object.sendCancelled = reader.readString(offsets[44]); + object.sendPending = reader.readString(offsets[45]); + object.stack = reader.readString(offsets[46]); + object.stackIcon = reader.readString(offsets[47]); + object.themePreview = reader.readString(offsets[48]); + object.themeSelector = reader.readString(offsets[49]); + object.txExchange = reader.readString(offsets[50]); + object.txExchangeFailed = reader.readString(offsets[51]); + object.txExchangePending = reader.readString(offsets[52]); + object.wownero = reader.readString(offsets[53]); + object.wowneroImage = reader.readString(offsets[54]); + object.wowneroImageSecondary = reader.readString(offsets[55]); return object; } @@ -18599,6 +18623,12 @@ P _themeAssetsDeserializeProp

( return (reader.readString(offset)) as P; case 52: return (reader.readString(offset)) as P; + case 53: + return (reader.readString(offset)) as P; + case 54: + return (reader.readString(offset)) as P; + case 55: + return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); } @@ -22170,6 +22200,417 @@ extension ThemeAssetsQueryFilter }); } + QueryBuilder + mimblewimblecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoin', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImage', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'mimblewimblecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'mimblewimblecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'mimblewimblecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + mimblewimblecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'mimblewimblecoinImageSecondary', + value: '', + )); + }); + } + QueryBuilder moneroEqualTo( String value, { bool caseSensitive = true, diff --git a/lib/models/mwcmqs_config_model.dart b/lib/models/mwcmqs_config_model.dart new file mode 100644 index 0000000000..6970905106 --- /dev/null +++ b/lib/models/mwcmqs_config_model.dart @@ -0,0 +1,118 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:convert'; + +import 'package:hive/hive.dart'; + +import 'mwcmqs_server_model.dart'; + +part 'type_adaptors/mwcmqs_config_model.g.dart'; + +@HiveType(typeId: 82) +class MwcMqsConfigModel { + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + @HiveField(3) + final bool? protocolInsecure; + @HiveField(4) + final int? addressIndex; + + MwcMqsConfigModel({ + required this.host, + this.port, + this.protocolInsecure, + this.addressIndex, + }); + + MwcMqsConfigModel copyWith({ + int? port, + bool? protocolInsecure, + int? addressIndex, + String? id, + String? name, + }) { + return MwcMqsConfigModel( + host: host, + port: this.port ?? 443, + protocolInsecure: this.protocolInsecure ?? false, + addressIndex: this.addressIndex ?? 0, + ); + } + + Map toMap() { + final Map map = {}; + map['mwcmqs_domain'] = host; + map['mwcmqs_port'] = port; + map['mwcmqs_protocol_insecure'] = protocolInsecure; + map['mwcmqs_address_index'] = addressIndex; + return map; + } + + Map toJson() { + return { + 'mwcmqs_domain': host, + 'mwcmqs_port': port, + 'mwcmqs_protocol_insecure': protocolInsecure, + 'mwcmqs_address_index': addressIndex, + }; + } + + @override + String toString() { + return json.encode(toJson()); + } + + static MwcMqsConfigModel fromString(String MwcMqsConfigString) { + final dynamic _mwcmqs = json.decode(MwcMqsConfigString); + + final oldDomain = _mwcmqs["domain"] ?? "empty"; + if (oldDomain != "empty") { + _mwcmqs['mwcmqs_domain'] = _mwcmqs['domain']; + } + final oldPort = _mwcmqs["port"] ?? "empty"; + if (oldPort != "empty") { + _mwcmqs['mwcmqs_port'] = _mwcmqs['port']; + } + final oldProtocolInsecure = _mwcmqs["protocol_insecure"] ?? "empty"; + if (oldProtocolInsecure != "empty") { + _mwcmqs['mwcmqs_protocol_insecure'] = _mwcmqs['protocol_insecure']; + } + final oldAddressIndex = _mwcmqs["address_index"] ?? "empty"; + if (oldAddressIndex != "empty") { + _mwcmqs['mwcmqs_address_index'] = _mwcmqs['address_index']; + } + + _mwcmqs['mwcmqs_protocol_insecure'] ??= false; + _mwcmqs['mwcmqs_address_index'] ??= 0; + + return MwcMqsConfigModel( + host: _mwcmqs['mwcmqs_domain'] as String, + port: _mwcmqs['mwcmqs_port'] as int, + protocolInsecure: _mwcmqs['mwcmqs_protocol_insecure'] as bool, + addressIndex: _mwcmqs['mwcmqs_address_index'] as int, + ); + } + + static MwcMqsConfigModel fromServer( + MwcMqsServerModel server, { + bool? protocolInsecure, + int? addressIndex, + }) { + return MwcMqsConfigModel( + host: server.host, + port: server.port ?? 443, + protocolInsecure: protocolInsecure ?? false, + addressIndex: addressIndex ?? 0, + ); + } +} diff --git a/lib/models/mwcmqs_server_model.dart b/lib/models/mwcmqs_server_model.dart new file mode 100644 index 0000000000..c6ffd757f9 --- /dev/null +++ b/lib/models/mwcmqs_server_model.dart @@ -0,0 +1,93 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:hive/hive.dart'; + +part 'type_adaptors/mwcmqs_server_model.g.dart'; + +@HiveType(typeId: 81) +class MwcMqsServerModel { + @HiveField(0) + final String id; + @HiveField(1) + final String host; + @HiveField(2) + final int? port; + @HiveField(3) + final String name; + @HiveField(4) + final bool? useSSL; + @HiveField(5) + final bool? enabled; + @HiveField(6) + final bool? isFailover; + @HiveField(7) + final bool? isDown; + + MwcMqsServerModel({ + required this.id, + required this.host, + this.port, + required this.name, + this.useSSL, + this.enabled, + this.isFailover, + this.isDown, + }); + + MwcMqsServerModel copyWith({ + String? host, + int? port, + String? name, + bool? useSSL, + bool? enabled, + bool? isFailover, + bool? isDown, + }) { + return MwcMqsServerModel( + id: id, + host: host ?? this.host, + port: port ?? this.port, + name: name ?? this.name, + useSSL: useSSL ?? this.useSSL, + enabled: enabled ?? this.enabled, + isFailover: isFailover ?? this.isFailover, + isDown: isDown ?? this.isDown, + ); + } + + Map toMap() { + final Map map = {}; + map['id'] = id; + map['host'] = host; + map['port'] = port; + map['name'] = name; + map['useSSL'] = useSSL; + map['enabled'] = enabled; + map['isFailover'] = isFailover; + map['isDown'] = isDown; + return map; + } + + bool get isDefault => id.startsWith("default_"); + + Map toJson() { + return { + 'id': id, + 'host': host, + 'port': port, + 'name': name, + 'useSSL': useSSL, + 'enabled': enabled, + 'isFailover': isFailover, + 'isDown': isDown, + }; + } +} diff --git a/lib/models/type_adaptors/mwcmqs_config_model.g.dart b/lib/models/type_adaptors/mwcmqs_config_model.g.dart new file mode 100644 index 0000000000..e64fc54082 --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_config_model.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_config_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsConfigModelAdapter extends TypeAdapter { + @override + final int typeId = 82; + + @override + MwcMqsConfigModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsConfigModel( + host: fields[1] as String, + port: fields[2] as int?, + protocolInsecure: fields[3] as bool?, + addressIndex: fields[4] as int?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsConfigModel obj) { + writer + ..writeByte(4) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.protocolInsecure) + ..writeByte(4) + ..write(obj.addressIndex); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsConfigModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/mwcmqs_server_model.g.dart b/lib/models/type_adaptors/mwcmqs_server_model.g.dart new file mode 100644 index 0000000000..dabdd9464d --- /dev/null +++ b/lib/models/type_adaptors/mwcmqs_server_model.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../mwcmqs_server_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class MwcMqsServerModelAdapter extends TypeAdapter { + @override + final int typeId = 81; + + @override + MwcMqsServerModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return MwcMqsServerModel( + id: fields[0] as String, + host: fields[1] as String, + port: fields[2] as int?, + name: fields[3] as String, + useSSL: fields[4] as bool?, + enabled: fields[5] as bool?, + isFailover: fields[6] as bool?, + isDown: fields[7] as bool?, + ); + } + + @override + void write(BinaryWriter writer, MwcMqsServerModel obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.name) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.enabled) + ..writeByte(6) + ..write(obj.isFailover) + ..writeByte(7) + ..write(obj.isDown); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MwcMqsServerModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} 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 44a0c2157d..47400a0acb 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 @@ -255,24 +255,32 @@ class _RestoreWalletViewState extends ConsumerState { if (height < 0) { height = 0; } - - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index - if (widget.coin is Mimblewimblecoin) { - final int secondsSinceEpoch = - widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; - const int mimblewimblecoinFirstBlock = 1565370278; + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: height, + restoreHeight: height, + creationHeight: height, + ).toMap(), + ), + }, + ); + } else if (widget.coin is Mimblewimblecoin) { + final int secondsSinceEpoch = widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + const int mimblewimblecoinFirstBlock = 1573462801; const double overestimateSecondsPerBlock = 61; final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; - //todo: check if print needed - // debugPrint( - // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); height = approximateHeight; if (height < 0) { height = 0; } - otherDataJsonString = jsonEncode( { WalletInfoKeys.mimblewimblecoinData: jsonEncode( 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 2a217dcb92..ea21bd3250 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 @@ -732,7 +732,7 @@ class MwcMqsInfoForm extends ConsumerStatefulWidget { final String walletId; @override - ConsumerState createState() => _MwcMqsInfoFormState(); + ConsumerState createState() => _MwcmqsInfoFormState(); } class _MwcmqsInfoFormState extends ConsumerState { diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index aa9bb6c3c9..eab56867d9 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -234,7 +234,7 @@ class AddressUtils { } /// Formats an address string to remove any unnecessary prefixes or suffixes. - String formatMimblewimblecoinAddress(String mimblewimblecoinAddress) { + String formatAddressMwc(String mimblewimblecoinAddress) { // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an mwcmqs address) if ((mimblewimblecoinAddress.startsWith("http://") || mimblewimblecoinAddress.startsWith("https://")) && diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart new file mode 100644 index 0000000000..44433a1036 --- /dev/null +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -0,0 +1,81 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:convert'; + +import '../networking/http.dart'; +import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import '../services/tor_service.dart'; +import 'logger.dart'; +import 'prefs.dart'; + +Future _testMwcMqsNodeConnection(Uri uri) async { + final HTTP client = HTTP(); + try { + final response = await client + .get( + url: uri, + headers: {'Content-Type': 'application/json'}, + proxyInfo: Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ) + .timeout( + const Duration(milliseconds: 2000), + onTimeout: () async => Response(utf8.encode('Error'), 408), + ); + + final json = jsonDecode(response.body); + + if (response.code == 200 && json["node_version"] != null) { + return true; + } else { + return false; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return false; + } +} + +// returns node data with properly formatted host/url if successful, otherwise null +Future testMwcNodeConnection(NodeFormData data) async { + if (data.host == null || data.port == null || data.useSSL == null) { + return null; + } + const String path_postfix = "/v1/version"; + + if (data.host!.startsWith("https://")) { + data.useSSL = true; + } else if (data.host!.startsWith("http://")) { + data.useSSL = false; + } else { + if (data.useSSL!) { + data.host = "https://${data.host!}"; + } else { + data.host = "http://${data.host!}"; + } + } + + Uri uri = Uri.parse(data.host! + path_postfix); + + uri = uri.replace(port: data.port); + + try { + if (await _testMwcMqsNodeConnection(uri)) { + return data; + } else { + return null; + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + return null; + } +} diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index b06ec1f1d4..9ea295dd4e 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -14,7 +14,7 @@ class Mimblewimblecoin extends Bip39Currency { switch (network) { case CryptoCurrencyNetwork.main: _id = _idMain; - _name = "Mimblewimblecoin"; + _name = "MimbleWimbleCoin"; _ticker = "MWC"; default: throw Exception("Unsupported network: $network"); @@ -63,7 +63,7 @@ class Mimblewimblecoin extends Bip39Currency { } } - return mimblewimblecoin.LibMwc.validateSendAddress(address: address); + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } @override @@ -71,7 +71,7 @@ class Mimblewimblecoin extends Bip39Currency { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( - host: "http://epiccash.stackwallet.com", + host: "http://mwc713.mwc.mw", port: 3413, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(this), diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index ed77e34bc7..9edce8a5a1 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -31,7 +31,8 @@ import '../../../utilities/amount/amount.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/stack_file_system.dart'; -//import '../../../utilities/test_epic_box_connection.dart'; +import '../../../utilities/default_mwcmqs.dart'; +import '../../../utilities/test_mwcmqs_connection.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; @@ -67,20 +68,20 @@ class MimblewimblecoinWallet extends Bip39Wallet { return restorePercent < 0 ? 0.0 : restorePercent; } - /*Future updateEpicboxConfig(String host, int port) async { + Future updateMwcmqsConfig(String host, int port) async { final String stringConfig = jsonEncode({ - "epicbox_domain": host, - "epicbox_port": port, - "epicbox_protocol_unsecure": false, - "epicbox_address_index": 0, + "mwcmqs_domain": host, + "mwcmqs_port": port, + "mwcmqs_protocol_unsecure": false, + "mwcmqs_address_index": 0, }); await secureStorageInterface.write( - key: '${walletId}_epicboxConfig', + key: '${walletId}_mwcmqsConfig', value: stringConfig, ); - // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed + // TODO: refresh anything that needs to be refreshed/updated due to mwcmqs info changed } - */ + /// returns an empty String on success, error message on failure Future cancelPendingTransactionAndPost(String txSlateId) async { try { @@ -88,7 +89,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', ))!; - final result = await mimblewimblecoin.LibMwc.cancelTransaction( + final result = await mimblewimblecoin.Libmwc.cancelTransaction( wallet: wallet, transactionId: txSlateId, ); @@ -108,24 +109,24 @@ class MimblewimblecoinWallet extends Bip39Wallet { DefaultMwcMqs.defaultMwcMqsServer, ); - //Get the default Epicbox server and check if it's conected - // bool isEpicboxConnected = await _testEpicboxServer( - // DefaultEpicBoxes.defaultEpicBoxServer.host, DefaultEpicBoxes.defaultEpicBoxServer.port ?? 443); + //Get the default mwcmqs server and check if it's conected + // bool ismwcmqsConnected = await _testmwcmqsServer( + // Defaultmwcmqses.defaultmwcmqsServer.host, Defaultmwcmqses.defaultmwcmqsServer.port ?? 443); - // if (isEpicboxConnected) { - //Use default server for as Epicbox config + // if (ismwcmqsConnected) { + //Use default server for as mwcmqs config // } // else { // //Use Europe config - // _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + // _mwcmqsConfig = mwcmqsConfigModel.fromServer(Defaultmwcmqses.europe); // } // // example of selecting another random server from the default list // // alternative servers: copy list of all default EB servers but remove the default default - // // List alternativeServers = DefaultEpicBoxes.all; - // // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // // List alternativeServers = Defaultmwcmqses.all; + // // alternativeServers.removeWhere((opt) => opt.name == Defaultmwcmqses.defaultmwcmqsServer.name); // // alternativeServers.shuffle(); // randomize which server is used - // // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + // // _mwcmqsConfig = mwcmqsConfigModel.fromServer(alternativeServers.first); // // // TODO test this connection before returning it // } @@ -154,9 +155,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { config["check_node_api_http_addr"] = nodeApiAddress; config["chain"] = "mainnet"; config["account"] = "default"; - config["api_listen_port"] = port; - config["api_listen_interface"] = - nodeApiAddress.replaceFirst(uri.scheme, ""); final String stringConfig = jsonEncode(config); return stringConfig; } @@ -177,7 +175,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await mimblewimblecoin.LibMwc.getTransactionFees( + final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees( wallet: wallet!, amount: satoshiAmount, minimumConfirmations: cryptoCurrency.minConfirms, @@ -206,7 +204,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (!syncMutex.isLocked) { await syncMutex.protect(() async { // How does getWalletBalances start syncing???? - await mimblewimblecoin.LibMwc.getWalletBalances( + await mimblewimblecoin.Libmwc.getWalletBalances( wallet: wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: 10, @@ -226,16 +224,16 @@ class MimblewimblecoinWallet extends Bip39Wallet { })> _allWalletBalances() async { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const refreshFromNode = 0; - return await mimblewimblecoin.LibMwc.getWalletBalances( + return await mimblewimblecoin.Libmwc.getWalletBalances( wallet: wallet!, refreshFromNode: refreshFromNode, minimumConfirmations: cryptoCurrency.minConfirms, ); } - /*Future _testEpicboxServer(EpicBoxConfigModel epicboxConfig) async { - final host = epicboxConfig.host; - final port = epicboxConfig.port ?? 443; + Future _testMwcmqsServer(MwcMqsConfigModel mwcmqsConfig) async { + final host = mwcmqsConfig.host; + final port = mwcmqsConfig.port ?? 443; WebSocketChannel? channel; try { final uri = Uri.parse('wss://$host:$port'); @@ -253,14 +251,14 @@ class MimblewimblecoinWallet extends Bip39Wallet { return response is String && response.contains("Challenge"); } catch (_) { Logging.instance.log( - "_testEpicBoxConnection failed on \"$host:$port\"", + "_testMwcmqsConnection failed on \"$host:$port\"", level: LogLevel.Info, ); return false; } finally { await channel?.sink.close(); } - }*/ + } Future _putSendToAddresses( ({String slateId, String commitId}) slateData, @@ -308,7 +306,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (address != null) { final splitted = address.value.split('@'); - //Check if the address is the same as the current epicbox domain + //Check if the address is the same as the current mwcmqs domain //Since we're only using one epicbpox now this doesn't apply but will be // useful in the future final mwcmqsConfig = await getMwcMqsConfig(); @@ -336,10 +334,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { ) async { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); - final walletAddress = await mimblewimblecoin.LibMwc.getAddressInfo( + final walletAddress = await mimblewimblecoin.Libmwc.getAddressInfo( wallet: wallet!, - index: index, - mwcmqsConfig: mwcmqsConfig.toString(), + index: index ); Logging.instance.log( @@ -363,7 +360,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future _startScans() async { try { //First stop the current listener - mimblewimblecoin.LibMwc.stopMwcmqsListener(); + mimblewimblecoin.Libmwc.stopMwcMqsListener(); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); @@ -385,7 +382,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { level: LogLevel.Info, ); - final int nextScannedBlock = await mimblewimblecoin.LibMwc.scanOutputs( + final int nextScannedBlock = await mimblewimblecoin.Libmwc.scanOutputs( wallet: wallet!, startHeight: lastScannedBlock, numberOfBlocks: scanChunkSize, @@ -426,7 +423,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); - mimblewimblecoin.LibMwc.startMwcMqsListener( + mimblewimblecoin.Libmwc.startMwcMqsListener( wallet: wallet!, mwcmqsConfig: mwcmqsConfig.toString(), ); @@ -509,7 +506,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String name = walletId; - await mimblewimblecoin.LibMwc.initializeNewWallet( + await mimblewimblecoin.Libmwc.initializeNewWallet( config: stringConfig, mnemonic: mnemonicString, password: password, @@ -517,7 +514,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open wallet - encodedWallet = await mimblewimblecoin.LibMwc.openWallet( + encodedWallet = await mimblewimblecoin.Libmwc.openWallet( config: stringConfig, password: password, ); @@ -559,7 +556,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final password = await secureStorageInterface.read(key: '${walletId}_password'); - final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + final walletOpen = await mimblewimblecoin.Libmwc.openWallet( config: config, password: password!, ); @@ -608,7 +605,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { - transaction = await mimblewimblecoin.LibMwc.txHttpSend( + transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, minimumConfirmations: cryptoCurrency.minConfirms, @@ -617,12 +614,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { address: txData.recipients!.first.address, ); } else { - transaction = await mimblewimblecoin.LibMwc.createTransaction( + transaction = await mimblewimblecoin.Libmwc.createTransaction( wallet: wallet!, amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, secretKeyIndex: 0, - mwcmqsConfig: mwsmqsConfig.toString(), + mwcmqsConfig: mwcmqsConfig.toString(), minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, ); @@ -724,7 +721,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: mwcmqsConfig.toString(), ); - await mimblewimblecoin.LibMwc.recoverWallet( + await mimblewimblecoin.Libmwc.recoverWallet( config: stringConfig, password: password, mnemonic: await getMnemonic(), @@ -748,7 +745,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Open Wallet - final walletOpen = await mimblewimblecoin.LibMwc.openWallet( + final walletOpen = await mimblewimblecoin.Libmwc.openWallet( config: stringConfig, password: password, ); @@ -943,7 +940,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { .findAll(); final myAddressesSet = myAddresses.toSet(); - final transactions = await mimblewimblecoin.LibMwc.getTransactions( + final transactions = await mimblewimblecoin.Libmwc.getTransactions( wallet: wallet!, refreshFromNode: refreshFromNode, ); @@ -1095,7 +1092,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // force unwrap optional as we want connection test to fail if wallet // wasn't initialized or mwcmqs node was set to null - return await testMimblewimblecoinNodeConnection( + return await testMwcNodeConnection( NodeFormData() ..host = node!.host ..useSSL = node.useSSL @@ -1112,7 +1109,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future updateChainHeight() async { final config = await _getRealConfig(); final latestHeight = - await mimblewimblecoin.LibMwc.getChainHeight(config: config); + await mimblewimblecoin.Libmwc.getChainHeight(config: config); await info.updateCachedChainHeight( newHeight: latestHeight, isar: mainDB.isar, @@ -1185,7 +1182,7 @@ Future deleteMimblewimblecoinWallet({ return "Tried to delete non existent mimblewimblecoin wallet file with walletId=$walletId"; } else { try { - return mimblewimblecoin.LibMwc.deleteWallet( + return mimblewimblecoin.Libmwc.deleteWallet( wallet: wallet, config: config!, ); diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart index 724657079e..0c876d5c57 100644 --- a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -5,7 +5,7 @@ import '../../../utilities/logger.dart'; import '../../isar/models/wallet_info.dart'; extension MimblewimblecoinWalletInfoExtension on WalletInfo { - ExtraMimblewimblecoinWalletInfo? get epicData { + ExtraMimblewimblecoinWalletInfo? get mimblewimblecoinData { final String? data = otherData[WalletInfoKeys.mimblewimblecoinData] as String?; if (data == null) { @@ -27,12 +27,12 @@ extension MimblewimblecoinWalletInfoExtension on WalletInfo { } Future updateExtraMimblewimblecoinWalletInfo({ - required ExtraMimblewimblecoinWalletInfo epicData, + required ExtraMimblewimblecoinWalletInfo mimblewimblecoinData, required Isar isar, }) async { await updateOtherData( newEntries: { - WalletInfoKeys.mimblewimblecoinData: jsonEncode(epicData.toMap()), + WalletInfoKeys.mimblewimblecoinData: jsonEncode(mimblewimblecoinData.toMap()), }, isar: isar, ); diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 78079da970..2b5ee1ac4d 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -361,7 +361,7 @@ abstract class Wallet { return EpiccashWallet(net); case const (Mimblewimblecoin): - return Mimblewimblecoin(net); + return MimblewimblecoinWallet(net); case const (Ethereum): return EthereumWallet(net); diff --git a/pubspec.lock b/pubspec.lock index 285d841128..9122380698 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -959,7 +959,7 @@ packages: source: hosted version: "2.4.4" frontend_server_client: - dependency: transitive + dependency: "direct dev" description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index d6ac3f66d3..3707ec1b4e 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -137,7 +137,7 @@ install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libepiccash/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libepic_cash_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libemwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_libmwc/scripts/linux/build/rust/target/x86_64-unknown-linux-gnu/release/libmwc_wallet.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../crypto_plugins/flutter_liblelantus/scripts/linux/build/libmobileliblelantus.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" diff --git a/scripts/linux/build_secp256k1.sh b/scripts/linux/build_secp256k1.sh index f00b6b82bf..cf7650ffe1 100755 --- a/scripts/linux/build_secp256k1.sh +++ b/scripts/linux/build_secp256k1.sh @@ -10,5 +10,5 @@ mkdir -p build && cd build cmake .. cmake --build . mkdir -p ../../../../../build -cp lib/libsecp256k1.so.2.2.2 "../../../../../build/libsecp256k1.so" +cp lib/libsecp256k1.so.2.*.* "../../../../../build/libsecp256k1.so" cd ../../../ diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 993f73f694..e37380e0b1 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -1113,6 +1113,19 @@ class MockPrefs extends _i1.Mock implements _i8.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index fee6a0d7f5..c20fd4e293 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -1129,6 +1129,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index b7a8d49d51..3f04c3fc27 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -558,6 +558,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index c2bc00d2c0..ee2de45f91 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -834,6 +834,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 5e878015ff..26f7b6cf34 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -709,6 +709,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index a5816bdcbb..d9234ff9bf 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -818,6 +818,19 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override + bool get enableExchange => (super.noSuchMethod( + Invocation.getter(#enableExchange), + returnValue: false, + ) as bool); + @override + set enableExchange(bool? showExchange) => super.noSuchMethod( + Invocation.setter( + #enableExchange, + showExchange, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, From 8610a7261ca57a9d55dbcfce15ff1f7fa21e5163 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 27 Oct 2024 17:07:52 +0400 Subject: [PATCH 08/91] disable PingCheck --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_liblelantus | 2 +- lib/utilities/default_mwcmqs.dart | 6 +-- .../wallet/impl/mimblewimblecoin_wallet.dart | 43 ++++++++++--------- pubspec.lock | 2 +- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 0bb1b1ced6..1d48c0a8aa 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 0bb1b1ced6e0d3c66e383698f89825754c692986 +Subproject commit 1d48c0a8aa394324e7c39267e5654038834aff95 diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index 5b08645a5b..8e9e20e2f9 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit 5b08645a5b5d30955f4bde2a624ff89ef516e452 +Subproject commit 8e9e20e2f90387dcc6e23833d186a84ad3ac372a diff --git a/lib/utilities/default_mwcmqs.dart b/lib/utilities/default_mwcmqs.dart index 75be1d247b..84e7356d84 100644 --- a/lib/utilities/default_mwcmqs.dart +++ b/lib/utilities/default_mwcmqs.dart @@ -17,7 +17,7 @@ abstract class DefaultMwcMqs { static List get defaultIds => ['americas', 'asia', 'europe']; static MwcMqsServerModel get americas => MwcMqsServerModel( - host: 'MwcMqs.stackwallet.com', + host: 'mqs.mwc.mw', port: 443, name: 'Americas', id: 'americas', @@ -28,7 +28,7 @@ abstract class DefaultMwcMqs { ); static MwcMqsServerModel get asia => MwcMqsServerModel( - host: 'MwcMqs.hyperbig.com', + host: 'mqs.mwc.mw', port: 443, name: 'Asia', id: 'asia', @@ -39,7 +39,7 @@ abstract class DefaultMwcMqs { ); static MwcMqsServerModel get europe => MwcMqsServerModel( - host: 'MwcMqs.fastepic.eu', + host: 'mqs.mwc.mw', port: 443, name: 'Europe', id: 'europe', diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 9edce8a5a1..83d038e9dd 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -524,7 +524,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); //Store MwcMqs address info - await _generateAndStoreReceivingAddressForIndex(0); + // await _generateAndStoreReceivingAddressForIndex(0); // subtract a couple days to ensure we have a buffer for SWB final bufferedCreateHeight = _calculateRestoreHeightFrom( @@ -754,9 +754,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: walletOpen, ); - await _generateAndStoreReceivingAddressForIndex( - mimblewimblecoinData.receivingIndex, - ); + // await _generateAndStoreReceivingAddressForIndex( + // mimblewimblecoinData.receivingIndex, + // ); } }); @@ -799,7 +799,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // this will always be zero???? final int curAdd = await _getCurrentIndex(); - await _generateAndStoreReceivingAddressForIndex(curAdd); + // await _generateAndStoreReceivingAddressForIndex(curAdd); await _startScans(); @@ -1087,22 +1087,23 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future pingCheck() async { - try { - final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); - - // force unwrap optional as we want connection test to fail if wallet - // wasn't initialized or mwcmqs node was set to null - return await testMwcNodeConnection( - NodeFormData() - ..host = node!.host - ..useSSL = node.useSSL - ..port = node.port, - ) != - null; - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); - return false; - } + return true; + //try { + // final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); +// + // // force unwrap optional as we want connection test to fail if wallet + // // wasn't initialized or mwcmqs node was set to null + // return await testMwcNodeConnection( + // NodeFormData() + // ..host = node!.host + // ..useSSL = node.useSSL + // ..port = node.port, + // ) != + // null; + //} catch (e, s) { + // Logging.instance.log("$e\n$s", level: LogLevel.Info); + // return false; + //} } @override diff --git a/pubspec.lock b/pubspec.lock index 9122380698..285d841128 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -959,7 +959,7 @@ packages: source: hosted version: "2.4.4" frontend_server_client: - dependency: "direct dev" + dependency: transitive description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 From 1bd8efe04461608951f6bbe8ab50e505eb350716 Mon Sep 17 00:00:00 2001 From: vekamo Date: Tue, 12 Nov 2024 22:00:58 +0400 Subject: [PATCH 09/91] few fixes and remove mwc-wallet logs --- .../transaction_details_view.dart | 58 ++++++++++- .../tx_v2/transaction_v2_details_view.dart | 16 +-- lib/utilities/test_mwcmqs_connection.dart | 16 ++- lib/utilities/test_node_connection.dart | 14 +++ .../coins/mimblewimblecoin.dart | 41 ++++---- .../wallet/impl/mimblewimblecoin_wallet.dart | 99 +++++++++---------- 6 files changed, 148 insertions(+), 96 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1071ffdaec..777f2b75ee 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -1730,7 +1731,7 @@ class _TransactionDetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && + floatingActionButton: ((coin is Epiccash || coin is Mimblewimblecoin) && _transaction.getConfirmations(currentHeight) < 1 && _transaction.isCancelled == false) ? ConditionalParent( @@ -1775,6 +1776,59 @@ class _TransactionDetailsViewState ), ); + final result = + await wallet.cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find MWC transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + ), + ); + final result = await wallet.cancelPendingTransactionAndPost(id); if (mounted) { @@ -1810,7 +1864,7 @@ class _TransactionDetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); 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 53c54ae22a..3468351888 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 @@ -2151,18 +2151,7 @@ class _TransactionV2DetailsViewState ); } } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - ), - ); - return; - } - - if (wallet is MimblewimblecoinWallet) { + } else if (wallet is MimblewimblecoinWallet) { final String? id = _transaction.slateId; if (id == null) { unawaited( @@ -2220,8 +2209,7 @@ class _TransactionV2DetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: - "ERROR: Wallet type is not Mimblewimblecoin", + message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart index 44433a1036..cfcd4a098f 100644 --- a/lib/utilities/test_mwcmqs_connection.dart +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -18,11 +18,21 @@ import 'prefs.dart'; Future _testMwcMqsNodeConnection(Uri uri) async { final HTTP client = HTTP(); - try { + try { + final headers = { + 'Content-Type': 'application/json', + }; + + if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { + const username = 'mwcmain'; + const password = '11ne3EAUtOXVKwhxm84U'; + final credentials = base64Encode(utf8.encode('$username:$password')); + headers['Authorization'] = 'Basic $credentials'; + } final response = await client .get( url: uri, - headers: {'Content-Type': 'application/json'}, + headers: headers, proxyInfo: Prefs.instance.useTor ? TorService.sharedInstance.getProxyInfo() : null, @@ -67,7 +77,7 @@ Future testMwcNodeConnection(NodeFormData data) async { Uri uri = Uri.parse(data.host! + path_postfix); uri = uri.replace(port: data.port); - + try { if (await _testMwcMqsNodeConnection(uri)) { return data; diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 127702b14c..9050087882 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -21,6 +21,7 @@ import '../wallets/wallet/impl/solana_wallet.dart'; import 'connection_check/electrum_connection_check.dart'; import 'logger.dart'; import 'test_epic_box_connection.dart'; +import 'test_mwcmqs_connection.dart'; import 'test_eth_node_connection.dart'; import 'test_monero_node_connection.dart'; import 'test_stellar_node_connection.dart'; @@ -126,6 +127,19 @@ Future testNodeConnection({ } break; + case Mimblewimblecoin(): + try { + final data = await testMwcNodeConnection(formData); + + if (data != null) { + testPassed = true; + onSuccess?.call(data); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + break; + case CryptonoteCurrency(): try { final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 9ea295dd4e..3127ef035b 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -50,36 +50,33 @@ class Mimblewimblecoin extends Bip39Currency { // change this to change the number of confirms a tx needs in order to show as confirmed int get minConfirms => 3; - @override - bool validateAddress(String address) { - // Invalid address that contains HTTP and mwcmqs domain - if ((address.startsWith("http://") || address.startsWith("https://")) && - address.contains("@")) { - return false; - } - if (address.startsWith("http://") || address.startsWith("https://")) { - if (Uri.tryParse(address) != null) { - return true; - } - } - - return mimblewimblecoin.Libmwc.validateSendAddress(address: address); + @override +bool validateAddress(String address) { + Uri? uri = Uri.tryParse(address); + if (uri != null && + (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "mwcmqs") && + uri.host.isNotEmpty && + !uri.host.endsWith(".onion")) { + return true; } + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); +} + @override NodeModel get defaultNode { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( - host: "http://mwc713.mwc.mw", - port: 3413, + host: "https://mwc713.mwc.mw", + port: 443, name: DefaultNodes.defaultName, id: DefaultNodes.buildId(this), - useSSL: false, + useSSL: true, enabled: true, coinName: identifier, isFailover: true, - isDown: false, + isDown: false ); default: @@ -88,10 +85,10 @@ class Mimblewimblecoin extends Bip39Currency { } @override - int get defaultSeedPhraseLength => 24; + int get defaultSeedPhraseLength => 12; @override - int get fractionDigits => 8; + int get fractionDigits => 9; @override bool get hasBuySupport => false; @@ -100,13 +97,13 @@ class Mimblewimblecoin extends Bip39Currency { bool get hasMnemonicPassphraseSupport => false; @override - List get possibleMnemonicLengths => [defaultSeedPhraseLength, 12]; + List get possibleMnemonicLengths => [defaultSeedPhraseLength, 24]; @override AddressType get defaultAddressType => AddressType.mimbleWimble; @override - BigInt get satsPerCoin => BigInt.from(100000000); + BigInt get satsPerCoin => BigInt.from(1000000000); @override int get targetBlockTimeSeconds => 60; diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 83d038e9dd..5ce8a6b0ea 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -38,6 +38,7 @@ import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; + // // refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart // @@ -48,7 +49,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { final syncMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; - + bool _logsInitialized = false; + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = @@ -147,7 +149,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { final uri = Uri.parse(nodeAddress).replace(port: port); final String nodeApiAddress = uri.toString(); - final walletDir = await _currentWalletDirPath(); final Map config = {}; @@ -174,7 +175,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); try { final available = info.cachedBalance.spendable.raw.toInt(); - final transactionFees = await mimblewimblecoin.Libmwc.getTransactionFees( wallet: wallet!, amount: satoshiAmount, @@ -304,17 +304,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ) async { Address? address = await getCurrentReceivingAddress(); - if (address != null) { - final splitted = address.value.split('@'); - //Check if the address is the same as the current mwcmqs domain - //Since we're only using one epicbpox now this doesn't apply but will be - // useful in the future - final mwcmqsConfig = await getMwcMqsConfig(); - if (splitted[1] != mwcmqsConfig.host) { - //Update the address - address = await thisWalletAddress(index, mwcmqsConfig); - } - } else { + if (address == null) { final mwcmqsConfig = await getMwcMqsConfig(); address = await thisWalletAddress(index, mwcmqsConfig); } @@ -478,6 +468,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { + if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read(key: "${walletId}_wallet"); @@ -490,7 +481,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String password = generatePassword(); final String stringConfig = await _getConfig(); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); - + //if (!_logsInitialized) { + // await mimblewimblecoin.Libmwc.initLogs(config: stringConfig); + // _logsInitialized = true; // Set flag to true after initializing + // } await secureStorageInterface.write( key: '${walletId}_config', value: stringConfig, @@ -522,9 +516,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: encodedWallet, ); - //Store MwcMqs address info - // await _generateAndStoreReceivingAddressForIndex(0); + await _generateAndStoreReceivingAddressForIndex(0); // subtract a couple days to ensure we have a buffer for SWB final bufferedCreateHeight = _calculateRestoreHeightFrom( @@ -547,12 +540,11 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } else { try { - Logging.instance.log( - "initializeExisting() ${cryptoCurrency.prettyName} wallet", - level: LogLevel.Info, - ); - final config = await _getRealConfig(); + //if (!_logsInitialized) { + // await mimblewimblecoin.Libmwc.initLogs(config: config); + // _logsInitialized = true; // Set flag to true after initializing + //} final password = await secureStorageInterface.read(key: '${walletId}_password'); @@ -590,21 +582,22 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String receiverAddress = txData.recipients!.first.address; - if (!receiverAddress.startsWith("http://") || - !receiverAddress.startsWith("https://")) { - final bool isMwcmqsConnected = await _testMwcmqsServer( - mwcmqsConfig, - ); - if (!isMwcmqsConnected) { - throw Exception( - "Failed to send TX : Unable to reach mimblewimblecoin server"); - } - } + //if (!receiverAddress.startsWith("http://") || + // !receiverAddress.startsWith("https://")) { + // final bool isMwcmqsConnected = await _testMwcmqsServer( + // mwcmqsConfig, + // ); + // if (!isMwcmqsConnected) { + // throw Exception( + // "Failed to send TX : Unable to reach mimblewimblecoin server"); + // } + //} ({String commitId, String slateId}) transaction; if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://")) { + receiverAddress.startsWith("https://") || + receiverAddress.startsWith("mwcmqcs://")) { transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, @@ -753,10 +746,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: walletOpen, ); - - // await _generateAndStoreReceivingAddressForIndex( - // mimblewimblecoinData.receivingIndex, - // ); + + await _generateAndStoreReceivingAddressForIndex( + mimblewimblecoinData.receivingIndex, + ); } }); @@ -799,7 +792,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // this will always be zero???? final int curAdd = await _getCurrentIndex(); - // await _generateAndStoreReceivingAddressForIndex(curAdd); + await _generateAndStoreReceivingAddressForIndex(curAdd); await _startScans(); @@ -1087,23 +1080,19 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future pingCheck() async { - return true; - //try { - // final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); -// - // // force unwrap optional as we want connection test to fail if wallet - // // wasn't initialized or mwcmqs node was set to null - // return await testMwcNodeConnection( - // NodeFormData() - // ..host = node!.host - // ..useSSL = node.useSSL - // ..port = node.port, - // ) != - // null; - //} catch (e, s) { - // Logging.instance.log("$e\n$s", level: LogLevel.Info); - // return false; - //} + try { + final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency); + return await testMwcNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + return false; + } } @override From adb65319245cd3d13336c0b0940f8b84db6ef952 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 17 Nov 2024 14:59:54 +0400 Subject: [PATCH 10/91] last fixes mwc and clean up --- lib/models/mwcmqs_config_model.dart | 35 ++----------------- .../type_adaptors/mwcmqs_config_model.g.dart | 10 ++---- .../restore_options_view.dart | 12 +++---- .../restore_view_only_wallet_view.dart | 5 +++ .../restore_wallet_view.dart | 2 +- .../add_edit_node_view.dart | 8 ----- .../wallet_settings_view.dart | 1 - .../wallet/impl/mimblewimblecoin_wallet.dart | 19 +++++----- 8 files changed, 26 insertions(+), 66 deletions(-) diff --git a/lib/models/mwcmqs_config_model.dart b/lib/models/mwcmqs_config_model.dart index 6970905106..067d1a8ee5 100644 --- a/lib/models/mwcmqs_config_model.dart +++ b/lib/models/mwcmqs_config_model.dart @@ -22,30 +22,19 @@ class MwcMqsConfigModel { final String host; @HiveField(2) final int? port; - @HiveField(3) - final bool? protocolInsecure; - @HiveField(4) - final int? addressIndex; MwcMqsConfigModel({ required this.host, - this.port, - this.protocolInsecure, - this.addressIndex, + this.port }); MwcMqsConfigModel copyWith({ int? port, bool? protocolInsecure, - int? addressIndex, - String? id, - String? name, }) { return MwcMqsConfigModel( host: host, port: this.port ?? 443, - protocolInsecure: this.protocolInsecure ?? false, - addressIndex: this.addressIndex ?? 0, ); } @@ -53,8 +42,6 @@ class MwcMqsConfigModel { final Map map = {}; map['mwcmqs_domain'] = host; map['mwcmqs_port'] = port; - map['mwcmqs_protocol_insecure'] = protocolInsecure; - map['mwcmqs_address_index'] = addressIndex; return map; } @@ -62,8 +49,6 @@ class MwcMqsConfigModel { return { 'mwcmqs_domain': host, 'mwcmqs_port': port, - 'mwcmqs_protocol_insecure': protocolInsecure, - 'mwcmqs_address_index': addressIndex, }; } @@ -83,23 +68,11 @@ class MwcMqsConfigModel { if (oldPort != "empty") { _mwcmqs['mwcmqs_port'] = _mwcmqs['port']; } - final oldProtocolInsecure = _mwcmqs["protocol_insecure"] ?? "empty"; - if (oldProtocolInsecure != "empty") { - _mwcmqs['mwcmqs_protocol_insecure'] = _mwcmqs['protocol_insecure']; - } - final oldAddressIndex = _mwcmqs["address_index"] ?? "empty"; - if (oldAddressIndex != "empty") { - _mwcmqs['mwcmqs_address_index'] = _mwcmqs['address_index']; - } - _mwcmqs['mwcmqs_protocol_insecure'] ??= false; - _mwcmqs['mwcmqs_address_index'] ??= 0; return MwcMqsConfigModel( host: _mwcmqs['mwcmqs_domain'] as String, - port: _mwcmqs['mwcmqs_port'] as int, - protocolInsecure: _mwcmqs['mwcmqs_protocol_insecure'] as bool, - addressIndex: _mwcmqs['mwcmqs_address_index'] as int, + port: _mwcmqs['mwcmqs_port'] as int ); } @@ -110,9 +83,7 @@ class MwcMqsConfigModel { }) { return MwcMqsConfigModel( host: server.host, - port: server.port ?? 443, - protocolInsecure: protocolInsecure ?? false, - addressIndex: addressIndex ?? 0, + port: server.port ?? 443 ); } } diff --git a/lib/models/type_adaptors/mwcmqs_config_model.g.dart b/lib/models/type_adaptors/mwcmqs_config_model.g.dart index e64fc54082..f66fbdb56b 100644 --- a/lib/models/type_adaptors/mwcmqs_config_model.g.dart +++ b/lib/models/type_adaptors/mwcmqs_config_model.g.dart @@ -19,23 +19,17 @@ class MwcMqsConfigModelAdapter extends TypeAdapter { return MwcMqsConfigModel( host: fields[1] as String, port: fields[2] as int?, - protocolInsecure: fields[3] as bool?, - addressIndex: fields[4] as int?, ); } @override void write(BinaryWriter writer, MwcMqsConfigModel obj) { writer - ..writeByte(4) + ..writeByte(2) ..writeByte(1) ..write(obj.host) ..writeByte(2) - ..write(obj.port) - ..writeByte(3) - ..write(obj.protocolInsecure) - ..writeByte(4) - ..write(obj.addressIndex); + ..write(obj.port); } @override 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 384cacb0ca..c80eb13a28 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 @@ -362,7 +362,7 @@ class _SeedRestoreOptionState extends ConsumerState { return Column( children: [ - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) Text( "Choose start date", style: Util.isDesktop @@ -373,20 +373,20 @@ class _SeedRestoreOptionState extends ConsumerState { : STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 16 : 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RestoreFromDatePicker( onTap: widget.dateChooserFunction, controller: widget.dateController, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) const SizedBox( height: 8, ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) RoundedWhiteContainer( child: Center( child: Text( @@ -403,7 +403,7 @@ class _SeedRestoreOptionState extends ConsumerState { ), ), ), - if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25) + if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) SizedBox( height: Util.isDesktop ? 24 : 16, ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart index 3a7131762b..a116197071 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_view_only_wallet_view.dart @@ -29,6 +29,7 @@ import '../../../wallets/crypto_currency/intermediate/bip39_hd_currency.dart'; 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/impl/monero_wallet.dart'; import '../../../wallets/wallet/impl/wownero_wallet.dart'; import '../../../wallets/wallet/wallet.dart'; @@ -255,6 +256,10 @@ class _RestoreViewOnlyWalletViewState case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; + + case const (MimblewimblecoinWallet): + await (wallet as MimblewimblecoinWallet).init(isRestore: true); + break; case const (MoneroWallet): await (wallet as MoneroWallet).init(isRestore: true); 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 47400a0acb..e7e0e4691b 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 @@ -271,7 +271,7 @@ class _RestoreWalletViewState extends ConsumerState { }, ); } else if (widget.coin is Mimblewimblecoin) { - final int secondsSinceEpoch = widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000; + final int secondsSinceEpoch = widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; const int mimblewimblecoinFirstBlock = 1573462801; const double overestimateSecondsPerBlock = 61; final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; 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 e7f0824e40..b899201c2c 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 @@ -1081,14 +1081,6 @@ class _NodeFormState extends ConsumerState { } else { enableSSLCheckbox = true; } - } else if (widget.coin is CwBasedInterface) { - if (newValue.startsWith("https://")) { - _useSSL = true; - } else if (newValue.startsWith("http://")) { - _useSSL = false; - } else { - _useSSL = true; - } } _updateState(); setState(() {}); 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 ea21bd3250..d0b8913a5f 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 @@ -40,7 +40,6 @@ 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/impl/mimblewimblecoin_wallet.dart'; -import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 5ce8a6b0ea..31339e2fcb 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -39,9 +39,6 @@ import '../intermediate/bip39_wallet.dart'; import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; -// -// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart -// class MimblewimblecoinWallet extends Bip39Wallet { MimblewimblecoinWallet(CryptoCurrencyNetwork network) : super(Mimblewimblecoin(network)); @@ -73,9 +70,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future updateMwcmqsConfig(String host, int port) async { final String stringConfig = jsonEncode({ "mwcmqs_domain": host, - "mwcmqs_port": port, - "mwcmqs_protocol_unsecure": false, - "mwcmqs_address_index": 0, + "mwcmqs_port": port }); await secureStorageInterface.write( key: '${walletId}_mwcmqsConfig', @@ -434,7 +429,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { return config!; } - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index int _calculateRestoreHeightFrom({required DateTime date}) { final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; const int mimblewimblecoinFirstBlock = 1565370278; @@ -596,8 +590,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ({String commitId, String slateId}) transaction; if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://") || - receiverAddress.startsWith("mwcmqcs://")) { + receiverAddress.startsWith("https://")) { transaction = await mimblewimblecoin.Libmwc.txHttpSend( wallet: wallet!, selectionStrategyIsAll: 0, @@ -606,7 +599,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { amount: txData.recipients!.first.amount.raw.toInt(), address: txData.recipients!.first.address, ); - } else { + } else if (receiverAddress.startsWith("mwcmqs://")) { transaction = await mimblewimblecoin.Libmwc.createTransaction( wallet: wallet!, amount: txData.recipients!.first.amount.raw.toInt(), @@ -616,6 +609,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, ); + + } else { + throw Exception( + "Unsupported address format: $receiverAddress. Please use a valid address.", + ); + } final Map txAddressInfo = {}; From ab525e8db88482d0ac9a0b829ffd82f88ffa3d26 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sun, 15 Dec 2024 13:13:35 +0400 Subject: [PATCH 11/91] tor enabled & minCoinbaseConfirms --- .../isar/models/blockchain_data/v2/transaction_v2.dart | 8 ++++---- lib/wallets/crypto_currency/coins/mimblewimblecoin.dart | 4 +++- linux/flutter/generated_plugin_registrant.cc | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 32ade6e6b1..01f1e440bc 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -297,7 +297,7 @@ class TransactionV2 { if (isCancelled) { return "Cancelled"; } else if (type == TransactionType.incoming) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Received"; } else { if (numberOfMessages == 1) { @@ -309,7 +309,7 @@ class TransactionV2 { } } } else if (type == TransactionType.outgoing) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Sent (confirmed)"; } else { if (numberOfMessages == 1) { @@ -331,7 +331,7 @@ class TransactionV2 { if (isCancelled) { return "Cancelled"; } else if (type == TransactionType.incoming) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Received"; } else { if (numberOfMessages == 1) { @@ -343,7 +343,7 @@ class TransactionV2 { } } } else if (type == TransactionType.outgoing) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Sent (confirmed)"; } else { if (numberOfMessages == 1) { diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 3127ef035b..cd0066828e 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -76,7 +76,9 @@ bool validateAddress(String address) { enabled: true, coinName: identifier, isFailover: true, - isDown: false + isDown: false, + torEnabled: true, + clearnetEnabled: true, ); default: diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 8faaf8a515..aefca0f569 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From e225b4c9f874f3c4a1706384189d4188c57b534e Mon Sep 17 00:00:00 2001 From: vekamo Date: Fri, 20 Dec 2024 22:35:44 +0100 Subject: [PATCH 12/91] submodule repo update --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 2008080fd8..2eb30bee56 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 +Subproject commit 2eb30bee56944ac50d0c1f82423d3944a2f7a04c From f05631a14e519f58d9991042c0473507f3d3f4cb Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 21 Dec 2024 14:14:17 +0100 Subject: [PATCH 13/91] receive slatepack ui --- .../desktop_mwc_txs_method_toggle.dart | 63 ++++ .../sub_widgets/desktop_receive.dart | 290 ++++++++++++++++-- lib/utilities/enums/txs_method_mwc_enum.dart | 14 + 3 files changed, 343 insertions(+), 24 deletions(-) create mode 100644 lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart create mode 100644 lib/utilities/enums/txs_method_mwc_enum.dart diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart new file mode 100644 index 0000000000..18bab217ae --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart @@ -0,0 +1,63 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/util.dart'; +import '../../../../widgets/toggle.dart'; + +class MwcTxsMethodToggle extends ConsumerWidget { + const MwcTxsMethodToggle({ + super.key, + this.onChanged, + }); + + final void Function(TxsMethodMwcType)? onChanged; + + @override + Widget build(BuildContext context, WidgetRef ref) { + debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + + return Toggle( + onValueChanged: (value) { + if (value) { + onChanged?.call(TxsMethodMwcType.slatepack); + } else { + onChanged?.call(TxsMethodMwcType.automatic); + } + }, + isOn: false, + onColor: isDesktop + ? Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOn + : Theme.of(context).extension()!.rateTypeToggleColorOn, + offColor: isDesktop + ? Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOff + : Theme.of(context).extension()!.rateTypeToggleColorOff, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onIcon: Assets.svg.gear, + onText: "Slatepack", + offIcon: Assets.svg.radioSyncing, + offText: "Automatic", + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index b90cc2f6a0..de9682b9ec 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -27,16 +28,20 @@ import '../../../../providers/providers.dart'; import '../../../../route_generator.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; +import '../../../../utilities/amount/amount.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/derive_path_type_enum.dart'; +import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/logger.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/bitcoin_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; @@ -44,12 +49,19 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interfa import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; +import '../../../../widgets/toggle.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; +import 'desktop_mwc_txs_method_toggle.dart'; class DesktopReceive extends ConsumerStatefulWidget { const DesktopReceive({ @@ -73,13 +85,71 @@ class _DesktopReceiveState extends ConsumerState { late final ClipboardInterface clipboard; late final bool supportsSpark; late final bool showMultiType; + late final bool isMimblewimblecoin; + late TextEditingController receiveSlateController; + String? _address; + bool _addressToggleFlag = false; + final _addressFocusNode = FocusNode(); int _currentIndex = 0; + String? _selectedMethodMwc; // Variable to store selected dropdown value + String? _note; final List _walletAddressTypes = []; final Map _addressMap = {}; final Map> _addressSubMap = {}; + + + Future pasteAddress() async { + final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring(0, content.indexOf("\n")); + } + + try { + final paymentData = AddressUtils.parsePaymentUri( + content, + logging: Logging.instance, + ); + if (paymentData != null && + paymentData.coin?.uriScheme == coin.uriScheme) { + _address = paymentData.address; + receiveSlateController.text = _address!; + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } else { + content = content.split("\n").first.trim(); + if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); + } + + receiveSlateController.text = content; + _address = content; + + //_setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } + } catch (e) { + if (coin is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddressMwc(content); + } + receiveSlateController.text = content; + _address = content; + // Trigger validation after pasting. + //_setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } + } + } Future generateNewAddress() async { final wallet = ref.read(pWallets).getWallet(walletId); if (wallet is MultiAddressInterface) { @@ -200,11 +270,19 @@ class _DesktopReceiveState extends ConsumerState { @override void initState() { + receiveSlateController = TextEditingController(); walletId = widget.walletId; coin = ref.read(pWalletInfo(walletId)).coin; clipboard = widget.clipboard; final wallet = ref.read(pWallets).getWallet(walletId); supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface; + + isMimblewimblecoin = wallet is MimblewimblecoinWallet; + if (isMimblewimblecoin) { + _selectedMethodMwc = "Slatepack"; + } + debugPrint("Address generated: $isMimblewimblecoin"); + if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { showMultiType = false; @@ -299,6 +377,31 @@ class _DesktopReceiveState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + if (isMimblewimblecoin) + Padding( + padding: const EdgeInsets.all(16.0), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, // Fallback color + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).extension()?.backgroundAppBar ?? Colors.grey, // Fallback color + width: 1, + ), + ), + child: SizedBox( + height: 50, // Provide an explicit height to avoid infinite constraints + child: MwcTxsMethodToggle( + onChanged: (TxsMethodMwcType type) { + setState(() { + _selectedMethodMwc = type == TxsMethodMwcType.automatic ? 'Slatepack' : 'Automatic'; + }); + }, + ), + ), + ), + ), + ConditionalParent( condition: showMultiType, builder: (child) => Column( @@ -484,6 +587,126 @@ class _DesktopReceiveState extends ConsumerState { const SizedBox( height: 32, ), + if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Label Text + Text( + "Send to", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("sendViewAddressFieldKey"), + controller: receiveSlateController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue; + //_setValidAddressProviders(_address); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter Slatepack Message", + _addressFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, // Adjust vertical padding for better alignment + ), + suffixIcon: Padding( + padding: receiveSlateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key("sendViewClearAddressFieldButtonKey"), + onTap: () { + receiveSlateController.text = ""; + _address = ""; + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: pasteAddress, + child: receiveSlateController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ) + + //Padding( + // padding: const EdgeInsets.symmetric(vertical: 8.0), + // child: TextField( + // maxLines: 8, // Set to a higher number to make the height larger + // minLines: 5, // Allow it to shrink if input is small + // decoration: InputDecoration( + // labelText: 'Enter Slatepack Message', + // alignLabelWithHint: true, + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // filled: true, + // fillColor: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, + // contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0), + // ), + // onChanged: (value) { + // // Handle text input changes (e.g., store in a state variable) + // debugPrint('Slatepack Message: $value'); + // }, + // ), + //) + + else Center( child: QR( data: AddressUtils.buildUriString( @@ -498,6 +721,21 @@ class _DesktopReceiveState extends ConsumerState { height: 32, ), // TODO: create transparent button class to account for hover + // Conditional logic for 'Submit' button or QR code + + if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Submit", + enabled: true, + onPressed: () { + debugPrint('Submit button pressed for Mimblewimblecoin Slatepack'); + }, + ), + ) + else GestureDetector( onTap: () async { if (Util.isDesktop) { @@ -555,38 +793,42 @@ class _DesktopReceiveState extends ConsumerState { } }, child: Container( - color: Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.qrcode, - width: 14, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorBlue, - ), - const SizedBox( - width: 8, - ), - Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( - "Create new QR code", - style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.qrcode, + width: 14, + height: 16, color: Theme.of(context) .extension()! .accentColorBlue, ), - ), + const SizedBox(width: 8), + Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + "Create new QR code", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorBlue, + ), + ), + ), + ], ), - ], - ), + ], ), ), + ), ], ); } } + + diff --git a/lib/utilities/enums/txs_method_mwc_enum.dart b/lib/utilities/enums/txs_method_mwc_enum.dart new file mode 100644 index 0000000000..a77e08492e --- /dev/null +++ b/lib/utilities/enums/txs_method_mwc_enum.dart @@ -0,0 +1,14 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +enum TxsMethodMwcType { + slatepack, + automatic +} From e60b3600c17c91bcc6fd0ea08e1d4c6f8a66418b Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 21 Dec 2024 14:14:41 +0100 Subject: [PATCH 14/91] aligment receive ui --- .../wallet_view/sub_widgets/desktop_receive.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index de9682b9ec..b9ef65119f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -379,7 +379,7 @@ class _DesktopReceiveState extends ConsumerState { children: [ if (isMimblewimblecoin) Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(0), child: Container( decoration: BoxDecoration( color: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, // Fallback color @@ -401,7 +401,9 @@ class _DesktopReceiveState extends ConsumerState { ), ), ), - + const SizedBox( + height: 20, + ), ConditionalParent( condition: showMultiType, builder: (child) => Column( @@ -585,7 +587,7 @@ class _DesktopReceiveState extends ConsumerState { label: "Generate new address", ), const SizedBox( - height: 32, + height: 20, ), if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') Column( @@ -593,7 +595,7 @@ class _DesktopReceiveState extends ConsumerState { children: [ // Label Text Text( - "Send to", + "Receive Slatepack", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! @@ -728,7 +730,7 @@ class _DesktopReceiveState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: PrimaryButton( buttonHeight: ButtonHeight.l, - label: "Submit", + label: "Preview Receive Slatepack", enabled: true, onPressed: () { debugPrint('Submit button pressed for Mimblewimblecoin Slatepack'); From 1466110c1bb40b44816f963c9bb5d0e979a016a5 Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 21 Dec 2024 14:46:08 +0100 Subject: [PATCH 15/91] send ui slatepack --- .../sub_widgets/desktop_receive.dart | 3 ++ .../wallet_view/sub_widgets/desktop_send.dart | 37 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index b9ef65119f..1ecf44cde9 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -377,6 +377,9 @@ class _DesktopReceiveState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + const SizedBox( + height: 4, + ), if (isMimblewimblecoin) Padding( padding: const EdgeInsets.all(0), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 0721bbb832..369806dbbc 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -40,6 +40,7 @@ import '../../../../utilities/barcode_scanner_interface.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/fee_rate_type_enum.dart'; +import '../../../../utilities/enums/txs_method_mwc_enum.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; import '../../../../utilities/text_styles.dart'; @@ -75,6 +76,7 @@ import '../../../coin_control/desktop_coin_control_use_dialog.dart'; import '../../../desktop_home_view.dart'; import 'address_book_address_chooser/address_book_address_chooser.dart'; import 'desktop_fee_dropdown.dart'; +import 'desktop_mwc_txs_method_toggle.dart'; class DesktopSend extends ConsumerStatefulWidget { const DesktopSend({ @@ -108,6 +110,7 @@ class _DesktopSendState extends ConsumerState { // late TextEditingController feeController; late TextEditingController memoController; + late final SendViewAutoFillData? _data; final _addressFocusNode = FocusNode(); @@ -116,7 +119,9 @@ class _DesktopSendState extends ConsumerState { final _memoFocus = FocusNode(); late final bool isStellar; + late final bool isMimblewimblecoin; + String? _selectedMethodMwc; String? _note; String? _onChainNote; @@ -963,6 +968,10 @@ class _DesktopSendState extends ConsumerState { clipboard = widget.clipboard; scanner = widget.barcodeScanner; isStellar = coin is Stellar; + isMimblewimblecoin = coin is Mimblewimblecoin; + if (isMimblewimblecoin) { + _selectedMethodMwc = "Slatepack"; + } sendToController = TextEditingController(); cryptoAmountController = TextEditingController(); @@ -1101,6 +1110,32 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 4, ), + if (isMimblewimblecoin) + Padding( + padding: const EdgeInsets.all(0), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, // Fallback color + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).extension()?.backgroundAppBar ?? Colors.grey, // Fallback color + width: 1, + ), + ), + child: SizedBox( + height: 50, // Provide an explicit height to avoid infinite constraints + child: MwcTxsMethodToggle( + onChanged: (TxsMethodMwcType type) { + setState(() { + _selectedMethodMwc = type == TxsMethodMwcType.automatic ? 'Slatepack' : 'Automatic'; + }); + }, + ), + ), + ), + ), + if (isMimblewimblecoin) + if (coin is Firo) Text( "Send from", @@ -1225,7 +1260,7 @@ class _DesktopSendState extends ConsumerState { ), ), ), - if (coin is Firo) + if (coin is Firo || isMimblewimblecoin) const SizedBox( height: 20, ), From 041ce04254c77779a84cd380045dc7ee29fdf09e Mon Sep 17 00:00:00 2001 From: vekamo Date: Sat, 21 Dec 2024 14:46:21 +0100 Subject: [PATCH 16/91] finalize ui slatepack --- .../sub_widgets/desktop_finalize.dart | 374 ++++++++++++++++++ .../wallet_view/sub_widgets/my_wallet.dart | 16 + 2 files changed, 390 insertions(+) create mode 100644 lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart new file mode 100644 index 0000000000..2f07f39563 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart @@ -0,0 +1,374 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:tuple/tuple.dart'; + +import '../../../../models/isar/models/isar_models.dart'; +import '../../../../models/keys/view_only_wallet_data.dart'; +import '../../../../notifications/show_flush_bar.dart'; +import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; +import '../../../../providers/db/main_db_provider.dart'; +import '../../../../providers/providers.dart'; +import '../../../../route_generator.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/address_utils.dart'; +import '../../../../utilities/amount/amount.dart'; +import '../../../../utilities/assets.dart'; +import '../../../../utilities/clipboard_interface.dart'; +import '../../../../utilities/constants.dart'; +import '../../../../utilities/enums/derive_path_type_enum.dart'; +import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/logger.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../utilities/util.dart'; +import '../../../../wallets/crypto_currency/crypto_currency.dart'; +import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; +import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/impl/bitcoin_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; +import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; +import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/desktop/primary_button.dart'; +import '../../../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; +import '../../../../widgets/toggle.dart'; +import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../../widgets/custom_loading_overlay.dart'; +import '../../../../widgets/desktop/desktop_dialog.dart'; +import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/qr.dart'; +import '../../../../widgets/rounded_white_container.dart'; +import 'desktop_mwc_txs_method_toggle.dart'; + +class DesktopFinalize extends ConsumerStatefulWidget { + const DesktopFinalize({ + super.key, + required this.walletId, + this.contractAddress, + this.clipboard = const ClipboardWrapper(), + }); + + final String walletId; + final String? contractAddress; + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => _DesktopFinalizeState(); +} + +class _DesktopFinalizeState extends ConsumerState { + late final CryptoCurrency coin; + late final String walletId; + late final ClipboardInterface clipboard; + late final bool supportsSpark; + late final bool showMultiType; + late final bool isMimblewimblecoin; + late TextEditingController receiveSlateController; + String? _address; + bool _addressToggleFlag = false; + final _addressFocusNode = FocusNode(); + + int _currentIndex = 0; + String? _selectedMethodMwc; // Variable to store selected dropdown value + String? _note; + + final List _walletAddressTypes = []; + final Map _addressMap = {}; + final Map> _addressSubMap = {}; + + + + Future pasteAddress() async { + final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring(0, content.indexOf("\n")); + } + + try { + final paymentData = AddressUtils.parsePaymentUri( + content, + logging: Logging.instance, + ); + if (paymentData != null && + paymentData.coin?.uriScheme == coin.uriScheme) { + _address = paymentData.address; + receiveSlateController.text = _address!; + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } else { + content = content.split("\n").first.trim(); + if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); + } + + receiveSlateController.text = content; + _address = content; + + //_setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } + } catch (e) { + if (coin is Mimblewimblecoin) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddressMwc(content); + } + receiveSlateController.text = content; + _address = content; + // Trigger validation after pasting. + //_setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = receiveSlateController.text.isNotEmpty; + }); + } + } + } + + + @override + void initState() { + receiveSlateController = TextEditingController(); + walletId = widget.walletId; + coin = ref.read(pWalletInfo(walletId)).coin; + clipboard = widget.clipboard; + final wallet = ref.read(pWallets).getWallet(walletId); + supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface; + + isMimblewimblecoin = wallet is MimblewimblecoinWallet; + if (isMimblewimblecoin) { + _selectedMethodMwc = "Slatepack"; + } + debugPrint("Address generated: $isMimblewimblecoin"); + + + if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { + showMultiType = false; + } else { + showMultiType = supportsSpark || + (wallet is! BCashInterface && + wallet is Bip39HDWallet && + wallet.supportedAddressTypes.length > 1); + } + + _walletAddressTypes.add(wallet.info.mainAddressType); + + if (showMultiType) { + if (supportsSpark) { + _walletAddressTypes.insert(0, AddressType.spark); + } else { + _walletAddressTypes.addAll( + (wallet as Bip39HDWallet) + .supportedAddressTypes + .where((e) => e != wallet.info.mainAddressType), + ); + } + } + + if (_walletAddressTypes.length > 1 && wallet is BitcoinWallet) { + _walletAddressTypes.removeWhere((e) => e == AddressType.p2pkh); + } + + _addressMap[_walletAddressTypes[_currentIndex]] = + ref.read(pWalletReceivingAddress(walletId)); + + if (showMultiType) { + for (final type in _walletAddressTypes) { + _addressSubMap[type] = ref + .read(mainDBProvider) + .isar + .addresses + .where() + .walletIdEqualTo(walletId) + .filter() + .typeEqualTo(type) + .sortByDerivationIndexDesc() + .findFirst() + .asStream() + .listen((event) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _addressMap[type] = + event?.value ?? _addressMap[type] ?? "[No address yet]"; + }); + } + }); + }); + } + } + + super.initState(); + } + + @override + void dispose() { + for (final subscription in _addressSubMap.values) { + subscription.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final String address; + if (showMultiType) { + address = _addressMap[_walletAddressTypes[_currentIndex]]!; + } else { + address = ref.watch(pWalletReceivingAddress(walletId)); + } + + final wallet = ref.watch(pWallets.select((value) => value.getWallet(walletId))); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 4, + ), + const SizedBox( + height: 20, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Label Text + Text( + "Finalize Slatepack", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("sendViewAddressFieldKey"), + controller: receiveSlateController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue; + //_setValidAddressProviders(_address); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter Final Slatepack Message", + _addressFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, // Adjust vertical padding for better alignment + ), + suffixIcon: Padding( + padding: receiveSlateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key("sendViewClearAddressFieldButtonKey"), + onTap: () { + receiveSlateController.text = ""; + _address = ""; + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: pasteAddress, + child: receiveSlateController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + const SizedBox( + height: 32, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Preview Receive Slatepack", + enabled: true, + onPressed: () { + debugPrint('Submit button pressed for Mimblewimblecoin Slatepack'); + }, + ), + ) + ], + ); + } +} + + diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index 622630c4a7..e16254aa51 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -17,12 +17,14 @@ import '../../../../pages/wallet_view/transaction_views/tx_v2/transaction_v2_lis import '../../../../providers/global/wallets_provider.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../../widgets/custom_tab_view.dart'; import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/frost_scaffold.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../my_stack_view.dart'; +import 'desktop_finalize.dart'; import 'desktop_receive.dart'; import 'desktop_send.dart'; import 'desktop_token_send.dart'; @@ -50,6 +52,7 @@ class _MyWalletState extends ConsumerState { late final bool isEth; late final CryptoCurrency coin; late final bool isFrost; + late final bool isMimblewimblecoin; late final bool isViewOnly; @override @@ -59,6 +62,11 @@ class _MyWalletState extends ConsumerState { isFrost = wallet is BitcoinFrostWallet; isEth = coin is Ethereum; + + if (isMimblewimblecoin) { + titles.add("Finalize"); + } + if (isEth && widget.contractAddress == null) { titles.add("Transactions"); } @@ -167,6 +175,14 @@ class _MyWalletState extends ConsumerState { contractAddress: widget.contractAddress, ), ), + if (isMimblewimblecoin) + Padding( + padding: const EdgeInsets.all(20), + child: DesktopFinalize( + walletId: widget.walletId, + ), + ), + if (isEth && widget.contractAddress == null) Padding( padding: const EdgeInsets.only(top: 8.0), From d8eb90d49967b682aefa52824cd6765b5c52fc40 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 11:42:09 -0500 Subject: [PATCH 17/91] fix: revert mwc db migration code see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021465080 --- lib/db/db_version_migration.dart | 75 ++------------------------------ 1 file changed, 3 insertions(+), 72 deletions(-) diff --git a/lib/db/db_version_migration.dart b/lib/db/db_version_migration.dart index 81aec9b255..fcf632669c 100644 --- a/lib/db/db_version_migration.dart +++ b/lib/db/db_version_migration.dart @@ -532,71 +532,6 @@ class DbVersionMigrator with WalletDB { await MainDB.instance.addNewTransactionData(transactionsData, walletId); } - // we need to manually migrate mimblewimblecoin transactions as they are not - // stored on the mimblewimblecoin blockchain - final mimblewimblecoin = Mimblewimblecoin(CryptoCurrencyNetwork.main); - if (info.coinIdentifier == mimblewimblecoin.identifier) { - final txnData = walletBox.get("latest_tx_model") as TransactionData?; - - // we ever only used index 0 in the past - const rcvIndex = 0; - - final List> - transactionsData = []; - if (txnData != null) { - final txns = txnData.getAllTransactions(); - - for (final tx in txns.values) { - final bool isIncoming = tx.txType == "Received"; - - final iTx = isar_models.Transaction( - walletId: walletId, - txid: tx.txid, - timestamp: tx.timestamp, - type: isIncoming - ? isar_models.TransactionType.incoming - : isar_models.TransactionType.outgoing, - subType: isar_models.TransactionSubType.none, - amount: tx.amount, - amountString: Amount( - rawValue: BigInt.from(tx.amount), - fractionDigits: mimblewimblecoin.fractionDigits, - ).toJsonString(), - fee: tx.fees, - height: tx.height, - isCancelled: tx.isCancelled, - isLelantus: false, - slateId: tx.slateId, - otherData: tx.otherData, - nonce: null, - inputs: [], - outputs: [], - numberOfMessages: tx.numberOfMessages, - ); - - if (tx.address.isEmpty) { - transactionsData.add(Tuple2(iTx, null)); - } else { - final address = isar_models.Address( - walletId: walletId, - value: tx.address, - publicKey: [], - derivationIndex: isIncoming ? rcvIndex : -1, - derivationPath: null, - type: isIncoming - ? isar_models.AddressType.mimbleWimble - : isar_models.AddressType.unknown, - subType: isIncoming - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.unknown, - ); - transactionsData.add(Tuple2(iTx, address)); - } - } - } - await MainDB.instance.addNewTransactionData(transactionsData, walletId); - } - // delete data from hive await walletBox.delete(receiveAddressesPrefix); await walletBox.delete("${receiveAddressesPrefix}P2PKH"); @@ -618,13 +553,9 @@ class DbVersionMigrator with WalletDB { ); } - // doing this for epiccash/mimblewimblecoin will delete transaction history as it is not - // stored on the epiccash/mimblewimblecoin blockchain - final excludedIdentifiers = [ - epic.identifier, - mimblewimblecoin.identifier - ]; - if ((!excludedIdentifiers.contains(info.coinIdentifier))) { + // doing this for epic cash will delete transaction history as it is not + // stored on the epic cash blockchain + if (info.coinIdentifier != epic.identifier) { // set flag to initiate full rescan on opening wallet await DB.instance.put( boxName: DB.boxNameDBInfo, From 62f111ea85fd48ecca7f16e0f6b4cdb7a6e7457c Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 11:43:45 -0500 Subject: [PATCH 18/91] fix: revert mwc db migration code see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021465367 --- lib/db/migrate_wallets_to_isar.dart | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/db/migrate_wallets_to_isar.dart b/lib/db/migrate_wallets_to_isar.dart index d0f75a2e37..cd54a4063f 100644 --- a/lib/db/migrate_wallets_to_isar.dart +++ b/lib/db/migrate_wallets_to_isar.dart @@ -11,7 +11,6 @@ import '../wallets/isar/models/token_wallet_info.dart'; import '../wallets/isar/models/wallet_info.dart'; import '../wallets/isar/models/wallet_info_meta.dart'; import '../wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; -import '../wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart'; import 'hive/db.dart'; import 'isar/main_db.dart'; @@ -147,21 +146,6 @@ Future migrateWalletsToIsar({ otherData[WalletInfoKeys.epiccashData] = jsonEncode( epicWalletInfo.toMap(), ); - } else if (old.coinIdentifier == - Mimblewimblecoin(CryptoCurrencyNetwork.main)) { - final mimblewimblecoinWalletInfo = - ExtraMimblewimblecoinWalletInfo.fromMap({ - "receivingIndex": walletBox.get("receivingIndex") as int? ?? 0, - "changeIndex": walletBox.get("changeIndex") as int? ?? 0, - "slatesToAddresses": walletBox.get("slate_to_address") as Map? ?? {}, - "slatesToCommits": walletBox.get("slatesToCommits") as Map? ?? {}, - "lastScannedBlock": walletBox.get("lastScannedBlock") as int? ?? 0, - "restoreHeight": walletBox.get("restoreHeight") as int? ?? 0, - "creationHeight": walletBox.get("creationHeight") as int? ?? 0, - }); - otherData[WalletInfoKeys.mimblewimblecoinData] = jsonEncode( - mimblewimblecoinWalletInfo.toMap(), - ); } else if (old.coinIdentifier == Firo(CryptoCurrencyNetwork.main).identifier || old.coinIdentifier == Firo(CryptoCurrencyNetwork.test).identifier) { From e6035e1025b60dc7d69169db0f2820b759c7038b Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 11:44:51 -0500 Subject: [PATCH 19/91] fix: ignore generating and updating the isar schema for this value see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021529886 --- lib/models/isar/models/blockchain_data/v2/transaction_v2.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 01f1e440bc..47b18ad725 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -95,8 +95,11 @@ class TransactionV2 { bool get isEpiccashTransaction => _getFromOtherData(key: TxV2OdKeys.isEpiccashTransaction) == true; + + @ignore bool get isMimblewimblecoinTransaction => _getFromOtherData(key: TxV2OdKeys.isMimblewimblecoinTransaction) == true; + int? get numberOfMessages => _getFromOtherData(key: TxV2OdKeys.numberOfMessages) as int?; String? get slateId => _getFromOtherData(key: TxV2OdKeys.slateId) as String?; From ccfdb665298c58503c2cca40d561ec98ae3fa750 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 12:01:08 -0500 Subject: [PATCH 20/91] fix: revert mwc tx migration scaffolding see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021531199 --- .../blockchain_data/v2/transaction_v2.g.dart | 142 +++++------------- 1 file changed, 41 insertions(+), 101 deletions(-) diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart index 3bb48c3508..68d8a18c57 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.g.dart @@ -53,71 +53,66 @@ const TransactionV2Schema = CollectionSchema( name: r'isEpiccashTransaction', type: IsarType.bool, ), - r'isMimblewimblecoinTransaction': PropertySchema( - id: 7, - name: r'isMimblewimblecoinTransaction', - type: IsarType.bool, - ), r'nonce': PropertySchema( - id: 8, + id: 7, name: r'nonce', type: IsarType.long, ), r'numberOfMessages': PropertySchema( - id: 9, + id: 8, name: r'numberOfMessages', type: IsarType.long, ), r'onChainNote': PropertySchema( - id: 10, + id: 9, name: r'onChainNote', type: IsarType.string, ), r'otherData': PropertySchema( - id: 11, + id: 10, name: r'otherData', type: IsarType.string, ), r'outputs': PropertySchema( - id: 12, + id: 11, name: r'outputs', type: IsarType.objectList, target: r'OutputV2', ), r'slateId': PropertySchema( - id: 13, + id: 12, name: r'slateId', type: IsarType.string, ), r'subType': PropertySchema( - id: 14, + id: 13, name: r'subType', type: IsarType.byte, enumMap: _TransactionV2subTypeEnumValueMap, ), r'timestamp': PropertySchema( - id: 15, + id: 14, name: r'timestamp', type: IsarType.long, ), r'txid': PropertySchema( - id: 16, + id: 15, name: r'txid', type: IsarType.string, ), r'type': PropertySchema( - id: 17, + id: 16, name: r'type', type: IsarType.byte, enumMap: _TransactionV2typeEnumValueMap, ), r'version': PropertySchema( - id: 18, + id: 17, name: r'version', type: IsarType.long, ), r'walletId': PropertySchema( - id: 19, + id: 18, name: r'walletId', type: IsarType.string, ) @@ -261,24 +256,23 @@ void _transactionV2Serialize( ); writer.writeBool(offsets[5], object.isCancelled); writer.writeBool(offsets[6], object.isEpiccashTransaction); - writer.writeBool(offsets[7], object.isMimblewimblecoinTransaction); - writer.writeLong(offsets[8], object.nonce); - writer.writeLong(offsets[9], object.numberOfMessages); - writer.writeString(offsets[10], object.onChainNote); - writer.writeString(offsets[11], object.otherData); + writer.writeLong(offsets[7], object.nonce); + writer.writeLong(offsets[8], object.numberOfMessages); + writer.writeString(offsets[9], object.onChainNote); + writer.writeString(offsets[10], object.otherData); writer.writeObjectList( - offsets[12], + offsets[11], allOffsets, OutputV2Schema.serialize, object.outputs, ); - writer.writeString(offsets[13], object.slateId); - writer.writeByte(offsets[14], object.subType.index); - writer.writeLong(offsets[15], object.timestamp); - writer.writeString(offsets[16], object.txid); - writer.writeByte(offsets[17], object.type.index); - writer.writeLong(offsets[18], object.version); - writer.writeString(offsets[19], object.walletId); + writer.writeString(offsets[12], object.slateId); + writer.writeByte(offsets[13], object.subType.index); + writer.writeLong(offsets[14], object.timestamp); + writer.writeString(offsets[15], object.txid); + writer.writeByte(offsets[16], object.type.index); + writer.writeLong(offsets[17], object.version); + writer.writeString(offsets[18], object.walletId); } TransactionV2 _transactionV2Deserialize( @@ -298,23 +292,23 @@ TransactionV2 _transactionV2Deserialize( InputV2(), ) ?? [], - otherData: reader.readStringOrNull(offsets[11]), + otherData: reader.readStringOrNull(offsets[10]), outputs: reader.readObjectList( - offsets[12], + offsets[11], OutputV2Schema.deserialize, allOffsets, OutputV2(), ) ?? [], subType: - _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? + _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[13])] ?? TransactionSubType.none, - timestamp: reader.readLong(offsets[15]), - txid: reader.readString(offsets[16]), - type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[17])] ?? + timestamp: reader.readLong(offsets[14]), + txid: reader.readString(offsets[15]), + type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[16])] ?? TransactionType.outgoing, - version: reader.readLong(offsets[18]), - walletId: reader.readString(offsets[19]), + version: reader.readLong(offsets[17]), + walletId: reader.readString(offsets[18]), ); object.id = id; return object; @@ -348,16 +342,14 @@ P _transactionV2DeserializeProp

( case 6: return (reader.readBool(offset)) as P; case 7: - return (reader.readBool(offset)) as P; + return (reader.readLongOrNull(offset)) as P; case 8: return (reader.readLongOrNull(offset)) as P; case 9: - return (reader.readLongOrNull(offset)) as P; + return (reader.readStringOrNull(offset)) as P; case 10: return (reader.readStringOrNull(offset)) as P; case 11: - return (reader.readStringOrNull(offset)) as P; - case 12: return (reader.readObjectList( offset, OutputV2Schema.deserialize, @@ -365,22 +357,22 @@ P _transactionV2DeserializeProp

( OutputV2(), ) ?? []) as P; - case 13: + case 12: return (reader.readStringOrNull(offset)) as P; - case 14: + case 13: return (_TransactionV2subTypeValueEnumMap[ reader.readByteOrNull(offset)] ?? TransactionSubType.none) as P; - case 15: + case 14: return (reader.readLong(offset)) as P; - case 16: + case 15: return (reader.readString(offset)) as P; - case 17: + case 16: return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? TransactionType.outgoing) as P; - case 18: + case 17: return (reader.readLong(offset)) as P; - case 19: + case 18: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -1521,16 +1513,6 @@ extension TransactionV2QueryFilter }); } - QueryBuilder - isMimblewimblecoinTransactionEqualTo(bool value) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'isMimblewimblecoinTransaction', - value: value, - )); - }); - } - QueryBuilder nonceIsNull() { return QueryBuilder.apply(this, (query) { @@ -2825,20 +2807,6 @@ extension TransactionV2QuerySortBy }); } - QueryBuilder - sortByIsMimblewimblecoinTransaction() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); - }); - } - - QueryBuilder - sortByIsMimblewimblecoinTransactionDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); - }); - } - QueryBuilder sortByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3070,20 +3038,6 @@ extension TransactionV2QuerySortThenBy }); } - QueryBuilder - thenByIsMimblewimblecoinTransaction() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.asc); - }); - } - - QueryBuilder - thenByIsMimblewimblecoinTransactionDesc() { - return QueryBuilder.apply(this, (query) { - return query.addSortBy(r'isMimblewimblecoinTransaction', Sort.desc); - }); - } - QueryBuilder thenByNonce() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'nonce', Sort.asc); @@ -3267,13 +3221,6 @@ extension TransactionV2QueryWhereDistinct }); } - QueryBuilder - distinctByIsMimblewimblecoinTransaction() { - return QueryBuilder.apply(this, (query) { - return query.addDistinctBy(r'isMimblewimblecoinTransaction'); - }); - } - QueryBuilder distinctByNonce() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'nonce'); @@ -3400,13 +3347,6 @@ extension TransactionV2QueryProperty }); } - QueryBuilder - isMimblewimblecoinTransactionProperty() { - return QueryBuilder.apply(this, (query) { - return query.addPropertyName(r'isMimblewimblecoinTransaction'); - }); - } - QueryBuilder nonceProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'nonce'); From 3442aab0dcfa0d16fe9dec57d0035b1f21522b54 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 12:03:27 -0500 Subject: [PATCH 21/91] fix: revert legacy theme mwc changes see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021493501 --- lib/models/isar/stack_theme.dart | 15 +- lib/models/isar/stack_theme.g.dart | 603 ++++------------------------- 2 files changed, 83 insertions(+), 535 deletions(-) diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index 4f831732f0..efa2f7d05e 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -13,6 +13,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:isar/isar.dart'; + import '../../app_config.dart'; import '../../utilities/extensions/impl/box_shadow.dart'; import '../../utilities/extensions/impl/gradient.dart'; @@ -1884,10 +1885,7 @@ class StackTheme { (map[mainNetId] as String).toBigIntFromHex.toInt(), ); } else { - Logging.instance.log( - "Color not found in theme for $mainNetId", - level: LogLevel.Error, - ); + Logging.instance.w("Color not found in theme for $mainNetId"); } } @@ -1939,7 +1937,6 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincash; late final String dogecoin; late final String epicCash; - late final String mimblewimblecoin; late final String ethereum; late final String firo; late final String monero; @@ -1950,7 +1947,6 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImage; late final String dogecoinImage; late final String epicCashImage; - late final String mimblewimblecoinImage; late final String ethereumImage; late final String firoImage; late final String litecoinImage; @@ -1962,7 +1958,6 @@ class ThemeAssets implements IThemeAssets { late final String bitcoincashImageSecondary; late final String dogecoinImageSecondary; late final String epicCashImageSecondary; - late final String mimblewimblecoinImageSecondary; late final String ethereumImageSecondary; late final String firoImageSecondary; late final String litecoinImageSecondary; @@ -2009,8 +2004,6 @@ class ThemeAssets implements IThemeAssets { ..bitcoincash = "$themeId/assets/${json["bitcoincash"] as String}" ..dogecoin = "$themeId/assets/${json["dogecoin"] as String}" ..epicCash = "$themeId/assets/${json["epicCash"] as String}" - ..mimblewimblecoin = - "$themeId/assets/${json["mimblewimblecoin"] as String}" ..ethereum = "$themeId/assets/${json["ethereum"] as String}" ..firo = "$themeId/assets/${json["firo"] as String}" ..monero = "$themeId/assets/${json["monero"] as String}" @@ -2022,8 +2015,6 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["bitcoincash_image"] as String}" ..dogecoinImage = "$themeId/assets/${json["dogecoin_image"] as String}" ..epicCashImage = "$themeId/assets/${json["epicCash_image"] as String}" - ..mimblewimblecoinImage = - "$themeId/assets/${json["mimblewimblecoin_image"] as String}" ..ethereumImage = "$themeId/assets/${json["ethereum_image"] as String}" ..firoImage = "$themeId/assets/${json["firo_image"] as String}" ..litecoinImage = "$themeId/assets/${json["litecoin_image"] as String}" @@ -2039,8 +2030,6 @@ class ThemeAssets implements IThemeAssets { "$themeId/assets/${json["dogecoin_image_secondary"] as String}" ..epicCashImageSecondary = "$themeId/assets/${json["epicCash_image_secondary"] as String}" - ..mimblewimblecoinImageSecondary = - "$themeId/assets/${json["mimblewimblecoin_image_secondary"] as String}" ..ethereumImageSecondary = "$themeId/assets/${json["ethereum_image_secondary"] as String}" ..firoImageSecondary = diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart index 0117ef7b9b..c77d979f54 100644 --- a/lib/models/isar/stack_theme.g.dart +++ b/lib/models/isar/stack_theme.g.dart @@ -18148,153 +18148,138 @@ const ThemeAssetsSchema = Schema( name: r'loadingGif', type: IsarType.string, ), - r'mimblewimblecoin': PropertySchema( - id: 26, - name: r'mimblewimblecoin', - type: IsarType.string, - ), - r'mimblewimblecoinImage': PropertySchema( - id: 27, - name: r'mimblewimblecoinImage', - type: IsarType.string, - ), - r'mimblewimblecoinImageSecondary': PropertySchema( - id: 28, - name: r'mimblewimblecoinImageSecondary', - type: IsarType.string, - ), r'monero': PropertySchema( - id: 29, + id: 26, name: r'monero', type: IsarType.string, ), r'moneroImage': PropertySchema( - id: 30, + id: 27, name: r'moneroImage', type: IsarType.string, ), r'moneroImageSecondary': PropertySchema( - id: 31, + id: 28, name: r'moneroImageSecondary', type: IsarType.string, ), r'namecoin': PropertySchema( - id: 32, + id: 29, name: r'namecoin', type: IsarType.string, ), r'namecoinImage': PropertySchema( - id: 33, + id: 30, name: r'namecoinImage', type: IsarType.string, ), r'namecoinImageSecondary': PropertySchema( - id: 34, + id: 31, name: r'namecoinImageSecondary', type: IsarType.string, ), r'particl': PropertySchema( - id: 35, + id: 32, name: r'particl', type: IsarType.string, ), r'particlImage': PropertySchema( - id: 36, + id: 33, name: r'particlImage', type: IsarType.string, ), r'particlImageSecondary': PropertySchema( - id: 37, + id: 34, name: r'particlImageSecondary', type: IsarType.string, ), r'personaEasy': PropertySchema( - id: 38, + id: 35, name: r'personaEasy', type: IsarType.string, ), r'personaIncognito': PropertySchema( - id: 39, + id: 36, name: r'personaIncognito', type: IsarType.string, ), r'receive': PropertySchema( - id: 40, + id: 37, name: r'receive', type: IsarType.string, ), r'receiveCancelled': PropertySchema( - id: 41, + id: 38, name: r'receiveCancelled', type: IsarType.string, ), r'receivePending': PropertySchema( - id: 42, + id: 39, name: r'receivePending', type: IsarType.string, ), r'send': PropertySchema( - id: 43, + id: 40, name: r'send', type: IsarType.string, ), r'sendCancelled': PropertySchema( - id: 44, + id: 41, name: r'sendCancelled', type: IsarType.string, ), r'sendPending': PropertySchema( - id: 45, + id: 42, name: r'sendPending', type: IsarType.string, ), r'stack': PropertySchema( - id: 46, + id: 43, name: r'stack', type: IsarType.string, ), r'stackIcon': PropertySchema( - id: 47, + id: 44, name: r'stackIcon', type: IsarType.string, ), r'themePreview': PropertySchema( - id: 48, + id: 45, name: r'themePreview', type: IsarType.string, ), r'themeSelector': PropertySchema( - id: 49, + id: 46, name: r'themeSelector', type: IsarType.string, ), r'txExchange': PropertySchema( - id: 50, + id: 47, name: r'txExchange', type: IsarType.string, ), r'txExchangeFailed': PropertySchema( - id: 51, + id: 48, name: r'txExchangeFailed', type: IsarType.string, ), r'txExchangePending': PropertySchema( - id: 52, + id: 49, name: r'txExchangePending', type: IsarType.string, ), r'wownero': PropertySchema( - id: 53, + id: 50, name: r'wownero', type: IsarType.string, ), r'wowneroImage': PropertySchema( - id: 54, + id: 51, name: r'wowneroImage', type: IsarType.string, ), r'wowneroImageSecondary': PropertySchema( - id: 55, + id: 52, name: r'wowneroImageSecondary', type: IsarType.string, ) @@ -18347,9 +18332,6 @@ int _themeAssetsEstimateSize( bytesCount += 3 + value.length * 3; } } - bytesCount += 3 + object.mimblewimblecoin.length * 3; - bytesCount += 3 + object.mimblewimblecoinImage.length * 3; - bytesCount += 3 + object.mimblewimblecoinImageSecondary.length * 3; bytesCount += 3 + object.monero.length * 3; bytesCount += 3 + object.moneroImage.length * 3; bytesCount += 3 + object.moneroImageSecondary.length * 3; @@ -18412,36 +18394,33 @@ void _themeAssetsSerialize( writer.writeString(offsets[23], object.litecoinImage); writer.writeString(offsets[24], object.litecoinImageSecondary); writer.writeString(offsets[25], object.loadingGif); - writer.writeString(offsets[26], object.mimblewimblecoin); - writer.writeString(offsets[27], object.mimblewimblecoinImage); - writer.writeString(offsets[28], object.mimblewimblecoinImageSecondary); - writer.writeString(offsets[29], object.monero); - writer.writeString(offsets[30], object.moneroImage); - writer.writeString(offsets[31], object.moneroImageSecondary); - writer.writeString(offsets[32], object.namecoin); - writer.writeString(offsets[33], object.namecoinImage); - writer.writeString(offsets[34], object.namecoinImageSecondary); - writer.writeString(offsets[35], object.particl); - writer.writeString(offsets[36], object.particlImage); - writer.writeString(offsets[37], object.particlImageSecondary); - writer.writeString(offsets[38], object.personaEasy); - writer.writeString(offsets[39], object.personaIncognito); - writer.writeString(offsets[40], object.receive); - writer.writeString(offsets[41], object.receiveCancelled); - writer.writeString(offsets[42], object.receivePending); - writer.writeString(offsets[43], object.send); - writer.writeString(offsets[44], object.sendCancelled); - writer.writeString(offsets[45], object.sendPending); - writer.writeString(offsets[46], object.stack); - writer.writeString(offsets[47], object.stackIcon); - writer.writeString(offsets[48], object.themePreview); - writer.writeString(offsets[49], object.themeSelector); - writer.writeString(offsets[50], object.txExchange); - writer.writeString(offsets[51], object.txExchangeFailed); - writer.writeString(offsets[52], object.txExchangePending); - writer.writeString(offsets[53], object.wownero); - writer.writeString(offsets[54], object.wowneroImage); - writer.writeString(offsets[55], object.wowneroImageSecondary); + writer.writeString(offsets[26], object.monero); + writer.writeString(offsets[27], object.moneroImage); + writer.writeString(offsets[28], object.moneroImageSecondary); + writer.writeString(offsets[29], object.namecoin); + writer.writeString(offsets[30], object.namecoinImage); + writer.writeString(offsets[31], object.namecoinImageSecondary); + writer.writeString(offsets[32], object.particl); + writer.writeString(offsets[33], object.particlImage); + writer.writeString(offsets[34], object.particlImageSecondary); + writer.writeString(offsets[35], object.personaEasy); + writer.writeString(offsets[36], object.personaIncognito); + writer.writeString(offsets[37], object.receive); + writer.writeString(offsets[38], object.receiveCancelled); + writer.writeString(offsets[39], object.receivePending); + writer.writeString(offsets[40], object.send); + writer.writeString(offsets[41], object.sendCancelled); + writer.writeString(offsets[42], object.sendPending); + writer.writeString(offsets[43], object.stack); + writer.writeString(offsets[44], object.stackIcon); + writer.writeString(offsets[45], object.themePreview); + writer.writeString(offsets[46], object.themeSelector); + writer.writeString(offsets[47], object.txExchange); + writer.writeString(offsets[48], object.txExchangeFailed); + writer.writeString(offsets[49], object.txExchangePending); + writer.writeString(offsets[50], object.wownero); + writer.writeString(offsets[51], object.wowneroImage); + writer.writeString(offsets[52], object.wowneroImageSecondary); } ThemeAssets _themeAssetsDeserialize( @@ -18477,36 +18456,33 @@ ThemeAssets _themeAssetsDeserialize( object.litecoinImage = reader.readString(offsets[23]); object.litecoinImageSecondary = reader.readString(offsets[24]); object.loadingGif = reader.readStringOrNull(offsets[25]); - object.mimblewimblecoin = reader.readString(offsets[26]); - object.mimblewimblecoinImage = reader.readString(offsets[27]); - object.mimblewimblecoinImageSecondary = reader.readString(offsets[28]); - object.monero = reader.readString(offsets[29]); - object.moneroImage = reader.readString(offsets[30]); - object.moneroImageSecondary = reader.readString(offsets[31]); - object.namecoin = reader.readString(offsets[32]); - object.namecoinImage = reader.readString(offsets[33]); - object.namecoinImageSecondary = reader.readString(offsets[34]); - object.particl = reader.readString(offsets[35]); - object.particlImage = reader.readString(offsets[36]); - object.particlImageSecondary = reader.readString(offsets[37]); - object.personaEasy = reader.readString(offsets[38]); - object.personaIncognito = reader.readString(offsets[39]); - object.receive = reader.readString(offsets[40]); - object.receiveCancelled = reader.readString(offsets[41]); - object.receivePending = reader.readString(offsets[42]); - object.send = reader.readString(offsets[43]); - object.sendCancelled = reader.readString(offsets[44]); - object.sendPending = reader.readString(offsets[45]); - object.stack = reader.readString(offsets[46]); - object.stackIcon = reader.readString(offsets[47]); - object.themePreview = reader.readString(offsets[48]); - object.themeSelector = reader.readString(offsets[49]); - object.txExchange = reader.readString(offsets[50]); - object.txExchangeFailed = reader.readString(offsets[51]); - object.txExchangePending = reader.readString(offsets[52]); - object.wownero = reader.readString(offsets[53]); - object.wowneroImage = reader.readString(offsets[54]); - object.wowneroImageSecondary = reader.readString(offsets[55]); + object.monero = reader.readString(offsets[26]); + object.moneroImage = reader.readString(offsets[27]); + object.moneroImageSecondary = reader.readString(offsets[28]); + object.namecoin = reader.readString(offsets[29]); + object.namecoinImage = reader.readString(offsets[30]); + object.namecoinImageSecondary = reader.readString(offsets[31]); + object.particl = reader.readString(offsets[32]); + object.particlImage = reader.readString(offsets[33]); + object.particlImageSecondary = reader.readString(offsets[34]); + object.personaEasy = reader.readString(offsets[35]); + object.personaIncognito = reader.readString(offsets[36]); + object.receive = reader.readString(offsets[37]); + object.receiveCancelled = reader.readString(offsets[38]); + object.receivePending = reader.readString(offsets[39]); + object.send = reader.readString(offsets[40]); + object.sendCancelled = reader.readString(offsets[41]); + object.sendPending = reader.readString(offsets[42]); + object.stack = reader.readString(offsets[43]); + object.stackIcon = reader.readString(offsets[44]); + object.themePreview = reader.readString(offsets[45]); + object.themeSelector = reader.readString(offsets[46]); + object.txExchange = reader.readString(offsets[47]); + object.txExchangeFailed = reader.readString(offsets[48]); + object.txExchangePending = reader.readString(offsets[49]); + object.wownero = reader.readString(offsets[50]); + object.wowneroImage = reader.readString(offsets[51]); + object.wowneroImageSecondary = reader.readString(offsets[52]); return object; } @@ -18623,12 +18599,6 @@ P _themeAssetsDeserializeProp

( return (reader.readString(offset)) as P; case 52: return (reader.readString(offset)) as P; - case 53: - return (reader.readString(offset)) as P; - case 54: - return (reader.readString(offset)) as P; - case 55: - return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); } @@ -22200,417 +22170,6 @@ extension ThemeAssetsQueryFilter }); } - QueryBuilder - mimblewimblecoinEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'mimblewimblecoin', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'mimblewimblecoin', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinMatches(String pattern, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'mimblewimblecoin', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoin', - value: '', - )); - }); - } - - QueryBuilder - mimblewimblecoinIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'mimblewimblecoin', - value: '', - )); - }); - } - - QueryBuilder - mimblewimblecoinImageEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'mimblewimblecoinImage', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageContains(String value, {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'mimblewimblecoinImage', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageMatches(String pattern, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'mimblewimblecoinImage', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoinImage', - value: '', - )); - }); - } - - QueryBuilder - mimblewimblecoinImageIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'mimblewimblecoinImage', - value: '', - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryEqualTo( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryGreaterThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - include: include, - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryLessThan( - String value, { - bool include = false, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.lessThan( - include: include, - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryBetween( - String lower, - String upper, { - bool includeLower = true, - bool includeUpper = true, - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.between( - property: r'mimblewimblecoinImageSecondary', - lower: lower, - includeLower: includeLower, - upper: upper, - includeUpper: includeUpper, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryStartsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.startsWith( - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryEndsWith( - String value, { - bool caseSensitive = true, - }) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.endsWith( - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryContains(String value, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.contains( - property: r'mimblewimblecoinImageSecondary', - value: value, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryMatches(String pattern, - {bool caseSensitive = true}) { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.matches( - property: r'mimblewimblecoinImageSecondary', - wildcard: pattern, - caseSensitive: caseSensitive, - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryIsEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.equalTo( - property: r'mimblewimblecoinImageSecondary', - value: '', - )); - }); - } - - QueryBuilder - mimblewimblecoinImageSecondaryIsNotEmpty() { - return QueryBuilder.apply(this, (query) { - return query.addFilterCondition(FilterCondition.greaterThan( - property: r'mimblewimblecoinImageSecondary', - value: '', - )); - }); - } - QueryBuilder moneroEqualTo( String value, { bool caseSensitive = true, From aaba3cf9372c0dacd29e1b213d79971622e449b5 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 12:08:41 -0500 Subject: [PATCH 22/91] fix: deduplicate cases see https://github.com/cypherstack/stack_wallet/pull/1016/files/041ce04254c77779a84cd380045dc7ee29fdf09e#r2021536139 --- .../manage_nodes_views/add_edit_node_view.dart | 7 ------- 1 file changed, 7 deletions(-) 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 b899201c2c..f81246b5fb 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 @@ -95,13 +95,6 @@ class _AddEditNodeViewState extends ConsumerState { } else if (coin is CryptonoteCurrency) { ref.read(nodeFormDataProvider).host = data.host; } - if (coin is Mimblewimblecoin) { - ref.read(nodeFormDataProvider).host = data.host; - ref.read(nodeFormDataProvider).port = data.port; - ref.read(nodeFormDataProvider).useSSL = data.useSSL; - } else if (coin is CryptonoteCurrency) { - ref.read(nodeFormDataProvider).host = data.host; - } } Future attemptSave() async { From 5bd8175ee7539731c933fe31d33018ba0acf1911 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 12:11:29 -0500 Subject: [PATCH 23/91] feat: latest flutter_libmwc --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 2eb30bee56..2008080fd8 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 2eb30bee56944ac50d0c1f82423d3944a2f7a04c +Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 From 05592cc517590c0cbac60fbddda3e88614026ff9 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 5 Sep 2025 12:17:48 -0500 Subject: [PATCH 24/91] fix(epic): fix sqlite column name errors in database queries --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 25e6cb3a3e..ada6254ee8 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 25e6cb3a3e7bee04e425af6beccb47e8d0708fdb +Subproject commit ada6254ee8bab4a50e0d45bb6206a970dbac960d From 84073b62cf82e364a7646c22e7836cbdc7a2e493 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 4 Sep 2025 16:36:29 -0500 Subject: [PATCH 25/91] fix(mwc): fix rust versions script --- scripts/rust_version.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index c6a7f3584a..3f658fead0 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -20,6 +20,15 @@ set_rust_version_for_libepiccash() { } set_rust_to_1810() { + if rustup toolchain list | grep -q "1.81.0"; then + rustup default 1.81.0 + else + echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 + exit 1 + fi +} + +set_rust_to_1720() { if rustup toolchain list | grep -q "1.72.0"; then rustup default 1.72.0 else From c87506f89b55b838f777cefb731a476289035834 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 5 Sep 2025 15:55:26 -0500 Subject: [PATCH 26/91] fix(mwc): initialize isMimblewimblecoin --- .../my_stack_view/wallet_view/sub_widgets/my_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index e16254aa51..a644a2175c 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -61,7 +61,7 @@ class _MyWalletState extends ConsumerState { coin = wallet.info.coin; isFrost = wallet is BitcoinFrostWallet; isEth = coin is Ethereum; - + isMimblewimblecoin = coin is Mimblewimblecoin; if (isMimblewimblecoin) { titles.add("Finalize"); From aac8a64aec3cbce093895961d7fad5300d2f2557 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 3 Sep 2025 21:56:48 -0500 Subject: [PATCH 27/91] fix(mwc): resolve some merge conflicts (wip) these are still untested as issues remain, so are a WIP. some conflicts may have been merged incorrectly --- lib/models/isar/stack_theme.dart | 1 + .../new/steps/frost_create_step_5.dart | 7 +- .../restore_options_view.dart | 97 +-- .../restore_wallet_view.dart | 80 +-- lib/pages/coin_control/utxo_details_view.dart | 379 +++++----- .../exchange_view/trade_details_view.dart | 26 +- .../sub_widgets/name_details.dart | 680 +++++++++--------- .../addresses/address_details_view.dart | 5 +- .../send_steps/frost_send_step_4.dart | 204 +++--- lib/pages/send_view/send_view.dart | 566 ++++++--------- .../global_settings_view/about_view.dart | 238 +++--- .../helpers/restore_create_backup.dart | 9 +- .../frost_ms/frost_participants_view.dart | 143 ++-- .../wallet_backup_view.dart | 11 +- .../wallet_network_settings_view.dart | 42 +- .../delete_wallet_recovery_phrase_view.dart | 11 +- .../sub_widgets/spark_name_details.dart | 9 +- .../all_transactions_view.dart | 7 +- .../transaction_details_view.dart | 152 ++-- .../tx_v2/all_transactions_v2_view.dart | 7 +- .../tx_v2/transaction_v2_card.dart | 6 +- .../tx_v2/transaction_v2_details_view.dart | 2 +- lib/route_generator.dart | 7 +- lib/services/wallets.dart | 5 +- lib/utilities/test_mwcmqs_connection.dart | 19 +- lib/utilities/test_node_connection.dart | 65 +- .../coins/mimblewimblecoin.dart | 3 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 234 +++--- ...imblewimblecoin_wallet_info_extension.dart | 26 +- lib/widgets/tx_key_widget.dart | 57 +- 30 files changed, 1361 insertions(+), 1737 deletions(-) diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart index efa2f7d05e..7c2f0b7ab0 100644 --- a/lib/models/isar/stack_theme.dart +++ b/lib/models/isar/stack_theme.dart @@ -1943,6 +1943,7 @@ class ThemeAssets implements IThemeAssets { late final String wownero; late final String namecoin; late final String particl; + late final String mimblewimblecoin; late final String bitcoinImage; late final String bitcoincashImage; late final String dogecoinImage; diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart index 9d835ea4ef..0d7c53e8b0 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart @@ -27,7 +27,8 @@ import '../../../../../widgets/detail_item.dart'; import '../../../../../widgets/loading_indicator.dart'; import '../../../../../widgets/rounded_container.dart'; import '../../../../home_view/home_view.dart'; -import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tvd; class FrostCreateStep5 extends ConsumerStatefulWidget { const FrostCreateStep5({super.key}); @@ -90,7 +91,7 @@ class _FrostCreateStep5State extends ConsumerState { detail: multisigConfig, button: Util.isDesktop - ? IconCopyButton(data: multisigConfig) + ? tvd.IconCopyButton(data: multisigConfig) : SimpleCopyButton(data: multisigConfig), ), const SizedBox(height: 12), @@ -99,7 +100,7 @@ class _FrostCreateStep5State extends ConsumerState { detail: serializedKeys, button: Util.isDesktop - ? IconCopyButton(data: serializedKeys) + ? tvd.IconCopyButton(data: serializedKeys) : SimpleCopyButton(data: serializedKeys), ), if (!Util.isDesktop) const Spacer(), 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 3133675b09..c230578940 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 @@ -419,8 +419,9 @@ class _SeedRestoreOptionState extends ConsumerState { return Column( children: [ -<<<<<<< - if (isCnAnd25 || widget.coin is Epiccash) + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -446,23 +447,14 @@ class _SeedRestoreOptionState extends ConsumerState { !ref.read(_pIsUsingDate), ), ], -======= - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) - Text( - "Choose start date", - style: Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark3, - ) - : STextStyles.smallMed12(context), - textAlign: TextAlign.left, ->>>>>>> ), -<<<<<<< - if (isCnAnd25 || widget.coin is Epiccash) + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) SizedBox(height: Util.isDesktop ? 16 : 8), - if (isCnAnd25 || widget.coin is Epiccash) + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) ref.watch(_pIsUsingDate) ? RestoreFromDatePicker( onTap: widget.dateChooserFunction, @@ -519,53 +511,36 @@ class _SeedRestoreOptionState extends ConsumerState { ), ), ), - if (isCnAnd25 || widget.coin is Epiccash) const SizedBox(height: 8), - if (isCnAnd25 || widget.coin is Epiccash) -======= - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) - SizedBox( - height: Util.isDesktop ? 16 : 8, - ), - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) - RestoreFromDatePicker( - onTap: widget.dateChooserFunction, - controller: widget.dateController, - ), - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) - const SizedBox( - height: 8, - ), - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) ->>>>>>> - RoundedWhiteContainer( - child: Center( - child: Text( - ref.watch(_pIsUsingDate) - ? "Choose the date you made the wallet (approximate is fine)" - : "Enter the initial block height of the wallet", - style: - Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textSubtitle1, - ) - : STextStyles.smallMed12( - context, - ).copyWith(fontSize: 10), - ), + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) + const SizedBox(height: 8), + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) + SizedBox(height: Util.isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Center( + child: Text( + ref.watch(_pIsUsingDate) + ? "Choose the date you made the wallet (approximate is fine)" + : "Enter the initial block height of the wallet", + style: + Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, + ) + : STextStyles.smallMed12(context).copyWith(fontSize: 10), ), ), -<<<<<<< - if (isCnAnd25 || widget.coin is Epiccash) + ), + if (isCnAnd25 || + widget.coin is Epiccash || + widget.coin is Mimblewimblecoin) SizedBox(height: Util.isDesktop ? 24 : 16), -======= - if (isMoneroAnd25 || widget.coin is Epiccash || widget.coin is Mimblewimblecoin || isWowneroAnd25) - SizedBox( - height: Util.isDesktop ? 24 : 16, - ), ->>>>>>> Text( "Choose recovery phrase length", style: 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 2bf3ef2735..dceb76d80d 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 @@ -226,12 +226,11 @@ class _RestoreWalletViewState extends ConsumerState { } mnemonic = mnemonic.trim(); - final int height = widget.restoreBlockHeight; + int height = widget.restoreBlockHeight; String? otherDataJsonString; // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index if (widget.coin is Epiccash) { -<<<<<<< otherDataJsonString = jsonEncode({ WalletInfoKeys.epiccashData: jsonEncode( ExtraEpiccashWalletInfo( @@ -245,71 +244,34 @@ class _RestoreWalletViewState extends ConsumerState { ).toMap(), ), }); -======= - if (widget.restoreFromDate != null) { - final int secondsSinceEpoch = - widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; - const int epicCashFirstBlock = 1565370278; - const double overestimateSecondsPerBlock = 61; - final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; - final int approximateHeight = - chosenSeconds ~/ overestimateSecondsPerBlock; - - height = approximateHeight; - } - if (height < 0) { - height = 0; - } - otherDataJsonString = jsonEncode( - { - WalletInfoKeys.mimblewimblecoinData: jsonEncode( - ExtraMimblewimblecoinWalletInfo( - receivingIndex: 0, - changeIndex: 0, - slatesToAddresses: {}, - slatesToCommits: {}, - lastScannedBlock: height, - restoreHeight: height, - creationHeight: height, - ).toMap(), - ), - }, - ); } else if (widget.coin is Mimblewimblecoin) { - final int secondsSinceEpoch = widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; + // final int secondsSinceEpoch = + // widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000; + final int secondsSinceEpoch = + DateTime.now().millisecondsSinceEpoch ~/ 1000; const int mimblewimblecoinFirstBlock = 1573462801; const double overestimateSecondsPerBlock = 61; - final int chosenSeconds = secondsSinceEpoch - mimblewimblecoinFirstBlock; + final int chosenSeconds = + secondsSinceEpoch - mimblewimblecoinFirstBlock; final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; height = approximateHeight; if (height < 0) { height = 0; } - otherDataJsonString = jsonEncode( - { - WalletInfoKeys.mimblewimblecoinData: jsonEncode( - ExtraMimblewimblecoinWalletInfo( - receivingIndex: 0, - changeIndex: 0, - slatesToAddresses: {}, - slatesToCommits: {}, - lastScannedBlock: height, - restoreHeight: height, - creationHeight: height, - ).toMap(), - ), - }, - ); - } else if (widget.coin is Firo) { - otherDataJsonString = jsonEncode( - { - WalletInfoKeys.lelantusCoinIsarRescanRequired: false, - WalletInfoKeys.enableLelantusScanning: - widget.enableLelantusScanning, - }, - ); ->>>>>>> + otherDataJsonString = jsonEncode({ + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + ExtraMimblewimblecoinWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: height, + restoreHeight: height, + creationHeight: height, + ).toMap(), + ), + }); } // TODO: do actual check to make sure it is a valid mnemonic for monero + xelis @@ -392,7 +354,7 @@ class _RestoreWalletViewState extends ConsumerState { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; - + case const (MimblewimblecoinWallet): await (wallet as MimblewimblecoinWallet).init(isRestore: true); break; diff --git a/lib/pages/coin_control/utxo_details_view.dart b/lib/pages/coin_control/utxo_details_view.dart index 9959abdda6..3e4a93670a 100644 --- a/lib/pages/coin_control/utxo_details_view.dart +++ b/lib/pages/coin_control/utxo_details_view.dart @@ -35,7 +35,7 @@ import '../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../widgets/desktop/secondary_button.dart'; import '../../widgets/icon_widgets/utxo_status_icon.dart'; import '../../widgets/rounded_container.dart'; -import '../wallet_view/transaction_views/transaction_details_view.dart'; +import '../wallet_view/transaction_views/transaction_details_view.dart' as tdv; class UtxoDetailsView extends ConsumerStatefulWidget { const UtxoDetailsView({ @@ -83,10 +83,11 @@ class _UtxoDetailsViewState extends ConsumerState { @override void initState() { - utxo = MainDB.instance.isar.utxos - .where() - .idEqualTo(widget.utxoId) - .findFirstSync()!; + utxo = + MainDB.instance.isar.utxos + .where() + .idEqualTo(widget.utxoId) + .findFirstSync()!; streamUTXO = MainDB.instance.watchUTXO(id: widget.utxoId); @@ -112,53 +113,50 @@ class _UtxoDetailsViewState extends ConsumerState { final confirmed = _isConfirmed( utxo!, currentHeight, - ref.watch( - pWallets.select( - (s) => s.getWallet(widget.walletId), - ), - ), + ref.watch(pWallets.select((s) => s.getWallet(widget.walletId))), ); return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(_popWithRefresh ? "refresh" : null); - }, - ), - title: Text( - "Output details", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: child, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () { + Navigator.of( + context, + ).pop(_popWithRefresh ? "refresh" : null); + }, + ), + title: Text( + "Output details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight(child: child), + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ), - ), - ), child: StreamBuilder( stream: streamUTXO, builder: (context, snapshot) { @@ -184,8 +182,9 @@ class _UtxoDetailsViewState extends ConsumerState { ), DesktopDialogCloseButton( onPressedOverride: () { - Navigator.of(context) - .pop(_popWithRefresh ? "refresh" : null); + Navigator.of( + context, + ).pop(_popWithRefresh ? "refresh" : null); }, ), ], @@ -204,15 +203,14 @@ class _UtxoDetailsViewState extends ConsumerState { child: RoundedContainer( padding: EdgeInsets.zero, color: Colors.transparent, - borderColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, + borderColor: + Theme.of(context) + .extension()! + .textFieldDefaultBG, child: child, ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), SecondaryButton( buttonHeight: ButtonHeight.l, label: utxo!.isBlocked ? "Unfreeze" : "Freeze", @@ -229,15 +227,13 @@ class _UtxoDetailsViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (!isDesktop) - const SizedBox( - height: 10, - ), + if (!isDesktop) const SizedBox(height: 10), RoundedContainer( padding: const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + color: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -246,22 +242,23 @@ class _UtxoDetailsViewState extends ConsumerState { if (isDesktop) UTXOStatusIcon( blocked: utxo!.isBlocked, - status: confirmed - ? UTXOStatusIconStatus.confirmed - : UTXOStatusIconStatus.unconfirmed, - background: Theme.of(context) - .extension()! - .popupBG, + status: + confirmed + ? UTXOStatusIconStatus.confirmed + : UTXOStatusIconStatus.unconfirmed, + background: + Theme.of( + context, + ).extension()!.popupBG, selected: false, width: 32, height: 32, ), - if (isDesktop) - const SizedBox( - width: 16, - ), + if (isDesktop) const SizedBox(width: 16), Text( - ref.watch(pAmountFormatter(coin)).format( + ref + .watch(pAmountFormatter(coin)) + .format( utxo!.value.toAmountAsRaw( fractionDigits: coin.fractionDigits, ), @@ -274,18 +271,19 @@ class _UtxoDetailsViewState extends ConsumerState { utxo!.isBlocked ? "Frozen" : confirmed - ? "Available" - : "Unconfirmed", + ? "Available" + : "Unconfirmed", style: STextStyles.w500_14(context).copyWith( - color: utxo!.isBlocked - ? const Color(0xFF7FA2D4) // todo theme - : confirmed - ? Theme.of(context) - .extension()! - .accentColorGreen - : Theme.of(context) - .extension()! - .accentColorYellow, + color: + utxo!.isBlocked + ? const Color(0xFF7FA2D4) // todo theme + : confirmed + ? Theme.of( + context, + ).extension()!.accentColorGreen + : Theme.of( + context, + ).extension()!.accentColorYellow, ), ), ], @@ -293,12 +291,14 @@ class _UtxoDetailsViewState extends ConsumerState { ), const _Div(), RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -309,9 +309,10 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Label", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), SimpleEditButton( @@ -319,32 +320,27 @@ class _UtxoDetailsViewState extends ConsumerState { editLabel: "label", onValueChanged: (newName) { MainDB.instance.putUTXO( - utxo!.copyWith( - name: newName, - ), + utxo!.copyWith(name: newName), ); }, ), ], ), - const SizedBox( - height: 4, - ), - Text( - utxo!.name, - style: STextStyles.w500_14(context), - ), + const SizedBox(height: 4), + Text(utxo!.name, style: STextStyles.w500_14(context)), ], ), ), const _Div(), RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -355,39 +351,35 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Address", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), isDesktop - ? IconCopyButton( - data: utxo!.address!, - ) - : SimpleCopyButton( - data: utxo!.address!, - ), + ? tdv.IconCopyButton(data: utxo!.address!) + : SimpleCopyButton(data: utxo!.address!), ], ), - const SizedBox( - height: 4, - ), - Text( - utxo!.address!, - style: STextStyles.w500_14(context), - ), + const SizedBox(height: 4), + Text(utxo!.address!, style: STextStyles.w500_14(context)), ], ), ), if (label != null && label!.value.isNotEmpty) const _Div(), if (label != null && label!.value.isNotEmpty) RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -398,38 +390,32 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Address label", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), isDesktop - ? IconCopyButton( - data: label!.value, - ) - : SimpleCopyButton( - data: label!.value, - ), + ? tdv.IconCopyButton(data: label!.value) + : SimpleCopyButton(data: label!.value), ], ), - const SizedBox( - height: 4, - ), - Text( - label!.value, - style: STextStyles.w500_14(context), - ), + const SizedBox(height: 4), + Text(label!.value, style: STextStyles.w500_14(context)), ], ), ), const _Div(), RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -440,38 +426,32 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Transaction ID", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), isDesktop - ? IconCopyButton( - data: utxo!.txid, - ) - : SimpleCopyButton( - data: utxo!.txid, - ), + ? tdv.IconCopyButton(data: utxo!.txid) + : SimpleCopyButton(data: utxo!.txid), ], ), - const SizedBox( - height: 4, - ), - Text( - utxo!.txid, - style: STextStyles.w500_14(context), - ), + const SizedBox(height: 4), + Text(utxo!.txid, style: STextStyles.w500_14(context)), ], ), ), const _Div(), RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -479,14 +459,13 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Confirmations", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Text( "${utxo!.getConfirmations(currentHeight)}", style: STextStyles.w500_14(context), @@ -501,14 +480,16 @@ class _UtxoDetailsViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -519,9 +500,10 @@ class _UtxoDetailsViewState extends ConsumerState { Text( "Freeze reason", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of(context) + .extension()! + .textSubtitle1, ), ), SimpleEditButton( @@ -529,17 +511,13 @@ class _UtxoDetailsViewState extends ConsumerState { editLabel: "freeze reason", onValueChanged: (newReason) { MainDB.instance.putUTXO( - utxo!.copyWith( - blockedReason: newReason, - ), + utxo!.copyWith(blockedReason: newReason), ); }, ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Text( utxo!.blockedReason ?? "", style: STextStyles.w500_14(context), @@ -556,10 +534,7 @@ class _UtxoDetailsViewState extends ConsumerState { label: utxo!.isBlocked ? "Unfreeze" : "Freeze", onPressed: _toggleFreeze, ), - if (!isDesktop) - const SizedBox( - height: 16, - ), + if (!isDesktop) const SizedBox(height: 16), ], ), ); @@ -581,9 +556,7 @@ class _Div extends StatelessWidget { color: Theme.of(context).extension()!.textFieldDefaultBG, ); } else { - return const SizedBox( - height: 12, - ); + return const SizedBox(height: 12); } } } diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 6e56e57553..39623c3d84 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -57,7 +57,7 @@ import '../../widgets/rounded_container.dart'; import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; import '../wallet_view/transaction_views/edit_note_view.dart'; -import '../wallet_view/transaction_views/transaction_details_view.dart'; +import '../wallet_view/transaction_views/transaction_details_view.dart' as tdv; import 'edit_trade_note_view.dart'; import 'send_from_view.dart'; @@ -518,7 +518,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - IconCopyButton(data: trade.payInAmount), + tdv.IconCopyButton(data: trade.payInAmount), ], ), const SizedBox(height: 6), @@ -604,20 +604,20 @@ class _TradeDetailsViewState extends ConsumerState { maxHeight: MediaQuery.of(context).size.height - 64, maxWidth: 580, - child: TransactionDetailsView( + child: tdv.TransactionDetailsView( coin: coin, transaction: transactionIfSentFromStack!, walletId: walletId!, ), ), const RouteSettings( - name: TransactionDetailsView.routeName, + name: tdv.TransactionDetailsView.routeName, ), ), ); } else { Navigator.of(context).pushNamed( - TransactionDetailsView.routeName, + tdv.TransactionDetailsView.routeName, arguments: Tuple3( transactionIfSentFromStack!, coin, @@ -664,7 +664,7 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - if (isDesktop) IconCopyButton(data: trade.payInAddress), + if (isDesktop) tdv.IconCopyButton(data: trade.payInAddress), ], ), ), @@ -687,7 +687,7 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.itemSubtitle(context), ), isDesktop - ? IconCopyButton(data: trade.payInAddress) + ? tdv.IconCopyButton(data: trade.payInAddress) : GestureDetector( onTap: () async { final address = trade.payInAddress; @@ -840,7 +840,7 @@ class _TradeDetailsViewState extends ConsumerState { children: [ Text("Memo", style: STextStyles.itemSubtitle(context)), isDesktop - ? IconCopyButton(data: trade.payInExtraId) + ? tdv.IconCopyButton(data: trade.payInExtraId) : GestureDetector( onTap: () async { final address = trade.payInExtraId; @@ -904,7 +904,7 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.itemSubtitle(context), ), isDesktop - ? IconPencilButton( + ? tdv.IconPencilButton( onPressed: () { showDialog( context: context, @@ -984,7 +984,7 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.itemSubtitle(context), ), isDesktop - ? IconPencilButton( + ? tdv.IconPencilButton( onPressed: () { showDialog( context: context, @@ -1088,7 +1088,7 @@ class _TradeDetailsViewState extends ConsumerState { style: STextStyles.itemSubtitle12(context), ), if (isDesktop) - IconCopyButton( + tdv.IconCopyButton( data: Format.extractDateFrom( trade.timestamp.millisecondsSinceEpoch ~/ 1000, ), @@ -1121,7 +1121,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - if (isDesktop) IconCopyButton(data: trade.exchangeName), + if (isDesktop) tdv.IconCopyButton(data: trade.exchangeName), if (!isDesktop) SelectableText( trade.exchangeName, @@ -1155,7 +1155,7 @@ class _TradeDetailsViewState extends ConsumerState { ), ], ), - if (isDesktop) IconCopyButton(data: trade.tradeId), + if (isDesktop) tdv.IconCopyButton(data: trade.tradeId), if (!isDesktop) Row( children: [ diff --git a/lib/pages/namecoin_names/sub_widgets/name_details.dart b/lib/pages/namecoin_names/sub_widgets/name_details.dart index 7fa77f807f..d16ca0b1ac 100644 --- a/lib/pages/namecoin_names/sub_widgets/name_details.dart +++ b/lib/pages/namecoin_names/sub_widgets/name_details.dart @@ -23,7 +23,8 @@ import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/dialogs/s_dialog.dart'; import '../../../widgets/rounded_container.dart'; -import '../../wallet_view/transaction_views/transaction_details_view.dart'; +import '../../wallet_view/transaction_views/transaction_details_view.dart' + as tdv; import '../manage_domain_view.dart'; import 'transfer_option_widget.dart'; import 'update_option_widget.dart'; @@ -60,8 +61,10 @@ class _ManageDomainsWidgetState extends ConsumerState { final data = jsonDecode(utxo.otherData!) as Map; final nameData = jsonDecode(data["nameOpData"] as String) as Map; - opNameData = - OpNameData(nameData.cast(), utxo.blockHeight ?? currentHeight); + opNameData = OpNameData( + nameData.cast(), + utxo.blockHeight ?? currentHeight, + ); _setName(); } @@ -76,32 +79,29 @@ class _ManageDomainsWidgetState extends ConsumerState { ref .read(secureStoreProvider) .read( - key: nameSaltKeyBuilder( - utxo!.txid, - widget.walletId, - utxo!.vout, - ), + key: nameSaltKeyBuilder(utxo!.txid, widget.walletId, utxo!.vout), ) .then((onValue) { - if (onValue != null) { - final data = (jsonDecode(onValue) as Map).cast(); - WidgetsBinding.instance.addPostFrameCallback((_) { - constructedName = data["name"]!; - value = data["value"]!; - if (mounted) { - setState(() {}); - } - }); - } else { - WidgetsBinding.instance.addPostFrameCallback((_) { - constructedName = "UNKNOWN"; - value = ""; - if (mounted) { - setState(() {}); + if (onValue != null) { + final data = + (jsonDecode(onValue) as Map).cast(); + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = data["name"]!; + value = data["value"]!; + if (mounted) { + setState(() {}); + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + constructedName = "UNKNOWN"; + value = ""; + if (mounted) { + setState(() {}); + } + }); } }); - } - }); } } } @@ -114,10 +114,7 @@ class _ManageDomainsWidgetState extends ConsumerState { message = "Expires in $blocksNameExpiration+ blocks"; color = theme.accentColorGreen; } else { - final remaining = opNameData?.expiredBlockLeft( - currentChainHeight, - false, - ); + final remaining = opNameData?.expiredBlockLeft(currentChainHeight, false); final semiRemaining = opNameData?.expiredBlockLeft( currentChainHeight, true, @@ -141,10 +138,7 @@ class _ManageDomainsWidgetState extends ConsumerState { bool _checkConfirmedUtxo(int currentHeight) { return (ref.read(pWallets).getWallet(widget.walletId) as NamecoinWallet) - .checkUtxoConfirmed( - utxo!, - currentHeight, - ); + .checkUtxoConfirmed(utxo!, currentHeight); } @override @@ -165,10 +159,9 @@ class _ManageDomainsWidgetState extends ConsumerState { _setName(); if (utxo?.address != null) { - label = ref.read(mainDBProvider).getAddressLabelSync( - widget.walletId, - utxo!.address!, - ); + label = ref + .read(mainDBProvider) + .getAddressLabelSync(widget.walletId, utxo!.address!); if (label != null) { streamLabel = ref.read(mainDBProvider).watchAddressLabel(id: label!.id); @@ -187,67 +180,73 @@ class _ManageDomainsWidgetState extends ConsumerState { Theme.of(context).extension()!, ); - final canManage = utxo != null && + final canManage = + utxo != null && _checkConfirmedUtxo(currentHeight) && (opNameData?.op == OpName.nameUpdate || opNameData?.op == OpName.nameFirstUpdate); return ConditionalParent( condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - backgroundColor: Colors.transparent, - // Theme.of(context).extension()!.background, - leading: const AppBarBackButton(), - title: Text( - "Domain details", - style: STextStyles.navBarTitle(context), - ), - actions: canManage - ? [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: CustomTextButton( - key: const Key("addAddressBookEntryFavoriteButtonKey"), - text: "Manage", - onTap: () { - Navigator.of(context).pushNamed( - ManageDomainView.routeName, - arguments: (walletId: widget.walletId, utxo: utxo!), - ); - }, - ), - ), - ] - : null, - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: child, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + // Theme.of(context).extension()!.background, + leading: const AppBarBackButton(), + title: Text( + "Domain details", + style: STextStyles.navBarTitle(context), + ), + actions: + canManage + ? [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: CustomTextButton( + key: const Key( + "addAddressBookEntryFavoriteButtonKey", + ), + text: "Manage", + onTap: () { + Navigator.of(context).pushNamed( + ManageDomainView.routeName, + arguments: ( + walletId: widget.walletId, + utxo: utxo!, + ), + ); + }, + ), + ), + ] + : null, + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight(child: child), + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ), - ), - ), child: ConditionalParent( condition: Util.isDesktop, builder: (child) { @@ -278,9 +277,10 @@ class _ManageDomainsWidgetState extends ConsumerState { child: RoundedContainer( padding: EdgeInsets.zero, color: Colors.transparent, - borderColor: Theme.of(context) - .extension()! - .textFieldDefaultBG, + borderColor: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, child: child, ), ), @@ -341,9 +341,7 @@ class _ManageDomainsWidgetState extends ConsumerState { }, ), ), - const SizedBox( - width: 32, - ), + const SizedBox(width: 32), Expanded( child: SecondaryButton( label: "Update", @@ -398,10 +396,7 @@ class _ManageDomainsWidgetState extends ConsumerState { ], ), ), - if (canManage) - const SizedBox( - height: 32, - ), + if (canManage) const SizedBox(height: 32), ], ), ); @@ -415,193 +410,148 @@ class _ManageDomainsWidgetState extends ConsumerState { return utxo == null ? Center( - child: Text( - "Missing output. Was it used recently?", - style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorRed, - ), + child: Text( + "Missing output. Was it used recently?", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.accentColorRed, ), - ) + ), + ) : Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // if (!isDesktop) - // const SizedBox( - // height: 10, - // ), - RoundedContainer( - padding: const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText( - constructedName ?? "", - style: STextStyles.pageTitleH2(context), - ), - if (Util.isDesktop) - SelectableText( - opNameData!.op.name, - style: STextStyles.w500_14(context), - ), - ], - ), - if (!Util.isDesktop) + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // if (!isDesktop) + // const SizedBox( + // height: 10, + // ), + RoundedContainer( + padding: const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ SelectableText( - opNameData!.op.name, - style: STextStyles.w500_14(context), + constructedName ?? "", + style: STextStyles.pageTitleH2(context), ), - ], - ), - ), - const _Div(), - RoundedContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Value", - style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), + if (Util.isDesktop) + SelectableText( + opNameData!.op.name, + style: STextStyles.w500_14(context), ), - ], - ), - const SizedBox( - height: 4, - ), + ], + ), + if (!Util.isDesktop) SelectableText( - value ?? "", + opNameData!.op.name, style: STextStyles.w500_14(context), ), - ], - ), + ], ), - const _Div(), - RoundedContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Address", - style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Value", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), - Util.isDesktop - ? IconCopyButton( - data: utxo!.address!, - ) - : SimpleCopyButton( - data: utxo!.address!, - ), - ], - ), - const SizedBox( - height: 4, - ), - SelectableText( - utxo!.address!, - style: STextStyles.w500_14(context), - ), - ], - ), + ), + ], + ), + const SizedBox(height: 4), + SelectableText( + value ?? "", + style: STextStyles.w500_14(context), + ), + ], ), - if (label != null && label!.value.isNotEmpty) - const _Div(), - if (label != null && label!.value.isNotEmpty) - RoundedContainer( - padding: Util.isDesktop + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), - color: Util.isDesktop + color: + Util.isDesktop ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + : Theme.of( + context, + ).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Address label", - style: - STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - Util.isDesktop - ? IconCopyButton( - data: label!.value, - ) - : SimpleCopyButton( - data: label!.value, - ), - ], - ), - const SizedBox( - height: 4, - ), - SelectableText( - label!.value, - style: STextStyles.w500_14(context), + Text( + "Address", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, + ), ), + Util.isDesktop + ? tdv.IconCopyButton(data: utxo!.address!) + : SimpleCopyButton(data: utxo!.address!), ], ), - ), - const _Div(), + const SizedBox(height: 4), + SelectableText( + utxo!.address!, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (label != null && label!.value.isNotEmpty) const _Div(), + if (label != null && label!.value.isNotEmpty) RoundedContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -610,100 +560,138 @@ class _ManageDomainsWidgetState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Transaction ID", + "Address label", style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of(context) + .extension()! + .textSubtitle1, ), ), Util.isDesktop - ? IconCopyButton( - data: utxo!.txid, - ) - : SimpleCopyButton( - data: utxo!.txid, - ), + ? tdv.IconCopyButton(data: label!.value) + : SimpleCopyButton(data: label!.value), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SelectableText( - utxo!.txid, + label!.value, style: STextStyles.w500_14(context), ), ], ), ), - const _Div(), - RoundedContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Expiry", - style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - const SizedBox( - height: 4, - ), - SelectableText( - message, - style: STextStyles.w500_14(context).copyWith( - color: color, + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction ID", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, + ), ), - ), - ], - ), + Util.isDesktop + ? tdv.IconCopyButton(data: utxo!.txid) + : SimpleCopyButton(data: utxo!.txid), + ], + ), + const SizedBox(height: 4), + SelectableText( + utxo!.txid, + style: STextStyles.w500_14(context), + ), + ], ), - const _Div(), - RoundedContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - color: Util.isDesktop - ? Colors.transparent - : Theme.of(context) - .extension()! - .popupBG, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Confirmations", - style: STextStyles.w500_14(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - const SizedBox( - height: 4, + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Expiry", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), - SelectableText( - "${utxo!.getConfirmations(currentHeight)}", - style: STextStyles.w500_14(context), + ), + const SizedBox(height: 4), + SelectableText( + message, + style: STextStyles.w500_14( + context, + ).copyWith(color: color), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: + Util.isDesktop + ? Colors.transparent + : Theme.of( + context, + ).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirmations", + style: STextStyles.w500_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), - ], - ), + ), + const SizedBox(height: 4), + SelectableText( + "${utxo!.getConfirmations(currentHeight)}", + style: STextStyles.w500_14(context), + ), + ], ), - ], - ); + ), + ], + ); }, ), ), @@ -723,9 +711,7 @@ class _Div extends StatelessWidget { color: Theme.of(context).extension()!.textFieldDefaultBG, ); } else { - return const SizedBox( - height: 12, - ); + return const SizedBox(height: 12); } } } diff --git a/lib/pages/receive_view/addresses/address_details_view.dart b/lib/pages/receive_view/addresses/address_details_view.dart index 96c2e66a62..51dfd7e4ee 100644 --- a/lib/pages/receive_view/addresses/address_details_view.dart +++ b/lib/pages/receive_view/addresses/address_details_view.dart @@ -38,7 +38,8 @@ import '../../../widgets/qr.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/transaction_card.dart'; import '../../wallet_view/sub_widgets/no_transactions_found.dart'; -import '../../wallet_view/transaction_views/transaction_details_view.dart'; +import '../../wallet_view/transaction_views/transaction_details_view.dart' + as tdv; import '../../wallet_view/transaction_views/tx_v2/transaction_v2_card.dart'; import 'address_tag.dart'; @@ -306,7 +307,7 @@ class _AddressDetailsViewState extends ConsumerState { detail: address.value, button: isDesktop - ? IconCopyButton(data: address.value) + ? tdv.IconCopyButton(data: address.value) : SimpleCopyButton(data: address.value), ), const _Div(height: 12), diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart index ee45a6e70a..927a09b3cf 100644 --- a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart @@ -21,7 +21,8 @@ import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/detail_item.dart'; import '../../../../widgets/expandable.dart'; import '../../../../widgets/stack_dialog.dart'; -import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tvd; import '../../../wallet_view/wallet_view.dart'; class FrostSendStep4 extends ConsumerStatefulWidget { @@ -43,9 +44,9 @@ class _FrostSendStep4State extends ConsumerState { @override void initState() { - final wallet = ref.read(pWallets).getWallet( - ref.read(pFrostScaffoldArgs)!.walletId!, - ) as BitcoinFrostWallet; + final wallet = + ref.read(pWallets).getWallet(ref.read(pFrostScaffoldArgs)!.walletId!) + as BitcoinFrostWallet; cryptoCurrency = wallet.cryptoCurrency; @@ -80,79 +81,70 @@ class _FrostSendStep4State extends ConsumerState { DetailItem( title: "Tx hex (debug mode only)", detail: ref.watch(pFrostTxData)!.raw!, - button: Util.isDesktop - ? IconCopyButton( - data: ref.watch(pFrostTxData)!.raw!, - ) - : SimpleCopyButton( - data: ref.watch(pFrostTxData)!.raw!, - ), - ), - if (kDebugMode) - const SizedBox( - height: 12, + button: + Util.isDesktop + ? tvd.IconCopyButton(data: ref.watch(pFrostTxData)!.raw!) + : SimpleCopyButton(data: ref.watch(pFrostTxData)!.raw!), ), + if (kDebugMode) const SizedBox(height: 12), Text( "Send ${cryptoCurrency.ticker}", style: STextStyles.w600_20(context), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), recipients.length == 1 ? _Recipient( - address: recipients[0].address, - amount: ref - .watch(pAmountFormatter(cryptoCurrency)) - .format(recipients[0].amount), - ) + address: recipients[0].address, + amount: ref + .watch(pAmountFormatter(cryptoCurrency)) + .format(recipients[0].amount), + ) : Column( - children: [ - for (int i = 0; i < recipients.length; i++) - Padding( - padding: const EdgeInsets.only(top: 10), - child: Expandable( - onExpandChanged: (state) { - setState(() { - _expandedStates[i] = - state == ExpandableState.expanded; - }); - }, - header: Padding( - padding: const EdgeInsets.only(top: 12, bottom: 6), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Recipient ${i + 1}", - style: STextStyles.itemSubtitle(context), - ), - SvgPicture.asset( - _expandedStates[i] - ? Assets.svg.chevronUp - : Assets.svg.chevronDown, - width: 12, - height: 6, - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ], - ), - ), - body: _Recipient( - address: recipients[i].address, - amount: ref - .watch(pAmountFormatter(cryptoCurrency)) - .format(recipients[i].amount), + children: [ + for (int i = 0; i < recipients.length; i++) + Padding( + padding: const EdgeInsets.only(top: 10), + child: Expandable( + onExpandChanged: (state) { + setState(() { + _expandedStates[i] = + state == ExpandableState.expanded; + }); + }, + header: Padding( + padding: const EdgeInsets.only(top: 12, bottom: 6), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recipient ${i + 1}", + style: STextStyles.itemSubtitle(context), + ), + SvgPicture.asset( + _expandedStates[i] + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, + width: 12, + height: 6, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ], ), ), + body: _Recipient( + address: recipients[i].address, + amount: ref + .watch(pAmountFormatter(cryptoCurrency)) + .format(recipients[i].amount), + ), ), - ], - ), - const SizedBox( - height: 12, - ), + ), + ], + ), + const SizedBox(height: 12), DetailItem( title: "Transaction fee", detail: ref @@ -160,38 +152,27 @@ class _FrostSendStep4State extends ConsumerState { .format(ref.watch(pFrostTxData)!.fee!), horizontal: true, ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), DetailItem( title: "Total", - detail: ref.watch(pAmountFormatter(cryptoCurrency)).format( + detail: ref + .watch(pAmountFormatter(cryptoCurrency)) + .format( ref.watch(pFrostTxData)!.fee! + recipients.map((e) => e.amount).reduce((v, e) => v += e), ), horizontal: true, ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), DetailItem( title: "Note", detail: ref.watch(pFrostTxData)!.note ?? "", ), - const SizedBox( - height: 12, - ), - DetailItem( - title: "Signers", - detail: signers, - ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), + DetailItem(title: "Signers", detail: signers), + const SizedBox(height: 12), if (!Util.isDesktop) const Spacer(), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), PrimaryButton( label: "Approve transaction", onPressed: () async { @@ -205,12 +186,8 @@ class _FrostSendStep4State extends ConsumerState { final txData = await showLoading( whileFuture: ref .read(pWallets) - .getWallet( - ref.read(pFrostScaffoldArgs)!.walletId!, - ) - .confirmSend( - txData: ref.read(pFrostTxData)!, - ), + .getWallet(ref.read(pFrostScaffoldArgs)!.walletId!) + .confirmSend(txData: ref.read(pFrostTxData)!), context: context, message: "Broadcasting transaction to network", rootNavigator: true, // used to pop using root nav @@ -227,7 +204,10 @@ class _FrostSendStep4State extends ConsumerState { if (txData != null) { ref.read(pFrostScaffoldCanPopDesktop.notifier).state = true; ref.read(pFrostTxData.state).state = txData; - ref.read(pFrostScaffoldArgs)!.parentNav.popUntil( + ref + .read(pFrostScaffoldArgs)! + .parentNav + .popUntil( ModalRoute.withName( Util.isDesktop ? MyStackView.routeName @@ -237,17 +217,18 @@ class _FrostSendStep4State extends ConsumerState { } } } catch (e, s) { - Logging.instance.f("$e\n$s", error: e, stackTrace: s,); + Logging.instance.f("$e\n$s", error: e, stackTrace: s); if (context.mounted) { return await showDialog( context: context, - builder: (_) => StackOkDialog( - title: "Broadcast error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - onOkPressed: - Navigator.of(context, rootNavigator: true).pop, - ), + builder: + (_) => StackOkDialog( + title: "Broadcast error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + onOkPressed: + Navigator.of(context, rootNavigator: true).pop, + ), ); } } finally { @@ -262,11 +243,7 @@ class _FrostSendStep4State extends ConsumerState { } class _Recipient extends StatelessWidget { - const _Recipient({ - super.key, - required this.address, - required this.amount, - }); + const _Recipient({super.key, required this.address, required this.amount}); final String address; final String amount; @@ -276,18 +253,9 @@ class _Recipient extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, children: [ - DetailItem( - title: "Address", - detail: address, - ), - const SizedBox( - height: 6, - ), - DetailItem( - title: "Amount", - detail: amount, - horizontal: true, - ), + DetailItem(title: "Address", detail: address), + const SizedBox(height: 6), + DetailItem(title: "Amount", detail: amount, horizontal: true), ], ); } diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index e4c637e53e..923283bb70 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -242,9 +242,11 @@ class _SendViewState extends ConsumerState { }); } } catch (e) { + // strip http:// and https:// if content contains @ if (coin is Epiccash) { - // strip http:// and https:// if content contains @ content = AddressUtils().formatEpicCashAddress(content); + } else if (coin is Mimblewimblecoin) { + content = AddressUtils().formatAddressMwc(content); } await _checkSparkNameAndOrSetAddress(content); @@ -1281,7 +1283,6 @@ class _SendViewState extends ConsumerState { }); } -<<<<<<< Decimal? price; if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { price = ref.watch( @@ -1291,23 +1292,22 @@ class _SendViewState extends ConsumerState { ); } -======= - if (coin is Mimblewimblecoin) { - sendToController.addListener(() { - _address = sendToController.text.trim(); - - if (_address != null && _address!.isNotEmpty) { - _address = _address!.trim(); - if (_address!.contains("\n")) { - _address = _address!.substring(0, _address!.indexOf("\n")); - } - - sendToController.text = AddressUtils().formatAddress(_address!); - } - }); - } + // Probably not be needed. + // if (coin is Mimblewimblecoin) { + // sendToController.addListener(() { + // _address = sendToController.text.trim(); + // + // if (_address != null && _address!.isNotEmpty) { + // _address = _address!.trim(); + // if (_address!.contains("\n")) { + // _address = _address!.substring(0, _address!.indexOf("\n")); + // } + // + // sendToController.text = AddressUtils().formatAddressMwc(_address!); + // } + // }); + // } ->>>>>>> return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -1621,7 +1621,6 @@ class _SendViewState extends ConsumerState { key: const Key( "sendViewPasteAddressFieldButtonKey", ), -<<<<<<< onTap: _pasteAddress, child: sendToController @@ -1629,63 +1628,6 @@ class _SendViewState extends ConsumerState { .isEmpty ? const ClipboardIcon() : const XIcon(), -======= - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain, - ); - if (data?.text != null && - data! - .text!.isNotEmpty) { - String content = - data.text!.trim(); - if (content - .contains("\n")) { - content = - content.substring( - 0, - content.indexOf( - "\n", - ), - ); - } - - if (coin is Epiccash) { - // strip http:// and https:// if content contains @ - content = AddressUtils() - .formatAddress( - content, - ); - } - if (coin - is Mimblewimblecoin) { - // strip http:// and https:// if content contains @ - content = AddressUtils() - .formatAddress( - content, - ); - } - sendToController.text = - content.trim(); - _address = content.trim(); - - _setValidAddressProviders( - _address, - ); - setState(() { - _addressToggleFlag = - sendToController - .text - .isNotEmpty; - }); - } - }, - child: sendToController - .text.isEmpty - ? const ClipboardIcon() - : const XIcon(), ->>>>>>> ), if (sendToController.text.isEmpty) TextFieldIconButton( @@ -2288,15 +2230,8 @@ class _SendViewState extends ConsumerState { ), ), ), -<<<<<<< - if (coin is Epiccash) const SizedBox(height: 12), -======= - ), - const SizedBox( - height: 12, - ), - if (coin is Epiccash || coin is Mimblewimblecoin) ->>>>>>> + if (coin is Epiccash || coin is Mimblewimblecoin) + const SizedBox(height: 12), Text( (coin is Epiccash) ? "Local Note (optional)" @@ -2304,15 +2239,62 @@ class _SendViewState extends ConsumerState { style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), -<<<<<<< - const SizedBox(height: 8), -======= - if (coin is Epiccash || coin is Mimblewimblecoin) - const SizedBox( - height: 8, + if (coin is Epiccash || coin is Mimblewimblecoin) + const SizedBox(height: 8), + if (coin is Epiccash || coin is Mimblewimblecoin) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: + Util.isDesktop ? false : true, + controller: noteController, + focusNode: _noteFocusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Type something...", + _noteFocusNode, + context, + ).copyWith( + suffixIcon: + noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only( + right: 0, + ), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + noteController.text = + ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + if (coin is Epiccash || coin is Mimblewimblecoin) + const SizedBox(height: 12), + Text( + (coin is Epiccash || coin is Mimblewimblecoin) + ? "Local Note (optional)" + : "Note (optional)", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, ), - if (coin is Epiccash || coin is Mimblewimblecoin) ->>>>>>> + const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -2356,73 +2338,25 @@ class _SendViewState extends ConsumerState { ), ), ), -<<<<<<< const SizedBox(height: 12), - if (hasFees) + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! NanoCurrency && + coin is! Tezos) Text( - "Transaction fee ${isEth - ? isCustomFee.value - ? "" - : "(max)" - : "(estimated)"}", + "Transaction fee (estimated)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, -======= - if (coin is Epiccash || coin is Mimblewimblecoin) - const SizedBox( - height: 12, - ), - Text( - (coin is Epiccash || coin is Mimblewimblecoin) - ? "Local Note (optional)" - : "Note (optional)", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, - controller: noteController, - focusNode: _noteFocusNode, - style: STextStyles.field(context), - onChanged: (_) => setState(() {}), - decoration: standardInputDecoration( - "Type something...", - _noteFocusNode, - context, - ).copyWith( - suffixIcon: noteController.text.isNotEmpty - ? Padding( - padding: - const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - noteController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, ->>>>>>> ), -<<<<<<< - if (hasFees) const SizedBox(height: 8), - if (hasFees) + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! NanoCurrency && + coin is! Tezos) + const SizedBox(height: 8), + if (coin is! Epiccash && + coin is! Mimblewimblecoin && + coin is! NanoCurrency && + coin is! Tezos) Stack( children: [ TextField( @@ -2432,192 +2366,160 @@ class _SendViewState extends ConsumerState { controller: feeController, readOnly: true, textInputAction: TextInputAction.none, -======= - ), - ), - const SizedBox( - height: 12, - ), - if (coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! NanoCurrency && - coin is! Tezos) - Text( - "Transaction fee (estimated)", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - if (coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! NanoCurrency && - coin is! Tezos) - const SizedBox( - height: 8, - ), - if (coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! NanoCurrency && - coin is! Tezos) - Stack( - children: [ - TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: - Util.isDesktop ? false : true, - controller: feeController, - readOnly: true, - textInputAction: TextInputAction.none, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, ->>>>>>> ), Padding( padding: const EdgeInsets.symmetric( horizontal: 12, ), - child: RawMaterialButton( - splashColor: - Theme.of( - context, - ).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, ), - onPressed: - isFiro && - ref - .watch( - publicPrivateBalanceStateProvider - .state, - ) - .state != - BalanceType.public - ? null - : _onFeeSelectPressed, - child: - (isFiro && - ref - .watch( - publicPrivateBalanceStateProvider - .state, - ) - .state != - BalanceType.public) - ? Row( - children: [ - FutureBuilder( - future: - _calculateFeesFuture, - builder: ( - context, - snapshot, - ) { - if (snapshot.connectionState == - ConnectionState - .done && - snapshot.hasData) { - _setCurrentFee( - snapshot.data!, - false, - ); - return Text( - "~${snapshot.data!}", - style: - STextStyles.itemSubtitle( - context, - ), - ); - } else { - return AnimatedText( - stringsToLoopThrough: - stringsToLoopThrough, - style: - STextStyles.itemSubtitle( - context, - ), - ); - } - }, - ), - ], - ) - : Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - Row( - children: [ - Text( - ref + child: RawMaterialButton( + splashColor: + Theme.of(context) + .extension()! + .highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: + isFiro && + ref .watch( - feeRateTypeMobileStateProvider + publicPrivateBalanceStateProvider .state, ) - .state - .prettyName, - style: - STextStyles.itemSubtitle12( - context, - ), - ), - const SizedBox(width: 10), - FutureBuilder( - future: - _calculateFeesFuture, - builder: ( - context, - snapshot, - ) { - if (snapshot.connectionState == - ConnectionState - .done && - snapshot - .hasData) { - _setCurrentFee( - snapshot.data!, - false, - ); - return Text( - isCustomFee.value - ? "" - : "~${snapshot.data!}", - style: - STextStyles.itemSubtitle( - context, - ), - ); - } else { - return AnimatedText( - stringsToLoopThrough: - stringsToLoopThrough, - style: - STextStyles.itemSubtitle( - context, - ), - ); - } - }, - ), - ], - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: - Theme.of(context) - .extension< - StackColors - >()! - .textSubtitle2, - ), - ], - ), + .state != + BalanceType.public + ? null + : _onFeeSelectPressed, + child: + (isFiro && + ref + .watch( + publicPrivateBalanceStateProvider + .state, + ) + .state != + BalanceType.public) + ? Row( + children: [ + FutureBuilder( + future: + _calculateFeesFuture, + builder: ( + context, + snapshot, + ) { + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + _setCurrentFee( + snapshot.data!, + false, + ); + return Text( + "~${snapshot.data!}", + style: + STextStyles.itemSubtitle( + context, + ), + ); + } else { + return AnimatedText( + stringsToLoopThrough: + stringsToLoopThrough, + style: + STextStyles.itemSubtitle( + context, + ), + ); + } + }, + ), + ], + ) + : Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Row( + children: [ + Text( + ref + .watch( + feeRateTypeMobileStateProvider + .state, + ) + .state + .prettyName, + style: + STextStyles.itemSubtitle12( + context, + ), + ), + const SizedBox( + width: 10, + ), + FutureBuilder( + future: + _calculateFeesFuture, + builder: ( + context, + snapshot, + ) { + if (snapshot.connectionState == + ConnectionState + .done && + snapshot + .hasData) { + _setCurrentFee( + snapshot.data!, + false, + ); + return Text( + isCustomFee + .value + ? "" + : "~${snapshot.data!}", + style: + STextStyles.itemSubtitle( + context, + ), + ); + } else { + return AnimatedText( + stringsToLoopThrough: + stringsToLoopThrough, + style: + STextStyles.itemSubtitle( + context, + ), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: + Theme.of(context) + .extension< + StackColors + >()! + .textSubtitle2, + ), + ], + ), + ), ), ), ], diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index 9cbc155329..88ed1a06fe 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -204,7 +204,6 @@ class AboutView extends ConsumerWidget { "Epic Cash Build Commit", style: STextStyles.titleBold12(context), ), -<<<<<<< const SizedBox(height: 4), SelectableText( GitStatus.epicCashCommit, @@ -219,136 +218,95 @@ class AboutView extends ConsumerWidget { }, ), const SizedBox(height: 12), -======= - ), - ], - ), - ); - }, - ), - if (AppConfig.coins - .whereType() - .isNotEmpty) - const SizedBox( - height: 12, - ), - if (AppConfig.coins - .whereType() - .isNotEmpty) - FutureBuilder( - future: GitStatus.getMimblewimblecoinCommitStatus(), - builder: ( - context, - AsyncSnapshot snapshot, - ) { - CommitStatus stateOfCommit = - CommitStatus.notLoaded; + if (AppConfig.coins + .whereType() + .isNotEmpty) + const SizedBox(height: 12), + if (AppConfig.coins + .whereType() + .isNotEmpty) + FutureBuilder( + future: + GitStatus.getMimblewimblecoinCommitStatus(), + builder: ( + context, + AsyncSnapshot snapshot, + ) { + CommitStatus stateOfCommit = + CommitStatus.notLoaded; - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - stateOfCommit = snapshot.data!; - } + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + stateOfCommit = snapshot.data!; + } - return RoundedWhiteContainer( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Text( - "Mimblewimblecoin Build Commit", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - SelectableText( - GitStatus.mimblewimblecoinCommit, - style: GitStatus.styleForStatus( - stateOfCommit, - context, + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Text( + "Mimblewimblecoin Build Commit", + style: STextStyles.titleBold12(context), ), - ), - ], - ), - ); - }, - ), - if (AppConfig.coins.whereType().isNotEmpty) - const SizedBox( - height: 12, - ), - // if (AppConfig.coins.whereType().isNotEmpty) - // FutureBuilder( - // future: GitStatus.getMoneroCommitStatus(), - // builder: ( - // context, - // AsyncSnapshot snapshot, - // ) { - // CommitStatus stateOfCommit = - // CommitStatus.notLoaded; - // - // if (snapshot.connectionState == - // ConnectionState.done && - // snapshot.hasData) { - // stateOfCommit = snapshot.data!; - // } - // return RoundedWhiteContainer( - // child: Column( - // crossAxisAlignment: - // CrossAxisAlignment.stretch, - // children: [ - // Text( - // "Monero Build Commit", - // style: STextStyles.titleBold12(context), - // ), - // const SizedBox( - // height: 4, - // ), - // SelectableText( - // GitStatus.moneroCommit, - // style: GitStatus.styleForStatus( - // stateOfCommit, - // context, - // ), - // ), - // ], - // ), - // ); - // }, - // ), - // const SizedBox( - // height: 12, - // ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Website", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 4, - ), - CustomTextButton( - text: "https://stackwallet.com", - onTap: () { - launchUrl( - Uri.parse("https://stackwallet.com"), - mode: LaunchMode.externalApplication, - ); - }, - ), - ], - ), - ), - if (AppConfig.coins.whereType().isNotEmpty) - const SizedBox( - height: 12, - ), - if (AppConfig.coins.whereType().isNotEmpty) ->>>>>>> + const SizedBox(height: 4), + SelectableText( + GitStatus.mimblewimblecoinCommit, + style: GitStatus.styleForStatus( + stateOfCommit, + context, + ), + ), + ], + ), + ); + }, + ), + if (AppConfig.coins.whereType().isNotEmpty) + const SizedBox(height: 12), + // if (AppConfig.coins.whereType().isNotEmpty) + // FutureBuilder( + // future: GitStatus.getMoneroCommitStatus(), + // builder: ( + // context, + // AsyncSnapshot snapshot, + // ) { + // CommitStatus stateOfCommit = + // CommitStatus.notLoaded; + // + // if (snapshot.connectionState == + // ConnectionState.done && + // snapshot.hasData) { + // stateOfCommit = snapshot.data!; + // } + // return RoundedWhiteContainer( + // child: Column( + // crossAxisAlignment: + // CrossAxisAlignment.stretch, + // children: [ + // Text( + // "Monero Build Commit", + // style: STextStyles.titleBold12(context), + // ), + // const SizedBox( + // height: 4, + // ), + // SelectableText( + // GitStatus.moneroCommit, + // style: GitStatus.styleForStatus( + // stateOfCommit, + // context, + // ), + // ), + // ], + // ), + // ); + // }, + // ), + // const SizedBox( + // height: 12, + // ), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -370,6 +328,30 @@ class AboutView extends ConsumerWidget { ], ), ), + if (AppConfig.coins.whereType().isNotEmpty) + const SizedBox(height: 12), + if (AppConfig.coins.whereType().isNotEmpty) + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Website", + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 4), + CustomTextButton( + text: "https://stackwallet.com", + onTap: () { + launchUrl( + Uri.parse("https://stackwallet.com"), + mode: LaunchMode.externalApplication, + ); + }, + ), + ], + ), + ), if (AppConfig.coins.whereType().isNotEmpty) const SizedBox(height: 12), if (AppConfig.coins.whereType().isNotEmpty) 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 e1f263d3b2..a178dddab9 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 @@ -476,7 +476,7 @@ abstract class SWB { case const (EpiccashWallet): await (wallet as EpiccashWallet).init(isRestore: true); break; - + case const (MimblewimblecoinWallet): await (wallet as MimblewimblecoinWallet).init(isRestore: true); break; @@ -499,13 +499,10 @@ abstract class SWB { int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0; if (restoreHeight <= 0) { -<<<<<<< if (wallet is EpiccashWallet || wallet is LibMoneroWallet || - wallet is LibSalviumWallet) { -======= - if (wallet is EpiccashWallet || wallet is MimblewimblecoinWallet || wallet is LibMoneroWallet) { ->>>>>>> + wallet is LibSalviumWallet || + wallet is MimblewimblecoinWallet) { restoreHeight = 0; } else { restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0; diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart index a0b502da24..71064a134b 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; + import '../../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import '../../../../providers/db/main_db_provider.dart'; import '../../../../themes/stack_colors.dart'; @@ -15,12 +15,11 @@ import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/desktop/desktop_app_bar.dart'; import '../../../../widgets/desktop/desktop_scaffold.dart'; import '../../../../widgets/rounded_white_container.dart'; +import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tdv; class FrostParticipantsView extends ConsumerWidget { - const FrostParticipantsView({ - super.key, - required this.walletId, - }); + const FrostParticipantsView({super.key, required this.walletId}); static const String routeName = "/frostParticipantsView"; @@ -29,74 +28,73 @@ class FrostParticipantsView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // TODO: optimize this by creating watcher providers (similar to normal WalletInfo) - final frostInfo = ref - .read(mainDBProvider) - .isar - .frostWalletInfo - .getByWalletIdSync(walletId)!; + final frostInfo = + ref + .read(mainDBProvider) + .isar + .frostWalletInfo + .getByWalletIdSync(walletId)!; return ConditionalParent( condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - trailing: ExitToMyStackButton(), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), + builder: + (child) => DesktopScaffold( + background: Theme.of(context).extension()!.background, + appBar: const DesktopAppBar( + isCompactHeight: false, + leading: AppBarBackButton(), + trailing: ExitToMyStackButton(), + ), + body: SizedBox(width: 480, child: child), + ), child: ConditionalParent( condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Participants", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Participants", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), ), - ), - ), child: Column( - crossAxisAlignment: Util.isDesktop - ? CrossAxisAlignment.start - : CrossAxisAlignment.stretch, + crossAxisAlignment: + Util.isDesktop + ? CrossAxisAlignment.start + : CrossAxisAlignment.stretch, children: [ for (int i = 0; i < frostInfo.participants.length; i++) Padding( - padding: const EdgeInsets.symmetric( - vertical: 5, - ), + padding: const EdgeInsets.symmetric(vertical: 5), child: RoundedWhiteContainer( child: Row( children: [ @@ -104,12 +102,11 @@ class FrostParticipantsView extends ConsumerWidget { width: 26, height: 26, decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldActiveBG, - borderRadius: BorderRadius.circular( - 200, - ), + color: + Theme.of( + context, + ).extension()!.textFieldActiveBG, + borderRadius: BorderRadius.circular(200), ), child: Center( child: SvgPicture.asset( @@ -119,9 +116,7 @@ class FrostParticipantsView extends ConsumerWidget { ), ), ), - const SizedBox( - width: 8, - ), + const SizedBox(width: 8), Expanded( child: Text( frostInfo.participants[i] == frostInfo.myName @@ -130,12 +125,8 @@ class FrostParticipantsView extends ConsumerWidget { style: STextStyles.w500_14(context), ), ), - const SizedBox( - width: 8, - ), - IconCopyButton( - data: frostInfo.participants[i], - ), + const SizedBox(width: 8), + tdv.IconCopyButton(data: frostInfo.participants[i]), ], ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart index d94cca0882..dded7630c0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart @@ -39,7 +39,8 @@ import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; -import '../../../wallet_view/transaction_views/transaction_details_view.dart'; +import '../../../wallet_view/transaction_views/transaction_details_view.dart' + as tdv; import '../../sub_widgets/view_only_wallet_data_widget.dart'; import 'cn_wallet_keys.dart'; import 'wallet_xprivs.dart'; @@ -323,7 +324,7 @@ class _FrostKeys extends StatelessWidget { detail: frostWalletData!.config, button: Util.isDesktop - ? IconCopyButton(data: frostWalletData!.config) + ? tdv.IconCopyButton(data: frostWalletData!.config) : SimpleCopyButton(data: frostWalletData!.config), ), const SizedBox(height: 16), @@ -332,7 +333,7 @@ class _FrostKeys extends StatelessWidget { detail: frostWalletData!.keys, button: Util.isDesktop - ? IconCopyButton(data: frostWalletData!.keys) + ? tdv.IconCopyButton(data: frostWalletData!.keys) : SimpleCopyButton(data: frostWalletData!.keys), ), if (prevGen) const SizedBox(height: 24), @@ -350,7 +351,7 @@ class _FrostKeys extends StatelessWidget { detail: frostWalletData!.prevGen!.config, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: frostWalletData!.prevGen!.config, ) : SimpleCopyButton( @@ -364,7 +365,7 @@ class _FrostKeys extends StatelessWidget { detail: frostWalletData!.prevGen!.keys, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: frostWalletData!.prevGen!.keys, ) : SimpleCopyButton( 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 95a081d85f..75b121ff36 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 @@ -33,26 +33,18 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/coins/epiccash.dart'; -<<<<<<< import '../../../../wallets/crypto_currency/coins/litecoin.dart'; -======= 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/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/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart'; -======= -import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; -import '../../../../wallets/wallet/impl/monero_wallet.dart'; -import '../../../../wallets/wallet/impl/wownero_wallet.dart'; ->>>>>>> import '../../../../widgets/animated_text.dart'; import '../../../../widgets/background.dart'; import '../../../../widgets/conditional_parent.dart'; @@ -282,20 +274,14 @@ class _WalletNetworkSettingsViewState final coin = ref.read(pWalletCoin(widget.walletId)); -<<<<<<< // TODO: handle isMwebEnabled toggled if (coin is Monero || coin is Wownero || coin is Epiccash || coin is Salvium || + coin is Mimblewimblecoin || (coin is Litecoin && ref.read(pWalletInfo(widget.walletId)).isMwebEnabled)) { -======= - if (coin is Monero || - coin is Wownero || - coin is Epiccash || - coin is Mimblewimblecoin) { ->>>>>>> _blocksRemainingSubscription = eventBus.on().listen( (event) async { if (event.walletId == widget.walletId) { @@ -384,10 +370,10 @@ class _WalletNetworkSettingsViewState _percent = highestPercent.clamp(0.0, 1.0); } } else if (coin is Mimblewimblecoin) { - final double highestPercent = (ref - .watch(pWallets) - .getWallet(widget.walletId) as MimblewimblecoinWallet) - .highestPercent; + final double highestPercent = + (ref.watch(pWallets).getWallet(widget.walletId) + as MimblewimblecoinWallet) + .highestPercent; if (_percent < highestPercent) { _percent = highestPercent.clamp(0.0, 1.0); } @@ -682,19 +668,15 @@ class _WalletNetworkSettingsViewState ), if (coin is Monero || coin is Wownero || -<<<<<<< coin is Epiccash || coin is Salvium || + coin is Mimblewimblecoin || (coin is Litecoin && ref.watch( pWalletInfo( widget.walletId, ).select((s) => s.isMwebEnabled), ))) -======= - coin is Mimblewimblecoin || - coin is Epiccash) ->>>>>>> Text( " (Blocks to go: ${_blocksRemaining == -1 ? "?" : _blocksRemaining})", style: STextStyles.syncPercent( @@ -1024,21 +1006,13 @@ class _WalletNetworkSettingsViewState coin: ref.watch(pWalletCoin(widget.walletId)), popBackToRoute: WalletNetworkSettingsView.routeName, ), -<<<<<<< - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) - const SizedBox(height: 32), - if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash) -======= if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) - const SizedBox( - height: 32, - ), + const SizedBox(height: 32), if (isDesktop && ref.watch(pWalletCoin(widget.walletId)) is! Epiccash && ref.watch(pWalletCoin(widget.walletId)) is! Mimblewimblecoin) ->>>>>>> Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart index 03ba8cdf0e..2bcf90827f 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart @@ -35,7 +35,8 @@ import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; import '../../../home_view/home_view.dart'; -import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tdv; class DeleteWalletRecoveryPhraseView extends ConsumerStatefulWidget { const DeleteWalletRecoveryPhraseView({ @@ -236,7 +237,7 @@ class _DeleteWalletRecoveryPhraseViewState detail: widget.frostWalletData!.config, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: widget .frostWalletData! @@ -255,7 +256,7 @@ class _DeleteWalletRecoveryPhraseViewState detail: widget.frostWalletData!.keys, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: widget.frostWalletData!.keys, ) @@ -283,7 +284,7 @@ class _DeleteWalletRecoveryPhraseViewState .config, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: widget .frostWalletData! @@ -306,7 +307,7 @@ class _DeleteWalletRecoveryPhraseViewState widget.frostWalletData!.prevGen!.keys, button: Util.isDesktop - ? IconCopyButton( + ? tdv.IconCopyButton( data: widget .frostWalletData! diff --git a/lib/pages/spark_names/sub_widgets/spark_name_details.dart b/lib/pages/spark_names/sub_widgets/spark_name_details.dart index 97983735ab..372f7484e6 100644 --- a/lib/pages/spark_names/sub_widgets/spark_name_details.dart +++ b/lib/pages/spark_names/sub_widgets/spark_name_details.dart @@ -17,7 +17,8 @@ import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/dialogs/s_dialog.dart'; import '../../../widgets/rounded_container.dart'; -import '../../wallet_view/transaction_views/transaction_details_view.dart'; +import '../../wallet_view/transaction_views/transaction_details_view.dart' + as tvd; import '../buy_spark_name_view.dart'; class SparkNameDetailsView extends ConsumerStatefulWidget { @@ -288,7 +289,7 @@ class _SparkNameDetailsViewState extends ConsumerState { ), ), Util.isDesktop - ? IconCopyButton(data: name.address) + ? tvd.IconCopyButton(data: name.address) : SimpleCopyButton(data: name.address), ], ), @@ -344,7 +345,9 @@ class _SparkNameDetailsViewState extends ConsumerState { ), ), Util.isDesktop - ? IconCopyButton(data: label!.value) + ? tvd.IconCopyButton( + data: label!.value, + ) : SimpleCopyButton( data: label!.value, ), diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 9f8575e999..397fd7d798 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -22,7 +22,6 @@ import '../../../models/isar/models/contact_entry.dart'; import '../../../models/isar/models/transaction_note.dart'; import '../../../models/transaction_filter.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../../../providers/db/main_db_provider.dart'; import '../../../providers/global/address_book_service_provider.dart'; import '../../../providers/providers.dart'; import '../../../providers/ui/transaction_filter_provider.dart'; @@ -49,7 +48,7 @@ import '../../../widgets/stack_text_field.dart'; import '../../../widgets/textfield_icon_button.dart'; import '../../../widgets/transaction_card.dart'; import '../sub_widgets/tx_icon.dart'; -import 'transaction_details_view.dart'; +import 'transaction_details_view.dart' as tvd; import 'transaction_search_filter_view.dart'; typedef _GroupedTransactions = @@ -978,7 +977,7 @@ class _DesktopTransactionCardRowState (context) => DesktopDialog( maxHeight: MediaQuery.of(context).size.height - 64, maxWidth: 580, - child: TransactionDetailsView( + child: tvd.TransactionDetailsView( transaction: _transaction, coin: coin, walletId: walletId, @@ -988,7 +987,7 @@ class _DesktopTransactionCardRowState } else { unawaited( Navigator.of(context).pushNamed( - TransactionDetailsView.routeName, + tvd.TransactionDetailsView.routeName, arguments: Tuple3(_transaction, coin, walletId), ), ); diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 1d01598996..ca997e70b6 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1770,9 +1770,8 @@ class _TransactionDetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, -<<<<<<< floatingActionButton: - (coin is Epiccash && + ((coin is Epiccash || coin is Mimblewimblecoin) && _transaction.getConfirmations(currentHeight) < 1 && _transaction.isCancelled == false) ? ConditionalParent( @@ -1784,26 +1783,6 @@ class _TransactionDetailsViewState vertical: 16, ), child: child, -======= - floatingActionButton: ((coin is Epiccash || coin is Mimblewimblecoin) && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) - ? ConditionalParent( - condition: isDesktop, - builder: (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, - ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, ->>>>>>> ), child: SizedBox( width: MediaQuery.of(context).size.width - 32, @@ -1845,6 +1824,62 @@ class _TransactionDetailsViewState // pop progress dialog Navigator.of(context).pop(); + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find MWC transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + if (result.isEmpty) { await showDialog( context: context, @@ -1876,87 +1911,18 @@ class _TransactionDetailsViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", + message: + "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", context: context, ), ); return; } -<<<<<<< }, child: Text( "Cancel Transaction", style: STextStyles.button(context), ), -======= - } else if (wallet is MimblewimblecoinWallet) { - final String? id = _transaction.slateId; - if (id == null) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Could not find MWC transaction ID", - context: context, - ), - ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - ), - ); - - final result = - await wallet.cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, - ), - ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); - } - } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", - context: context, - ), - ); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), ->>>>>>> ), ), ) diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart index 3532646000..b4b95f1cb1 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart @@ -21,7 +21,6 @@ import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../../models/isar/models/contact_entry.dart'; import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/transaction_filter.dart'; -import '../../../../providers/db/main_db_provider.dart'; import '../../../../providers/global/address_book_service_provider.dart'; import '../../../../providers/providers.dart'; import '../../../../providers/ui/transaction_filter_provider.dart'; @@ -51,7 +50,7 @@ import '../../../../widgets/textfield_icon_button.dart'; import '../../sub_widgets/tx_icon.dart'; import '../transaction_search_filter_view.dart'; import 'transaction_v2_card.dart'; -import 'transaction_v2_details_view.dart'; +import 'transaction_v2_details_view.dart' as tvd; typedef _GroupedTransactions = ({String label, DateTime startDate, List transactions}); @@ -992,7 +991,7 @@ class _DesktopTransactionCardRowState (context) => DesktopDialog( maxHeight: MediaQuery.of(context).size.height - 64, maxWidth: 580, - child: TransactionV2DetailsView( + child: tvd.TransactionV2DetailsView( transaction: _transaction, coin: coin, walletId: walletId, @@ -1002,7 +1001,7 @@ class _DesktopTransactionCardRowState } else { unawaited( Navigator.of(context).pushNamed( - TransactionV2DetailsView.routeName, + tvd.TransactionV2DetailsView.routeName, arguments: (tx: _transaction, coin: coin, walletId: walletId), ), ); diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart index 3fb0aaaab4..a8b30fd5ae 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart @@ -25,7 +25,7 @@ import '../../../../widgets/coin_ticker_tag.dart'; import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../sub_widgets/tx_icon.dart'; -import 'transaction_v2_details_view.dart'; +import 'transaction_v2_details_view.dart' as tvd; class TransactionCardV2 extends ConsumerStatefulWidget { const TransactionCardV2({super.key, required this.transaction}); @@ -200,7 +200,7 @@ class _TransactionCardStateV2 extends ConsumerState { (context) => DesktopDialog( maxHeight: MediaQuery.of(context).size.height - 64, maxWidth: 580, - child: TransactionV2DetailsView( + child: tvd.TransactionV2DetailsView( transaction: _transaction, coin: coin, walletId: walletId, @@ -210,7 +210,7 @@ class _TransactionCardStateV2 extends ConsumerState { } else { unawaited( Navigator.of(context).pushNamed( - TransactionV2DetailsView.routeName, + tvd.TransactionV2DetailsView.routeName, arguments: (tx: _transaction, coin: coin, walletId: walletId), ), ); 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 89ec1299dc..e57a1f33d7 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 @@ -1228,7 +1228,7 @@ class _TransactionV2DetailsViewState padding: isDesktop ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), +e : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/route_generator.dart b/lib/route_generator.dart index ee09bdba0a..4c5e16ccfd 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -165,7 +165,8 @@ import 'pages/wallet_view/transaction_views/transaction_search_filter_view.dart' import 'pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart'; import 'pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart'; import 'pages/wallet_view/transaction_views/tx_v2/fusion_group_details_view.dart'; -import 'pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import 'pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tvd; import 'pages/wallet_view/wallet_view.dart'; import 'pages/wallets_view/wallets_overview.dart'; import 'pages/wallets_view/wallets_view.dart'; @@ -1614,13 +1615,13 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case TransactionV2DetailsView.routeName: + case tvd.TransactionV2DetailsView.routeName: if (args is ({TransactionV2 tx, CryptoCurrency coin, String walletId})) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: - (_) => TransactionV2DetailsView( + (_) => tvd.TransactionV2DetailsView( transaction: args.tx, coin: args.coin, walletId: args.walletId, diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index 3bfcb96b97..0f478d6ddc 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -26,9 +26,9 @@ import '../wallets/crypto_currency/crypto_currency.dart'; 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/lib_salvium_wallet.dart'; -import '../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../wallets/wallet/wallet.dart'; import 'event_bus/events/wallet_added_event.dart'; import 'event_bus/global_event_bus.dart'; @@ -138,9 +138,8 @@ class Wallets { walletId: walletId, secureStore: secureStorage, ); - Logging.instance.log( + Logging.instance.i( "Mimblewimblecoin wallet: $walletId deleted with result: $deleteResult", - level: LogLevel.Info, ); } diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart index cfcd4a098f..c0615b25da 100644 --- a/lib/utilities/test_mwcmqs_connection.dart +++ b/lib/utilities/test_mwcmqs_connection.dart @@ -18,10 +18,8 @@ import 'prefs.dart'; Future _testMwcMqsNodeConnection(Uri uri) async { final HTTP client = HTTP(); - try { - final headers = { - 'Content-Type': 'application/json', - }; + try { + final headers = {'Content-Type': 'application/json'}; if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') { const username = 'mwcmain'; @@ -33,9 +31,10 @@ Future _testMwcMqsNodeConnection(Uri uri) async { .get( url: uri, headers: headers, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ) .timeout( const Duration(milliseconds: 2000), @@ -50,7 +49,7 @@ Future _testMwcMqsNodeConnection(Uri uri) async { return false; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s"); return false; } } @@ -77,7 +76,7 @@ Future testMwcNodeConnection(NodeFormData data) async { Uri uri = Uri.parse(data.host! + path_postfix); uri = uri.replace(port: data.port); - + try { if (await _testMwcMqsNodeConnection(uri)) { return data; @@ -85,7 +84,7 @@ Future testMwcNodeConnection(NodeFormData data) async { return null; } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s"); return null; } } diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index 5b4d559d86..8491ca3db5 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:on_chain/ada/ada.dart'; import 'package:socks5_proxy/socks.dart'; +import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk; import '../networking/http.dart'; import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; @@ -21,22 +22,17 @@ import '../wallets/wallet/impl/solana_wallet.dart'; import 'connection_check/electrum_connection_check.dart'; import 'logger.dart'; import 'test_epic_box_connection.dart'; -import 'test_mwcmqs_connection.dart'; import 'test_eth_node_connection.dart'; import 'test_monero_node_connection.dart'; +import 'test_mwcmqs_connection.dart'; import 'test_stellar_node_connection.dart'; import 'tor_plain_net_option_enum.dart'; -import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk; - Future _xmrHelper( NodeFormData nodeFormData, BuildContext context, void Function(NodeFormData)? onSuccess, - ({ - InternetAddress host, - int port, - })? proxyInfo, + ({InternetAddress host, int port})? proxyInfo, ) async { final data = nodeFormData; final url = data.host!; @@ -129,11 +125,7 @@ Future testNodeConnection({ onSuccess?.call(data); } } catch (e, s) { - Logging.instance.w( - "$e\n$s", - error: e, - stackTrace: s, - ); + Logging.instance.w("$e\n$s", error: e, stackTrace: s); } break; @@ -146,15 +138,16 @@ Future testNodeConnection({ onSuccess?.call(data); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); + Logging.instance.w("$e\n$s"); } break; case CryptonoteCurrency(): try { - final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor - ? ref.read(pTorService).getProxyInfo() - : null; + final proxyInfo = + ref.read(prefsChangeNotifierProvider).useTor + ? ref.read(pTorService).getProxyInfo() + : null; final url = formData.host!; final uri = Uri.tryParse(url); @@ -193,11 +186,7 @@ Future testNodeConnection({ } } } catch (e, s) { - Logging.instance.w( - "$e\n$s", - error: e, - stackTrace: s, - ); + Logging.instance.w("$e\n$s", error: e, stackTrace: s); } break; @@ -228,8 +217,10 @@ Future testNodeConnection({ case Stellar(): try { - testPassed = - await testStellarNodeConnection(formData.host!, formData.port!); + testPassed = await testStellarNodeConnection( + formData.host!, + formData.port!, + ); } catch (_) {} break; @@ -240,14 +231,11 @@ Future testNodeConnection({ final response = await HTTP().post( url: uri, headers: {"Content-Type": "application/json"}, - body: jsonEncode( - { - "action": "version", - }, - ), - proxyInfo: ref.read(prefsChangeNotifierProvider).useTor - ? ref.read(pTorService).getProxyInfo() - : null, + body: jsonEncode({"action": "version"}), + proxyInfo: + ref.read(prefsChangeNotifierProvider).useTor + ? ref.read(pTorService).getProxyInfo() + : null, ); testPassed = response.code == 200; @@ -273,9 +261,7 @@ Future testNodeConnection({ ); final health = await rpcClient.getHealth(); - Logging.instance.i( - "Solana testNodeConnection \"health=$health\"", - ); + Logging.instance.i("Solana testNodeConnection \"health=$health\""); return true; } catch (_) { testPassed = false; @@ -287,10 +273,7 @@ Future testNodeConnection({ final client = HttpClient(); if (ref.read(prefsChangeNotifierProvider).useTor) { final proxyInfo = TorService.sharedInstance.getProxyInfo(); - final proxySettings = ProxySettings( - proxyInfo.host, - proxyInfo.port, - ); + final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port); SocksTCPClient.assignToHttpClient(client, [proxySettings]); } final blockfrostProvider = BlockforestProvider( @@ -304,9 +287,7 @@ Future testNodeConnection({ BlockfrostRequestBackendHealthStatus(), ); - Logging.instance.i( - "Cardano testNodeConnection \"health=$health\"", - ); + Logging.instance.i("Cardano testNodeConnection \"health=$health\""); return health; } catch (_) { @@ -319,7 +300,7 @@ Future testNodeConnection({ final daemon = xelis_sdk.DaemonClient( endPoint: "${formData.host!}:${formData.port!}", secureWebSocket: formData.useSSL ?? false, - timeout: 5000 + timeout: 5000, ); daemon.connect(); diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index cd0066828e..4dd1138062 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -64,7 +64,7 @@ bool validateAddress(String address) { @override - NodeModel get defaultNode { + NodeModel defaultNode({required bool isPrimary}) { switch (network) { case CryptoCurrencyNetwork.main: return NodeModel( @@ -79,6 +79,7 @@ bool validateAddress(String address) { isDown: false, torEnabled: true, clearnetEnabled: true, + isPrimary: isPrimary, ); default: diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 31339e2fcb..1a349a777b 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -13,12 +13,12 @@ import 'package:stack_wallet_backup/generate_password.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../../../models/balance.dart'; -import '../../../models/mwcmqs_config_model.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/transaction.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/mwcmqs_config_model.dart'; import '../../../models/node_model.dart'; import '../../../models/paymint/fee_object_model.dart'; import '../../../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; @@ -28,41 +28,42 @@ import '../../../services/event_bus/events/global/refresh_percent_changed_event. import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; import '../../../utilities/amount/amount.dart'; +import '../../../utilities/default_mwcmqs.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; import '../../../utilities/logger.dart'; import '../../../utilities/stack_file_system.dart'; -import '../../../utilities/default_mwcmqs.dart'; import '../../../utilities/test_mwcmqs_connection.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../models/tx_data.dart'; import '../intermediate/bip39_wallet.dart'; import '../supporting/mimblewimblecoin_wallet_info_extension.dart'; - class MimblewimblecoinWallet extends Bip39Wallet { MimblewimblecoinWallet(CryptoCurrencyNetwork network) - : super(Mimblewimblecoin(network)); + : super(Mimblewimblecoin(network)); final syncMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; bool _logsInitialized = false; - + double highestPercent = 0; Future get getSyncPercent async { final int lastScannedBlock = info.mimblewimblecoinData?.lastScannedBlock ?? 0; final _chainHeight = await chainHeight; final double restorePercent = lastScannedBlock / _chainHeight; - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercent, walletId)); + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highestPercent, walletId), + ); if (restorePercent > highestPercent) { highestPercent = restorePercent; } final int blocksRemaining = _chainHeight - lastScannedBlock; - GlobalEventBus.instance - .fire(BlocksRemainingEvent(blocksRemaining, walletId)); + GlobalEventBus.instance.fire( + BlocksRemainingEvent(blocksRemaining, walletId), + ); return restorePercent < 0 ? 0.0 : restorePercent; } @@ -70,7 +71,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { Future updateMwcmqsConfig(String host, int port) async { final String stringConfig = jsonEncode({ "mwcmqs_domain": host, - "mwcmqs_port": port + "mwcmqs_port": port, }); await secureStorageInterface.write( key: '${walletId}_mwcmqsConfig', @@ -82,21 +83,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// returns an empty String on success, error message on failure Future cancelPendingTransactionAndPost(String txSlateId) async { try { - final String wallet = (await secureStorageInterface.read( - key: '${walletId}_wallet', - ))!; + final String wallet = + (await secureStorageInterface.read(key: '${walletId}_wallet'))!; final result = await mimblewimblecoin.Libmwc.cancelTransaction( wallet: wallet, transactionId: txSlateId, ); - Logging.instance.log( - "cancel $txSlateId result: $result", - level: LogLevel.Info, - ); + Logging.instance.i("cancel $txSlateId result: $result"); return result; } catch (e, s) { - Logging.instance.log("$e, $s", level: LogLevel.Error); + Logging.instance.e("$e, $s"); return e.toString(); } } @@ -187,13 +184,13 @@ class MimblewimblecoinWallet extends Bip39Wallet { } return realFee; } catch (e, s) { - Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + Logging.instance.e("Error getting fees $e - $s"); rethrow; } } Future _startSync() async { - Logging.instance.log("request start sync", level: LogLevel.Info); + Logging.instance.i("request start sync"); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const int refreshFromNode = 1; if (!syncMutex.isLocked) { @@ -206,17 +203,19 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); }); } else { - Logging.instance.log("request start sync denied", level: LogLevel.Info); + Logging.instance.i("request start sync denied"); } } Future< - ({ - double awaitingFinalization, - double pending, - double spendable, - double total - })> _allWalletBalances() async { + ({ + double awaitingFinalization, + double pending, + double spendable, + double total, + }) + > + _allWalletBalances() async { final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const refreshFromNode = 0; return await mimblewimblecoin.Libmwc.getWalletBalances( @@ -233,9 +232,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { final uri = Uri.parse('wss://$host:$port'); - channel = WebSocketChannel.connect( - uri, - ); + channel = WebSocketChannel.connect(uri); await channel.ready; @@ -245,10 +242,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { return response is String && response.contains("Challenge"); } catch (_) { - Logging.instance.log( - "_testMwcmqsConnection failed on \"$host:$port\"", - level: LogLevel.Info, - ); + Logging.instance.i("_testMwcmqsConnection failed on \"$host:$port\""); return false; } finally { await channel?.sink.close(); @@ -276,8 +270,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); return true; } catch (e, s) { - Logging.instance - .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + Logging.instance.e("ERROR STORING ADDRESS $e $s"); return false; } } @@ -289,14 +282,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { // of the last one that has not been processed, or the index after the one most recently processed; return receivingIndex; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance.e("$e $s"); return 0; } } - Future

_generateAndStoreReceivingAddressForIndex( - int index, - ) async { + Future
_generateAndStoreReceivingAddressForIndex(int index) async { Address? address = await getCurrentReceivingAddress(); if (address == null) { @@ -321,13 +312,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { final walletAddress = await mimblewimblecoin.Libmwc.getAddressInfo( wallet: wallet!, - index: index + index: index, ); - Logging.instance.log( - "WALLET_ADDRESS_IS $walletAddress", - level: LogLevel.Info, - ); + Logging.instance.i("WALLET_ADDRESS_IS $walletAddress"); final address = Address( walletId: walletId, @@ -346,8 +334,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { //First stop the current listener mimblewimblecoin.Libmwc.stopMwcMqsListener(); - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); // max number of blocks to scan per loop iteration const scanChunkSize = 10000; @@ -362,9 +351,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { // loop while scanning in chain in chunks (of blocks?) while (lastScannedBlock < chainHeight) { - Logging.instance.log( + Logging.instance.i( "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", - level: LogLevel.Info, ); final int nextScannedBlock = await mimblewimblecoin.Libmwc.scanOutputs( @@ -389,23 +377,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { lastScannedBlock = nextScannedBlock; } - Logging.instance.log( - "_startScans successfully at the tip", - level: LogLevel.Info, - ); + Logging.instance.i("_startScans successfully at the tip"); //Once scanner completes restart listener await _listenToMwcmqs(); } catch (e, s) { - Logging.instance.log( - "_startScans failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("_startScans failed: $e\n$s"); rethrow; } } Future _listenToMwcmqs() async { - Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + Logging.instance.i("STARTING WALLET LISTENER ...."); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); mimblewimblecoin.Libmwc.startMwcMqsListener( @@ -462,10 +444,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { - if (isRestore != true) { - String? encodedWallet = - await secureStorageInterface.read(key: "${walletId}_wallet"); + String? encodedWallet = await secureStorageInterface.read( + key: "${walletId}_wallet", + ); // check if should create a new wallet if (encodedWallet == null) { @@ -539,8 +521,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { // await mimblewimblecoin.Libmwc.initLogs(config: config); // _logsInitialized = true; // Set flag to true after initializing //} - final password = - await secureStorageInterface.read(key: '${walletId}_password'); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); final walletOpen = await mimblewimblecoin.Libmwc.openWallet( config: config, @@ -554,10 +537,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { await updateNode(); } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance.log( - "$runtimeType init() failed: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("$runtimeType init() failed: $e\n$s"); } } } @@ -568,8 +548,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future confirmSend({required TxData txData}) async { try { - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); final MwcMqsConfigModel mwcmqsConfig = await getMwcMqsConfig(); // TODO determine whether it is worth sending change to a change address. @@ -609,12 +590,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { minimumConfirmations: cryptoCurrency.minConfirms, note: txData.noteOnChain!, ); - } else { throw Exception( "Unsupported address format: $receiverAddress. Please use a valid address.", ); - } final Map txAddressInfo = {}; @@ -622,14 +601,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { txAddressInfo['to'] = txData.recipients!.first.address; await _putSendToAddresses(transaction, txAddressInfo); - return txData.copyWith( - txid: transaction.slateId, - ); + return txData.copyWith(txid: transaction.slateId); } catch (e, s) { - Logging.instance.log( - "Mimblewimblecoin confirmSend: $e\n$s", - level: LogLevel.Error, - ); + Logging.instance.e("Mimblewimblecoin confirmSend: $e\n$s"); rethrow; } } @@ -639,11 +613,11 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { if (txData.recipients?.length != 1) { throw Exception( - "Mimblewimblecoin prepare send requires a single recipient!"); + "Mimblewimblecoin prepare send requires a single recipient!", + ); } - ({String address, Amount amount, bool isChange}) recipient = - txData.recipients!.first; + TxRecipient recipient = txData.recipients!.first; final int realFee = await _nativeFee(recipient.amount.raw.toInt()); final feeAmount = Amount( @@ -658,20 +632,17 @@ class MimblewimblecoinWallet extends Bip39Wallet { } if (info.cachedBalance.spendable == recipient.amount) { - recipient = ( + recipient = TxRecipient( address: recipient.address, amount: recipient.amount - feeAmount, isChange: recipient.isChange, + addressType: AddressType.mimbleWimble, ); } - return txData.copyWith( - recipients: [recipient], - fee: feeAmount, - ); + return txData.copyWith(recipients: [recipient], fee: feeAmount); } catch (e, s) { - Logging.instance - .log("Mimblewimblecoin prepareSend: $e\n$s", level: LogLevel.Error); + Logging.instance.e("Mimblewimblecoin prepareSend: $e\n$s"); rethrow; } } @@ -745,7 +716,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_wallet', value: walletOpen, ); - + await _generateAndStoreReceivingAddressForIndex( mimblewimblecoinData.receivingIndex, ); @@ -754,9 +725,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { unawaited(refresh()); } catch (e, s) { - Logging.instance.log( + Logging.instance.i( "Exception rethrown from electrumx_mixin recover(): $e\n$s", - level: LogLevel.Info, ); rethrow; @@ -869,9 +839,8 @@ class MimblewimblecoinWallet extends Bip39Wallet { cryptoCurrency, ), ); - Logging.instance.log( + Logging.instance.e( "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error, ); } finally { refreshMutex.release(); @@ -901,14 +870,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { ), ); - await info.updateBalance( - newBalance: balance, - isar: mainDB.isar, - ); + await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "Mimblewimblecoin wallet failed to update balance: $e\n$s", - level: LogLevel.Warning, ); } } @@ -916,20 +881,22 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future updateTransactions() async { try { - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); const refreshFromNode = 1; - final myAddresses = await mainDB - .getAddresses(walletId) - .filter() - .typeEqualTo(AddressType.mimbleWimble) - .and() - .subTypeEqualTo(AddressSubType.receiving) - .and() - .valueIsNotEmpty() - .valueProperty() - .findAll(); + final myAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .typeEqualTo(AddressType.mimbleWimble) + .and() + .subTypeEqualTo(AddressSubType.receiving) + .and() + .valueIsNotEmpty() + .valueProperty() + .findAll(); final myAddressesSet = myAddresses.toSet(); final transactions = await mimblewimblecoin.Libmwc.getTransactions( @@ -942,12 +909,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { final slatesToCommits = info.mimblewimblecoinData?.slatesToCommits ?? {}; for (final tx in transactions) { - Logging.instance.log("tx: $tx", level: LogLevel.Info); + Logging.instance.i("tx: $tx"); final isIncoming = tx.txType == mimblewimblecoin_models.TransactionType.TxReceived || - tx.txType == - mimblewimblecoin_models.TransactionType.TxReceivedCancelled; + tx.txType == + mimblewimblecoin_models.TransactionType.TxReceivedCancelled; final slateId = tx.txSlateId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; final numberOfMessages = tx.messages?.messages.length; @@ -968,9 +935,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: credit.toString(), - addresses: [ - if (addressFrom != null) addressFrom, - ], + addresses: [if (addressFrom != null) addressFrom], walletOwns: true, ); final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( @@ -1012,14 +977,16 @@ class MimblewimblecoinWallet extends Bip39Wallet { "numberOfMessages": numberOfMessages, "slateId": slateId, "onChainNote": onChainNote, - "isCancelled": tx.txType == + "isCancelled": + tx.txType == mimblewimblecoin_models.TransactionType.TxSentCancelled || tx.txType == mimblewimblecoin_models.TransactionType.TxReceivedCancelled, - "overrideFee": Amount( - rawValue: BigInt.from(fee), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + "overrideFee": + Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), }; final txn = TransactionV2( @@ -1049,10 +1016,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { await mainDB.isar.transactionV2s.putAll(txns); }); } catch (e, s) { - Logging.instance.log( + Logging.instance.w( "${cryptoCurrency.runtimeType} ${cryptoCurrency.network} net wallet" " \"${info.name}\"_${info.walletId} updateTransactions() failed: $e\n$s", - level: LogLevel.Warning, ); } } @@ -1089,7 +1055,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { ) != null; } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); + Logging.instance.i("$e\n$s"); return false; } } @@ -1097,8 +1063,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future updateChainHeight() async { final config = await _getRealConfig(); - final latestHeight = - await mimblewimblecoin.Libmwc.getChainHeight(config: config); + final latestHeight = await mimblewimblecoin.Libmwc.getChainHeight( + config: config, + ); await info.updateCachedChainHeight( newHeight: latestHeight, isar: mainDB.isar, @@ -1106,7 +1073,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? final int currentFee = await _nativeFee( amount.raw.toInt(), @@ -1126,9 +1093,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { numberOfBlocksFast: 10, numberOfBlocksAverage: 10, numberOfBlocksSlow: 10, - fast: 1, - medium: 1, - slow: 1, + fast: BigInt.one, + medium: BigInt.one, + slow: BigInt.one, ); } @@ -1143,8 +1110,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { timer?.cancel(); timer = null; await super.exit(); - Logging.instance - .log("Mimblewimblecoin_wallet exit finished", level: LogLevel.Info); + Logging.instance.i("Mimblewimblecoin_wallet exit finished"); } } @@ -1176,7 +1142,7 @@ Future deleteMimblewimblecoinWallet({ config: config!, ); } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); + Logging.instance.e("$e\n$s"); return "deleteMimblewimblecoinWallet($walletId) failed..."; } } diff --git a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart index 0c876d5c57..f9de38fca5 100644 --- a/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart +++ b/lib/wallets/wallet/supporting/mimblewimblecoin_wallet_info_extension.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:isar/isar.dart'; + import '../../../utilities/logger.dart'; import '../../isar/models/wallet_info.dart'; @@ -13,14 +14,11 @@ extension MimblewimblecoinWalletInfoExtension on WalletInfo { } try { return ExtraMimblewimblecoinWalletInfo.fromMap( - Map.from( - jsonDecode(data) as Map, - ), + Map.from(jsonDecode(data) as Map), ); } catch (e, s) { - Logging.instance.log( + Logging.instance.e( "ExtraMimblewimblecoinWalletInfo.fromMap failed: $e\n$s", - level: LogLevel.Error, ); return null; } @@ -32,7 +30,9 @@ extension MimblewimblecoinWalletInfoExtension on WalletInfo { }) async { await updateOtherData( newEntries: { - WalletInfoKeys.mimblewimblecoinData: jsonEncode(mimblewimblecoinData.toMap()), + WalletInfoKeys.mimblewimblecoinData: jsonEncode( + mimblewimblecoinData.toMap(), + ), }, isar: isar, ); @@ -76,13 +76,13 @@ class ExtraMimblewimblecoinWalletInfo { } ExtraMimblewimblecoinWalletInfo.fromMap(Map json) - : receivingIndex = json['receivingIndex'] as int, - changeIndex = json['changeIndex'] as int, - slatesToAddresses = json['slatesToAddresses'] as Map, - slatesToCommits = json['slatesToCommits'] as Map, - lastScannedBlock = json['lastScannedBlock'] as int, - restoreHeight = json['restoreHeight'] as int, - creationHeight = json['creationHeight'] as int; + : receivingIndex = json['receivingIndex'] as int, + changeIndex = json['changeIndex'] as int, + slatesToAddresses = json['slatesToAddresses'] as Map, + slatesToCommits = json['slatesToCommits'] as Map, + lastScannedBlock = json['lastScannedBlock'] as int, + restoreHeight = json['restoreHeight'] as int, + creationHeight = json['creationHeight'] as int; ExtraMimblewimblecoinWalletInfo copyWith({ int? receivingIndex, diff --git a/lib/widgets/tx_key_widget.dart b/lib/widgets/tx_key_widget.dart index ba8e9b33c4..f9e1fb4bc5 100644 --- a/lib/widgets/tx_key_widget.dart +++ b/lib/widgets/tx_key_widget.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../pages/pinpad_views/pinpad_dialog.dart'; -import '../pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; +import '../pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart' + as tvd; import '../pages_desktop_specific/password/request_desktop_auth_dialog.dart'; import '../providers/global/wallets_provider.dart'; import '../utilities/text_styles.dart'; @@ -14,11 +15,7 @@ import 'detail_item.dart'; class TxKeyWidget extends ConsumerStatefulWidget { /// The [walletId] MUST be the id of a [LibMoneroWallet]! - const TxKeyWidget({ - super.key, - required this.walletId, - required this.txid, - }); + const TxKeyWidget({super.key, required this.walletId, required this.txid}); final String walletId; final String txid; @@ -41,14 +38,18 @@ 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, ); @@ -75,23 +76,17 @@ class _TxKeyWidgetState extends ConsumerState { @override Widget build(BuildContext context) { return DetailItemBase( - button: _private == null - ? CustomTextButton( - text: "Show", - onTap: _loadTxKey, - enabled: _private == null, - ) - : Util.isDesktop - ? IconCopyButton( - data: _private!, - ) - : SimpleCopyButton( - data: _private!, - ), - title: Text( - "Private view key", - style: STextStyles.itemSubtitle(context), - ), + 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 _private ?? "*" * 52, // 52 is approx length From 7cbb227035964066554716ca2cd8e6926580de75 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 4 Sep 2025 16:00:33 -0500 Subject: [PATCH 28/91] fix(mwc): resolve some more merge conflicts (wip) I'm less sure about these than those in the previous commit --- .../sub_widgets/desktop_receive.dart | 377 +++++++++--------- .../wallet_view/sub_widgets/desktop_send.dart | 317 ++------------- .../coins/mimblewimblecoin.dart | 34 +- 3 files changed, 245 insertions(+), 483 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index ee9ebd300b..3e74a6258c 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -10,7 +10,6 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -27,7 +26,6 @@ import '../../../../providers/providers.dart'; import '../../../../route_generator.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; -import '../../../../utilities/amount/amount.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; @@ -50,18 +48,17 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../../widgets/conditional_parent.dart'; -import '../../../../widgets/desktop/primary_button.dart'; -import '../../../../widgets/icon_widgets/clipboard_icon.dart'; -import '../../../../widgets/icon_widgets/x_icon.dart'; -import '../../../../widgets/stack_text_field.dart'; -import '../../../../widgets/textfield_icon_button.dart'; -import '../../../../widgets/toggle.dart'; import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; +import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; +import '../../../../widgets/stack_text_field.dart'; +import '../../../../widgets/textfield_icon_button.dart'; import 'desktop_mwc_txs_method_toggle.dart'; class DesktopReceive extends ConsumerStatefulWidget { @@ -101,8 +98,6 @@ class _DesktopReceiveState extends ConsumerState { final Map _addressMap = {}; final Map> _addressSubMap = {}; - - Future pasteAddress() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { @@ -152,6 +147,7 @@ class _DesktopReceiveState extends ConsumerState { } } } + Future generateNewAddress() async { final wallet = ref.read(pWallets).getWallet(walletId); if (wallet is MultiAddressInterface) { @@ -307,13 +303,12 @@ class _DesktopReceiveState extends ConsumerState { wallet is MwebInterface && !wallet.info.isViewOnly && wallet.info.isMwebEnabled; - + isMimblewimblecoin = wallet is MimblewimblecoinWallet; if (isMimblewimblecoin) { _selectedMethodMwc = "Slatepack"; } debugPrint("Address generated: $isMimblewimblecoin"); - if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { showMultiType = false; @@ -471,36 +466,44 @@ class _DesktopReceiveState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox( - height: 4, - ), - if (isMimblewimblecoin) - Padding( - padding: const EdgeInsets.all(0), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, // Fallback color - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Theme.of(context).extension()?.backgroundAppBar ?? Colors.grey, // Fallback color - width: 1, + const SizedBox(height: 4), + if (isMimblewimblecoin) + Padding( + padding: const EdgeInsets.all(0), + child: Container( + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension()?.textFieldDefaultBG ?? + Colors.white, // Fallback color + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: + Theme.of( + context, + ).extension()?.backgroundAppBar ?? + Colors.grey, // Fallback color + width: 1, + ), ), - ), - child: SizedBox( - height: 50, // Provide an explicit height to avoid infinite constraints - child: MwcTxsMethodToggle( - onChanged: (TxsMethodMwcType type) { - setState(() { - _selectedMethodMwc = type == TxsMethodMwcType.automatic ? 'Slatepack' : 'Automatic'; - }); - }, + child: SizedBox( + height: + 50, // Provide an explicit height to avoid infinite constraints + child: MwcTxsMethodToggle( + onChanged: (TxsMethodMwcType type) { + setState(() { + _selectedMethodMwc = + type == TxsMethodMwcType.automatic + ? 'Slatepack' + : 'Automatic'; + }); + }, + ), ), ), ), - ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ConditionalParent( condition: showMultiType, builder: @@ -675,9 +678,7 @@ class _DesktopReceiveState extends ConsumerState { : generateNewAddress, label: "Generate new address", ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -686,9 +687,10 @@ class _DesktopReceiveState extends ConsumerState { Text( "Receive Slatepack", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), @@ -721,9 +723,10 @@ class _DesktopReceiveState extends ConsumerState { }, focusNode: _addressFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, height: 1.8, ), decoration: standardInputDecoration( @@ -734,37 +737,42 @@ class _DesktopReceiveState extends ConsumerState { ).copyWith( contentPadding: const EdgeInsets.symmetric( horizontal: 16, - vertical: 12, // Adjust vertical padding for better alignment + vertical: + 12, // Adjust vertical padding for better alignment ), suffixIcon: Padding( - padding: receiveSlateController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), + padding: + receiveSlateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _addressToggleFlag ? TextFieldIconButton( - key: const Key("sendViewClearAddressFieldButtonKey"), - onTap: () { - receiveSlateController.text = ""; - _address = ""; - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) + key: const Key( + "sendViewClearAddressFieldButtonKey", + ), + onTap: () { + receiveSlateController.text = ""; + _address = ""; + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey", - ), - onTap: pasteAddress, - child: receiveSlateController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), + key: const Key( + "sendViewPasteAddressFieldButtonKey", ), + onTap: pasteAddress, + child: + receiveSlateController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), ], ), ), @@ -774,40 +782,38 @@ class _DesktopReceiveState extends ConsumerState { ), ], ) - - //Padding( - // padding: const EdgeInsets.symmetric(vertical: 8.0), - // child: TextField( - // maxLines: 8, // Set to a higher number to make the height larger - // minLines: 5, // Allow it to shrink if input is small - // decoration: InputDecoration( - // labelText: 'Enter Slatepack Message', - // alignLabelWithHint: true, - // border: OutlineInputBorder( - // borderRadius: BorderRadius.circular(8), - // ), - // filled: true, - // fillColor: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, - // contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0), - // ), - // onChanged: (value) { - // // Handle text input changes (e.g., store in a state variable) - // debugPrint('Slatepack Message: $value'); - // }, - // ), - //) - + //Padding( + // padding: const EdgeInsets.symmetric(vertical: 8.0), + // child: TextField( + // maxLines: 8, // Set to a higher number to make the height larger + // minLines: 5, // Allow it to shrink if input is small + // decoration: InputDecoration( + // labelText: 'Enter Slatepack Message', + // alignLabelWithHint: true, + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // filled: true, + // fillColor: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, + // contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0), + // ), + // onChanged: (value) { + // // Handle text input changes (e.g., store in a state variable) + // debugPrint('Slatepack Message: $value'); + // }, + // ), + //) else - Center( - child: QR( - data: AddressUtils.buildUriString(coin.uriScheme, address, {}), - size: 200, + Center( + child: QR( + data: AddressUtils.buildUriString(coin.uriScheme, address, {}), + size: 200, + ), ), - ), const SizedBox(height: 32), + // TODO: create transparent button class to account for hover - // Conditional logic for 'Submit' button or QR code - + // Conditional logic for 'Submit' button or QR code if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -816,86 +822,87 @@ class _DesktopReceiveState extends ConsumerState { label: "Preview Receive Slatepack", enabled: true, onPressed: () { - debugPrint('Submit button pressed for Mimblewimblecoin Slatepack'); + debugPrint( + 'Submit button pressed for Mimblewimblecoin Slatepack', + ); }, ), ) else - GestureDetector( - onTap: () async { - if (Util.isDesktop) { - await showDialog( - context: context, - builder: - (context) => DesktopDialog( - maxHeight: double.infinity, - maxWidth: 580, - child: Column( - children: [ - Row( - children: [ - const AppBarBackButton(size: 40, iconSize: 24), - Text( - "Generate QR code", - style: STextStyles.desktopH3(context), - ), - ], - ), - IntrinsicHeight( - child: Navigator( - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: - (_, __) => [ - RouteGenerator.generateRoute( - RouteSettings( - name: GenerateUriQrCodeView.routeName, - arguments: Tuple2(coin, address), + GestureDetector( + onTap: () async { + if (Util.isDesktop) { + await showDialog( + context: context, + builder: + (context) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + children: [ + const AppBarBackButton(size: 40, iconSize: 24), + Text( + "Generate QR code", + style: STextStyles.desktopH3(context), + ), + ], + ), + IntrinsicHeight( + child: Navigator( + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: + (_, __) => [ + RouteGenerator.generateRoute( + RouteSettings( + name: GenerateUriQrCodeView.routeName, + arguments: Tuple2(coin, address), + ), ), - ), - ], + ], + ), ), + ], + ), + ), + ); + } else { + unawaited( + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: + (_) => GenerateUriQrCodeView( + coin: coin, + receivingAddress: address, ), - ], + settings: const RouteSettings( + name: GenerateUriQrCodeView.routeName, ), ), - ); - } else { - unawaited( - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: - (_) => GenerateUriQrCodeView( - coin: coin, - receivingAddress: address, - ), - settings: const RouteSettings( - name: GenerateUriQrCodeView.routeName, - ), ), - ), - ); - } - }, - child: Container( - color: Colors.transparent, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.qrcode, - width: 14, - height: 16, - color: - Theme.of( - context, - ).extension()!.accentColorBlue, - ), - const SizedBox(width: 8), - Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( + ); + } + }, + child: Container( + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.qrcode, + width: 14, + height: 16, + color: + Theme.of( + context, + ).extension()!.accentColorBlue, + ), + const SizedBox(width: 8), + Text( "Create new QR code", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: @@ -903,27 +910,27 @@ class _DesktopReceiveState extends ConsumerState { context, ).extension()!.accentColorBlue, ), - const SizedBox(width: 8), - Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( - "Create new QR code", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorBlue, - ), + ), + const SizedBox(width: 8), + Padding( + padding: const EdgeInsets.only(bottom: 2), + child: Text( + "Create new QR code", + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.accentColorBlue, ), ), - ], - ), - ], + ), + ], + ), + ), ), - ), - ), ], ); } } - - diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index f0f924919d..ae5733c968 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -40,12 +40,7 @@ import '../../../../utilities/amount/amount_unit.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; -<<<<<<< - -======= -import '../../../../utilities/enums/fee_rate_type_enum.dart'; import '../../../../utilities/enums/txs_method_mwc_enum.dart'; ->>>>>>> import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; import '../../../../utilities/text_styles.dart'; @@ -77,12 +72,8 @@ import '../../../../widgets/textfield_icon_button.dart'; import '../../../coin_control/desktop_coin_control_use_dialog.dart'; import '../../../desktop_home_view.dart'; import 'address_book_address_chooser/address_book_address_chooser.dart'; -<<<<<<< -import 'desktop_send_fee_form.dart'; -======= -import 'desktop_fee_dropdown.dart'; import 'desktop_mwc_txs_method_toggle.dart'; ->>>>>>> +import 'desktop_send_fee_form.dart'; class DesktopSend extends ConsumerStatefulWidget { const DesktopSend({ @@ -114,7 +105,6 @@ class _DesktopSendState extends ConsumerState { late TextEditingController memoController; late TextEditingController nonceController; - late final SendViewAutoFillData? _data; final _addressFocusNode = FocusNode(); @@ -126,7 +116,7 @@ class _DesktopSendState extends ConsumerState { late final bool isStellar; late final bool isMimblewimblecoin; - String? _selectedMethodMwc; + String? _selectedMethodMwc; String? _note; String? _onChainNote; @@ -492,9 +482,7 @@ class _DesktopSendState extends ConsumerState { txData = txData.copyWith(noteOnChain: _onChainNote ?? ""); } if (coin is Mimblewimblecoin) { - txData = txData.copyWith( - noteOnChain: _onChainNote ?? "", - ); + txData = txData.copyWith(noteOnChain: _onChainNote ?? ""); } } // pop building dialog @@ -1054,9 +1042,8 @@ class _DesktopSendState extends ConsumerState { }); } -<<<<<<< final balType = ref.watch(publicPrivateBalanceStateProvider); -======= + if (coin is Mimblewimblecoin) { sendToController.addListener(() { _address = sendToController.text; @@ -1072,9 +1059,6 @@ class _DesktopSendState extends ConsumerState { }); } - final firoType = ref.watch(publicPrivateBalanceStateProvider); ->>>>>>> - final isMwebEnabled = ref.watch( pWalletInfo(walletId).select((s) => s.isMwebEnabled), ); @@ -1107,41 +1091,46 @@ class _DesktopSendState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ -<<<<<<< const SizedBox(height: 4), - if (showPrivateBalance) -======= - const SizedBox( - height: 4, - ), + if (showPrivateBalance) const SizedBox(height: 4), if (isMimblewimblecoin) Padding( padding: const EdgeInsets.all(0), child: Container( decoration: BoxDecoration( - color: Theme.of(context).extension()?.textFieldDefaultBG ?? Colors.white, // Fallback color + color: + Theme.of( + context, + ).extension()?.textFieldDefaultBG ?? + Colors.white, // Fallback color borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context).extension()?.backgroundAppBar ?? Colors.grey, // Fallback color + color: + Theme.of( + context, + ).extension()?.backgroundAppBar ?? + Colors.grey, // Fallback color width: 1, ), ), child: SizedBox( - height: 50, // Provide an explicit height to avoid infinite constraints + height: + 50, // Provide an explicit height to avoid infinite constraints child: MwcTxsMethodToggle( onChanged: (TxsMethodMwcType type) { setState(() { - _selectedMethodMwc = type == TxsMethodMwcType.automatic ? 'Slatepack' : 'Automatic'; + _selectedMethodMwc = + type == TxsMethodMwcType.automatic + ? 'Slatepack' + : 'Automatic'; }); }, ), ), ), ), - if (isMimblewimblecoin) if (coin is Firo) ->>>>>>> Text( "Send from", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -1243,14 +1232,7 @@ class _DesktopSendState extends ConsumerState { ), ), ), -<<<<<<< if (showPrivateBalance) const SizedBox(height: 20), -======= - if (coin is Firo || isMimblewimblecoin) - const SizedBox( - height: 20, - ), ->>>>>>> if (isPaynymSend) Text( "Send to PayNym address", @@ -1747,9 +1729,11 @@ class _DesktopSendState extends ConsumerState { ), ), ), -<<<<<<< if (!isPaynymSend) const SizedBox(height: 20), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) + if (coin is! NanoCurrency && + coin is! Epiccash && + coin is! Tezos && + coin is! Mimblewimblecoin) DesktopSendFeeForm( walletId: walletId, isToken: false, @@ -1770,59 +1754,6 @@ class _DesktopSendState extends ConsumerState { Theme.of( context, ).extension()!.textFieldActiveSearchIconRight, -======= - if (!isPaynymSend) - const SizedBox( - height: 20, - ), - if (coin is! NanoCurrency && - coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! Tezos) - ConditionalParent( - condition: ref.watch(pWallets).getWallet(walletId) - is ElectrumXInterface && - !(((coin is Firo) && - (ref.watch(publicPrivateBalanceStateProvider.state).state == - FiroType.lelantus || - ref - .watch(publicPrivateBalanceStateProvider.state) - .state == - FiroType.spark))), - builder: (child) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - child, - CustomTextButton( - text: "Edit", - onTap: () async { - feeSelectionResult = await showDialog< - ( - FeeRateType, - String?, - String?, - )?>( - context: context, - builder: (_) => DesktopFeeDialog( - walletId: walletId, - ), - ); - - if (feeSelectionResult != null) { - if (isCustomFee && - feeSelectionResult!.$1 != FeeRateType.custom) { - isCustomFee = false; - } else if (!isCustomFee && - feeSelectionResult!.$1 == FeeRateType.custom) { - isCustomFee = true; - } - } - - setState(() {}); - }, - ), - ], ->>>>>>> ), textAlign: TextAlign.left, ), @@ -1864,195 +1795,7 @@ class _DesktopSendState extends ConsumerState { ), ), ), -<<<<<<< const SizedBox(height: 36), -======= - if (coin is! NanoCurrency && - coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! Tezos) - const SizedBox( - height: 10, - ), - if (coin is! NanoCurrency && - coin is! Epiccash && - coin is! Mimblewimblecoin && - coin is! Tezos) - if (!isCustomFee) - Padding( - padding: const EdgeInsets.all(10), - child: (feeSelectionResult?.$2 == null) - ? FutureBuilder( - future: ref.watch( - pWallets.select( - (value) => value.getWallet(walletId).fees, - ), - ), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - return DesktopFeeItem( - feeObject: snapshot.data, - feeRateType: FeeRateType.average, - walletId: walletId, - isButton: false, - feeFor: ({ - required Amount amount, - required FeeRateType feeRateType, - required int feeRate, - required CryptoCurrency coin, - }) async { - if (ref - .read(feeSheetSessionCacheProvider) - .average[amount] == - null) { - final wallet = - ref.read(pWallets).getWallet(walletId); - - if (coin is Monero || coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - lib_monero.TransactionPriority.medium.value, - ); - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = fee; - } else if ((coin is Firo) && - ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state != - FiroType.public) { - final firoWallet = wallet as FiroWallet; - - if (ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state == - FiroType.lelantus) { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = - await firoWallet - .estimateFeeForLelantus(amount); - } else if (ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state == - FiroType.spark) { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = - await firoWallet - .estimateFeeForSpark(amount); - } - } else { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = - await wallet.estimateFeeFor( - amount, - feeRate, - ); - } - } - return ref - .read(feeSheetSessionCacheProvider) - .average[amount]!; - }, - isSelected: true, - ); - } else { - return Row( - children: [ - AnimatedText( - stringsToLoopThrough: stringsToLoopThrough, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - ), - ), - ], - ); - } - }, - ) - : (coin is Firo) && - ref - .watch( - publicPrivateBalanceStateProvider.state, - ) - .state == - FiroType.lelantus - ? Text( - "~${ref.watch(pAmountFormatter(coin)).format( - Amount( - rawValue: BigInt.parse("3794"), - fractionDigits: coin.fractionDigits, - ), - indicatePrecisionLoss: false, - )}", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - ), - textAlign: TextAlign.left, - ) - : Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - feeSelectionResult?.$2 ?? "", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - ), - textAlign: TextAlign.left, - ), - Text( - feeSelectionResult?.$3 ?? "", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - ), - ], - ), - ), - if (isCustomFee) - Padding( - padding: const EdgeInsets.only( - bottom: 12, - top: 16, - ), - child: FeeSlider( - coin: coin, - onSatVByteChanged: (rate) { - customFeeRate = rate; - }, - ), - ), - const SizedBox( - height: 36, - ), ->>>>>>> PrimaryButton( buttonHeight: ButtonHeight.l, label: "Preview send", @@ -2090,8 +1833,10 @@ String formatAddressMwc(String mimblewimblecoinAddress) { mimblewimblecoinAddress.startsWith("https://")) && mimblewimblecoinAddress.contains("@")) { mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll("http://", ""); - mimblewimblecoinAddress = - mimblewimblecoinAddress.replaceAll("https://", ""); + mimblewimblecoinAddress = mimblewimblecoinAddress.replaceAll( + "https://", + "", + ); } // strip mailto: prefix if (mimblewimblecoinAddress.startsWith("mailto:")) { @@ -2101,7 +1846,9 @@ String formatAddressMwc(String mimblewimblecoinAddress) { if (mimblewimblecoinAddress.endsWith("/") && mimblewimblecoinAddress.contains("@")) { mimblewimblecoinAddress = mimblewimblecoinAddress.substring( - 0, mimblewimblecoinAddress.length - 1); + 0, + mimblewimblecoinAddress.length - 1, + ); } return mimblewimblecoinAddress; } diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 4dd1138062..cc6d397c39 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -50,18 +50,19 @@ class Mimblewimblecoin extends Bip39Currency { // change this to change the number of confirms a tx needs in order to show as confirmed int get minConfirms => 3; - @override -bool validateAddress(String address) { - Uri? uri = Uri.tryParse(address); - if (uri != null && - (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "mwcmqs") && - uri.host.isNotEmpty && - !uri.host.endsWith(".onion")) { - return true; + @override + bool validateAddress(String address) { + Uri? uri = Uri.tryParse(address); + if (uri != null && + (uri.scheme == "http" || + uri.scheme == "https" || + uri.scheme == "mwcmqs") && + uri.host.isNotEmpty && + !uri.host.endsWith(".onion")) { + return true; + } + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } - return mimblewimblecoin.Libmwc.validateSendAddress(address: address); -} - @override NodeModel defaultNode({required bool isPrimary}) { @@ -79,7 +80,7 @@ bool validateAddress(String address) { isDown: false, torEnabled: true, clearnetEnabled: true, - isPrimary: isPrimary, + isPrimary: true, ); default: @@ -112,7 +113,8 @@ bool validateAddress(String address) { int get targetBlockTimeSeconds => 60; @override - DerivePathType get defaultDerivePathType => throw UnsupportedError( + DerivePathType get defaultDerivePathType => + throw UnsupportedError( "$runtimeType does not use bitcoin style derivation paths", ); @@ -125,4 +127,10 @@ bool validateAddress(String address) { ); } } + + @override + AddressType? getAddressType(String address) { + // TODO: implement getAddressType. + throw UnimplementedError(); + } } From b6e0796800507a9776fc4c1e6d12d1a053c5ef77 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 5 Sep 2025 01:05:14 -0500 Subject: [PATCH 29/91] fix(mwc): revert transaction details view changes these will need to be incorporated correctly later. for now, I just want to test that flutter_libmwc integration has gone well. --- .../tx_v2/transaction_v2_details_view.dart | 2759 +++++++---------- 1 file changed, 1127 insertions(+), 1632 deletions(-) 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 e57a1f33d7..af044e6825 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 @@ -44,7 +44,6 @@ import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; -import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../widgets/background.dart'; @@ -137,34 +136,34 @@ class _TransactionV2DetailsViewState context: context, builder: (context) => DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Boost transaction", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Flexible( - child: SingleChildScrollView( - child: BoostTransactionView( - transaction: _transaction, - ), + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Boost transaction", + style: STextStyles.desktopH3(context), ), ), + const DesktopDialogCloseButton(), ], ), - ), + Flexible( + child: SingleChildScrollView( + child: BoostTransactionView( + transaction: _transaction, + ), + ), + ), + ], + ), + ), ); } } else { @@ -189,14 +188,14 @@ class _TransactionV2DetailsViewState hasTxKeyProbably = (wallet is LibMoneroWallet || wallet is LibSalviumWallet) && - (_transaction.type == TransactionType.outgoing || - _transaction.type == TransactionType.sentToSelf); + (_transaction.type == TransactionType.outgoing || + _transaction.type == TransactionType.sentToSelf); if (_transaction.type - case TransactionType.sentToSelf || TransactionType.outgoing) { + case TransactionType.sentToSelf || TransactionType.outgoing) { supportsRbf = _transaction.subType == TransactionSubType.none && - wallet is RbfInterface; + wallet is RbfInterface; } else { supportsRbf = false; } @@ -249,43 +248,14 @@ class _TransactionV2DetailsViewState _transaction.outputs .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, ), - ) - .toList(); - } else if (_transaction.isMimblewimblecoinTransaction) { - switch (_transaction.type) { - case TransactionType.outgoing: - case TransactionType.unknown: - amount = _transaction.getAmountSentFromThisWallet( - fractionDigits: fractionDigits, - ); - break; - - case TransactionType.incoming: - case TransactionType.sentToSelf: - amount = _transaction.getAmountReceivedInThisWallet( - fractionDigits: fractionDigits, - ); - break; - } - data = _transaction.outputs - .map( - (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ) ), ) - .toList(); + .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( fractionDigits: fractionDigits, @@ -295,13 +265,13 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); } else { switch (_transaction.type) { @@ -315,13 +285,13 @@ class _TransactionV2DetailsViewState .where((e) => !e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; @@ -341,17 +311,17 @@ class _TransactionV2DetailsViewState .and() .heightEqualTo(_transaction.height) .anyOf( - _transaction.outputs - .where( - (e) => - e.walletOwns && - e.addresses.isEmpty && - e.scriptPubKeyHex.length >= 488, - ) - .map((e) => e.scriptPubKeyHex.substring(2, 488)) - .toList(), + _transaction.outputs + .where( + (e) => + e.walletOwns && + e.addresses.isEmpty && + e.scriptPubKeyHex.length >= 488, + ) + .map((e) => e.scriptPubKeyHex.substring(2, 488)) + .toList(), (q, element) => q.serializedCoinB64StartsWith(element), - ) + ) .memoProperty() .findFirstSync(); } @@ -368,7 +338,7 @@ class _TransactionV2DetailsViewState rawValue: _transaction.outputs .where( (e) => e.walletOwns && !e.addresses.contains(changeAddress), - ) + ) .fold(BigInt.zero, (p, e) => p + e.value), fractionDigits: coin.fractionDigits, ); @@ -382,13 +352,13 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; @@ -402,13 +372,13 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; } @@ -426,11 +396,11 @@ class _TransactionV2DetailsViewState currentChainHeight: height, minConfirms: minConfirms, minCoinbaseConfirms: - ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .minCoinbaseConfirms, + ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); Future fetchContactNameFor(String address) async { @@ -443,10 +413,10 @@ class _TransactionV2DetailsViewState .contacts .where( (element) => - element.addresses - .where((element) => element.address == address) - .isNotEmpty, - ); + element.addresses + .where((element) => element.address == address) + .isNotEmpty, + ); if (contacts.isNotEmpty) { return contacts.first.name; } else { @@ -467,7 +437,7 @@ class _TransactionV2DetailsViewState return StackDialog( title: "Attention", message: - "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", icon: Row( children: [ Consumer( @@ -475,7 +445,7 @@ class _TransactionV2DetailsViewState return Checkbox( value: ref.watch( prefsChangeNotifierProvider.select( - (value) => value.hideBlockExplorerWarning, + (value) => value.hideBlockExplorerWarning, ), ), onChanged: (value) { @@ -503,9 +473,9 @@ class _TransactionV2DetailsViewState "Cancel", style: STextStyles.button(context).copyWith( color: - Theme.of( - context, - ).extension()!.accentColorDark, + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), @@ -538,7 +508,7 @@ class _TransactionV2DetailsViewState return Checkbox( value: ref.watch( prefsChangeNotifierProvider.select( - (value) => value.hideBlockExplorerWarning, + (value) => value.hideBlockExplorerWarning, ), ), onChanged: (value) { @@ -628,10 +598,10 @@ class _TransactionV2DetailsViewState )) { price = ref.watch( priceAnd24hChangeNotifierProvider.select( - (value) => - isTokenTx - ? value.getTokenPrice(_transaction.contractAddress!)?.value - : value.getPrice(coin)?.value, + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.contractAddress!)?.value + : value.getPrice(coin)?.value, ), ); } @@ -641,37 +611,37 @@ class _TransactionV2DetailsViewState builder: (child) => Background(child: child), child: Scaffold( backgroundColor: - isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, appBar: - isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Transaction details", - style: STextStyles.navBarTitle(context), - ), - ), + isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Transaction details", + style: STextStyles.navBarTitle(context), + ), + ), body: ConditionalParent( condition: !isDesktop, builder: (child) => SafeArea(child: child), child: Padding( padding: - isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -689,19 +659,19 @@ class _TransactionV2DetailsViewState Flexible( child: Padding( padding: - isDesktop - ? const EdgeInsets.only(right: 32, bottom: 32) - : const EdgeInsets.all(0), + isDesktop + ? const EdgeInsets.only(right: 32, bottom: 32) + : const EdgeInsets.all(0), child: ConditionalParent( condition: isDesktop, builder: (child) { return RoundedWhiteContainer( borderColor: - isDesktop - ? Theme.of( - context, - ).extension()!.backgroundAppBar - : null, + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, padding: const EdgeInsets.all(0), child: child, ); @@ -710,43 +680,43 @@ class _TransactionV2DetailsViewState primary: isDesktop ? false : null, child: Padding( padding: - isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(4), + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(4), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), child: Container( decoration: - isDesktop - ? BoxDecoration( - color: - Theme.of(context) - .extension()! - .backgroundAppBar, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants - .size - .circularBorderRadius, - ), - ), - ) - : null, + isDesktop + ? BoxDecoration( + color: + Theme.of(context) + .extension()! + .backgroundAppBar, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants + .size + .circularBorderRadius, + ), + ), + ) + : null, child: Padding( padding: - isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), + isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ if (isDesktop) Row( @@ -763,73 +733,73 @@ class _TransactionV2DetailsViewState currentHeight, ), style: - STextStyles.desktopTextMedium( - context, - ), + STextStyles.desktopTextMedium( + context, + ), ), ], ), Column( crossAxisAlignment: - isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "$amountPrefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.titleBold12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.titleBold12( + context, + ), ), const SizedBox(height: 2), if (price != null) Builder( builder: (context) { final total = - (amount.decimal * price!) - .toAmount( - fractionDigits: 2, - ); + (amount.decimal * price!) + .toAmount( + fractionDigits: 2, + ); final formatted = total .fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => - value - .locale, - ), - ), - ); + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => + value + .locale, + ), + ), + ); final ticker = ref.watch( prefsChangeNotifierProvider .select( (value) => - value.currency, - ), + value.currency, + ), ); return SelectableText( "$amountPrefix$formatted $ticker", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ); }, ), @@ -852,23 +822,23 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Status", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -877,32 +847,32 @@ class _TransactionV2DetailsViewState SelectableText( whatIsIt(_transaction, currentHeight), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - _transaction.type == - TransactionType - .outgoing && - _transaction - .subType != - TransactionSubType - .cashFusion - ? Theme.of(context) - .extension< - StackColors - >()! - .accentColorOrange - : Theme.of(context) - .extension< - StackColors - >()! - .accentColorGreen, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + _transaction.type == + TransactionType + .outgoing && + _transaction + .subType != + TransactionSubType + .cashFusion + ? Theme.of(context) + .extension< + StackColors + >()! + .accentColorOrange + : Theme.of(context) + .extension< + StackColors + >()! + .accentColorGreen, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -910,8 +880,8 @@ class _TransactionV2DetailsViewState ), ), if (!((coin is Monero || coin is Wownero) && - _transaction.type == - TransactionType.outgoing) && + _transaction.type == + TransactionType.outgoing) && !((coin is Firo) && _transaction.subType == TransactionSubType.mint)) @@ -919,34 +889,34 @@ class _TransactionV2DetailsViewState ? const _Divider() : const SizedBox(height: 12), if (!((coin is Monero || coin is Wownero) && - _transaction.type == - TransactionType.outgoing) && + _transaction.type == + TransactionType.outgoing) && !((coin is Firo) && _transaction.subType == TransactionSubType.mint)) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ ConditionalParent( condition: kDebugMode, builder: (child) { return Row( mainAxisAlignment: - MainAxisAlignment - .spaceBetween, + MainAxisAlignment + .spaceBetween, children: [ child, // CustomTextButton( @@ -996,25 +966,25 @@ class _TransactionV2DetailsViewState child: Text( outputLabel, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ), const SizedBox(height: 8), Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ if (data.length == 1 && data - .first - .addresses - .length == + .first + .addresses + .length == 1) FutureBuilder( future: fetchContactNameFor( @@ -1024,10 +994,10 @@ class _TransactionV2DetailsViewState .first, ), builder: ( - builderContext, - AsyncSnapshot - snapshot, - ) { + builderContext, + AsyncSnapshot + snapshot, + ) { String addressOrContactName = data @@ -1035,87 +1005,87 @@ class _TransactionV2DetailsViewState .addresses .first; if (snapshot.connectionState == - ConnectionState - .done && + ConnectionState + .done && snapshot.hasData) { addressOrContactName = - snapshot.data!; + snapshot.data!; } return SelectableText( addressOrContactName, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ); }, ) else for ( - int i = 0; - i < data.length; - i++ + int i = 0; + i < data.length; + i++ ) ConditionalParent( condition: i > 0, builder: (child) => Column( - crossAxisAlignment: - CrossAxisAlignment - .stretch, - children: [ - const _Divider(), - child, - ], - ), + crossAxisAlignment: + CrossAxisAlignment + .stretch, + children: [ + const _Divider(), + child, + ], + ), child: Padding( padding: - const EdgeInsets.all( - 8.0, - ), + const EdgeInsets.all( + 8.0, + ), child: Column( crossAxisAlignment: - CrossAxisAlignment - .start, + CrossAxisAlignment + .start, children: [ ...data[i].addresses.map(( - e, - ) { + e, + ) { return FutureBuilder( future: - fetchContactNameFor( - e, - ), + fetchContactNameFor( + e, + ), builder: ( - builderContext, - AsyncSnapshot< - String - > - snapshot, - ) { + builderContext, + AsyncSnapshot< + String + > + snapshot, + ) { final String addressOrContactName; if (snapshot.connectionState == - ConnectionState - .done && + ConnectionState + .done && snapshot .hasData) { addressOrContactName = - snapshot - .data!; + snapshot + .data!; } else { addressOrContactName = e; @@ -1123,10 +1093,10 @@ class _TransactionV2DetailsViewState return OutputCard( address: - addressOrContactName, + addressOrContactName, amount: - data[i] - .amount, + data[i] + .amount, coin: coin, ); }, @@ -1148,7 +1118,6 @@ class _TransactionV2DetailsViewState ], ), ), -<<<<<<< if (coin is Epiccash) isDesktop ? const _Divider() @@ -1156,49 +1125,49 @@ class _TransactionV2DetailsViewState if (coin is Epiccash) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "On chain note", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), const SizedBox(height: 8), SelectableText( _transaction.onChainNote ?? "", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1210,258 +1179,117 @@ class _TransactionV2DetailsViewState ], ), ), -======= - ), - if (coin is Epiccash || coin is Mimblewimblecoin) ->>>>>>> isDesktop ? const _Divider() -<<<<<<< : const SizedBox(height: 12), -======= - : const SizedBox( - height: 12, - ), - if (coin is Epiccash || coin is Mimblewimblecoin) ->>>>>>> RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) -e : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( (coin is Epiccash) ? "Local Note" : "Note ", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), -<<<<<<< - ), - isDesktop - ? IconPencilButton( -======= - ), - const SizedBox( - height: 8, + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, ), - SelectableText( - _transaction.onChainNote ?? "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, + ), + isDesktop + ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: _transaction.txid, + walletId: walletId, ), - ), - ], - ), - ), - if (isDesktop) - IconCopyButton( - data: _transaction.onChainNote ?? "", - ), - ], - ), - ), - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - (coin is Epiccash || - coin is Mimblewimblecoin) - ? "Local Note" - : "Note ", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), - ), - isDesktop - ? IconPencilButton( ->>>>>>> - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: _transaction.txid, - walletId: walletId, - ), - ); - }, ); }, - ) + ); + }, + ) : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple2( - _transaction.txid, - walletId, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: - Theme.of(context) - .extension< - StackColors - >()! - .infoItemIcons, - ), - const SizedBox(width: 4), - Text( - "Edit", - style: STextStyles.link2( - context, - ), - ), - ], + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple2( + _transaction.txid, + walletId, ), - ), -<<<<<<< - -======= - ], - ), - const SizedBox( - height: 8, - ), - SelectableText( - ref - .watch( - pTransactionNote( - ( - txid: (coin is Epiccash || - coin - is Mimblewimblecoin) - ? _transaction.slateId - .toString() - : _transaction.txid, - walletId: walletId - ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: + Theme.of(context) + .extension< + StackColors + >()! + .infoItemIcons, ), - ) - ?.value ?? - "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - if (_sparkMemo != null) - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - if (_sparkMemo != null) - RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "Memo", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( + const SizedBox(width: 4), + Text( + "Edit", + style: STextStyles.link2( context, ), + ), + ], + ), ), ->>>>>>> ], ), const SizedBox(height: 8), SelectableText( ref - .watch( - pTransactionNote(( - txid: - (coin is Epiccash) - ? _transaction.slateId - .toString() - : _transaction.txid, - walletId: walletId, - )), - ) - ?.value ?? + .watch( + pTransactionNote(( + txid: + (coin is Epiccash) + ? _transaction.slateId + .toString() + : _transaction.txid, + walletId: walletId, + )), + ) + ?.value ?? "", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1473,25 +1301,25 @@ e : const EdgeInsets.all(12), if (_sparkMemo != null) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Row( children: [ Text( "Memo", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ], ), @@ -1499,20 +1327,20 @@ e : const EdgeInsets.all(12), SelectableText( _sparkMemo!, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1522,28 +1350,28 @@ e : const EdgeInsets.all(12), : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Date", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1553,20 +1381,20 @@ e : const EdgeInsets.all(12), _transaction.timestamp, ), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1576,20 +1404,20 @@ e : const EdgeInsets.all(12), _transaction.timestamp, ), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton( @@ -1600,7 +1428,6 @@ e : const EdgeInsets.all(12), ], ), ), -<<<<<<< if (coin is! NanoCurrency && !(coin is Xelis && _transaction.type == @@ -1614,98 +1441,48 @@ e : const EdgeInsets.all(12), TransactionType.incoming)) RoundedWhiteContainer( padding: - isDesktop -======= - isDesktop - ? const _Divider() - : const SizedBox( - height: 12, - ), - Builder( - builder: (context) { - final String height; - final String confirmations; - final confirms = _transaction.getConfirmations( - currentHeight, - ); - - if (widget.coin is Bitcoincash || - widget.coin is Ecash) { - height = _transaction.height != null && - _transaction.height! > 0 - ? "${_transaction.height!}" - : "Pending"; - confirmations = confirms.toString(); - } else if (widget.coin is Epiccash || - coin is Mimblewimblecoin && - _transaction.slateId == null) { - confirmations = "Unknown"; - height = "Unknown"; - } else { - final confirmed = _transaction.isConfirmed( - currentHeight, - minConfirms, - coin.minCoinbaseConfirms); - if (widget.coin is! Epiccash && - widget.coin is! Mimblewimblecoin && - confirmed) { - height = - "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; - } else { - height = confirms > 0 - ? "${_transaction.height}" - : "Pending"; - } - - confirmations = confirms.toString(); - } - - return Column( - children: [ - RoundedWhiteContainer( - padding: isDesktop ->>>>>>> - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Builder( builder: (context) { final String feeString = - showFeePending - ? _transaction.isConfirmed( - currentHeight, - minConfirms, - coin.minCoinbaseConfirms, - ) - ? ref - .watch( - pAmountFormatter(coin), - ) - .format(fee) - : "Pending" - : ref - .watch(pAmountFormatter(coin)) - .format(fee); + showFeePending + ? _transaction.isConfirmed( + currentHeight, + minConfirms, + coin.minCoinbaseConfirms, + ) + ? ref + .watch( + pAmountFormatter(coin), + ) + .format(fee) + : "Pending" + : ref + .watch(pAmountFormatter(coin)) + .format(fee); return Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Transaction fee", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1713,22 +1490,22 @@ e : const EdgeInsets.all(12), SelectableText( feeString, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (supportsRbf && !confirmedTxn) const SizedBox(height: 8), @@ -1743,20 +1520,20 @@ e : const EdgeInsets.all(12), SelectableText( feeString, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: feeString), @@ -1778,10 +1555,10 @@ e : const EdgeInsets.all(12), if (widget.coin is Bitcoincash || widget.coin is Ecash) { height = - _transaction.height != null && - _transaction.height! > 0 - ? "${_transaction.height!}" - : "Pending"; + _transaction.height != null && + _transaction.height! > 0 + ? "${_transaction.height!}" + : "Pending"; confirmations = confirms.toString(); } else if (widget.coin is Epiccash && _transaction.slateId == null) { @@ -1795,12 +1572,12 @@ e : const EdgeInsets.all(12), ); if (widget.coin is! Epiccash && confirmed) { height = - "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; + "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { height = - confirms > 0 - ? "${_transaction.height}" - : "Pending"; + confirms > 0 + ? "${_transaction.height}" + : "Pending"; } confirmations = confirms.toString(); @@ -1810,29 +1587,29 @@ e : const EdgeInsets.all(12), children: [ RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Block height", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1840,22 +1617,22 @@ e : const EdgeInsets.all(12), SelectableText( height, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1863,20 +1640,20 @@ e : const EdgeInsets.all(12), SelectableText( height, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1888,29 +1665,29 @@ e : const EdgeInsets.all(12), : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Confirmations", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1918,22 +1695,22 @@ e : const EdgeInsets.all(12), SelectableText( confirmations, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1941,20 +1718,20 @@ e : const EdgeInsets.all(12), SelectableText( confirmations, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1974,43 +1751,43 @@ e : const EdgeInsets.all(12), _transaction.type != TransactionType.incoming) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Nonce", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.nonce.toString(), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -2022,548 +1799,359 @@ e : const EdgeInsets.all(12), if (kDebugMode) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Tx sub type", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.subType.toString(), - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), - ), - ], - ), - ), - if (hasTxKeyProbably) - isDesktop - ? const _Divider() - : const SizedBox(height: 12), - if (hasTxKeyProbably) - TxKeyWidget( - walletId: walletId, - txid: _transaction.txid, - ), - isDesktop - ? const _Divider() - : const SizedBox(height: 12), - - _transaction.txid.startsWith("mweb_outputId_") && - _transaction.subType == - TransactionSubType.mweb - ? RoundedWhiteContainer( - padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - ConditionalParent( - condition: !isDesktop, - builder: - (child) => Row( - children: [ - Expanded(child: child), - SimpleCopyButton( - data: _transaction - .txid - .replaceFirst( - "mweb_outputId_", - "", - ), - ), - ], - ), - child: Text( - "MWEB Output ID", - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), - ), - ), - const SizedBox(height: 8), - SelectableText( - _transaction.txid.replaceFirst( - "mweb_outputId_", - "", - ), - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), - ), - // if (coin is Litecoin && - // coin.network == - // CryptoCurrencyNetwork - // .main) - // const SizedBox(height: 8), - // if (coin is Litecoin && - // coin.network == - // CryptoCurrencyNetwork - // .main) - // CustomTextButton( - // text: - // "Open in block explorer", - // onTap: () async { - // final uri = - // getBlockExplorerTransactionUrlFor( - // coin: coin, - // txid: _transaction - // .txid - // .replaceFirst( - // "mweb_outputId_", - // "", - // ), - // ); - // - // if (ref - // .read( - // prefsChangeNotifierProvider, - // ) - // .hideBlockExplorerWarning == - // false) { - // final shouldContinue = - // await showExplorerWarning( - // "${uri.scheme}://${uri.host}", - // ); - // - // if (!shouldContinue) { - // return; - // } - // } - // try { - // await launchUrl( - // uri, - // mode: - // LaunchMode - // .externalApplication, - // ); - // } catch (_) { - // if (context.mounted) { - // unawaited( - // showDialog( - // context: context, - // builder: - // ( - // _, - // ) => StackOkDialog( - // title: - // "Could not open in block explorer", - // message: - // "Failed to open \"${uri.toString()}\"", - // ), - // ), - // ); - // } - // } - // }, - // ), - ], - ), - ), - if (isDesktop) - const SizedBox(width: 12), - if (isDesktop) - IconCopyButton( - data: _transaction.txid - .replaceFirst( - "mweb_outputId_", - "", - ), -<<<<<<< - -======= - ), - if (coin is! Epiccash && - coin is! Mimblewimblecoin) - const SizedBox( - height: 8, ->>>>>>> - ), -<<<<<<< - -======= - if (coin is! Epiccash && - coin is! Mimblewimblecoin) - CustomTextButton( - text: "Open in block explorer", - onTap: () async { - final uri = - getBlockExplorerTransactionUrlFor( - coin: coin, - txid: _transaction.txid, - ); - - if (ref - .read( - prefsChangeNotifierProvider, - ) - .hideBlockExplorerWarning == - false) { - final shouldContinue = - await showExplorerWarning( - "${uri.scheme}://${uri.host}", - ); - - if (!shouldContinue) { - return; - } - } - - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - try { - await launchUrl( - uri, - mode: LaunchMode - .externalApplication, - ); - } catch (_) { - if (mounted) { - unawaited( - showDialog( - context: context, - builder: (_) => - StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), - ), - ); - } - } finally { - // Future.delayed( - // const Duration(seconds: 1), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - } - }, - ), - // ), - // ), ->>>>>>> - ], - ), -<<<<<<< - ) - : RoundedWhiteContainer( - padding: + style: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( -======= + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + ], ), - if (isDesktop) - const SizedBox( - width: 12, - ), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, - ), - ], - ), - ), - // if ((coin is FiroTestNet || coin is Firo) && - // _transaction.subType == "mint") - // const SizedBox( - // height: 12, - // ), - // if ((coin is FiroTestNet || coin is Firo) && - // _transaction.subType == "mint") - // RoundedWhiteContainer( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text( - // "Mint Transaction ID", - // style: STextStyles.itemSubtitle(context), - // ), - // ], - // ), - // const SizedBox( - // height: 8, - // ), - // // Flexible( - // // child: FittedBox( - // // fit: BoxFit.scaleDown, - // // child: - // SelectableText( - // _transaction.otherData ?? "Unknown", - // style: STextStyles.itemSubtitle12(context), - // ), - // // ), - // // ), - // const SizedBox( - // height: 8, - // ), - // BlueTextButton( - // text: "Open in block explorer", - // onTap: () async { - // final uri = getBlockExplorerTransactionUrlFor( - // coin: coin, - // txid: _transaction.otherData ?? "Unknown", - // ); - // // ref - // // .read( - // // shouldShowLockscreenOnResumeStateProvider - // // .state) - // // .state = false; - // try { - // await launchUrl( - // uri, - // mode: LaunchMode.externalApplication, - // ); - // } catch (_) { - // unawaited(showDialog( - // context: context, - // builder: (_) => StackOkDialog( - // title: "Could not open in block explorer", - // message: - // "Failed to open \"${uri.toString()}\"", - // ), - // )); - // } finally { - // // Future.delayed( - // // const Duration(seconds: 1), - // // () => ref - // // .read( - // // shouldShowLockscreenOnResumeStateProvider - // // .state) - // // .state = true, - // // ); - // } - // }, - // ), - // ], - // ), - // ), - if (coin is Epiccash || coin is Mimblewimblecoin) + ), + if (hasTxKeyProbably) + isDesktop + ? const _Divider() + : const SizedBox(height: 12), + if (hasTxKeyProbably) + TxKeyWidget( + walletId: walletId, + txid: _transaction.txid, + ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), - if (coin is Epiccash || coin is Mimblewimblecoin) - RoundedWhiteContainer( - padding: isDesktop + : const SizedBox(height: 12), + + _transaction.txid.startsWith("mweb_outputId_") && + _transaction.subType == + TransactionSubType.mweb + ? RoundedWhiteContainer( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ - Column( ->>>>>>> - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - ConditionalParent( - condition: !isDesktop, - builder: - (child) => Row( - children: [ - Expanded(child: child), - SimpleCopyButton( - data: - _transaction.txid, - ), - ], - ), - child: Text( - "Transaction ID", - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ConditionalParent( + condition: !isDesktop, + builder: + (child) => Row( + children: [ + Expanded(child: child), + SimpleCopyButton( + data: _transaction + .txid + .replaceFirst( + "mweb_outputId_", + "", + ), ), + ], + ), + child: Text( + "MWEB Output ID", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, ), - const SizedBox(height: 8), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - _transaction.txid, - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), - ), - if (coin is! Epiccash) - const SizedBox(height: 8), - if (coin is! Epiccash) - CustomTextButton( - text: - "Open in block explorer", - onTap: () async { - final uri = - getBlockExplorerTransactionUrlFor( - coin: coin, - txid: - _transaction.txid, - ); - - if (ref - .read( - prefsChangeNotifierProvider, - ) - .hideBlockExplorerWarning == - false) { - final shouldContinue = - await showExplorerWarning( - "${uri.scheme}://${uri.host}", - ); - - if (!shouldContinue) { - return; - } - } - - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - try { - await launchUrl( - uri, - mode: - LaunchMode - .externalApplication, - ); - } catch (_) { - if (context.mounted) { - unawaited( - showDialog( - context: context, - builder: - ( - _, - ) => StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), - ), - ); - } - } finally { - // Future.delayed( - // const Duration(seconds: 1), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - } - }, - ), - // ), - // ), - ], + ), ), + const SizedBox(height: 8), + SelectableText( + _transaction.txid.replaceFirst( + "mweb_outputId_", + "", + ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + // if (coin is Litecoin && + // coin.network == + // CryptoCurrencyNetwork + // .main) + // const SizedBox(height: 8), + // if (coin is Litecoin && + // coin.network == + // CryptoCurrencyNetwork + // .main) + // CustomTextButton( + // text: + // "Open in block explorer", + // onTap: () async { + // final uri = + // getBlockExplorerTransactionUrlFor( + // coin: coin, + // txid: _transaction + // .txid + // .replaceFirst( + // "mweb_outputId_", + // "", + // ), + // ); + // + // if (ref + // .read( + // prefsChangeNotifierProvider, + // ) + // .hideBlockExplorerWarning == + // false) { + // final shouldContinue = + // await showExplorerWarning( + // "${uri.scheme}://${uri.host}", + // ); + // + // if (!shouldContinue) { + // return; + // } + // } + // try { + // await launchUrl( + // uri, + // mode: + // LaunchMode + // .externalApplication, + // ); + // } catch (_) { + // if (context.mounted) { + // unawaited( + // showDialog( + // context: context, + // builder: + // ( + // _, + // ) => StackOkDialog( + // title: + // "Could not open in block explorer", + // message: + // "Failed to open \"${uri.toString()}\"", + // ), + // ), + // ); + // } + // } + // }, + // ), + ], + ), + ), + if (isDesktop) + const SizedBox(width: 12), + if (isDesktop) + IconCopyButton( + data: _transaction.txid + .replaceFirst( + "mweb_outputId_", + "", ), - if (isDesktop) - const SizedBox(width: 12), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, + ), + ], + ), + ) + : RoundedWhiteContainer( + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ConditionalParent( + condition: !isDesktop, + builder: + (child) => Row( + children: [ + Expanded(child: child), + SimpleCopyButton( + data: + _transaction.txid, + ), + ], + ), + child: Text( + "Transaction ID", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), + ), ), - ], + const SizedBox(height: 8), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.txid, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + if (coin is! Epiccash) + const SizedBox(height: 8), + if (coin is! Epiccash) + CustomTextButton( + text: + "Open in block explorer", + onTap: () async { + final uri = + getBlockExplorerTransactionUrlFor( + coin: coin, + txid: + _transaction.txid, + ); + + if (ref + .read( + prefsChangeNotifierProvider, + ) + .hideBlockExplorerWarning == + false) { + final shouldContinue = + await showExplorerWarning( + "${uri.scheme}://${uri.host}", + ); + + if (!shouldContinue) { + return; + } + } + + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + try { + await launchUrl( + uri, + mode: + LaunchMode + .externalApplication, + ); + } catch (_) { + if (context.mounted) { + unawaited( + showDialog( + context: context, + builder: + ( + _, + ) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), + ), + ); + } + } finally { + // Future.delayed( + // const Duration(seconds: 1), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + } + }, + ), + // ), + // ), + ], + ), ), - ), + if (isDesktop) + const SizedBox(width: 12), + if (isDesktop) + IconCopyButton( + data: _transaction.txid, + ), + ], + ), + ), // if ((coin is FiroTestNet || coin is Firo) && // _transaction.subType == "mint") // const SizedBox( @@ -2648,29 +2236,29 @@ e : const EdgeInsets.all(12), if (coin is Epiccash) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Slate ID", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -2679,20 +2267,20 @@ e : const EdgeInsets.all(12), SelectableText( _transaction.slateId ?? "Unknown", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -2702,7 +2290,7 @@ e : const EdgeInsets.all(12), if (isDesktop) IconCopyButton( data: - _transaction.slateId ?? "Unknown", + _transaction.slateId ?? "Unknown", ), ], ), @@ -2730,199 +2318,106 @@ e : const EdgeInsets.all(12), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, -<<<<<<< floatingActionButton: - (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) - ? ConditionalParent( - condition: isDesktop, - builder: - (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, -======= - floatingActionButton: (coin is Epiccash || - coin is Mimblewimblecoin && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) + (coin is Epiccash && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) ? ConditionalParent( - condition: isDesktop, - builder: (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, + condition: isDesktop, + builder: + (child) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, + ), + child: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, ->>>>>>> - ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, - ), - ), - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); - - if (wallet is EpiccashWallet) { - final String? id = _transaction.slateId; - if (id == null) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Could not find Epic transaction ID", - context: context, - ), - ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: - (_) => - const CancellingTransactionProgressDialog(), - ), - ); - - final result = await wallet - .cancelPendingTransactionAndPost(id); - if (context.mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: - (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, - ), - ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: - (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); - } - } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - ), - ); - return; - } -<<<<<<< - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), + ), + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + + if (wallet is EpiccashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, ), -======= - } else if (wallet is MimblewimblecoinWallet) { - final String? id = _transaction.slateId; - if (id == null) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Could not find Mimblewimblecoin transaction ID", - context: context, - ), - ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - ), - ); - - final result = - await wallet.cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, - ), - ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (context.mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, ), ); - } - } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", - context: context, - ), - ); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), ->>>>>>> + }, + ), + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "ERROR: Wallet type is not Epic Cash", + context: context, ), - ), - ) - : null, + ); + return; + } + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), + ), + ), + ) + : null, ), ); } @@ -2948,18 +2443,18 @@ class OutputCard extends ConsumerWidget { Text( "Address", style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( address, style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), const SizedBox(height: 10), Row( @@ -2968,23 +2463,23 @@ class OutputCard extends ConsumerWidget { Text( "Amount", style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( ref.watch(pAmountFormatter(coin)).format(amount), style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), ], ), @@ -3017,7 +2512,7 @@ class IconCopyButton extends StatelessWidget { width: 26, child: RawMaterialButton( fillColor: - Theme.of(context).extension()!.buttonBackSecondary, + Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), @@ -3058,7 +2553,7 @@ class IconPencilButton extends StatelessWidget { width: 26, child: RawMaterialButton( fillColor: - Theme.of(context).extension()!.buttonBackSecondary, + Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), From 574e24ce51fef86611f2431b66da4c75c08bd83d Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:02:46 -0500 Subject: [PATCH 30/91] feat(mwc): MWCMQS, Slatepack txs. WIP UI --- crypto_plugins/flutter_libmwc | 2 +- lib/models/mwc_transaction_method.dart | 70 + lib/pages/receive_view/receive_view.dart | 22 + .../mwc_slatepack_import_dialog.dart | 587 +++++ .../send_view/confirm_transaction_view.dart | 119 +- lib/pages/send_view/send_view.dart | 398 +-- .../sub_widgets/mwc_slatepack_dialog.dart | 323 +++ .../mwc_transaction_method_selector.dart | 272 ++ .../tx_v2/transaction_v2_details_view.dart | 2344 +++++++++-------- .../sub_widgets/desktop_finalize.dart | 274 +- .../desktop_mwc_txs_method_toggle.dart | 36 +- .../sub_widgets/desktop_receive.dart | 22 +- .../wallet_view/sub_widgets/desktop_send.dart | 166 +- .../ui/preview_tx_button_state_provider.dart | 13 + lib/services/mwc_wallet_service.dart | 552 ++++ .../coins/mimblewimblecoin.dart | 92 +- .../wallet/impl/mimblewimblecoin_wallet.dart | 450 +++- 17 files changed, 4245 insertions(+), 1497 deletions(-) create mode 100644 lib/models/mwc_transaction_method.dart create mode 100644 lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart create mode 100644 lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart create mode 100644 lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart create mode 100644 lib/services/mwc_wallet_service.dart diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 2008080fd8..29c7ea5267 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 2008080fd8e3a68081f4ca1334c2568421539157 +Subproject commit 29c7ea526772a170d1f9488c4661333c0b2564fd diff --git a/lib/models/mwc_transaction_method.dart b/lib/models/mwc_transaction_method.dart new file mode 100644 index 0000000000..1663d834c6 --- /dev/null +++ b/lib/models/mwc_transaction_method.dart @@ -0,0 +1,70 @@ +/// Enum to represent different MWC transaction methods. +enum TransactionMethod { + /// Manual slatepack exchange (copy/paste, QR codes, files). + slatepack, + + /// Automatic transaction via MWCMQS. + mwcmqs, + + /// Direct HTTP/HTTPS to recipient's wallet. + http, + + /// Unknown or unsupported method. + unknown; + + /// Human readable name for the transaction method. + String get displayName { + switch (this) { + case TransactionMethod.slatepack: + return 'Slatepack'; + case TransactionMethod.mwcmqs: + return 'MWCMQS'; + case TransactionMethod.http: + return 'HTTP'; + case TransactionMethod.unknown: + return 'Unknown'; + } + } + + /// Description of how the transaction method works. + String get description { + switch (this) { + case TransactionMethod.slatepack: + return 'Manual exchange via text, QR codes, or files'; + case TransactionMethod.mwcmqs: + return 'Automatic exchange via MWCMQS messaging'; + case TransactionMethod.http: + return 'Direct connection to recipient wallet'; + case TransactionMethod.unknown: + return 'Unsupported transaction method'; + } + } + + /// Whether this method requires manual intervention. + bool get isManual { + switch (this) { + case TransactionMethod.slatepack: + return true; + case TransactionMethod.mwcmqs: + return false; + case TransactionMethod.http: + return false; + case TransactionMethod.unknown: + return true; + } + } + + /// Whether this method works offline. + bool get worksOffline { + switch (this) { + case TransactionMethod.slatepack: + return true; + case TransactionMethod.mwcmqs: + return false; + case TransactionMethod.http: + return false; + case TransactionMethod.unknown: + return false; + } + } +} diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index fe44acc9bf..e461978e2b 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -33,6 +33,7 @@ import '../../utilities/text_styles.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/wallet/impl/bitcoin_wallet.dart'; +import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../wallets/wallet/intermediate/bip39_hd_wallet.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart'; import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; @@ -51,6 +52,7 @@ import '../../widgets/qr.dart'; import '../../widgets/rounded_white_container.dart'; import 'addresses/wallet_addresses_view.dart'; import 'generate_receiving_uri_qr_code_view.dart'; +import 'sub_widgets/mwc_slatepack_import_dialog.dart'; class ReceiveView extends ConsumerStatefulWidget { const ReceiveView({ @@ -692,6 +694,26 @@ class _ReceiveViewState extends ConsumerState { ? generateNewSparkAddress : generateNewAddress, ), + // MWC Slatepack import button. + if (coin is Mimblewimblecoin) ...[ + const SizedBox(height: 12), + SecondaryButton( + label: "Import Slatepack", + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + if (wallet is MimblewimblecoinWallet) { + await showDialog( + context: context, + builder: + (context) => MwcSlatepackImportDialog( + wallet: wallet, + clipboard: clipboard, + ), + ); + } + }, + ), + ], const SizedBox(height: 30), RoundedWhiteContainer( child: Padding( diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart new file mode 100644 index 0000000000..300e11656d --- /dev/null +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -0,0 +1,587 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../notifications/show_flush_bar.dart'; +import '../../../services/mwc_wallet_service.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; +import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; + +class MwcSlatepackImportDialog extends ConsumerStatefulWidget { + const MwcSlatepackImportDialog({ + super.key, + required this.wallet, + this.clipboard = const ClipboardWrapper(), + }); + + final MimblewimblecoinWallet wallet; + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => + _MwcSlatepackImportDialogState(); +} + +class _MwcSlatepackImportDialogState + extends ConsumerState { + late final TextEditingController slatepackController; + late final FocusNode slatepackFocusNode; + + bool _isProcessing = false; + String? _validationError; + SlatepackDecodeResult? _decodedSlatepack; + String? _slatepackType; + + @override + void initState() { + super.initState(); + slatepackController = TextEditingController(); + slatepackFocusNode = FocusNode(); + } + + @override + void dispose() { + slatepackController.dispose(); + slatepackFocusNode.dispose(); + super.dispose(); + } + + void _pasteFromClipboard() async { + try { + final data = await widget.clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null) { + slatepackController.text = data!.text!; + _validateSlatepack(); + } + } catch (e) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to paste from clipboard", + context: context, + ); + } + } + + void _validateSlatepack() async { + final text = slatepackController.text.trim(); + + if (text.isEmpty) { + setState(() { + _validationError = null; + _decodedSlatepack = null; + _slatepackType = null; + }); + return; + } + + // Basic format validation. + final coin = widget.wallet.cryptoCurrency as Mimblewimblecoin; + if (!coin.isSlatepack(text)) { + setState(() { + _validationError = "Invalid slatepack format"; + _decodedSlatepack = null; + _slatepackType = null; + }); + return; + } + + try { + // Attempt to decode. + final decoded = await widget.wallet.decodeSlatepack(text); + + if (decoded.success) { + final analysis = await widget.wallet.analyzeSlatepack(text); + + final String slatepackType = switch (analysis.status) { + 'S1' => "S1 (Initial Send)", + 'S2' => "S2 (Response)", + 'S3' => "S3 (Finalized)", + _ => _determineSlatepackType(decoded), // Fallback. + }; + + setState(() { + _validationError = null; + _decodedSlatepack = decoded; + _slatepackType = slatepackType; + }); + } else { + setState(() { + _validationError = decoded.error ?? "Failed to decode slatepack"; + _decodedSlatepack = null; + _slatepackType = null; + }); + } + } catch (e) { + setState(() { + _validationError = "Error decoding slatepack: $e"; + _decodedSlatepack = null; + _slatepackType = null; + }); + } + } + + String _determineSlatepackType(SlatepackDecodeResult decoded) { + // Fallback analysis based on sender/recipient addresses. + if (decoded.senderAddress != null && decoded.recipientAddress != null) { + return "S2 (Response)"; + } else if (decoded.senderAddress != null) { + return "S1 (Initial)"; + } else { + return "Unknown"; + } + } + + void _processSlatepack() async { + if (_decodedSlatepack == null || slatepackController.text.trim().isEmpty) { + return; + } + + setState(() { + _isProcessing = true; + }); + + try { + final slatepackText = slatepackController.text.trim(); + + // Determine action based on slatepack type. + if (_slatepackType?.contains("S1") == true) { + // This is an initial slatepack - receive it and create response. + final result = await widget.wallet.receiveSlatepack(slatepackText); + + if (result.success && result.responseSlatepack != null) { + // Show response slatepack. + if (mounted) { + Navigator.of(context).pop(); // Close this dialog. + + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => _SlatepackResponseDialog( + responseSlatepack: result.responseSlatepack!, + wasEncrypted: result.wasEncrypted ?? false, + clipboard: widget.clipboard, + ), + ); + } + } else { + throw Exception(result.error ?? 'Failed to process slatepack'); + } + } else if (_slatepackType?.contains("S2") == true) { + // This is a response slatepack - finalize it. + final result = await widget.wallet.finalizeSlatepack(slatepackText); + + if (result.success) { + if (mounted) { + Navigator.of(context).pop(); // Close this dialog. + + showFloatingFlushBar( + type: FlushBarType.success, + message: "Transaction finalized and broadcast successfully!", + context: context, + ); + } + } else { + throw Exception(result.error ?? 'Failed to finalize slatepack'); + } + } else { + throw Exception('Unsupported slatepack type: $_slatepackType'); + } + } catch (e) { + if (mounted) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to process slatepack: $e", + context: context, + ); + } + } finally { + if (mounted) { + setState(() { + _isProcessing = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = + Platform.isLinux || Platform.isMacOS || Platform.isWindows; + final canProcess = _decodedSlatepack != null && !_isProcessing; + + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Import Slatepack", style: STextStyles.pageTitleH2(context)), + AppBarIconButton( + size: 36, + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.x, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, + width: 24, + height: 24, + ), + onPressed: + _isProcessing ? null : () => Navigator.of(context).pop(), + ), + ], + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Paste or enter a slatepack to process the transaction.", + style: STextStyles.subtitle(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + + // Slatepack input field. + Container( + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: + Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + ), + child: TextField( + controller: slatepackController, + focusNode: slatepackFocusNode, + maxLines: 8, + onChanged: (_) => _validateSlatepack(), + decoration: InputDecoration( + hintText: "BEGINSLATEPACK...", + labelText: "Slatepack", + border: InputBorder.none, + contentPadding: const EdgeInsets.all(12), + suffixIcon: IconButton( + onPressed: _pasteFromClipboard, + icon: SvgPicture.asset( + Assets.svg.clipboard, + width: 16, + height: 16, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ), + ), + ), + ), + + // Validation status. + if (_validationError != null) ...[ + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.error_outline, + size: 16, + color: + Theme.of( + context, + ).extension()!.textError, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _validationError!, + style: STextStyles.w400_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textError, + ), + ), + ), + ], + ), + ] else if (_decodedSlatepack != null) ...[ + const SizedBox(height: 8), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.check_circle_outline, + size: 16, + color: + Theme.of( + context, + ).extension()!.accentColorGreen, + ), + const SizedBox(width: 8), + Text( + "Valid Slatepack", + style: STextStyles.label(context).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorGreen, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + "Type: $_slatepackType", + style: STextStyles.w400_14(context), + ), + if (_decodedSlatepack!.wasEncrypted == true) + Text( + "Encrypted: Yes", + style: STextStyles.w400_14(context), + ), + if (_decodedSlatepack!.senderAddress != null) + Text( + "From: ${_decodedSlatepack!.senderAddress}", + style: STextStyles.w400_14(context), + ), + ], + ), + ), + ], + + const SizedBox(height: 24), + + // Action buttons. + if (isDesktop) ...[ + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: + _isProcessing + ? null + : () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 12), + Expanded( + child: PrimaryButton( + label: _isProcessing ? "Processing..." : "Process", + onPressed: canProcess ? _processSlatepack : null, + ), + ), + ], + ), + ] else ...[ + PrimaryButton( + label: + _isProcessing ? "Processing..." : "Process Slatepack", + onPressed: canProcess ? _processSlatepack : null, + ), + const SizedBox(height: 12), + SecondaryButton( + label: "Cancel", + onPressed: + _isProcessing + ? null + : () => Navigator.of(context).pop(), + ), + ], + ], + ), + ), + ], + ), + ); + } +} + +class _SlatepackResponseDialog extends StatelessWidget { + const _SlatepackResponseDialog({ + required this.responseSlatepack, + required this.wasEncrypted, + required this.clipboard, + }); + + final String responseSlatepack; + final bool wasEncrypted; + final ClipboardInterface clipboard; + + void _copySlatepack(BuildContext context) { + clipboard.setData(ClipboardData(text: responseSlatepack)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Response slatepack copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Response Slatepack", + style: STextStyles.pageTitleH2(context), + ), + AppBarIconButton( + size: 36, + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.x, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, + width: 24, + height: 24, + ), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Return this slatepack to the sender to complete the transaction.", + style: STextStyles.subtitle(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + + if (wasEncrypted) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .infoItemIcons + .withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.lock, + size: 16, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 8), + Text( + "Encrypted Response", + style: STextStyles.label(context), + ), + ], + ), + ), + const SizedBox(height: 16), + + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Response Slatepack", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + GestureDetector( + onTap: () => _copySlatepack(context), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + constraints: const BoxConstraints( + maxHeight: 200, + minHeight: 100, + ), + child: SingleChildScrollView( + child: SelectableText( + responseSlatepack, + style: STextStyles.w400_14( + context, + ).copyWith(fontFamily: 'monospace'), + ), + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + PrimaryButton( + label: "Copy Response", + onPressed: () => _copySlatepack(context), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index b584b036d0..d30e62fef7 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -9,12 +9,12 @@ */ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_libepiccash/lib.dart'; -import 'package:flutter_libmwc/lib.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -41,6 +41,7 @@ import '../../wallets/isar/providers/eth/current_token_wallet_provider.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/wallet_mixin_interfaces/paynym_interface.dart'; import '../../widgets/background.dart'; import '../../widgets/conditional_parent.dart'; @@ -56,6 +57,7 @@ import '../../widgets/stack_text_field.dart'; import '../../widgets/textfield_icon_button.dart'; import '../pinpad_views/lock_screen_view.dart'; import '../wallet_view/wallet_view.dart'; +import 'sub_widgets/mwc_slatepack_dialog.dart'; import 'sub_widgets/sending_transaction_dialog.dart'; class ConfirmTransactionView extends ConsumerStatefulWidget { @@ -101,6 +103,83 @@ class _ConfirmTransactionViewState late final FocusNode _onChainNoteFocusNode; late final TextEditingController onChainNoteController; + /// Handle MWC slatepack creation for manual exchange. + Future _handleMwcSlatepackCreation( + BuildContext context, + MimblewimblecoinWallet wallet, + ) async { + try { + // Close the progress dialog first. + Navigator.of(context).pop(); + + // Get recipient information from txData. + final recipient = widget.txData.recipients?.first; + if (recipient == null) { + throw Exception('No recipient found in transaction data'); + } + + // Create slatepack. + final slatepackResult = await wallet.createSlatepack( + amount: recipient.amount, + recipientAddress: + recipient.address.isNotEmpty ? recipient.address : null, + message: + onChainNoteController.text.isNotEmpty + ? onChainNoteController.text + : null, + encrypt: + recipient + .address + .isNotEmpty, // Encrypt if we have a recipient address. + ); + + if (!slatepackResult.success || slatepackResult.slatepack == null) { + throw Exception(slatepackResult.error ?? 'Failed to create slatepack'); + } + + // Show slatepack dialog. + if (context.mounted) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => MwcSlatepackDialog(slatepackResult: slatepackResult), + ); + + // After slatepack dialog is closed, navigate back to wallet. + if (context.mounted) { + widget.onSuccess.call(); + if (widget.onSuccessInsteadOfRouteOnSuccess == null) { + Navigator.of( + context, + ).popUntil(ModalRoute.withName(routeOnSuccessName)); + } else { + widget.onSuccessInsteadOfRouteOnSuccess!.call(); + } + } + } + } catch (e, s) { + Logging.instance.e('Failed to create MWC slatepack: $e\n$s'); + + if (context.mounted) { + await showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Text('Slatepack Creation Failed'), + content: Text('Failed to create slatepack: $e'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } + } + } + Future _attemptSend(BuildContext context) async { final wallet = ref.read(pWallets).getWallet(walletId); final coin = wallet.info.coin; @@ -157,7 +236,31 @@ class _ConfirmTransactionViewState break; } } else { - if (coin is Epiccash || coin is Mimblewimblecoin) { + if (coin is Mimblewimblecoin) { + // Check if this is a slatepack transaction (manual exchange). + final otherDataMap = + widget.txData.otherData != null + ? jsonDecode(widget.txData.otherData!) + : null; + final transactionMethod = + otherDataMap?['transactionMethod'] as String?; + + if (transactionMethod == 'slatepack') { + // Handle slatepack creation instead of direct send. + await _handleMwcSlatepackCreation( + context, + wallet as MimblewimblecoinWallet, + ); + return; // Exit early, don't continue with normal transaction flow. + } else { + // Handle MWCMQS or HTTP transactions normally. + txDataFuture = wallet.confirmSend( + txData: widget.txData.copyWith( + noteOnChain: onChainNoteController.text, + ), + ); + } + } else if (coin is Epiccash) { txDataFuture = wallet.confirmSend( txData: widget.txData.copyWith( noteOnChain: onChainNoteController.text, @@ -566,9 +669,7 @@ class _ConfirmTransactionViewState ), if ((coin is Epiccash || coin is Mimblewimblecoin) && widget.txData.noteOnChain!.isNotEmpty) - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), if ((coin is Epiccash || coin is Mimblewimblecoin) && widget.txData.noteOnChain!.isNotEmpty) RoundedWhiteContainer( @@ -941,9 +1042,7 @@ class _ConfirmTransactionViewState textAlign: TextAlign.left, ), if (coin is Epiccash || coin is Mimblewimblecoin) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (coin is Epiccash || coin is Mimblewimblecoin) ClipRRect( borderRadius: BorderRadius.circular( @@ -987,9 +1086,7 @@ class _ConfirmTransactionViewState ), ), if (coin is Epiccash || coin is Mimblewimblecoin) - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), SelectableText( (coin is Epiccash || coin is Mimblewimblecoin) ? "Local Note (optional)" diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 923283bb70..795befa109 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -9,6 +9,7 @@ */ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:cs_monero/cs_monero.dart' as lib_monero; @@ -21,6 +22,7 @@ import 'package:tuple/tuple.dart'; import '../../models/input.dart'; import '../../models/isar/models/isar_models.dart'; +import '../../models/mwc_transaction_method.dart'; import '../../models/paynym/paynym_account_lite.dart'; import '../../models/send_view_auto_fill_data.dart'; import '../../providers/providers.dart'; @@ -76,6 +78,7 @@ import '../coin_control/coin_control_view.dart'; import 'confirm_transaction_view.dart'; import 'sub_widgets/building_transaction_dialog.dart'; import 'sub_widgets/dual_balance_selection_sheet.dart'; +import 'sub_widgets/mwc_transaction_method_selector.dart'; import 'sub_widgets/transaction_fee_selection_sheet.dart'; class SendView extends ConsumerStatefulWidget { @@ -145,6 +148,9 @@ class _SendViewState extends ConsumerState { Set selectedUTXOs = {}; + // MWC transaction method selection. + TransactionMethod? _selectedTransactionMethod; + void _applyUri(PaymentUriData paymentData) { try { // auto fill address @@ -846,6 +852,40 @@ class _SendViewState extends ConsumerState { ); } else { final memo = coin is Stellar ? memoController.text : null; + + // For MWC, determine transaction method and add it to otherData. + Map? otherData; + if (coin is Mimblewimblecoin) { + String transactionMethod = 'slatepack'; // Default to slatepack. + + if (_selectedTransactionMethod != null) { + switch (_selectedTransactionMethod!) { + case TransactionMethod.slatepack: + transactionMethod = 'slatepack'; + break; + case TransactionMethod.mwcmqs: + transactionMethod = 'mwcmqs'; + break; + case TransactionMethod.http: + transactionMethod = 'http'; + break; + case TransactionMethod.unknown: + // Auto-detect from address format. + final mwcCoin = coin as Mimblewimblecoin; + final method = mwcCoin.getTransactionMethod(_address!); + transactionMethod = method.toString().split('.').last; + break; + } + } else { + // Auto-detect from address format if no method selected. + final mwcCoin = coin as Mimblewimblecoin; + final method = mwcCoin.getTransactionMethod(_address!); + transactionMethod = method.toString().split('.').last; + } + + otherData = {'transactionMethod': transactionMethod}; + } + txDataFuture = wallet.prepareSend( txData: TxData( recipients: [ @@ -860,6 +900,10 @@ class _SendViewState extends ConsumerState { feeRateType: ref.read(feeRateTypeMobileStateProvider), satsPerVByte: isCustomFee.value ? customFeeRate : null, ethEIP1559Fee: ethFee, + otherData: + otherData != null + ? jsonEncode(otherData) + : null, // Include MWC transaction method info. utxos: (wallet is CoinControlInterface && coinControlEnabled && @@ -1091,6 +1135,12 @@ class _SendViewState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) { ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); + + // Initialize MWC transaction method to default (non-slatepack). + if (coin is Mimblewimblecoin) { + ref.read(pSelectedMwcTransactionMethod.notifier).state = + null; // No method selected initially. + } }); isCustomFee.addListener(() { if (!isCustomFee.value) { @@ -1483,186 +1533,222 @@ class _SendViewState extends ConsumerState { ), ), const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - isPaynymSend - ? "Send to PayNym address" - : "Send to", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - // if (coin is Monero) - // CustomTextButton( - // text: "Use OpenAlias", - // onTap: () async { - // await showModalBottomSheet( - // context: context, - // builder: (context) => - // OpenAliasBottomSheet( - // onSelected: (address) { - // sendToController.text = address; - // }, - // ), - // ); - // }, - // ), - ], - ), - const SizedBox(height: 8), - if (isPaynymSend) - TextField( - key: const Key("sendViewPaynymAddressFieldKey"), - controller: sendToController, - enabled: false, - readOnly: true, - style: STextStyles.fieldLabel(context), + + // MWC Transaction Method Selector (moved before "Send to" field). + if (coin is Mimblewimblecoin) ...[ + MwcTransactionMethodSelector( + onMethodSelected: (method) { + setState(() { + _selectedTransactionMethod = method; + }); + // Update the provider as well. + ref + .read( + pSelectedMwcTransactionMethod.notifier, + ) + .state = method; + }, + selectedMethod: _selectedTransactionMethod, + addressText: sendToController.text.trim(), ), - if (!isPaynymSend) - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("sendViewAddressFieldKey"), + const SizedBox(height: 16), + ], + + // Hide "Send to" field for MWC slatepack transactions. + if (!(coin is Mimblewimblecoin && + _selectedTransactionMethod == + TransactionMethod.slatepack)) ...[ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + isPaynymSend + ? "Send to PayNym address" + : "Send to", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + // if (coin is Monero) + // CustomTextButton( + // text: "Use OpenAlias", + // onTap: () async { + // await showModalBottomSheet( + // context: context, + // builder: (context) => + // OpenAliasBottomSheet( + // onSelected: (address) { + // sendToController.text = address; + // }, + // ), + // ); + // }, + // ), + ], + ), + const SizedBox(height: 8), + if (isPaynymSend) + TextField( + key: const Key( + "sendViewPaynymAddressFieldKey", + ), controller: sendToController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, + enabled: false, + readOnly: true, + style: STextStyles.fieldLabel(context), + ), + if (!isPaynymSend) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onChanged: (newValue) async { - final trimmed = newValue.trim(); - - if ((trimmed.length - - (_address?.length ?? 0)) - .abs() > - 1) { - final parsed = - AddressUtils.parsePaymentUri( - trimmed, - logging: Logging.instance, + child: TextField( + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) async { + final trimmed = newValue.trim(); + + if ((trimmed.length - + (_address?.length ?? 0)) + .abs() > + 1) { + final parsed = + AddressUtils.parsePaymentUri( + trimmed, + logging: Logging.instance, + ); + if (parsed != null) { + _applyUri(parsed); + } else { + await _checkSparkNameAndOrSetAddress( + newValue, ); - if (parsed != null) { - _applyUri(parsed); + } } else { await _checkSparkNameAndOrSetAddress( newValue, + setController: false, ); } - } else { - await _checkSparkNameAndOrSetAddress( - newValue, - setController: false, - ); - } - - _setValidAddressProviders(_address); - - setState(() { - _addressToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter ${coin.ticker} address", - _addressFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: - sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( + + _setValidAddressProviders(_address); + + setState(() { + _addressToggleFlag = + newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${coin.ticker} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: + sendToController.text.isEmpty + ? const EdgeInsets.only( + right: 8, + ) + : const EdgeInsets.only( + right: 0, + ), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Address Field Input.", + key: const Key( + "sendViewClearAddressFieldButtonKey", + ), + onTap: () { + sendToController.text = + ""; + _address = ""; + _setValidAddressProviders( + _address, + ); + setState(() { + _addressToggleFlag = + false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Address Field Input.", + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: _pasteAddress, + child: + sendToController + .text + .isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( semanticsLabel: - "Clear Button. Clears The Address Field Input.", + "Address Book Button. Opens Address Book For Address Field.", key: const Key( - "sendViewClearAddressFieldButtonKey", + "sendViewAddressBookButtonKey", ), onTap: () { - sendToController.text = ""; - _address = ""; - _setValidAddressProviders( - _address, + Navigator.of( + context, + ).pushNamed( + AddressBookView.routeName, + arguments: widget.coin, ); - setState(() { - _addressToggleFlag = - false; - }); }, - child: const XIcon(), - ) - : TextFieldIconButton( + child: + const AddressBookIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( semanticsLabel: - "Paste Button. Pastes From Clipboard To Address Field Input.", + "Scan QR Button. Opens Camera For Scanning QR Code.", key: const Key( - "sendViewPasteAddressFieldButtonKey", + "sendViewScanQrButtonKey", ), - onTap: _pasteAddress, - child: - sendToController - .text - .isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - semanticsLabel: - "Address Book Button. Opens Address Book For Address Field.", - key: const Key( - "sendViewAddressBookButtonKey", - ), - onTap: () { - Navigator.of( - context, - ).pushNamed( - AddressBookView.routeName, - arguments: widget.coin, - ); - }, - child: const AddressBookIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - semanticsLabel: - "Scan QR Button. Opens Camera For Scanning QR Code.", - key: const Key( - "sendViewScanQrButtonKey", + onTap: _scanQr, + child: const QrCodeIcon(), ), - onTap: _scanQr, - child: const QrCodeIcon(), - ), - ], + ], + ), ), ), ), ), ), - ), + ], // End of conditional hiding "Send to" field for MWC slatepack const SizedBox(height: 10), if (isStellar || ref.watch(pValidSparkSendToAddress)) diff --git a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart new file mode 100644 index 0000000000..d04ef92c47 --- /dev/null +++ b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart @@ -0,0 +1,323 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +import '../../../notifications/show_flush_bar.dart'; +import '../../../services/mwc_wallet_service.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_dialog.dart'; + +class MwcSlatepackDialog extends ConsumerStatefulWidget { + const MwcSlatepackDialog({ + super.key, + required this.slatepackResult, + this.clipboard = const ClipboardWrapper(), + }); + + final SlatepackResult slatepackResult; + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => _MwcSlatepackDialogState(); +} + +class _MwcSlatepackDialogState extends ConsumerState { + int _currentView = 0; // 0: Slatepack text, 1: QR Code. + + void _copySlatepack() { + widget.clipboard.setData( + ClipboardData(text: widget.slatepackResult.slatepack!), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Slatepack copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + } + + void _shareSlatepack() { + // TODO: Implement file sharing for desktop platforms. + showFloatingFlushBar( + type: FlushBarType.info, + message: "Share functionality coming soon", + context: context, + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = + Platform.isLinux || Platform.isMacOS || Platform.isWindows; + + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Send Slatepack", style: STextStyles.pageTitleH2(context)), + AppBarIconButton( + size: 36, + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.x, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, + width: 24, + height: 24, + ), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Info text. + Text( + "Share this slatepack with the recipient to complete the transaction.", + style: STextStyles.subtitle(context), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + + // Encryption status. + if (widget.slatepackResult.wasEncrypted == true) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .infoItemIcons + .withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.lock, + size: 16, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 8), + Text( + "Encrypted for recipient", + style: STextStyles.label(context).copyWith( + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + + // View toggle buttons. + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Text", + buttonHeight: ButtonHeight.m, + onPressed: + _currentView == 0 + ? null + : () => setState(() => _currentView = 0), + ), + ), + const SizedBox(width: 12), + Expanded( + child: SecondaryButton( + label: "QR Code", + buttonHeight: ButtonHeight.m, + onPressed: + _currentView == 1 + ? null + : () => setState(() => _currentView = 1), + ), + ), + ], + ), + const SizedBox(height: 16), + + // Content display. + if (_currentView == 0) ...[ + // Slatepack text view. + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Slatepack", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + GestureDetector( + onTap: _copySlatepack, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: + Theme.of(context) + .extension()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + Container( + constraints: const BoxConstraints( + maxHeight: 200, + minHeight: 100, + ), + child: SingleChildScrollView( + child: SelectableText( + widget.slatepackResult.slatepack!, + style: STextStyles.w400_14( + context, + ).copyWith(fontFamily: 'monospace'), + ), + ), + ), + ], + ), + ), + ] else ...[ + // QR Code view. + RoundedWhiteContainer( + child: Column( + children: [ + Text( + "Scan QR Code", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: QrImageView( + data: widget.slatepackResult.slatepack!, + size: 200, + backgroundColor: Colors.white, + foregroundColor: Colors.black, + errorCorrectionLevel: QrErrorCorrectLevel.M, + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 24), + + // Action buttons. + if (isDesktop) ...[ + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Share File", + onPressed: _shareSlatepack, + ), + ), + const SizedBox(width: 12), + Expanded( + child: PrimaryButton( + label: "Copy Text", + onPressed: _copySlatepack, + ), + ), + ], + ), + ] else ...[ + PrimaryButton( + label: "Copy Slatepack", + onPressed: _copySlatepack, + ), + const SizedBox(height: 12), + SecondaryButton(label: "Share", onPressed: _shareSlatepack), + ], + + const SizedBox(height: 16), + + // Instructions. + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.infoItemIcons.withOpacity(0.05), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Next Steps:", + style: STextStyles.label( + context, + ).copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 8), + Text( + "1. Share this slatepack with the recipient\n" + "2. Wait for them to return the response slatepack\n" + "3. Import their response to finalize the transaction", + style: STextStyles.w400_14(context), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart b/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart new file mode 100644 index 0000000000..33cbcf5123 --- /dev/null +++ b/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../models/mwc_transaction_method.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/rounded_white_container.dart'; + +class MwcTransactionMethodSelector extends ConsumerStatefulWidget { + const MwcTransactionMethodSelector({ + super.key, + required this.onMethodSelected, + this.selectedMethod, + this.addressText, + }); + + final void Function(TransactionMethod) onMethodSelected; + final TransactionMethod? selectedMethod; + final String? addressText; + + @override + ConsumerState createState() => + _MwcTransactionMethodSelectorState(); +} + +class _MwcTransactionMethodSelectorState + extends ConsumerState { + TransactionMethod? _selectedMethod; + + @override + void initState() { + super.initState(); + _selectedMethod = widget.selectedMethod; + } + + void _selectMethod(TransactionMethod method) { + setState(() { + _selectedMethod = method; + }); + widget.onMethodSelected(method); + } + + Widget _buildMethodTile({ + required TransactionMethod method, + required String title, + required String subtitle, + required IconData icon, + required bool available, + String? unavailableReason, + }) { + final isSelected = _selectedMethod == method; + final canSelect = available && widget.addressText?.isNotEmpty != true; + + return GestureDetector( + onTap: canSelect ? () => _selectMethod(method) : null, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: + isSelected + ? Theme.of(context) + .extension()! + .infoItemIcons + .withValues(alpha: 0.1) + : Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: + isSelected + ? Theme.of(context).extension()!.infoItemIcons + : Theme.of(context).extension()!.background, + width: 2, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: + available + ? Theme.of(context) + .extension()! + .infoItemIcons + .withValues(alpha: 0.1) + : Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + size: 20, + color: + available + ? Theme.of( + context, + ).extension()!.infoItemIcons + : Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: STextStyles.w600_14(context).copyWith( + color: + available + ? Theme.of( + context, + ).extension()!.textDark + : Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ), + const SizedBox(height: 2), + Text( + available ? subtitle : (unavailableReason ?? subtitle), + style: STextStyles.w400_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ), + ], + ), + ), + if (isSelected) + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + shape: BoxShape.circle, + ), + child: Icon( + Icons.check, + size: 12, + color: Theme.of(context).extension()!.popupBG, + ), + ), + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final hasAddressInput = widget.addressText?.isNotEmpty == true; + + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.swap, + width: 16, + height: 16, + colorFilter: ColorFilter.mode( + Theme.of(context).extension()!.infoItemIcons, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 8), + Text("Transaction Method", style: STextStyles.w600_14(context)), + ], + ), + const SizedBox(height: 4), + Text( + hasAddressInput + ? "Method detected from address format" + : "Choose how to send this transaction", + style: STextStyles.w400_14(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle2, + ), + ), + const SizedBox(height: 16), + + Column( + children: [ + // Slatepack method. + _buildMethodTile( + method: TransactionMethod.slatepack, + title: "Slatepack (Manual)", + subtitle: "Copy/paste, QR codes, or files", + icon: Icons.qr_code, + available: true, + ), + const SizedBox(height: 12), + + // MWCMQS method. + _buildMethodTile( + method: TransactionMethod.mwcmqs, + title: "MWCMQS (Automatic)", + subtitle: "Direct messaging to recipient", + icon: Icons.message, + available: true, + unavailableReason: "Requires MWCMQS address", + ), + const SizedBox(height: 12), + + // HTTP method. + _buildMethodTile( + method: TransactionMethod.http, + title: "HTTP (Direct)", + subtitle: "Direct connection to wallet", + icon: Icons.http, + available: true, + unavailableReason: "Requires HTTP address", + ), + ], + ), + + if (hasAddressInput) ...[ + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .infoItemIcons + .withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "Transaction method is automatically detected from the address format. Clear the address field to manually select a method.", + style: STextStyles.w400_14(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ), + ), + ], + ), + ), + ], + ], + ), + ); + } +} 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 af044e6825..c7ef4cacb6 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -42,6 +42,7 @@ import '../../../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../../../wallets/isar/models/spark_coin.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/epiccash_wallet.dart'; +import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart'; import '../../../../wallets/wallet/intermediate/lib_salvium_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; @@ -136,34 +137,34 @@ class _TransactionV2DetailsViewState context: context, builder: (context) => DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Boost transaction", - style: STextStyles.desktopH3(context), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Boost transaction", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Flexible( + child: SingleChildScrollView( + child: BoostTransactionView( + transaction: _transaction, + ), ), ), - const DesktopDialogCloseButton(), ], ), - Flexible( - child: SingleChildScrollView( - child: BoostTransactionView( - transaction: _transaction, - ), - ), - ), - ], - ), - ), + ), ); } } else { @@ -188,14 +189,14 @@ class _TransactionV2DetailsViewState hasTxKeyProbably = (wallet is LibMoneroWallet || wallet is LibSalviumWallet) && - (_transaction.type == TransactionType.outgoing || - _transaction.type == TransactionType.sentToSelf); + (_transaction.type == TransactionType.outgoing || + _transaction.type == TransactionType.sentToSelf); if (_transaction.type - case TransactionType.sentToSelf || TransactionType.outgoing) { + case TransactionType.sentToSelf || TransactionType.outgoing) { supportsRbf = _transaction.subType == TransactionSubType.none && - wallet is RbfInterface; + wallet is RbfInterface; } else { supportsRbf = false; } @@ -248,13 +249,13 @@ class _TransactionV2DetailsViewState _transaction.outputs .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( @@ -265,13 +266,42 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) + .toList(); + } else if (_transaction.isMimblewimblecoinTransaction) { + switch (_transaction.type) { + case TransactionType.outgoing: + case TransactionType.unknown: + amount = _transaction.getAmountSentFromThisWallet( + fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, + ); + break; + + case TransactionType.incoming: + case TransactionType.sentToSelf: + amount = _transaction.getAmountReceivedInThisWallet( + fractionDigits: fractionDigits, + ); + break; + } + data = + _transaction.outputs + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); } else { switch (_transaction.type) { @@ -285,13 +315,13 @@ class _TransactionV2DetailsViewState .where((e) => !e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; @@ -311,17 +341,17 @@ class _TransactionV2DetailsViewState .and() .heightEqualTo(_transaction.height) .anyOf( - _transaction.outputs - .where( - (e) => - e.walletOwns && - e.addresses.isEmpty && - e.scriptPubKeyHex.length >= 488, - ) - .map((e) => e.scriptPubKeyHex.substring(2, 488)) - .toList(), + _transaction.outputs + .where( + (e) => + e.walletOwns && + e.addresses.isEmpty && + e.scriptPubKeyHex.length >= 488, + ) + .map((e) => e.scriptPubKeyHex.substring(2, 488)) + .toList(), (q, element) => q.serializedCoinB64StartsWith(element), - ) + ) .memoProperty() .findFirstSync(); } @@ -338,7 +368,7 @@ class _TransactionV2DetailsViewState rawValue: _transaction.outputs .where( (e) => e.walletOwns && !e.addresses.contains(changeAddress), - ) + ) .fold(BigInt.zero, (p, e) => p + e.value), fractionDigits: coin.fractionDigits, ); @@ -352,13 +382,13 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; @@ -372,13 +402,13 @@ class _TransactionV2DetailsViewState .where((e) => e.walletOwns) .map( (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ), - ), - ) + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) .toList(); break; } @@ -396,11 +426,11 @@ class _TransactionV2DetailsViewState currentChainHeight: height, minConfirms: minConfirms, minCoinbaseConfirms: - ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .minCoinbaseConfirms, + ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); Future fetchContactNameFor(String address) async { @@ -413,10 +443,10 @@ class _TransactionV2DetailsViewState .contacts .where( (element) => - element.addresses - .where((element) => element.address == address) - .isNotEmpty, - ); + element.addresses + .where((element) => element.address == address) + .isNotEmpty, + ); if (contacts.isNotEmpty) { return contacts.first.name; } else { @@ -437,7 +467,7 @@ class _TransactionV2DetailsViewState return StackDialog( title: "Attention", message: - "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", + "You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.", icon: Row( children: [ Consumer( @@ -445,7 +475,7 @@ class _TransactionV2DetailsViewState return Checkbox( value: ref.watch( prefsChangeNotifierProvider.select( - (value) => value.hideBlockExplorerWarning, + (value) => value.hideBlockExplorerWarning, ), ), onChanged: (value) { @@ -473,9 +503,9 @@ class _TransactionV2DetailsViewState "Cancel", style: STextStyles.button(context).copyWith( color: - Theme.of( - context, - ).extension()!.accentColorDark, + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), @@ -508,7 +538,7 @@ class _TransactionV2DetailsViewState return Checkbox( value: ref.watch( prefsChangeNotifierProvider.select( - (value) => value.hideBlockExplorerWarning, + (value) => value.hideBlockExplorerWarning, ), ), onChanged: (value) { @@ -598,10 +628,10 @@ class _TransactionV2DetailsViewState )) { price = ref.watch( priceAnd24hChangeNotifierProvider.select( - (value) => - isTokenTx - ? value.getTokenPrice(_transaction.contractAddress!)?.value - : value.getPrice(coin)?.value, + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.contractAddress!)?.value + : value.getPrice(coin)?.value, ), ); } @@ -611,37 +641,37 @@ class _TransactionV2DetailsViewState builder: (child) => Background(child: child), child: Scaffold( backgroundColor: - isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, appBar: - isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Transaction details", - style: STextStyles.navBarTitle(context), - ), - ), + isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Transaction details", + style: STextStyles.navBarTitle(context), + ), + ), body: ConditionalParent( condition: !isDesktop, builder: (child) => SafeArea(child: child), child: Padding( padding: - isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -659,19 +689,19 @@ class _TransactionV2DetailsViewState Flexible( child: Padding( padding: - isDesktop - ? const EdgeInsets.only(right: 32, bottom: 32) - : const EdgeInsets.all(0), + isDesktop + ? const EdgeInsets.only(right: 32, bottom: 32) + : const EdgeInsets.all(0), child: ConditionalParent( condition: isDesktop, builder: (child) { return RoundedWhiteContainer( borderColor: - isDesktop - ? Theme.of( - context, - ).extension()!.backgroundAppBar - : null, + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, padding: const EdgeInsets.all(0), child: child, ); @@ -680,43 +710,43 @@ class _TransactionV2DetailsViewState primary: isDesktop ? false : null, child: Padding( padding: - isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(4), + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(4), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), child: Container( decoration: - isDesktop - ? BoxDecoration( - color: - Theme.of(context) - .extension()! - .backgroundAppBar, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants - .size - .circularBorderRadius, - ), - ), - ) - : null, + isDesktop + ? BoxDecoration( + color: + Theme.of(context) + .extension()! + .backgroundAppBar, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants + .size + .circularBorderRadius, + ), + ), + ) + : null, child: Padding( padding: - isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), + isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ if (isDesktop) Row( @@ -733,73 +763,73 @@ class _TransactionV2DetailsViewState currentHeight, ), style: - STextStyles.desktopTextMedium( - context, - ), + STextStyles.desktopTextMedium( + context, + ), ), ], ), Column( crossAxisAlignment: - isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "$amountPrefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.titleBold12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.titleBold12( + context, + ), ), const SizedBox(height: 2), if (price != null) Builder( builder: (context) { final total = - (amount.decimal * price!) - .toAmount( - fractionDigits: 2, - ); + (amount.decimal * price!) + .toAmount( + fractionDigits: 2, + ); final formatted = total .fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => - value - .locale, - ), - ), - ); + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => + value + .locale, + ), + ), + ); final ticker = ref.watch( prefsChangeNotifierProvider .select( (value) => - value.currency, - ), + value.currency, + ), ); return SelectableText( "$amountPrefix$formatted $ticker", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ); }, ), @@ -822,23 +852,23 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Status", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -847,32 +877,32 @@ class _TransactionV2DetailsViewState SelectableText( whatIsIt(_transaction, currentHeight), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - _transaction.type == - TransactionType - .outgoing && - _transaction - .subType != - TransactionSubType - .cashFusion - ? Theme.of(context) - .extension< - StackColors - >()! - .accentColorOrange - : Theme.of(context) - .extension< - StackColors - >()! - .accentColorGreen, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + _transaction.type == + TransactionType + .outgoing && + _transaction + .subType != + TransactionSubType + .cashFusion + ? Theme.of(context) + .extension< + StackColors + >()! + .accentColorOrange + : Theme.of(context) + .extension< + StackColors + >()! + .accentColorGreen, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -880,8 +910,8 @@ class _TransactionV2DetailsViewState ), ), if (!((coin is Monero || coin is Wownero) && - _transaction.type == - TransactionType.outgoing) && + _transaction.type == + TransactionType.outgoing) && !((coin is Firo) && _transaction.subType == TransactionSubType.mint)) @@ -889,34 +919,34 @@ class _TransactionV2DetailsViewState ? const _Divider() : const SizedBox(height: 12), if (!((coin is Monero || coin is Wownero) && - _transaction.type == - TransactionType.outgoing) && + _transaction.type == + TransactionType.outgoing) && !((coin is Firo) && _transaction.subType == TransactionSubType.mint)) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ ConditionalParent( condition: kDebugMode, builder: (child) { return Row( mainAxisAlignment: - MainAxisAlignment - .spaceBetween, + MainAxisAlignment + .spaceBetween, children: [ child, // CustomTextButton( @@ -966,25 +996,25 @@ class _TransactionV2DetailsViewState child: Text( outputLabel, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ), const SizedBox(height: 8), Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ if (data.length == 1 && data - .first - .addresses - .length == + .first + .addresses + .length == 1) FutureBuilder( future: fetchContactNameFor( @@ -994,10 +1024,10 @@ class _TransactionV2DetailsViewState .first, ), builder: ( - builderContext, - AsyncSnapshot - snapshot, - ) { + builderContext, + AsyncSnapshot + snapshot, + ) { String addressOrContactName = data @@ -1005,87 +1035,87 @@ class _TransactionV2DetailsViewState .addresses .first; if (snapshot.connectionState == - ConnectionState - .done && + ConnectionState + .done && snapshot.hasData) { addressOrContactName = - snapshot.data!; + snapshot.data!; } return SelectableText( addressOrContactName, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ); }, ) else for ( - int i = 0; - i < data.length; - i++ + int i = 0; + i < data.length; + i++ ) ConditionalParent( condition: i > 0, builder: (child) => Column( - crossAxisAlignment: - CrossAxisAlignment - .stretch, - children: [ - const _Divider(), - child, - ], - ), + crossAxisAlignment: + CrossAxisAlignment + .stretch, + children: [ + const _Divider(), + child, + ], + ), child: Padding( padding: - const EdgeInsets.all( - 8.0, - ), + const EdgeInsets.all( + 8.0, + ), child: Column( crossAxisAlignment: - CrossAxisAlignment - .start, + CrossAxisAlignment + .start, children: [ ...data[i].addresses.map(( - e, - ) { + e, + ) { return FutureBuilder( future: - fetchContactNameFor( - e, - ), + fetchContactNameFor( + e, + ), builder: ( - builderContext, - AsyncSnapshot< - String - > - snapshot, - ) { + builderContext, + AsyncSnapshot< + String + > + snapshot, + ) { final String addressOrContactName; if (snapshot.connectionState == - ConnectionState - .done && + ConnectionState + .done && snapshot .hasData) { addressOrContactName = - snapshot - .data!; + snapshot + .data!; } else { addressOrContactName = e; @@ -1093,10 +1123,10 @@ class _TransactionV2DetailsViewState return OutputCard( address: - addressOrContactName, + addressOrContactName, amount: - data[i] - .amount, + data[i] + .amount, coin: coin, ); }, @@ -1118,56 +1148,56 @@ class _TransactionV2DetailsViewState ], ), ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox(height: 12), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "On chain note", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), const SizedBox(height: 8), SelectableText( _transaction.onChainNote ?? "", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1184,112 +1214,115 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( - (coin is Epiccash) + (coin is Epiccash || + coin is Mimblewimblecoin) ? "Local Note" : "Note ", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), - ), - isDesktop - ? IconPencilButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: _transaction.txid, - walletId: walletId, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), + ), + isDesktop + ? IconPencilButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: _transaction.txid, + walletId: walletId, + ), + ); + }, ); }, - ); - }, - ) + ) : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple2( - _transaction.txid, - walletId, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: - Theme.of(context) - .extension< - StackColors - >()! - .infoItemIcons, - ), - const SizedBox(width: 4), - Text( - "Edit", - style: STextStyles.link2( - context, - ), + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple2( + _transaction.txid, + walletId, + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: + Theme.of(context) + .extension< + StackColors + >()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Edit", + style: STextStyles.link2( + context, + ), + ), + ], ), - ], - ), - ), + ), ], ), const SizedBox(height: 8), SelectableText( ref - .watch( - pTransactionNote(( - txid: - (coin is Epiccash) - ? _transaction.slateId - .toString() - : _transaction.txid, - walletId: walletId, - )), - ) - ?.value ?? + .watch( + pTransactionNote(( + txid: + (coin is Epiccash || + coin + is Mimblewimblecoin) + ? _transaction.slateId + .toString() + : _transaction.txid, + walletId: walletId, + )), + ) + ?.value ?? "", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1301,25 +1334,25 @@ class _TransactionV2DetailsViewState if (_sparkMemo != null) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Row( children: [ Text( "Memo", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ], ), @@ -1327,20 +1360,20 @@ class _TransactionV2DetailsViewState SelectableText( _sparkMemo!, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1350,28 +1383,28 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Date", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1381,20 +1414,20 @@ class _TransactionV2DetailsViewState _transaction.timestamp, ), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1404,20 +1437,20 @@ class _TransactionV2DetailsViewState _transaction.timestamp, ), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton( @@ -1441,48 +1474,48 @@ class _TransactionV2DetailsViewState TransactionType.incoming)) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Builder( builder: (context) { final String feeString = - showFeePending - ? _transaction.isConfirmed( - currentHeight, - minConfirms, - coin.minCoinbaseConfirms, - ) - ? ref - .watch( - pAmountFormatter(coin), - ) - .format(fee) - : "Pending" - : ref - .watch(pAmountFormatter(coin)) - .format(fee); + showFeePending + ? _transaction.isConfirmed( + currentHeight, + minConfirms, + coin.minCoinbaseConfirms, + ) + ? ref + .watch( + pAmountFormatter(coin), + ) + .format(fee) + : "Pending" + : ref + .watch(pAmountFormatter(coin)) + .format(fee); return Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Transaction fee", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1490,22 +1523,22 @@ class _TransactionV2DetailsViewState SelectableText( feeString, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (supportsRbf && !confirmedTxn) const SizedBox(height: 8), @@ -1520,20 +1553,20 @@ class _TransactionV2DetailsViewState SelectableText( feeString, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: feeString), @@ -1555,10 +1588,10 @@ class _TransactionV2DetailsViewState if (widget.coin is Bitcoincash || widget.coin is Ecash) { height = - _transaction.height != null && - _transaction.height! > 0 - ? "${_transaction.height!}" - : "Pending"; + _transaction.height != null && + _transaction.height! > 0 + ? "${_transaction.height!}" + : "Pending"; confirmations = confirms.toString(); } else if (widget.coin is Epiccash && _transaction.slateId == null) { @@ -1572,12 +1605,12 @@ class _TransactionV2DetailsViewState ); if (widget.coin is! Epiccash && confirmed) { height = - "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; + "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { height = - confirms > 0 - ? "${_transaction.height}" - : "Pending"; + confirms > 0 + ? "${_transaction.height}" + : "Pending"; } confirmations = confirms.toString(); @@ -1587,29 +1620,29 @@ class _TransactionV2DetailsViewState children: [ RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Block height", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1617,22 +1650,22 @@ class _TransactionV2DetailsViewState SelectableText( height, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1640,20 +1673,20 @@ class _TransactionV2DetailsViewState SelectableText( height, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1665,29 +1698,29 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Confirmations", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) const SizedBox(height: 2), @@ -1695,22 +1728,22 @@ class _TransactionV2DetailsViewState SelectableText( confirmations, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1718,20 +1751,20 @@ class _TransactionV2DetailsViewState SelectableText( confirmations, style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1751,43 +1784,43 @@ class _TransactionV2DetailsViewState _transaction.type != TransactionType.incoming) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Nonce", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.nonce.toString(), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1799,43 +1832,43 @@ class _TransactionV2DetailsViewState if (kDebugMode) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Text( "Tx sub type", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.subType.toString(), style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1854,304 +1887,306 @@ class _TransactionV2DetailsViewState : const SizedBox(height: 12), _transaction.txid.startsWith("mweb_outputId_") && - _transaction.subType == - TransactionSubType.mweb + _transaction.subType == + TransactionSubType.mweb ? RoundedWhiteContainer( - padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - ConditionalParent( - condition: !isDesktop, - builder: - (child) => Row( - children: [ - Expanded(child: child), - SimpleCopyButton( - data: _transaction - .txid - .replaceFirst( - "mweb_outputId_", - "", - ), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ConditionalParent( + condition: !isDesktop, + builder: + (child) => Row( + children: [ + Expanded(child: child), + SimpleCopyButton( + data: _transaction + .txid + .replaceFirst( + "mweb_outputId_", + "", + ), + ), + ], + ), + child: Text( + "MWEB Output ID", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), - ], - ), - child: Text( - "MWEB Output ID", - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, ), - ), + const SizedBox(height: 8), + SelectableText( + _transaction.txid.replaceFirst( + "mweb_outputId_", + "", + ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + // if (coin is Litecoin && + // coin.network == + // CryptoCurrencyNetwork + // .main) + // const SizedBox(height: 8), + // if (coin is Litecoin && + // coin.network == + // CryptoCurrencyNetwork + // .main) + // CustomTextButton( + // text: + // "Open in block explorer", + // onTap: () async { + // final uri = + // getBlockExplorerTransactionUrlFor( + // coin: coin, + // txid: _transaction + // .txid + // .replaceFirst( + // "mweb_outputId_", + // "", + // ), + // ); + // + // if (ref + // .read( + // prefsChangeNotifierProvider, + // ) + // .hideBlockExplorerWarning == + // false) { + // final shouldContinue = + // await showExplorerWarning( + // "${uri.scheme}://${uri.host}", + // ); + // + // if (!shouldContinue) { + // return; + // } + // } + // try { + // await launchUrl( + // uri, + // mode: + // LaunchMode + // .externalApplication, + // ); + // } catch (_) { + // if (context.mounted) { + // unawaited( + // showDialog( + // context: context, + // builder: + // ( + // _, + // ) => StackOkDialog( + // title: + // "Could not open in block explorer", + // message: + // "Failed to open \"${uri.toString()}\"", + // ), + // ), + // ); + // } + // } + // }, + // ), + ], ), - const SizedBox(height: 8), - SelectableText( - _transaction.txid.replaceFirst( - "mweb_outputId_", - "", - ), - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + ), + if (isDesktop) + const SizedBox(width: 12), + if (isDesktop) + IconCopyButton( + data: _transaction.txid + .replaceFirst( + "mweb_outputId_", + "", + ), ), - // if (coin is Litecoin && - // coin.network == - // CryptoCurrencyNetwork - // .main) - // const SizedBox(height: 8), - // if (coin is Litecoin && - // coin.network == - // CryptoCurrencyNetwork - // .main) - // CustomTextButton( - // text: - // "Open in block explorer", - // onTap: () async { - // final uri = - // getBlockExplorerTransactionUrlFor( - // coin: coin, - // txid: _transaction - // .txid - // .replaceFirst( - // "mweb_outputId_", - // "", - // ), - // ); - // - // if (ref - // .read( - // prefsChangeNotifierProvider, - // ) - // .hideBlockExplorerWarning == - // false) { - // final shouldContinue = - // await showExplorerWarning( - // "${uri.scheme}://${uri.host}", - // ); - // - // if (!shouldContinue) { - // return; - // } - // } - // try { - // await launchUrl( - // uri, - // mode: - // LaunchMode - // .externalApplication, - // ); - // } catch (_) { - // if (context.mounted) { - // unawaited( - // showDialog( - // context: context, - // builder: - // ( - // _, - // ) => StackOkDialog( - // title: - // "Could not open in block explorer", - // message: - // "Failed to open \"${uri.toString()}\"", - // ), - // ), - // ); - // } - // } - // }, - // ), - ], - ), + ], ), - if (isDesktop) - const SizedBox(width: 12), - if (isDesktop) - IconCopyButton( - data: _transaction.txid - .replaceFirst( - "mweb_outputId_", - "", - ), - ), - ], - ), - ) + ) : RoundedWhiteContainer( - padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - ConditionalParent( - condition: !isDesktop, - builder: - (child) => Row( - children: [ - Expanded(child: child), - SimpleCopyButton( - data: - _transaction.txid, + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ConditionalParent( + condition: !isDesktop, + builder: + (child) => Row( + children: [ + Expanded(child: child), + SimpleCopyButton( + data: + _transaction.txid, + ), + ], + ), + child: Text( + "Transaction ID", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), - ], - ), - child: Text( - "Transaction ID", - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, ), - ), + const SizedBox(height: 8), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.txid, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + if (coin is! Epiccash && + coin is! Mimblewimblecoin) + const SizedBox(height: 8), + if (coin is! Epiccash && + coin is! Mimblewimblecoin) + CustomTextButton( + text: + "Open in block explorer", + onTap: () async { + final uri = + getBlockExplorerTransactionUrlFor( + coin: coin, + txid: + _transaction.txid, + ); + + if (ref + .read( + prefsChangeNotifierProvider, + ) + .hideBlockExplorerWarning == + false) { + final shouldContinue = + await showExplorerWarning( + "${uri.scheme}://${uri.host}", + ); + + if (!shouldContinue) { + return; + } + } + + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + try { + await launchUrl( + uri, + mode: + LaunchMode + .externalApplication, + ); + } catch (_) { + if (context.mounted) { + unawaited( + showDialog( + context: context, + builder: + ( + _, + ) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), + ), + ); + } + } finally { + // Future.delayed( + // const Duration(seconds: 1), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + } + }, + ), + // ), + // ), + ], ), - const SizedBox(height: 8), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: - SelectableText( - _transaction.txid, - style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + ), + if (isDesktop) + const SizedBox(width: 12), + if (isDesktop) + IconCopyButton( + data: _transaction.txid, ), - if (coin is! Epiccash) - const SizedBox(height: 8), - if (coin is! Epiccash) - CustomTextButton( - text: - "Open in block explorer", - onTap: () async { - final uri = - getBlockExplorerTransactionUrlFor( - coin: coin, - txid: - _transaction.txid, - ); - - if (ref - .read( - prefsChangeNotifierProvider, - ) - .hideBlockExplorerWarning == - false) { - final shouldContinue = - await showExplorerWarning( - "${uri.scheme}://${uri.host}", - ); - - if (!shouldContinue) { - return; - } - } - - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - try { - await launchUrl( - uri, - mode: - LaunchMode - .externalApplication, - ); - } catch (_) { - if (context.mounted) { - unawaited( - showDialog( - context: context, - builder: - ( - _, - ) => StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), - ), - ); - } - } finally { - // Future.delayed( - // const Duration(seconds: 1), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - } - }, - ), - // ), - // ), - ], - ), + ], ), - if (isDesktop) - const SizedBox(width: 12), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, - ), - ], - ), - ), + ), // if ((coin is FiroTestNet || coin is Firo) && // _transaction.subType == "mint") // const SizedBox( @@ -2229,36 +2264,36 @@ class _TransactionV2DetailsViewState // ], // ), // ), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) isDesktop ? const _Divider() : const SizedBox(height: 12), - if (coin is Epiccash) + if (coin is Epiccash || coin is Mimblewimblecoin) RoundedWhiteContainer( padding: - isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, mainAxisAlignment: - MainAxisAlignment.spaceBetween, + MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: - CrossAxisAlignment.start, + CrossAxisAlignment.start, children: [ Text( "Slate ID", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -2267,20 +2302,20 @@ class _TransactionV2DetailsViewState SelectableText( _transaction.slateId ?? "Unknown", style: - isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension< - StackColors - >()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -2290,7 +2325,7 @@ class _TransactionV2DetailsViewState if (isDesktop) IconCopyButton( data: - _transaction.slateId ?? "Unknown", + _transaction.slateId ?? "Unknown", ), ], ), @@ -2319,105 +2354,162 @@ class _TransactionV2DetailsViewState ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: - (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) - ? ConditionalParent( - condition: isDesktop, - builder: - (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, - ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, - ), - ), - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); - - if (wallet is EpiccashWallet) { - final String? id = _transaction.slateId; - if (id == null) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Could not find Epic transaction ID", - context: context, + ((coin is Epiccash || coin is Mimblewimblecoin) && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) + ? ConditionalParent( + condition: isDesktop, + builder: + (child) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, ), - ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: - (_) => - const CancellingTransactionProgressDialog(), - ), - ); - - final result = await wallet - .cancelPendingTransactionAndPost(id); - if (context.mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: - (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, + child: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, + ), + ), + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + + if (wallet is EpiccashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, ), ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: - (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); - } - } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (context.mounted) { + // Pop progress dialog. + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else if (wallet is MimblewimblecoinWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find MWC transaction ID", + context: context, + ), + ); + return; + } + + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), + ), + ); + + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (context.mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "ERROR: Wallet type is not Epic Cash or MimbleWimbleCoin", + context: context, + ), + ); + return; + } + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), ), - ); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), - ), - ), - ), - ) - : null, + ), + ) + : null, ), ); } @@ -2443,18 +2535,18 @@ class OutputCard extends ConsumerWidget { Text( "Address", style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( address, style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), const SizedBox(height: 10), Row( @@ -2463,23 +2555,23 @@ class OutputCard extends ConsumerWidget { Text( "Amount", style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( ref.watch(pAmountFormatter(coin)).format(amount), style: - Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), ], ), @@ -2512,7 +2604,7 @@ class IconCopyButton extends StatelessWidget { width: 26, child: RawMaterialButton( fillColor: - Theme.of(context).extension()!.buttonBackSecondary, + Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), @@ -2553,7 +2645,7 @@ class IconPencilButton extends StatelessWidget { width: 26, child: RawMaterialButton( fillColor: - Theme.of(context).extension()!.buttonBackSecondary, + Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart index 2f07f39563..c425f79e7a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart @@ -10,58 +10,34 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; -import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:isar/isar.dart'; -import 'package:tuple/tuple.dart'; import '../../../../models/isar/models/isar_models.dart'; -import '../../../../models/keys/view_only_wallet_data.dart'; -import '../../../../notifications/show_flush_bar.dart'; -import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; -import '../../../../providers/db/main_db_provider.dart'; +import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; import '../../../../providers/providers.dart'; -import '../../../../route_generator.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; -import '../../../../utilities/amount/amount.dart'; -import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/derive_path_type_enum.dart'; -import '../../../../utilities/enums/txs_method_mwc_enum.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/text_styles.dart'; -import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; -import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/impl/bitcoin_wallet.dart'; import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; -import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; -import '../../../../widgets/toggle.dart'; -import '../../../../widgets/custom_buttons/app_bar_icon_button.dart'; -import '../../../../widgets/custom_loading_overlay.dart'; -import '../../../../widgets/desktop/desktop_dialog.dart'; -import '../../../../widgets/desktop/secondary_button.dart'; -import '../../../../widgets/qr.dart'; -import '../../../../widgets/rounded_white_container.dart'; -import 'desktop_mwc_txs_method_toggle.dart'; class DesktopFinalize extends ConsumerStatefulWidget { const DesktopFinalize({ @@ -99,8 +75,6 @@ class _DesktopFinalizeState extends ConsumerState { final Map _addressMap = {}; final Map> _addressSubMap = {}; - - Future pasteAddress() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { @@ -150,7 +124,6 @@ class _DesktopFinalizeState extends ConsumerState { } } } - @override void initState() { @@ -160,18 +133,18 @@ class _DesktopFinalizeState extends ConsumerState { clipboard = widget.clipboard; final wallet = ref.read(pWallets).getWallet(walletId); supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface; - + isMimblewimblecoin = wallet is MimblewimblecoinWallet; if (isMimblewimblecoin) { _selectedMethodMwc = "Slatepack"; } debugPrint("Address generated: $isMimblewimblecoin"); - if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { showMultiType = false; } else { - showMultiType = supportsSpark || + showMultiType = + supportsSpark || (wallet is! BCashInterface && wallet is Bip39HDWallet && wallet.supportedAddressTypes.length > 1); @@ -184,9 +157,9 @@ class _DesktopFinalizeState extends ConsumerState { _walletAddressTypes.insert(0, AddressType.spark); } else { _walletAddressTypes.addAll( - (wallet as Bip39HDWallet) - .supportedAddressTypes - .where((e) => e != wallet.info.mainAddressType), + (wallet as Bip39HDWallet).supportedAddressTypes.where( + (e) => e != wallet.info.mainAddressType, + ), ); } } @@ -195,8 +168,9 @@ class _DesktopFinalizeState extends ConsumerState { _walletAddressTypes.removeWhere((e) => e == AddressType.p2pkh); } - _addressMap[_walletAddressTypes[_currentIndex]] = - ref.read(pWalletReceivingAddress(walletId)); + _addressMap[_walletAddressTypes[_currentIndex]] = ref.read( + pWalletReceivingAddress(walletId), + ); if (showMultiType) { for (final type in _walletAddressTypes) { @@ -212,15 +186,15 @@ class _DesktopFinalizeState extends ConsumerState { .findFirst() .asStream() .listen((event) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _addressMap[type] = - event?.value ?? _addressMap[type] ?? "[No address yet]"; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _addressMap[type] = + event?.value ?? _addressMap[type] ?? "[No address yet]"; + }); + } }); - } - }); - }); + }); } } @@ -246,129 +220,141 @@ class _DesktopFinalizeState extends ConsumerState { address = ref.watch(pWalletReceivingAddress(walletId)); } - final wallet = ref.watch(pWallets.select((value) => value.getWallet(walletId))); + final wallet = ref.watch( + pWallets.select((value) => value.getWallet(walletId)), + ); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox( - height: 4, - ), - const SizedBox( - height: 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Label Text - Text( - "Finalize Slatepack", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - textAlign: TextAlign.left, + const SizedBox(height: 4), + const SizedBox(height: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Label Text + Text( + "Finalize Slatepack", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), - const SizedBox(height: 8), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + textAlign: TextAlign.left, + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("sendViewAddressFieldKey"), + controller: receiveSlateController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - child: TextField( - minLines: 1, - maxLines: 5, - key: const Key("sendViewAddressFieldKey"), - controller: receiveSlateController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - onChanged: (newValue) { - _address = newValue; - //_setValidAddressProviders(_address); + onChanged: (newValue) { + _address = newValue; + //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - height: 1.8, + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter Final Slatepack Message", + _addressFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: + 12, // Adjust vertical padding for better alignment ), - decoration: standardInputDecoration( - "Enter Final Slatepack Message", - _addressFocusNode, - context, - desktopMed: true, - ).copyWith( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, // Adjust vertical padding for better alignment - ), - suffixIcon: Padding( - padding: receiveSlateController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( - key: const Key("sendViewClearAddressFieldButtonKey"), - onTap: () { - receiveSlateController.text = ""; - _address = ""; - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey", - ), - onTap: pasteAddress, - child: receiveSlateController.text.isEmpty + suffixIcon: Padding( + padding: + receiveSlateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey", + ), + onTap: () { + receiveSlateController.text = ""; + _address = ""; + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey", + ), + onTap: pasteAddress, + child: + receiveSlateController.text.isEmpty ? const ClipboardIcon() : const XIcon(), - ), - ], - ), + ), + ], ), ), ), ), ), - ], - ), - const SizedBox( - height: 32, + ), + ], ), + const SizedBox(height: 32), Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: PrimaryButton( buttonHeight: ButtonHeight.l, - label: "Preview Receive Slatepack", + label: "Finalize Slatepack", enabled: true, - onPressed: () { - debugPrint('Submit button pressed for Mimblewimblecoin Slatepack'); + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + await showDialog( + context: context, + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 700, + child: MwcSlatepackImportDialog( + wallet: wallet as MimblewimblecoinWallet, + ), + ), + ); }, ), - ) + ), ], ); } } - - diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart index 18bab217ae..918e0abaa8 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; @@ -18,10 +19,7 @@ import '../../../../utilities/util.dart'; import '../../../../widgets/toggle.dart'; class MwcTxsMethodToggle extends ConsumerWidget { - const MwcTxsMethodToggle({ - super.key, - this.onChanged, - }); + const MwcTxsMethodToggle({super.key, this.onChanged}); final void Function(TxsMethodMwcType)? onChanged; @@ -33,22 +31,28 @@ class MwcTxsMethodToggle extends ConsumerWidget { return Toggle( onValueChanged: (value) { if (value) { - onChanged?.call(TxsMethodMwcType.slatepack); - } else { onChanged?.call(TxsMethodMwcType.automatic); + } else { + onChanged?.call(TxsMethodMwcType.slatepack); } }, isOn: false, - onColor: isDesktop - ? Theme.of(context) - .extension()! - .rateTypeToggleDesktopColorOn - : Theme.of(context).extension()!.rateTypeToggleColorOn, - offColor: isDesktop - ? Theme.of(context) - .extension()! - .rateTypeToggleDesktopColorOff - : Theme.of(context).extension()!.rateTypeToggleColorOff, + onColor: + isDesktop + ? Theme.of( + context, + ).extension()!.rateTypeToggleDesktopColorOn + : Theme.of( + context, + ).extension()!.rateTypeToggleColorOn, + offColor: + isDesktop + ? Theme.of( + context, + ).extension()!.rateTypeToggleDesktopColorOff + : Theme.of( + context, + ).extension()!.rateTypeToggleColorOff, decoration: BoxDecoration( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 3e74a6258c..260637369f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -22,6 +22,7 @@ import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/keys/view_only_wallet_data.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; +import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; import '../../../../providers/providers.dart'; import '../../../../route_generator.dart'; import '../../../../themes/stack_colors.dart'; @@ -114,7 +115,8 @@ class _DesktopReceiveState extends ConsumerState { if (paymentData != null && paymentData.coin?.uriScheme == coin.uriScheme) { _address = paymentData.address; - receiveSlateController.text = _address!; + receiveSlateController.text = + _selectedMethodMwc == "Slatepack" ? "" : _address!; setState(() { _addressToggleFlag = receiveSlateController.text.isNotEmpty; }); @@ -819,11 +821,21 @@ class _DesktopReceiveState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 8.0), child: PrimaryButton( buttonHeight: ButtonHeight.l, - label: "Preview Receive Slatepack", + label: "Receive Slatepack", enabled: true, - onPressed: () { - debugPrint( - 'Submit button pressed for Mimblewimblecoin Slatepack', + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + await showDialog( + context: context, + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 700, + child: MwcSlatepackImportDialog( + wallet: wallet as MimblewimblecoinWallet, + clipboard: widget.clipboard, + ), + ), ); }, ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index ae5733c968..94a2043ed7 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -20,10 +20,12 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../../models/isar/models/blockchain_data/address.dart'; import '../../../../models/isar/models/blockchain_data/utxo.dart'; import '../../../../models/isar/models/contact_entry.dart'; +import '../../../../models/mwc_transaction_method.dart'; import '../../../../models/paynym/paynym_account_lite.dart'; import '../../../../models/send_view_auto_fill_data.dart'; import '../../../../pages/send_view/confirm_transaction_view.dart'; import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.dart'; +import '../../../../pages/send_view/sub_widgets/mwc_slatepack_dialog.dart'; import '../../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; import '../../../../providers/providers.dart'; import '../../../../providers/ui/fee_rate_type_state_provider.dart'; @@ -50,6 +52,7 @@ 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/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'; @@ -116,7 +119,7 @@ class _DesktopSendState extends ConsumerState { late final bool isStellar; late final bool isMimblewimblecoin; - String? _selectedMethodMwc; + String? _selectedMethodMwc = 'Slatepack'; String? _note; String? _onChainNote; @@ -164,9 +167,135 @@ class _DesktopSendState extends ConsumerState { } } + /// Handle MWC slatepack creation for desktop. + Future _handleDesktopSlatepackCreation( + MimblewimblecoinWallet wallet, + ) async { + try { + final amount = ref.read(pSendAmount)!; + + // Show building dialog. + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => BuildingTransactionDialog( + coin: coin, + onCancel: + () => Navigator.of(context, rootNavigator: true).pop(), + isSpark: false, + ), + ), + ); + + // Create slatepack. + final slatepackResult = await wallet.createSlatepack( + amount: amount, + recipientAddress: null, // No specific recipient for manual slatepack. + message: _onChainNote?.isNotEmpty == true ? _onChainNote : null, + encrypt: false, // No encryption without recipient address. + ); + + // Close building dialog. + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + } + + if (!slatepackResult.success || slatepackResult.slatepack == null) { + throw Exception(slatepackResult.error ?? 'Failed to create slatepack'); + } + + // Show slatepack dialog. + if (mounted) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 700, + child: MwcSlatepackDialog(slatepackResult: slatepackResult), + ), + ); + + // Clear form after slatepack dialog is closed. + clearSendForm(); + } + } catch (e, s) { + Logging.instance.e('Failed to create MWC slatepack on desktop: $e\n$s'); + + // Close building dialog if still open. + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + } + + if (mounted) { + await showDialog( + context: context, + builder: + (context) => DesktopDialog( + maxWidth: 450, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.only(left: 32, bottom: 32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Slatepack Creation Failed', + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(right: 32), + child: Text( + 'Failed to create slatepack: $e', + textAlign: TextAlign.left, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith(fontSize: 18), + ), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.only(right: 32), + child: Row( + children: [ + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: 'OK', + onPressed: () => Navigator.of(context).pop(), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + } + } + Future previewSend() async { final wallet = ref.read(pWallets).getWallet(walletId); + // Handle MWC slatepack transactions directly. + if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') { + await _handleDesktopSlatepackCreation(wallet as MimblewimblecoinWallet); + return; + } + final Amount amount = ref.read(pSendAmount)!; final Amount availableBalance; if (coin is Firo || ref.read(pWalletInfo(walletId)).isMwebEnabled) { @@ -442,10 +571,18 @@ class _DesktopSendState extends ConsumerState { txData: TxData( recipients: [ TxRecipient( - address: _address!, + address: + coin is Mimblewimblecoin && + _selectedMethodMwc == "Slatepack" + ? "BEGINSLATEPACK. .ENDSLATEPACK" // TODO: This is a hack so that the address validation does not fail. Need to handle this better. + : _address!, amount: amount, isChange: false, - addressType: wallet.cryptoCurrency.getAddressType(_address!)!, + addressType: + coin is Mimblewimblecoin && + _selectedMethodMwc == "Slatepack" + ? AddressType.mimbleWimble + : wallet.cryptoCurrency.getAddressType(_address!)!, ), ], memo: memo, @@ -949,6 +1086,11 @@ class _DesktopSendState extends ConsumerState { isMimblewimblecoin = coin is Mimblewimblecoin; if (isMimblewimblecoin) { _selectedMethodMwc = "Slatepack"; + // Initialize provider to MWCMQS (automatic method). + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(pSelectedMwcTransactionMethod.notifier).state = + TransactionMethod.mwcmqs; + }); } sendToController = TextEditingController(); @@ -1120,10 +1262,15 @@ class _DesktopSendState extends ConsumerState { onChanged: (TxsMethodMwcType type) { setState(() { _selectedMethodMwc = - type == TxsMethodMwcType.automatic + type == TxsMethodMwcType.slatepack ? 'Slatepack' : 'Automatic'; }); + // Update the provider as well. + ref.read(pSelectedMwcTransactionMethod.notifier).state = + type == TxsMethodMwcType.slatepack + ? TransactionMethod.slatepack + : TransactionMethod.mwcmqs; }, ), ), @@ -1429,7 +1576,9 @@ class _DesktopSendState extends ConsumerState { ), ), const SizedBox(height: 20), - if (!isPaynymSend) + // Hide "Send to" field for MWC slatepack transactions. + if (!isPaynymSend && + !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) Text( "Send to", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -1440,8 +1589,11 @@ class _DesktopSendState extends ConsumerState { ), textAlign: TextAlign.left, ), - if (!isPaynymSend) const SizedBox(height: 10), - if (!isPaynymSend) + if (!isPaynymSend && + !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) + const SizedBox(height: 10), + if (!isPaynymSend && + !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, diff --git a/lib/providers/ui/preview_tx_button_state_provider.dart b/lib/providers/ui/preview_tx_button_state_provider.dart index 9ff71aca7d..f8955f8888 100644 --- a/lib/providers/ui/preview_tx_button_state_provider.dart +++ b/lib/providers/ui/preview_tx_button_state_provider.dart @@ -10,6 +10,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../models/mwc_transaction_method.dart'; import '../../utilities/amount/amount.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../wallet/public_private_balance_state_provider.dart'; @@ -20,10 +21,22 @@ final pValidSparkSendToAddress = StateProvider.autoDispose((_) => false); final pIsExchangeAddress = StateProvider((_) => false); +// MWC Transaction Method Provider. +final pSelectedMwcTransactionMethod = + StateProvider.autoDispose((_) => null); + final pPreviewTxButtonEnabled = Provider.autoDispose .family((ref, coin) { final amount = ref.watch(pSendAmount) ?? Amount.zero; + // For MWC slatepack transactions, address validation is not required. + if (coin is Mimblewimblecoin) { + final selectedMethod = ref.watch(pSelectedMwcTransactionMethod); + if (selectedMethod == TransactionMethod.slatepack) { + return amount > Amount.zero; + } + } + if (coin is Firo) { final firoType = ref.watch(publicPrivateBalanceStateProvider); switch (firoType) { diff --git a/lib/services/mwc_wallet_service.dart b/lib/services/mwc_wallet_service.dart new file mode 100644 index 0000000000..c44f84380a --- /dev/null +++ b/lib/services/mwc_wallet_service.dart @@ -0,0 +1,552 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_libmwc/lib.dart' as mwc; + +import '../models/mwcmqs_config_model.dart'; +import '../utilities/amount/amount.dart'; +import '../utilities/default_mwcmqs.dart'; +import '../utilities/logger.dart'; +import '../utilities/stack_file_system.dart'; + +/// Service layer that wraps flutter_libmwc FFI functions for Stack Wallet integration. +/// +/// Based on the flutter_libmwc example WalletService but adapted for Stack Wallet patterns. +class MwcWalletService { + static bool _isInitialized = false; + static final Map _walletHandles = {}; + + /// Initialize the MWC wallet service. + static Future initialize() async { + if (_isInitialized) return; + + Logging.instance.i('MWC Wallet Service initialized'); + _isInitialized = true; + } + + /// Create a new MWC wallet. + static Future createWallet({ + required String walletId, + required String password, + String? customMnemonic, + }) async { + try { + Logging.instance.i('Creating MWC wallet: $walletId'); + + final config = await _getWalletConfig(walletId); + final mnemonic = customMnemonic ?? mwc.Libmwc.getMnemonic(); + + final result = await mwc.Libmwc.initializeNewWallet( + config: config, + mnemonic: mnemonic, + password: password, + name: walletId, + ); + + if (result.toUpperCase().contains('ERROR')) { + return MwcWalletResult( + success: false, + error: 'Failed to create wallet: $result', + ); + } + + _walletHandles[walletId] = result; + + Logging.instance.i('MWC wallet created successfully: $walletId'); + return MwcWalletResult( + success: true, + walletId: walletId, + data: {'mnemonic': mnemonic, 'handle': result}, + ); + } catch (e, s) { + Logging.instance.e('Failed to create MWC wallet: $e\n$s'); + return MwcWalletResult( + success: false, + error: 'Failed to create wallet: $e', + ); + } + } + + /// Recover MWC wallet from mnemonic. + static Future recoverWallet({ + required String walletId, + required String password, + required String mnemonic, + }) async { + try { + Logging.instance.i('Recovering MWC wallet: $walletId'); + + final config = await _getWalletConfig(walletId); + + await mwc.Libmwc.recoverWallet( + config: config, + password: password, + mnemonic: mnemonic, + name: walletId, + ); + + final openResult = await openWallet( + walletId: walletId, + password: password, + ); + + if (openResult.success) { + Logging.instance.i('MWC wallet recovered successfully: $walletId'); + return MwcWalletResult( + success: true, + walletId: walletId, + data: {'recovered': true}, + ); + } + + return openResult; + } catch (e, s) { + Logging.instance.e('Failed to recover MWC wallet: $e\n$s'); + return MwcWalletResult( + success: false, + error: 'Failed to recover wallet: $e', + ); + } + } + + /// Open an existing MWC wallet. + static Future openWallet({ + required String walletId, + required String password, + }) async { + try { + Logging.instance.i('Opening MWC wallet: $walletId'); + + final config = await _getWalletConfig(walletId); + + final result = await mwc.Libmwc.openWallet( + config: config, + password: password, + ); + + if (result.toUpperCase().contains('ERROR')) { + return MwcWalletResult( + success: false, + error: 'Failed to open wallet: $result', + ); + } + + _walletHandles[walletId] = result; + + Logging.instance.i('MWC wallet opened successfully: $walletId'); + return MwcWalletResult( + success: true, + walletId: walletId, + data: {'handle': result}, + ); + } catch (e, s) { + Logging.instance.e('Failed to open MWC wallet: $e\n$s'); + return MwcWalletResult( + success: false, + error: 'Failed to open wallet: $e', + ); + } + } + + /// Create a slatepack for sending MWC. + static Future createSlatepack({ + required String walletId, + required Amount amount, + String? recipientAddress, + String? message, + bool encrypt = false, + int minimumConfirmations = 1, + }) async { + try { + final handle = _walletHandles[walletId]; + if (handle == null) { + return SlatepackResult( + success: false, + error: 'Wallet not open: $walletId', + ); + } + + Logging.instance.i('Creating slatepack for wallet: $walletId'); + + // Generate S1 slate JSON. + final s1Json = await mwc.Libmwc.txInit( + wallet: handle, + amount: amount.raw.toInt(), + minimumConfirmations: minimumConfirmations, + selectionStrategyIsAll: false, + message: message ?? '', + ); + + // Encode to slatepack. + final slatepackEncode = await mwc.Libmwc.encodeSlatepack( + slateJson: s1Json, + recipientAddress: recipientAddress, + encrypt: encrypt, + wallet: handle, + ); + + Logging.instance.i('Slatepack created successfully'); + return SlatepackResult( + success: true, + slatepack: slatepackEncode.slatepack, + slateJson: s1Json, + wasEncrypted: slatepackEncode.wasEncrypted, + recipientAddress: slatepackEncode.recipientAddress, + ); + } catch (e, s) { + Logging.instance.e('Failed to create slatepack: $e\n$s'); + return SlatepackResult( + success: false, + error: 'Failed to create slatepack: $e', + ); + } + } + + /// Decode a slatepack. + static Future decodeSlatepack({ + required String slatepack, + String? walletId, + }) async { + try { + Logging.instance.i('Decoding slatepack'); + + // Use wallet-aware decode if wallet is open (handles encrypted slatepacks) + final handle = walletId != null ? _walletHandles[walletId] : null; + final result = + (handle != null) + ? await mwc.Libmwc.decodeSlatepackWithWallet( + wallet: handle, + slatepack: slatepack, + ) + : await mwc.Libmwc.decodeSlatepack(slatepack: slatepack); + + Logging.instance.i('Slatepack decoded successfully'); + return SlatepackDecodeResult( + success: true, + slateJson: result.slateJson, + wasEncrypted: result.wasEncrypted, + senderAddress: result.senderAddress, + recipientAddress: result.recipientAddress, + ); + } catch (e, s) { + Logging.instance.e('Failed to decode slatepack: $e\n$s'); + return SlatepackDecodeResult( + success: false, + error: 'Failed to decode slatepack: $e', + ); + } + } + + /// Receive a slatepack and return response slatepack. + static Future receiveSlatepack({ + required String walletId, + required String slatepack, + }) async { + try { + final handle = _walletHandles[walletId]; + if (handle == null) { + return ReceiveResult( + success: false, + error: 'Wallet not open: $walletId', + ); + } + + Logging.instance.i('Receiving slatepack for wallet: $walletId'); + + // Decode to get slate JSON and sender address. + final decoded = await decodeSlatepack( + slatepack: slatepack, + walletId: walletId, + ); + if (!decoded.success || decoded.slateJson == null) { + return ReceiveResult( + success: false, + error: decoded.error ?? 'Failed to decode slatepack', + ); + } + + // Receive and get updated slate JSON. + final received = await mwc.Libmwc.txReceiveDetailed( + wallet: handle, + slateJson: decoded.slateJson!, + ); + + // Encode response slatepack back to sender. + final encoded = await mwc.Libmwc.encodeSlatepack( + slateJson: received.slateJson, + recipientAddress: decoded.senderAddress, + encrypt: decoded.senderAddress != null, + wallet: handle, + ); + + Logging.instance.i('Slatepack received successfully'); + return ReceiveResult( + success: true, + slateId: received.slateId, + commitId: received.commitId, + responseSlatepack: encoded.slatepack, + wasEncrypted: encoded.wasEncrypted, + recipientAddress: decoded.senderAddress, + ); + } catch (e, s) { + Logging.instance.e('Failed to receive slatepack: $e\n$s'); + return ReceiveResult( + success: false, + error: 'Failed to receive slatepack: $e', + ); + } + } + + /// Finalize a slatepack (sender step 3). + static Future finalizeSlatepack({ + required String walletId, + required String slatepack, + }) async { + try { + final handle = _walletHandles[walletId]; + if (handle == null) { + return FinalizeResult( + success: false, + error: 'Wallet not open: $walletId', + ); + } + + Logging.instance.i('Finalizing slatepack for wallet: $walletId'); + + // Decode to get slate JSON. + final decoded = await decodeSlatepack( + slatepack: slatepack, + walletId: walletId, + ); + if (!decoded.success || decoded.slateJson == null) { + return FinalizeResult( + success: false, + error: decoded.error ?? 'Failed to decode slatepack', + ); + } + + // Finalize transaction. + final finalized = await mwc.Libmwc.txFinalize( + wallet: handle, + slateJson: decoded.slateJson!, + ); + + Logging.instance.i('Slatepack finalized successfully'); + return FinalizeResult( + success: true, + slateId: finalized.slateId, + commitId: finalized.commitId, + ); + } catch (e, s) { + Logging.instance.e('Failed to finalize slatepack: $e\n$s'); + return FinalizeResult( + success: false, + error: 'Failed to finalize slatepack: $e', + ); + } + } + + /// Start MWCMQS listener. + static Future startMwcqsListener({ + required String walletId, + MwcMqsConfigModel? config, + }) async { + try { + final handle = _walletHandles[walletId]; + if (handle == null) { + throw Exception('Wallet not open: $walletId'); + } + + Logging.instance.i('Starting MWCMQS listener for wallet: $walletId'); + + final mwcmqsConfig = + config ?? + MwcMqsConfigModel.fromServer(DefaultMwcMqs.defaultMwcMqsServer); + + mwc.Libmwc.startMwcMqsListener( + wallet: handle, + mwcmqsConfig: mwcmqsConfig.toString(), + ); + + Logging.instance.i('MWCMQS listener started successfully'); + } catch (e, s) { + Logging.instance.e('Failed to start MWCMQS listener: $e\n$s'); + rethrow; + } + } + + /// Stop MWCMQS listener. + static Future stopMwcqsListener() async { + try { + Logging.instance.i('Stopping MWCMQS listener'); + + mwc.Libmwc.stopMwcMqsListener(); + + Logging.instance.i('MWCMQS listener stopped successfully'); + } catch (e, s) { + Logging.instance.e('Failed to stop MWCMQS listener: $e\n$s'); + } + } + + /// Get MWCMQS address for wallet. + static Future getMwcqsAddress({ + required String walletId, + int index = 0, + }) async { + try { + final handle = _walletHandles[walletId]; + if (handle == null) { + throw Exception('Wallet not open: $walletId'); + } + + final address = await mwc.Libmwc.getAddressInfo( + wallet: handle, + index: index, + ); + + return address; + } catch (e, s) { + Logging.instance.e('Failed to get MWCMQS address: $e\n$s'); + rethrow; + } + } + + /// Validate MWC address. + static bool validateAddress(String address) { + try { + return mwc.Libmwc.validateSendAddress(address: address); + } catch (e, s) { + Logging.instance.e('Failed to validate address: $e\n$s'); + return false; + } + } + + /// Generate mnemonic. + static String generateMnemonic() { + try { + return mwc.Libmwc.getMnemonic(); + } catch (e, s) { + Logging.instance.e('Failed to generate mnemonic: $e\n$s'); + rethrow; + } + } + + /// Check if wallet is open. + static bool isWalletOpen(String walletId) { + return _walletHandles.containsKey(walletId); + } + + /// Close wallet. + static void closeWallet(String walletId) { + _walletHandles.remove(walletId); + } + + /// Get wallet configuration. + static Future _getWalletConfig(String walletId) async { + final walletDir = await _getWalletDirectory(walletId); + + final config = { + 'wallet_dir': walletDir, + 'check_node_api_http_addr': 'https://mwc713.mwc.mw:443', + 'chain': 'mainnet', + 'account': 'default', + }; + + return jsonEncode(config); + } + + /// Get wallet directory path. + static Future _getWalletDirectory(String walletId) async { + final Directory appDir = await StackFileSystem.applicationRootDirectory(); + final path = "${appDir.path}/mimblewimblecoin"; + final String name = walletId.trim(); + return '$path/$name'; + } +} + +/// Result classes for MWC wallet operations. + +class MwcWalletResult { + final bool success; + final String? walletId; + final String? error; + final Map? data; + + MwcWalletResult({ + required this.success, + this.walletId, + this.error, + this.data, + }); +} + +class SlatepackResult { + final bool success; + final String? error; + final String? slatepack; + final String? slateJson; + final bool? wasEncrypted; + final String? recipientAddress; + + SlatepackResult({ + required this.success, + this.error, + this.slatepack, + this.slateJson, + this.wasEncrypted, + this.recipientAddress, + }); +} + +class SlatepackDecodeResult { + final bool success; + final String? error; + final String? slateJson; + final bool? wasEncrypted; + final String? senderAddress; + final String? recipientAddress; + + SlatepackDecodeResult({ + required this.success, + this.error, + this.slateJson, + this.wasEncrypted, + this.senderAddress, + this.recipientAddress, + }); +} + +class ReceiveResult { + final bool success; + final String? error; + final String? slateId; + final String? commitId; + final String? responseSlatepack; + final bool? wasEncrypted; + final String? recipientAddress; + + ReceiveResult({ + required this.success, + this.error, + this.slateId, + this.commitId, + this.responseSlatepack, + this.wasEncrypted, + this.recipientAddress, + }); +} + +class FinalizeResult { + final bool success; + final String? error; + final String? slateId; + final String? commitId; + + FinalizeResult({ + required this.success, + this.error, + this.slateId, + this.commitId, + }); +} diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index cc6d397c39..1e9e04691d 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -1,6 +1,7 @@ import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; import '../../../models/isar/models/blockchain_data/address.dart'; +import '../../../models/mwc_transaction_method.dart'; import '../../../models/node_model.dart'; import '../../../utilities/default_nodes.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; @@ -52,7 +53,13 @@ class Mimblewimblecoin extends Bip39Currency { @override bool validateAddress(String address) { - Uri? uri = Uri.tryParse(address); + // Check if it's a slatepack first. + if (isSlatepack(address)) { + return true; + } + + // Check URI schemes (HTTP, HTTPS, MWCMQS). + final Uri? uri = Uri.tryParse(address); if (uri != null && (uri.scheme == "http" || uri.scheme == "https" || @@ -61,9 +68,89 @@ class Mimblewimblecoin extends Bip39Currency { !uri.host.endsWith(".onion")) { return true; } + + // Use libmwc for other address validation. return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } + /// Check if data is a slatepack. + bool isSlatepack(String data) { + return data.trim().startsWith('BEGINSLATE') && + (data.trim().endsWith('ENDSLATEPACK') || + data.trim().endsWith('ENDSLATEPACK.') || + data.trim().endsWith('ENDSLATE_BIN') || + data.trim().endsWith('ENDSLATE_BIN.')); + } + + /// Check if address is MWCMQS format. + bool isMwcmqsAddress(String address) { + return address.startsWith('mwcmqs://'); + } + + /// Check if address is HTTP format. + bool isHttpAddress(String address) { + return address.startsWith('http://') || address.startsWith('https://'); + } + + /// Detect transaction type based on address/data format. + TransactionMethod getTransactionMethod(String addressOrData) { + if (isSlatepack(addressOrData)) { + return TransactionMethod.slatepack; + } else if (isMwcmqsAddress(addressOrData)) { + return TransactionMethod.mwcmqs; + } else if (isHttpAddress(addressOrData)) { + return TransactionMethod.http; + } else { + return TransactionMethod.unknown; + } + } + + /// Validate slatepack format. + bool validateSlatepack(String slatepack) { + try { + final trimmed = slatepack.trim(); + if (!isSlatepack(trimmed)) { + return false; + } + + // Basic structure validation. + final lines = trimmed.split('\n'); + if (lines.length < 3) { + return false; + } + + // Should have header, content, and footer. + return lines.first.startsWith('BEGINSLATEPACK.') && + lines.last.endsWith('.ENDSLATEPACK') && + lines.length > 2; + } catch (e) { + return false; + } + } + + /// Get expected slatepack type from content (S1, S2, S3). + String? getSlatepackType(String slatepack) { + if (!validateSlatepack(slatepack)) { + return null; + } + + try { + // This is a simplified approach - in reality you'd need to decode + // the slatepack content to determine the exact type. + final lines = slatepack.trim().split('\n'); + final header = lines.first; + + // Basic heuristic based on header format. + if (header.contains('BEGINSLATEPACK.')) { + return 'unknown'; // Would need proper decoding to determine S1/S2/S3. + } + + return null; + } catch (e) { + return null; + } + } + @override NodeModel defaultNode({required bool isPrimary}) { switch (network) { @@ -130,7 +217,6 @@ class Mimblewimblecoin extends Bip39Currency { @override AddressType? getAddressType(String address) { - // TODO: implement getAddressType. - throw UnimplementedError(); + return AddressType.mimbleWimble; } } diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 1a349a777b..e9a8330169 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -10,7 +10,6 @@ import 'package:flutter_libmwc/models/transaction.dart' import 'package:isar/isar.dart'; import 'package:mutex/mutex.dart'; import 'package:stack_wallet_backup/generate_password.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; import '../../../models/balance.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; @@ -27,6 +26,7 @@ import '../../../services/event_bus/events/global/node_connection_status_changed import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; +import '../../../services/mwc_wallet_service.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/default_mwcmqs.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; @@ -45,7 +45,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { final syncMutex = Mutex(); NodeModel? _mimblewimblecoinNode; Timer? timer; - bool _logsInitialized = false; double highestPercent = 0; Future get getSyncPercent async { @@ -128,6 +127,373 @@ class MimblewimblecoinWallet extends Bip39Wallet { return _mwcMqsConfig; } + // ================= Slatepack Operations =================================== + + /// Create a slatepack for sending MWC. + Future createSlatepack({ + required Amount amount, + String? recipientAddress, + String? message, + bool encrypt = false, + int? minimumConfirmations, + }) async { + try { + await MwcWalletService.initialize(); + + // Ensure wallet is open in service. + if (!MwcWalletService.isWalletOpen(walletId)) { + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + + final openResult = await MwcWalletService.openWallet( + walletId: walletId, + password: password, + ); + + if (!openResult.success) { + throw Exception(openResult.error ?? 'Failed to open wallet'); + } + } + + return await MwcWalletService.createSlatepack( + walletId: walletId, + amount: amount, + recipientAddress: recipientAddress, + message: message, + encrypt: encrypt, + minimumConfirmations: + minimumConfirmations ?? cryptoCurrency.minConfirms, + ); + } catch (e, s) { + Logging.instance.e('Failed to create slatepack: $e\n$s'); + return SlatepackResult(success: false, error: e.toString()); + } + } + + /// Decode a slatepack. + Future decodeSlatepack(String slatepack) async { + try { + await MwcWalletService.initialize(); + return await MwcWalletService.decodeSlatepack( + slatepack: slatepack, + walletId: MwcWalletService.isWalletOpen(walletId) ? walletId : null, + ); + } catch (e, s) { + Logging.instance.e('Failed to decode slatepack: $e\n$s'); + return SlatepackDecodeResult(success: false, error: e.toString()); + } + } + + /// Receive a slatepack and return response slatepack. + Future receiveSlatepack(String slatepack) async { + try { + await MwcWalletService.initialize(); + + // Ensure wallet is open in service. + if (!MwcWalletService.isWalletOpen(walletId)) { + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + + final openResult = await MwcWalletService.openWallet( + walletId: walletId, + password: password, + ); + + if (!openResult.success) { + throw Exception(openResult.error ?? 'Failed to open wallet'); + } + } + + return await MwcWalletService.receiveSlatepack( + walletId: walletId, + slatepack: slatepack, + ); + } catch (e, s) { + Logging.instance.e('Failed to receive slatepack: $e\n$s'); + return ReceiveResult(success: false, error: e.toString()); + } + } + + /// Finalize a slatepack (sender step 3). + Future finalizeSlatepack(String slatepack) async { + try { + await MwcWalletService.initialize(); + + // Ensure wallet is open in service. + if (!MwcWalletService.isWalletOpen(walletId)) { + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + + final openResult = await MwcWalletService.openWallet( + walletId: walletId, + password: password, + ); + + if (!openResult.success) { + throw Exception(openResult.error ?? 'Failed to open wallet'); + } + } + + return await MwcWalletService.finalizeSlatepack( + walletId: walletId, + slatepack: slatepack, + ); + } catch (e, s) { + Logging.instance.e('Failed to finalize slatepack: $e\n$s'); + return FinalizeResult(success: false, error: e.toString()); + } + } + + /// Start MWCMQS listener for automatic transaction processing. + Future startSlatepackListener() async { + try { + await MwcWalletService.initialize(); + + if (!MwcWalletService.isWalletOpen(walletId)) { + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); + if (password == null) { + throw Exception('Wallet password not found'); + } + + final openResult = await MwcWalletService.openWallet( + walletId: walletId, + password: password, + ); + + if (!openResult.success) { + throw Exception(openResult.error ?? 'Failed to open wallet'); + } + } + + final mwcmqsConfig = await getMwcMqsConfig(); + await MwcWalletService.startMwcqsListener( + walletId: walletId, + config: mwcmqsConfig, + ); + } catch (e, s) { + Logging.instance.e('Failed to start slatepack listener: $e\n$s'); + rethrow; + } + } + + /// Stop MWCMQS listener. + Future stopSlatepackListener() async { + try { + await MwcWalletService.stopMwcqsListener(); + } catch (e, s) { + Logging.instance.e('Failed to stop slatepack listener: $e\n$s'); + } + } + + /// Validate MWC address. + bool validateMwcAddress(String address) { + return MwcWalletService.validateAddress(address); + } + + /// Detect if an address is a slatepack. + bool isSlatepack(String data) { + return data.trim().startsWith('BEGINSLATE') && + (data.trim().endsWith('ENDSLATEPACK') || + data.trim().endsWith('ENDSLATEPACK.') || + data.trim().endsWith('ENDSLATE_BIN') || + data.trim().endsWith('ENDSLATE_BIN.')); + } + + /// Detect if an address is MWCMQS format. + bool isMwcmqsAddress(String address) { + return address.startsWith('mwcmqs://'); + } + + /// Detect if an address is HTTP format. + bool isHttpAddress(String address) { + return address.startsWith('http://') || address.startsWith('https://'); + } + + /// Analyze a slatepack and determine transaction type and metadata. + /// Returns a record with transaction type and slate information. + Future< + ({ + String type, + String status, + String? amount, + bool wasEncrypted, + String? senderAddress, + String? recipientAddress, + String slateId, + }) + > + analyzeSlatepack(String slatepack) async { + try { + // Get wallet handle if available + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); + + // Decode the slatepack + final decoded = + wallet != null + ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( + wallet: wallet, + slatepack: slatepack, + ) + : await mimblewimblecoin.Libmwc.decodeSlatepack( + slatepack: slatepack, + ); + + // Parse the slate JSON to extract metadata + final slateData = jsonDecode(decoded.slateJson); + final String slateId = "${slateData['id'] ?? ''}"; + final String? amountStr = slateData['amount']?.toString(); + + print(121212); + print(slateData); + + // Determine slate status from the slate structure + String status = 'Unknown'; + String type = 'Unknown'; + + // Check participant data to determine slate status + final List? participants = + slateData['participant_data'] as List?; + if (participants != null && participants.isNotEmpty) { + // Count how many participants have signatures + int signedParticipants = 0; + for (final participant in participants) { + if (participant['part_sig'] != null) { + signedParticipants++; + } + } + + // Determine status based on signatures and participant count + if (signedParticipants == 0) { + status = 'S1'; + type = 'Outgoing'; // Initial send slate - this is outgoing + } else if (signedParticipants == 1) { + status = 'S2'; + type = 'Incoming'; // Response slate - this means we're receiving + } else if (signedParticipants >= participants.length) { + status = 'S3'; + type = 'Outgoing'; // Finalized slate - completed outgoing transaction + } + } + + // Fallback: check for explicit 'sta' field (some slates may have this) + if (status == 'Unknown' && slateData['sta'] != null) { + status = "${slateData['sta']}"; + if (status == 'S1') { + type = 'Outgoing'; + } else if (status == 'S2') { + type = 'Incoming'; + } else if (status == 'S3') { + type = 'Outgoing'; + } + } + + return ( + type: type, + status: status, + amount: amountStr, + wasEncrypted: decoded.wasEncrypted, + senderAddress: decoded.senderAddress, + recipientAddress: decoded.recipientAddress, + slateId: slateId, + ); + } catch (e) { + // If we can't decode it, return unknown + return ( + type: 'Unknown', + status: 'Unknown', + amount: null, + wasEncrypted: false, + senderAddress: null, + recipientAddress: null, + slateId: '', + ); + } + } + + /// Improved transaction type detection for slatepacks. + /// This replaces "Unknown" types with better determined types based on slate analysis. + Future getSlatepackTransactionType(String address) async { + try { + // Check if the address is actually a slatepack + if (!isSlatepack(address)) { + return 'Unknown'; + } + + // Analyze the slatepack to determine the actual transaction type + final analysis = await analyzeSlatepack(address); + + // Map slate status to meaningful transaction types + switch (analysis.status) { + case 'S1': + return 'Outgoing'; // Initial send slate - this is outgoing + case 'S2': + return 'Incoming'; // Response slate - this means we're receiving + case 'S3': + return 'Outgoing'; // Finalized slate - completed outgoing transaction + default: + return analysis.type; // Fall back to our basic analysis + } + } catch (e) { + // If analysis fails, return Unknown + return 'Unknown'; + } + } + + /// Enhanced transaction type detection that can analyze slatepack transactions. + /// Use this method to improve "Unknown" transaction types after they're loaded. + Future getEnhancedTransactionType( + TransactionV2 transaction, + ) async { + try { + // If transaction is already properly typed, return as-is + if (transaction.type != TransactionType.unknown) { + return transaction.type; + } + + // Check if this is a MWC transaction with slatepack data + if (transaction.isMimblewimblecoinTransaction) { + // Try to analyze any slatepack addresses in the transaction + for (final output in transaction.outputs) { + for (final address in output.addresses) { + if (isSlatepack(address)) { + final slatepackType = await getSlatepackTransactionType(address); + switch (slatepackType) { + case 'Outgoing': + return TransactionType.outgoing; + case 'Incoming': + return TransactionType.incoming; + default: + continue; + } + } + } + } + } + + // If we can't determine a better type, return unknown + return TransactionType.unknown; + } catch (e) { + Logging.instance.w("Failed to enhance transaction type: $e"); + return transaction.type; + } + } + // ================= Private ================================================= Future _getConfig() async { @@ -225,30 +591,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } - Future _testMwcmqsServer(MwcMqsConfigModel mwcmqsConfig) async { - final host = mwcmqsConfig.host; - final port = mwcmqsConfig.port ?? 443; - WebSocketChannel? channel; - try { - final uri = Uri.parse('wss://$host:$port'); - - channel = WebSocketChannel.connect(uri); - - await channel.ready; - - final response = await channel.stream.first.timeout( - const Duration(seconds: 2), - ); - - return response is String && response.contains("Challenge"); - } catch (_) { - Logging.instance.i("_testMwcmqsConnection failed on \"$host:$port\""); - return false; - } finally { - await channel?.sink.close(); - } - } - Future _putSendToAddresses( ({String slateId, String commitId}) slateData, Map txAddressInfo, @@ -444,6 +786,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { + // Initialize MWC wallet service. + await MwcWalletService.initialize(); + if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read( key: "${walletId}_wallet", @@ -618,7 +963,19 @@ class MimblewimblecoinWallet extends Bip39Wallet { } TxRecipient recipient = txData.recipients!.first; + final String receiverAddress = recipient.address; + + // Check if this is a slatepack being provided instead of an address. + if (isSlatepack(receiverAddress)) { + // For slatepack input, we need different handling + // This would be used for receiving/finalizing slatepacks. + return txData.copyWith( + fee: Amount.zeroWith(fractionDigits: cryptoCurrency.fractionDigits), + otherData: jsonEncode({'isSlatepackInput': true}), + ); + } + // For regular address-based sends, calculate fee final int realFee = await _nativeFee(recipient.amount.raw.toInt()); final feeAmount = Amount( rawValue: BigInt.from(realFee), @@ -640,7 +997,21 @@ class MimblewimblecoinWallet extends Bip39Wallet { ); } - return txData.copyWith(recipients: [recipient], fee: feeAmount); + // Determine transaction method based on address format. + String txMethod = 'unknown'; + if (isMwcmqsAddress(receiverAddress)) { + txMethod = 'mwcmqs'; + } else if (isHttpAddress(receiverAddress)) { + txMethod = 'http'; + } else if (validateMwcAddress(receiverAddress)) { + txMethod = 'slatepack'; // Manual slatepack exchange. + } + + return txData.copyWith( + recipients: [recipient], + fee: feeAmount, + otherData: jsonEncode({'transactionMethod': txMethod}), + ); } catch (e, s) { Logging.instance.e("Mimblewimblecoin prepareSend: $e\n$s"); rethrow; @@ -966,7 +1337,30 @@ class MimblewimblecoinWallet extends Bip39Wallet { walletOwns: true, ); } else { - txType = TransactionType.outgoing; + // For outgoing transactions, check if we have a slatepack address to analyze + TransactionType determinedType = TransactionType.outgoing; + + // Try to get better type determination for slatepack transactions + if (addressTo != null) { + try { + final slatepackType = await getSlatepackTransactionType( + addressTo, + ); + if (slatepackType == 'Incoming') { + determinedType = TransactionType.incoming; + } else if (slatepackType == 'Outgoing') { + determinedType = TransactionType.outgoing; + } + // If slatepackType is 'Unknown', we keep the original outgoing type + } catch (e) { + // If analysis fails, keep original type determination + Logging.instance.w( + "Failed to analyze slatepack for better type detection: $e", + ); + } + } + + txType = determinedType; } outputs.add(output); From 0f367d2c3cdba0aed11097d885735f686532546f Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:09:03 -0500 Subject: [PATCH 31/91] fix(mwc): fix logic error and remove slatepack validation hack TODO: make sure that we can enter the slatepack flow without an address /etc --- .../wallet_view/sub_widgets/desktop_send.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 94a2043ed7..4537d32856 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -571,18 +571,10 @@ class _DesktopSendState extends ConsumerState { txData: TxData( recipients: [ TxRecipient( - address: - coin is Mimblewimblecoin && - _selectedMethodMwc == "Slatepack" - ? "BEGINSLATEPACK. .ENDSLATEPACK" // TODO: This is a hack so that the address validation does not fail. Need to handle this better. - : _address!, + address: _address!, amount: amount, isChange: false, - addressType: - coin is Mimblewimblecoin && - _selectedMethodMwc == "Slatepack" - ? AddressType.mimbleWimble - : wallet.cryptoCurrency.getAddressType(_address!)!, + addressType: wallet.cryptoCurrency.getAddressType(_address!)!, ), ], memo: memo, From e47c838b4be8184959055a9de4c4a5b2c3f22115 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:13:36 -0500 Subject: [PATCH 32/91] chore: fix typo, replace debug print w log, add logging --- lib/services/mwc_wallet_service.dart | 10 +++++++--- lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart | 7 +++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/services/mwc_wallet_service.dart b/lib/services/mwc_wallet_service.dart index c44f84380a..e9fa146db8 100644 --- a/lib/services/mwc_wallet_service.dart +++ b/lib/services/mwc_wallet_service.dart @@ -348,7 +348,7 @@ class MwcWalletService { } /// Start MWCMQS listener. - static Future startMwcqsListener({ + static Future startMwcmqsListener({ required String walletId, MwcMqsConfigModel? config, }) async { @@ -377,7 +377,7 @@ class MwcWalletService { } /// Stop MWCMQS listener. - static Future stopMwcqsListener() async { + static Future stopMwcmqsListener() async { try { Logging.instance.i('Stopping MWCMQS listener'); @@ -390,7 +390,7 @@ class MwcWalletService { } /// Get MWCMQS address for wallet. - static Future getMwcqsAddress({ + static Future getMwcmqsAddress({ required String walletId, int index = 0, }) async { @@ -405,6 +405,10 @@ class MwcWalletService { index: index, ); + if (address.isEmpty) { + throw Exception('Failed to generate MWCMQS address'); + } + return address; } catch (e, s) { Logging.instance.e('Failed to get MWCMQS address: $e\n$s'); diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index e9a8330169..2341c9152c 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -280,7 +280,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { } final mwcmqsConfig = await getMwcMqsConfig(); - await MwcWalletService.startMwcqsListener( + await MwcWalletService.startMwcmqsListener( walletId: walletId, config: mwcmqsConfig, ); @@ -293,7 +293,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Stop MWCMQS listener. Future stopSlatepackListener() async { try { - await MwcWalletService.stopMwcqsListener(); + await MwcWalletService.stopMwcmqsListener(); } catch (e, s) { Logging.instance.e('Failed to stop slatepack listener: $e\n$s'); } @@ -359,8 +359,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { final String slateId = "${slateData['id'] ?? ''}"; final String? amountStr = slateData['amount']?.toString(); - print(121212); - print(slateData); + Logging.instance.d('Analyzed slatepack with ID: $slateId'); // Determine slate status from the slate structure String status = 'Unknown'; From 97bca34832a20c69b3d26b42056530242b6d76ac Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:14:12 -0500 Subject: [PATCH 33/91] chore: user friendly error msg --- lib/pages/send_view/confirm_transaction_view.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index d30e62fef7..93a6f052ad 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -162,6 +162,13 @@ class _ConfirmTransactionViewState Logging.instance.e('Failed to create MWC slatepack: $e\n$s'); if (context.mounted) { + // Show user-friendly error message. + final errorMessage = e.toString().contains('insufficient funds') + ? 'Insufficient funds for this transaction' + : e.toString().contains('wallet not open') + ? 'Wallet not accessible. Please restart the app.' + : 'Failed to create slatepack: ${e.toString()}'; + await showDialog( context: context, builder: From 4af4522e36a3d03451d80edda43c88b5c3479b0a Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:17:01 -0500 Subject: [PATCH 34/91] feat: MWCMQS config needs more testing --- .../wallet/impl/mimblewimblecoin_wallet.dart | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 2341c9152c..f31d8a65a5 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -76,10 +76,20 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_mwcmqsConfig', value: stringConfig, ); - // TODO: refresh anything that needs to be refreshed/updated due to mwcmqs info changed + + // Restart MWCMQS listener with new configuration if wallet is active. + try { + if (MwcWalletService.isWalletOpen(walletId)) { + await stopSlatepackListener(); + await startSlatepackListener(); + Logging.instance.i('Restarted MWCMQS listener with new config: $host:$port'); + } + } catch (e, s) { + Logging.instance.e('Failed to restart MWCMQS listener after config update: $e\n$s'); + } } - /// returns an empty String on success, error message on failure + /// Returns an empty String on success, error message on failure. Future cancelPendingTransactionAndPost(String txSlateId) async { try { final String wallet = @@ -98,33 +108,30 @@ class MimblewimblecoinWallet extends Bip39Wallet { } Future getMwcMqsConfig() async { - final MwcMqsConfigModel _mwcMqsConfig = MwcMqsConfigModel.fromServer( - DefaultMwcMqs.defaultMwcMqsServer, + // Check if there's a custom MWCMQS config stored. + final customConfigJson = await secureStorageInterface.read( + key: '${walletId}_mwcmqsConfig', ); - //Get the default mwcmqs server and check if it's conected - // bool ismwcmqsConnected = await _testmwcmqsServer( - // Defaultmwcmqses.defaultmwcmqsServer.host, Defaultmwcmqses.defaultmwcmqsServer.port ?? 443); - - // if (ismwcmqsConnected) { - //Use default server for as mwcmqs config - - // } - // else { - // //Use Europe config - // _mwcmqsConfig = mwcmqsConfigModel.fromServer(Defaultmwcmqses.europe); - // } - // // example of selecting another random server from the default list - // // alternative servers: copy list of all default EB servers but remove the default default - // // List alternativeServers = Defaultmwcmqses.all; - // // alternativeServers.removeWhere((opt) => opt.name == Defaultmwcmqses.defaultmwcmqsServer.name); - // // alternativeServers.shuffle(); // randomize which server is used - // // _mwcmqsConfig = mwcmqsConfigModel.fromServer(alternativeServers.first); - // - // // TODO test this connection before returning it - // } - - return _mwcMqsConfig; + if (customConfigJson != null) { + try { + final customConfig = jsonDecode(customConfigJson) as Map; + final host = customConfig['mwcmqs_domain'] as String?; + final port = customConfig['mwcmqs_port'] as int?; + + if (host != null && port != null) { + return MwcMqsConfigModel( + host: host, + port: port, + ); + } + } catch (e) { + Logging.instance.w('Failed to parse custom MWCMQS config: $e'); + } + } + + // Fall back to default server. + return MwcMqsConfigModel.fromServer(DefaultMwcMqs.defaultMwcMqsServer); } // ================= Slatepack Operations =================================== From 62e159abd77f9b9353147c30bbaee207f4b75f96 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:26:33 -0500 Subject: [PATCH 35/91] fix: fix inverted mwc txs method toggle logic, allow address w slatepacks --- lib/pages/send_view/send_view.dart | 337 +++++++++--------- .../desktop_mwc_txs_method_toggle.dart | 7 +- .../sub_widgets/desktop_receive.dart | 2 +- .../wallet_view/sub_widgets/desktop_send.dart | 29 +- 4 files changed, 185 insertions(+), 190 deletions(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 795befa109..e4a206ce96 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1534,7 +1534,7 @@ class _SendViewState extends ConsumerState { ), const SizedBox(height: 16), - // MWC Transaction Method Selector (moved before "Send to" field). + // MWC Transaction Method Selector. if (coin is Mimblewimblecoin) ...[ MwcTransactionMethodSelector( onMethodSelected: (method) { @@ -1554,201 +1554,194 @@ class _SendViewState extends ConsumerState { const SizedBox(height: 16), ], - // Hide "Send to" field for MWC slatepack transactions. - if (!(coin is Mimblewimblecoin && - _selectedTransactionMethod == - TransactionMethod.slatepack)) ...[ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - isPaynymSend - ? "Send to PayNym address" - : "Send to", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - // if (coin is Monero) - // CustomTextButton( - // text: "Use OpenAlias", - // onTap: () async { - // await showModalBottomSheet( - // context: context, - // builder: (context) => - // OpenAliasBottomSheet( - // onSelected: (address) { - // sendToController.text = address; - // }, - // ), - // ); - // }, - // ), - ], + // "Send to" field - optional for MWC slatepack transactions. + // Always show the field, but make it optional for slatepack. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isPaynymSend + ? "Send to PayNym address" + : "Send to", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + // if (coin is Monero) + // CustomTextButton( + // text: "Use OpenAlias", + // onTap: () async { + // await showModalBottomSheet( + // context: context, + // builder: (context) => + // OpenAliasBottomSheet( + // onSelected: (address) { + // sendToController.text = address; + // }, + // ), + // ); + // }, + // ), + ], + ), + const SizedBox(height: 8), + if (isPaynymSend) + TextField( + key: const Key("sendViewPaynymAddressFieldKey"), + controller: sendToController, + enabled: false, + readOnly: true, + style: STextStyles.fieldLabel(context), ), - const SizedBox(height: 8), - if (isPaynymSend) - TextField( - key: const Key( - "sendViewPaynymAddressFieldKey", - ), - controller: sendToController, - enabled: false, - readOnly: true, - style: STextStyles.fieldLabel(context), + if (!isPaynymSend) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - if (!isPaynymSend) - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + child: TextField( + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - child: TextField( - key: const Key("sendViewAddressFieldKey"), - controller: sendToController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - onChanged: (newValue) async { - final trimmed = newValue.trim(); - - if ((trimmed.length - - (_address?.length ?? 0)) - .abs() > - 1) { - final parsed = - AddressUtils.parsePaymentUri( - trimmed, - logging: Logging.instance, - ); - if (parsed != null) { - _applyUri(parsed); - } else { - await _checkSparkNameAndOrSetAddress( - newValue, + onChanged: (newValue) async { + final trimmed = newValue.trim(); + + if ((trimmed.length - + (_address?.length ?? 0)) + .abs() > + 1) { + final parsed = + AddressUtils.parsePaymentUri( + trimmed, + logging: Logging.instance, ); - } + if (parsed != null) { + _applyUri(parsed); } else { await _checkSparkNameAndOrSetAddress( newValue, - setController: false, ); } - - _setValidAddressProviders(_address); - - setState(() { - _addressToggleFlag = - newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter ${coin.ticker} address", - _addressFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: - sendToController.text.isEmpty - ? const EdgeInsets.only( - right: 8, - ) - : const EdgeInsets.only( - right: 0, - ), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Address Field Input.", - key: const Key( - "sendViewClearAddressFieldButtonKey", - ), - onTap: () { - sendToController.text = - ""; - _address = ""; - _setValidAddressProviders( - _address, - ); - setState(() { - _addressToggleFlag = - false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Address Field Input.", - key: const Key( - "sendViewPasteAddressFieldButtonKey", - ), - onTap: _pasteAddress, - child: - sendToController - .text - .isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( + } else { + await _checkSparkNameAndOrSetAddress( + newValue, + setController: false, + ); + } + + _setValidAddressProviders(_address); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + coin is Mimblewimblecoin && + _selectedTransactionMethod == + TransactionMethod + .slatepack || + _selectedTransactionMethod == null + ? "Enter ${coin.ticker} address (optional)" + : "Enter ${coin.ticker} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: + sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( semanticsLabel: - "Address Book Button. Opens Address Book For Address Field.", + "Clear Button. Clears The Address Field Input.", key: const Key( - "sendViewAddressBookButtonKey", + "sendViewClearAddressFieldButtonKey", ), onTap: () { - Navigator.of( - context, - ).pushNamed( - AddressBookView.routeName, - arguments: widget.coin, + sendToController.text = ""; + _address = ""; + _setValidAddressProviders( + _address, ); + setState(() { + _addressToggleFlag = + false; + }); }, - child: - const AddressBookIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( + child: const XIcon(), + ) + : TextFieldIconButton( semanticsLabel: - "Scan QR Button. Opens Camera For Scanning QR Code.", + "Paste Button. Pastes From Clipboard To Address Field Input.", key: const Key( - "sendViewScanQrButtonKey", + "sendViewPasteAddressFieldButtonKey", ), - onTap: _scanQr, - child: const QrCodeIcon(), + onTap: _pasteAddress, + child: + sendToController + .text + .isEmpty + ? const ClipboardIcon() + : const XIcon(), ), - ], - ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Address Book Button. Opens Address Book For Address Field.", + key: const Key( + "sendViewAddressBookButtonKey", + ), + onTap: () { + Navigator.of( + context, + ).pushNamed( + AddressBookView.routeName, + arguments: widget.coin, + ); + }, + child: const AddressBookIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key( + "sendViewScanQrButtonKey", + ), + onTap: _scanQr, + child: const QrCodeIcon(), + ), + ], ), ), ), ), ), - ], // End of conditional hiding "Send to" field for MWC slatepack + ), const SizedBox(height: 10), if (isStellar || ref.watch(pValidSparkSendToAddress)) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart index 918e0abaa8..3817d139f6 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart @@ -30,10 +30,13 @@ class MwcTxsMethodToggle extends ConsumerWidget { return Toggle( onValueChanged: (value) { + // Align visual labels with callback semantics: + // When the toggle shows "Slatepack" (on), emit slatepack. + // When it shows "Automatic" (off), emit automatic. if (value) { - onChanged?.call(TxsMethodMwcType.automatic); - } else { onChanged?.call(TxsMethodMwcType.slatepack); + } else { + onChanged?.call(TxsMethodMwcType.automatic); } }, isOn: false, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 260637369f..359d25ff60 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -496,7 +496,7 @@ class _DesktopReceiveState extends ConsumerState { onChanged: (TxsMethodMwcType type) { setState(() { _selectedMethodMwc = - type == TxsMethodMwcType.automatic + type == TxsMethodMwcType.slatepack ? 'Slatepack' : 'Automatic'; }); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 4537d32856..694aae1861 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1078,10 +1078,10 @@ class _DesktopSendState extends ConsumerState { isMimblewimblecoin = coin is Mimblewimblecoin; if (isMimblewimblecoin) { _selectedMethodMwc = "Slatepack"; - // Initialize provider to MWCMQS (automatic method). + // Initialize provider to match the UI (slatepack method). WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(pSelectedMwcTransactionMethod.notifier).state = - TransactionMethod.mwcmqs; + TransactionMethod.slatepack; }); } @@ -1255,14 +1255,14 @@ class _DesktopSendState extends ConsumerState { setState(() { _selectedMethodMwc = type == TxsMethodMwcType.slatepack - ? 'Slatepack' - : 'Automatic'; + ? 'Automatic' + : 'Slatepack'; }); // Update the provider as well. ref.read(pSelectedMwcTransactionMethod.notifier).state = type == TxsMethodMwcType.slatepack - ? TransactionMethod.slatepack - : TransactionMethod.mwcmqs; + ? TransactionMethod.mwcmqs + : TransactionMethod.slatepack; }, ), ), @@ -1568,9 +1568,7 @@ class _DesktopSendState extends ConsumerState { ), ), const SizedBox(height: 20), - // Hide "Send to" field for MWC slatepack transactions. - if (!isPaynymSend && - !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) + if (!isPaynymSend) Text( "Send to", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -1581,11 +1579,8 @@ class _DesktopSendState extends ConsumerState { ), textAlign: TextAlign.left, ), - if (!isPaynymSend && - !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) - const SizedBox(height: 10), - if (!isPaynymSend && - !(isMimblewimblecoin && _selectedMethodMwc == 'Slatepack')) + if (!isPaynymSend) const SizedBox(height: 10), + if (!isPaynymSend) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1643,7 +1638,11 @@ class _DesktopSendState extends ConsumerState { height: 1.8, ), decoration: standardInputDecoration( - "Enter ${coin.ticker} address", + _selectedMethodMwc == "Slatepack" || + _selectedMethodMwc == null || + _selectedMethodMwc == "" + ? "Enter ${coin.ticker} address (optional)" + : "Enter ${coin.ticker} address", _addressFocusNode, context, desktopMed: true, From 305a257abbe0018f51b27fa13dc0f2cf3000ddd6 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:27:41 -0500 Subject: [PATCH 36/91] fix: allow onion addresses for mwc idk if there are but there should be. make it so --- lib/wallets/crypto_currency/coins/mimblewimblecoin.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 1e9e04691d..9e9f962e05 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -64,8 +64,7 @@ class Mimblewimblecoin extends Bip39Currency { (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "mwcmqs") && - uri.host.isNotEmpty && - !uri.host.endsWith(".onion")) { + uri.host.isNotEmpty) { return true; } From c891afcd7d92e9d8f53cef366f06d9ceb4a7b874 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 00:35:21 -0500 Subject: [PATCH 37/91] feat: collapse mwc slatepack service into mwc wallet needs more testing --- lib/models/mwc_slatepack_models.dart | 70 +++ .../mwc_slatepack_import_dialog.dart | 2 +- .../sub_widgets/mwc_slatepack_dialog.dart | 2 +- lib/services/mwc_wallet_service.dart | 556 ------------------ .../wallet/impl/mimblewimblecoin_wallet.dart | 204 +++---- 5 files changed, 176 insertions(+), 658 deletions(-) create mode 100644 lib/models/mwc_slatepack_models.dart delete mode 100644 lib/services/mwc_wallet_service.dart diff --git a/lib/models/mwc_slatepack_models.dart b/lib/models/mwc_slatepack_models.dart new file mode 100644 index 0000000000..f8a30fb3f5 --- /dev/null +++ b/lib/models/mwc_slatepack_models.dart @@ -0,0 +1,70 @@ +class SlatepackResult { + final bool success; + final String? error; + final String? slatepack; + final String? slateJson; + final bool? wasEncrypted; + final String? recipientAddress; + + SlatepackResult({ + required this.success, + this.error, + this.slatepack, + this.slateJson, + this.wasEncrypted, + this.recipientAddress, + }); +} + +class SlatepackDecodeResult { + final bool success; + final String? error; + final String? slateJson; + final bool? wasEncrypted; + final String? senderAddress; + final String? recipientAddress; + + SlatepackDecodeResult({ + required this.success, + this.error, + this.slateJson, + this.wasEncrypted, + this.senderAddress, + this.recipientAddress, + }); +} + +class ReceiveResult { + final bool success; + final String? error; + final String? slateId; + final String? commitId; + final String? responseSlatepack; + final bool? wasEncrypted; + final String? recipientAddress; + + ReceiveResult({ + required this.success, + this.error, + this.slateId, + this.commitId, + this.responseSlatepack, + this.wasEncrypted, + this.recipientAddress, + }); +} + +class FinalizeResult { + final bool success; + final String? error; + final String? slateId; + final String? commitId; + + FinalizeResult({ + required this.success, + this.error, + this.slateId, + this.commitId, + }); +} + diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 300e11656d..ba7cf88278 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -6,7 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../../../services/mwc_wallet_service.dart'; +import '../../../models/mwc_slatepack_models.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; diff --git a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart index d04ef92c47..1941e7483c 100644 --- a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart +++ b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart @@ -7,7 +7,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; import '../../../notifications/show_flush_bar.dart'; -import '../../../services/mwc_wallet_service.dart'; +import '../../../models/mwc_slatepack_models.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; diff --git a/lib/services/mwc_wallet_service.dart b/lib/services/mwc_wallet_service.dart deleted file mode 100644 index e9fa146db8..0000000000 --- a/lib/services/mwc_wallet_service.dart +++ /dev/null @@ -1,556 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_libmwc/lib.dart' as mwc; - -import '../models/mwcmqs_config_model.dart'; -import '../utilities/amount/amount.dart'; -import '../utilities/default_mwcmqs.dart'; -import '../utilities/logger.dart'; -import '../utilities/stack_file_system.dart'; - -/// Service layer that wraps flutter_libmwc FFI functions for Stack Wallet integration. -/// -/// Based on the flutter_libmwc example WalletService but adapted for Stack Wallet patterns. -class MwcWalletService { - static bool _isInitialized = false; - static final Map _walletHandles = {}; - - /// Initialize the MWC wallet service. - static Future initialize() async { - if (_isInitialized) return; - - Logging.instance.i('MWC Wallet Service initialized'); - _isInitialized = true; - } - - /// Create a new MWC wallet. - static Future createWallet({ - required String walletId, - required String password, - String? customMnemonic, - }) async { - try { - Logging.instance.i('Creating MWC wallet: $walletId'); - - final config = await _getWalletConfig(walletId); - final mnemonic = customMnemonic ?? mwc.Libmwc.getMnemonic(); - - final result = await mwc.Libmwc.initializeNewWallet( - config: config, - mnemonic: mnemonic, - password: password, - name: walletId, - ); - - if (result.toUpperCase().contains('ERROR')) { - return MwcWalletResult( - success: false, - error: 'Failed to create wallet: $result', - ); - } - - _walletHandles[walletId] = result; - - Logging.instance.i('MWC wallet created successfully: $walletId'); - return MwcWalletResult( - success: true, - walletId: walletId, - data: {'mnemonic': mnemonic, 'handle': result}, - ); - } catch (e, s) { - Logging.instance.e('Failed to create MWC wallet: $e\n$s'); - return MwcWalletResult( - success: false, - error: 'Failed to create wallet: $e', - ); - } - } - - /// Recover MWC wallet from mnemonic. - static Future recoverWallet({ - required String walletId, - required String password, - required String mnemonic, - }) async { - try { - Logging.instance.i('Recovering MWC wallet: $walletId'); - - final config = await _getWalletConfig(walletId); - - await mwc.Libmwc.recoverWallet( - config: config, - password: password, - mnemonic: mnemonic, - name: walletId, - ); - - final openResult = await openWallet( - walletId: walletId, - password: password, - ); - - if (openResult.success) { - Logging.instance.i('MWC wallet recovered successfully: $walletId'); - return MwcWalletResult( - success: true, - walletId: walletId, - data: {'recovered': true}, - ); - } - - return openResult; - } catch (e, s) { - Logging.instance.e('Failed to recover MWC wallet: $e\n$s'); - return MwcWalletResult( - success: false, - error: 'Failed to recover wallet: $e', - ); - } - } - - /// Open an existing MWC wallet. - static Future openWallet({ - required String walletId, - required String password, - }) async { - try { - Logging.instance.i('Opening MWC wallet: $walletId'); - - final config = await _getWalletConfig(walletId); - - final result = await mwc.Libmwc.openWallet( - config: config, - password: password, - ); - - if (result.toUpperCase().contains('ERROR')) { - return MwcWalletResult( - success: false, - error: 'Failed to open wallet: $result', - ); - } - - _walletHandles[walletId] = result; - - Logging.instance.i('MWC wallet opened successfully: $walletId'); - return MwcWalletResult( - success: true, - walletId: walletId, - data: {'handle': result}, - ); - } catch (e, s) { - Logging.instance.e('Failed to open MWC wallet: $e\n$s'); - return MwcWalletResult( - success: false, - error: 'Failed to open wallet: $e', - ); - } - } - - /// Create a slatepack for sending MWC. - static Future createSlatepack({ - required String walletId, - required Amount amount, - String? recipientAddress, - String? message, - bool encrypt = false, - int minimumConfirmations = 1, - }) async { - try { - final handle = _walletHandles[walletId]; - if (handle == null) { - return SlatepackResult( - success: false, - error: 'Wallet not open: $walletId', - ); - } - - Logging.instance.i('Creating slatepack for wallet: $walletId'); - - // Generate S1 slate JSON. - final s1Json = await mwc.Libmwc.txInit( - wallet: handle, - amount: amount.raw.toInt(), - minimumConfirmations: minimumConfirmations, - selectionStrategyIsAll: false, - message: message ?? '', - ); - - // Encode to slatepack. - final slatepackEncode = await mwc.Libmwc.encodeSlatepack( - slateJson: s1Json, - recipientAddress: recipientAddress, - encrypt: encrypt, - wallet: handle, - ); - - Logging.instance.i('Slatepack created successfully'); - return SlatepackResult( - success: true, - slatepack: slatepackEncode.slatepack, - slateJson: s1Json, - wasEncrypted: slatepackEncode.wasEncrypted, - recipientAddress: slatepackEncode.recipientAddress, - ); - } catch (e, s) { - Logging.instance.e('Failed to create slatepack: $e\n$s'); - return SlatepackResult( - success: false, - error: 'Failed to create slatepack: $e', - ); - } - } - - /// Decode a slatepack. - static Future decodeSlatepack({ - required String slatepack, - String? walletId, - }) async { - try { - Logging.instance.i('Decoding slatepack'); - - // Use wallet-aware decode if wallet is open (handles encrypted slatepacks) - final handle = walletId != null ? _walletHandles[walletId] : null; - final result = - (handle != null) - ? await mwc.Libmwc.decodeSlatepackWithWallet( - wallet: handle, - slatepack: slatepack, - ) - : await mwc.Libmwc.decodeSlatepack(slatepack: slatepack); - - Logging.instance.i('Slatepack decoded successfully'); - return SlatepackDecodeResult( - success: true, - slateJson: result.slateJson, - wasEncrypted: result.wasEncrypted, - senderAddress: result.senderAddress, - recipientAddress: result.recipientAddress, - ); - } catch (e, s) { - Logging.instance.e('Failed to decode slatepack: $e\n$s'); - return SlatepackDecodeResult( - success: false, - error: 'Failed to decode slatepack: $e', - ); - } - } - - /// Receive a slatepack and return response slatepack. - static Future receiveSlatepack({ - required String walletId, - required String slatepack, - }) async { - try { - final handle = _walletHandles[walletId]; - if (handle == null) { - return ReceiveResult( - success: false, - error: 'Wallet not open: $walletId', - ); - } - - Logging.instance.i('Receiving slatepack for wallet: $walletId'); - - // Decode to get slate JSON and sender address. - final decoded = await decodeSlatepack( - slatepack: slatepack, - walletId: walletId, - ); - if (!decoded.success || decoded.slateJson == null) { - return ReceiveResult( - success: false, - error: decoded.error ?? 'Failed to decode slatepack', - ); - } - - // Receive and get updated slate JSON. - final received = await mwc.Libmwc.txReceiveDetailed( - wallet: handle, - slateJson: decoded.slateJson!, - ); - - // Encode response slatepack back to sender. - final encoded = await mwc.Libmwc.encodeSlatepack( - slateJson: received.slateJson, - recipientAddress: decoded.senderAddress, - encrypt: decoded.senderAddress != null, - wallet: handle, - ); - - Logging.instance.i('Slatepack received successfully'); - return ReceiveResult( - success: true, - slateId: received.slateId, - commitId: received.commitId, - responseSlatepack: encoded.slatepack, - wasEncrypted: encoded.wasEncrypted, - recipientAddress: decoded.senderAddress, - ); - } catch (e, s) { - Logging.instance.e('Failed to receive slatepack: $e\n$s'); - return ReceiveResult( - success: false, - error: 'Failed to receive slatepack: $e', - ); - } - } - - /// Finalize a slatepack (sender step 3). - static Future finalizeSlatepack({ - required String walletId, - required String slatepack, - }) async { - try { - final handle = _walletHandles[walletId]; - if (handle == null) { - return FinalizeResult( - success: false, - error: 'Wallet not open: $walletId', - ); - } - - Logging.instance.i('Finalizing slatepack for wallet: $walletId'); - - // Decode to get slate JSON. - final decoded = await decodeSlatepack( - slatepack: slatepack, - walletId: walletId, - ); - if (!decoded.success || decoded.slateJson == null) { - return FinalizeResult( - success: false, - error: decoded.error ?? 'Failed to decode slatepack', - ); - } - - // Finalize transaction. - final finalized = await mwc.Libmwc.txFinalize( - wallet: handle, - slateJson: decoded.slateJson!, - ); - - Logging.instance.i('Slatepack finalized successfully'); - return FinalizeResult( - success: true, - slateId: finalized.slateId, - commitId: finalized.commitId, - ); - } catch (e, s) { - Logging.instance.e('Failed to finalize slatepack: $e\n$s'); - return FinalizeResult( - success: false, - error: 'Failed to finalize slatepack: $e', - ); - } - } - - /// Start MWCMQS listener. - static Future startMwcmqsListener({ - required String walletId, - MwcMqsConfigModel? config, - }) async { - try { - final handle = _walletHandles[walletId]; - if (handle == null) { - throw Exception('Wallet not open: $walletId'); - } - - Logging.instance.i('Starting MWCMQS listener for wallet: $walletId'); - - final mwcmqsConfig = - config ?? - MwcMqsConfigModel.fromServer(DefaultMwcMqs.defaultMwcMqsServer); - - mwc.Libmwc.startMwcMqsListener( - wallet: handle, - mwcmqsConfig: mwcmqsConfig.toString(), - ); - - Logging.instance.i('MWCMQS listener started successfully'); - } catch (e, s) { - Logging.instance.e('Failed to start MWCMQS listener: $e\n$s'); - rethrow; - } - } - - /// Stop MWCMQS listener. - static Future stopMwcmqsListener() async { - try { - Logging.instance.i('Stopping MWCMQS listener'); - - mwc.Libmwc.stopMwcMqsListener(); - - Logging.instance.i('MWCMQS listener stopped successfully'); - } catch (e, s) { - Logging.instance.e('Failed to stop MWCMQS listener: $e\n$s'); - } - } - - /// Get MWCMQS address for wallet. - static Future getMwcmqsAddress({ - required String walletId, - int index = 0, - }) async { - try { - final handle = _walletHandles[walletId]; - if (handle == null) { - throw Exception('Wallet not open: $walletId'); - } - - final address = await mwc.Libmwc.getAddressInfo( - wallet: handle, - index: index, - ); - - if (address.isEmpty) { - throw Exception('Failed to generate MWCMQS address'); - } - - return address; - } catch (e, s) { - Logging.instance.e('Failed to get MWCMQS address: $e\n$s'); - rethrow; - } - } - - /// Validate MWC address. - static bool validateAddress(String address) { - try { - return mwc.Libmwc.validateSendAddress(address: address); - } catch (e, s) { - Logging.instance.e('Failed to validate address: $e\n$s'); - return false; - } - } - - /// Generate mnemonic. - static String generateMnemonic() { - try { - return mwc.Libmwc.getMnemonic(); - } catch (e, s) { - Logging.instance.e('Failed to generate mnemonic: $e\n$s'); - rethrow; - } - } - - /// Check if wallet is open. - static bool isWalletOpen(String walletId) { - return _walletHandles.containsKey(walletId); - } - - /// Close wallet. - static void closeWallet(String walletId) { - _walletHandles.remove(walletId); - } - - /// Get wallet configuration. - static Future _getWalletConfig(String walletId) async { - final walletDir = await _getWalletDirectory(walletId); - - final config = { - 'wallet_dir': walletDir, - 'check_node_api_http_addr': 'https://mwc713.mwc.mw:443', - 'chain': 'mainnet', - 'account': 'default', - }; - - return jsonEncode(config); - } - - /// Get wallet directory path. - static Future _getWalletDirectory(String walletId) async { - final Directory appDir = await StackFileSystem.applicationRootDirectory(); - final path = "${appDir.path}/mimblewimblecoin"; - final String name = walletId.trim(); - return '$path/$name'; - } -} - -/// Result classes for MWC wallet operations. - -class MwcWalletResult { - final bool success; - final String? walletId; - final String? error; - final Map? data; - - MwcWalletResult({ - required this.success, - this.walletId, - this.error, - this.data, - }); -} - -class SlatepackResult { - final bool success; - final String? error; - final String? slatepack; - final String? slateJson; - final bool? wasEncrypted; - final String? recipientAddress; - - SlatepackResult({ - required this.success, - this.error, - this.slatepack, - this.slateJson, - this.wasEncrypted, - this.recipientAddress, - }); -} - -class SlatepackDecodeResult { - final bool success; - final String? error; - final String? slateJson; - final bool? wasEncrypted; - final String? senderAddress; - final String? recipientAddress; - - SlatepackDecodeResult({ - required this.success, - this.error, - this.slateJson, - this.wasEncrypted, - this.senderAddress, - this.recipientAddress, - }); -} - -class ReceiveResult { - final bool success; - final String? error; - final String? slateId; - final String? commitId; - final String? responseSlatepack; - final bool? wasEncrypted; - final String? recipientAddress; - - ReceiveResult({ - required this.success, - this.error, - this.slateId, - this.commitId, - this.responseSlatepack, - this.wasEncrypted, - this.recipientAddress, - }); -} - -class FinalizeResult { - final bool success; - final String? error; - final String? slateId; - final String? commitId; - - FinalizeResult({ - required this.success, - this.error, - this.slateId, - this.commitId, - }); -} diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index f31d8a65a5..3da15ba0e5 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -26,7 +26,7 @@ import '../../../services/event_bus/events/global/node_connection_status_changed import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; -import '../../../services/mwc_wallet_service.dart'; +import '../../../models/mwc_slatepack_models.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/default_mwcmqs.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; @@ -77,9 +77,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { value: stringConfig, ); - // Restart MWCMQS listener with new configuration if wallet is active. + // Restart MWCMQS listener with new configuration if wallet has a handle. try { - if (MwcWalletService.isWalletOpen(walletId)) { + final handle = await secureStorageInterface.read(key: '${walletId}_wallet'); + if (handle != null && handle.isNotEmpty) { await stopSlatepackListener(); await startSlatepackListener(); Logging.instance.i('Restarted MWCMQS listener with new config: $host:$port'); @@ -89,6 +90,23 @@ class MimblewimblecoinWallet extends Bip39Wallet { } } + Future _ensureWalletOpen() async { + final existing = await secureStorageInterface.read(key: '${walletId}_wallet'); + if (existing != null && existing.isNotEmpty) return existing; + + final config = await _getRealConfig(); + final password = await secureStorageInterface.read(key: '${walletId}_password'); + if (password == null) { + throw Exception('Wallet password not found'); + } + final opened = await mimblewimblecoin.Libmwc.openWallet( + config: config, + password: password, + ); + await secureStorageInterface.write(key: '${walletId}_wallet', value: opened); + return opened; + } + /// Returns an empty String on success, error message on failure. Future cancelPendingTransactionAndPost(String txSlateId) async { try { @@ -145,35 +163,32 @@ class MimblewimblecoinWallet extends Bip39Wallet { int? minimumConfirmations, }) async { try { - await MwcWalletService.initialize(); + final handle = await _ensureWalletOpen(); - // Ensure wallet is open in service. - if (!MwcWalletService.isWalletOpen(walletId)) { - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } - - final openResult = await MwcWalletService.openWallet( - walletId: walletId, - password: password, - ); - - if (!openResult.success) { - throw Exception(openResult.error ?? 'Failed to open wallet'); - } - } + // Generate S1 slate JSON. + final s1Json = await mimblewimblecoin.Libmwc.txInit( + wallet: handle, + amount: amount.raw.toInt(), + minimumConfirmations: + minimumConfirmations ?? cryptoCurrency.minConfirms, + selectionStrategyIsAll: false, + message: message ?? '', + ); - return await MwcWalletService.createSlatepack( - walletId: walletId, - amount: amount, + // Encode to slatepack. + final encoded = await mimblewimblecoin.Libmwc.encodeSlatepack( + slateJson: s1Json, recipientAddress: recipientAddress, - message: message, encrypt: encrypt, - minimumConfirmations: - minimumConfirmations ?? cryptoCurrency.minConfirms, + wallet: handle, + ); + + return SlatepackResult( + success: true, + slatepack: encoded.slatepack, + slateJson: s1Json, + wasEncrypted: encoded.wasEncrypted, + recipientAddress: encoded.recipientAddress, ); } catch (e, s) { Logging.instance.e('Failed to create slatepack: $e\n$s'); @@ -184,10 +199,20 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Decode a slatepack. Future decodeSlatepack(String slatepack) async { try { - await MwcWalletService.initialize(); - return await MwcWalletService.decodeSlatepack( - slatepack: slatepack, - walletId: MwcWalletService.isWalletOpen(walletId) ? walletId : null, + final handle = await secureStorageInterface.read(key: '${walletId}_wallet'); + final result = handle != null + ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( + wallet: handle, + slatepack: slatepack, + ) + : await mimblewimblecoin.Libmwc.decodeSlatepack(slatepack: slatepack); + + return SlatepackDecodeResult( + success: true, + slateJson: result.slateJson, + wasEncrypted: result.wasEncrypted, + senderAddress: result.senderAddress, + recipientAddress: result.recipientAddress, ); } catch (e, s) { Logging.instance.e('Failed to decode slatepack: $e\n$s'); @@ -198,30 +223,35 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Receive a slatepack and return response slatepack. Future receiveSlatepack(String slatepack) async { try { - await MwcWalletService.initialize(); + final handle = await _ensureWalletOpen(); - // Ensure wallet is open in service. - if (!MwcWalletService.isWalletOpen(walletId)) { - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } + // Decode to get slate JSON and sender address. + final decoded = await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( + wallet: handle, + slatepack: slatepack, + ); - final openResult = await MwcWalletService.openWallet( - walletId: walletId, - password: password, - ); + // Receive and get updated slate JSON. + final received = await mimblewimblecoin.Libmwc.txReceiveDetailed( + wallet: handle, + slateJson: decoded.slateJson!, + ); - if (!openResult.success) { - throw Exception(openResult.error ?? 'Failed to open wallet'); - } - } + // Encode response back to sender if address available. + final encoded = await mimblewimblecoin.Libmwc.encodeSlatepack( + slateJson: received.slateJson, + recipientAddress: decoded.senderAddress, + encrypt: decoded.senderAddress != null, + wallet: handle, + ); - return await MwcWalletService.receiveSlatepack( - walletId: walletId, - slatepack: slatepack, + return ReceiveResult( + success: true, + slateId: received.slateId, + commitId: received.commitId, + responseSlatepack: encoded.slatepack, + wasEncrypted: encoded.wasEncrypted, + recipientAddress: decoded.senderAddress, ); } catch (e, s) { Logging.instance.e('Failed to receive slatepack: $e\n$s'); @@ -232,30 +262,24 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Finalize a slatepack (sender step 3). Future finalizeSlatepack(String slatepack) async { try { - await MwcWalletService.initialize(); - - // Ensure wallet is open in service. - if (!MwcWalletService.isWalletOpen(walletId)) { - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } + final handle = await _ensureWalletOpen(); - final openResult = await MwcWalletService.openWallet( - walletId: walletId, - password: password, - ); + // Decode to get slate JSON. + final decoded = await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( + wallet: handle, + slatepack: slatepack, + ); - if (!openResult.success) { - throw Exception(openResult.error ?? 'Failed to open wallet'); - } - } + // Finalize transaction. + final finalized = await mimblewimblecoin.Libmwc.txFinalize( + wallet: handle, + slateJson: decoded.slateJson!, + ); - return await MwcWalletService.finalizeSlatepack( - walletId: walletId, - slatepack: slatepack, + return FinalizeResult( + success: true, + slateId: finalized.slateId, + commitId: finalized.commitId, ); } catch (e, s) { Logging.instance.e('Failed to finalize slatepack: $e\n$s'); @@ -266,30 +290,12 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Start MWCMQS listener for automatic transaction processing. Future startSlatepackListener() async { try { - await MwcWalletService.initialize(); - - if (!MwcWalletService.isWalletOpen(walletId)) { - final password = await secureStorageInterface.read( - key: '${walletId}_password', - ); - if (password == null) { - throw Exception('Wallet password not found'); - } - - final openResult = await MwcWalletService.openWallet( - walletId: walletId, - password: password, - ); - - if (!openResult.success) { - throw Exception(openResult.error ?? 'Failed to open wallet'); - } - } - + await _ensureWalletOpen(); final mwcmqsConfig = await getMwcMqsConfig(); - await MwcWalletService.startMwcmqsListener( - walletId: walletId, - config: mwcmqsConfig, + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + mimblewimblecoin.Libmwc.startMwcMqsListener( + wallet: wallet!, + mwcmqsConfig: mwcmqsConfig.toString(), ); } catch (e, s) { Logging.instance.e('Failed to start slatepack listener: $e\n$s'); @@ -300,7 +306,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Stop MWCMQS listener. Future stopSlatepackListener() async { try { - await MwcWalletService.stopMwcmqsListener(); + mimblewimblecoin.Libmwc.stopMwcMqsListener(); } catch (e, s) { Logging.instance.e('Failed to stop slatepack listener: $e\n$s'); } @@ -308,7 +314,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Validate MWC address. bool validateMwcAddress(String address) { - return MwcWalletService.validateAddress(address); + return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } /// Detect if an address is a slatepack. @@ -792,8 +798,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { - // Initialize MWC wallet service. - await MwcWalletService.initialize(); if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read( From 00ae9a971daec1492b247877356adc6914fc7f2c Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 11:29:28 -0500 Subject: [PATCH 38/91] fix: remove unnecessary assertion --- lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 3da15ba0e5..42e807fc2a 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -234,7 +234,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // Receive and get updated slate JSON. final received = await mimblewimblecoin.Libmwc.txReceiveDetailed( wallet: handle, - slateJson: decoded.slateJson!, + slateJson: decoded.slateJson, ); // Encode response back to sender if address available. @@ -273,7 +273,7 @@ class MimblewimblecoinWallet extends Bip39Wallet { // Finalize transaction. final finalized = await mimblewimblecoin.Libmwc.txFinalize( wallet: handle, - slateJson: decoded.slateJson!, + slateJson: decoded.slateJson, ); return FinalizeResult( From 7eb25615865718edd9fc9c534066638b6b6ec02c Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 12:25:32 -0500 Subject: [PATCH 39/91] fix: unify txs selected method --- .../wallet_view/sub_widgets/desktop_send.dart | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 694aae1861..a2728522ee 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -119,7 +119,6 @@ class _DesktopSendState extends ConsumerState { late final bool isStellar; late final bool isMimblewimblecoin; - String? _selectedMethodMwc = 'Slatepack'; String? _note; String? _onChainNote; @@ -291,7 +290,8 @@ class _DesktopSendState extends ConsumerState { final wallet = ref.read(pWallets).getWallet(walletId); // Handle MWC slatepack transactions directly. - if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') { + if (isMimblewimblecoin && + ref.read(pSelectedMwcTransactionMethod) == TransactionMethod.slatepack) { await _handleDesktopSlatepackCreation(wallet as MimblewimblecoinWallet); return; } @@ -1077,8 +1077,7 @@ class _DesktopSendState extends ConsumerState { isStellar = coin is Stellar; isMimblewimblecoin = coin is Mimblewimblecoin; if (isMimblewimblecoin) { - _selectedMethodMwc = "Slatepack"; - // Initialize provider to match the UI (slatepack method). + // Initialize provider to slatepack method. WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(pSelectedMwcTransactionMethod.notifier).state = TransactionMethod.slatepack; @@ -1252,17 +1251,12 @@ class _DesktopSendState extends ConsumerState { 50, // Provide an explicit height to avoid infinite constraints child: MwcTxsMethodToggle( onChanged: (TxsMethodMwcType type) { - setState(() { - _selectedMethodMwc = - type == TxsMethodMwcType.slatepack - ? 'Automatic' - : 'Slatepack'; - }); - // Update the provider as well. + // Update the provider directly. ref.read(pSelectedMwcTransactionMethod.notifier).state = type == TxsMethodMwcType.slatepack ? TransactionMethod.mwcmqs : TransactionMethod.slatepack; + setState(() {}); // Trigger rebuild }, ), ), @@ -1638,9 +1632,7 @@ class _DesktopSendState extends ConsumerState { height: 1.8, ), decoration: standardInputDecoration( - _selectedMethodMwc == "Slatepack" || - _selectedMethodMwc == null || - _selectedMethodMwc == "" + ref.watch(pSelectedMwcTransactionMethod) == TransactionMethod.slatepack ? "Enter ${coin.ticker} address (optional)" : "Enter ${coin.ticker} address", _addressFocusNode, From 0360146fa656a562790d755e6411a1a92de5b0a9 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Fri, 12 Sep 2025 17:01:38 -0500 Subject: [PATCH 40/91] fix: "Preview send" -> "Create slatepack" and formatting --- .../wallet_view/sub_widgets/desktop_send.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index a2728522ee..3dee26b7cc 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -290,8 +290,9 @@ class _DesktopSendState extends ConsumerState { final wallet = ref.read(pWallets).getWallet(walletId); // Handle MWC slatepack transactions directly. - if (isMimblewimblecoin && - ref.read(pSelectedMwcTransactionMethod) == TransactionMethod.slatepack) { + if (isMimblewimblecoin && + ref.read(pSelectedMwcTransactionMethod) == + TransactionMethod.slatepack) { await _handleDesktopSlatepackCreation(wallet as MimblewimblecoinWallet); return; } @@ -1632,7 +1633,8 @@ class _DesktopSendState extends ConsumerState { height: 1.8, ), decoration: standardInputDecoration( - ref.watch(pSelectedMwcTransactionMethod) == TransactionMethod.slatepack + ref.watch(pSelectedMwcTransactionMethod) == + TransactionMethod.slatepack ? "Enter ${coin.ticker} address (optional)" : "Enter ${coin.ticker} address", _addressFocusNode, @@ -1933,7 +1935,11 @@ class _DesktopSendState extends ConsumerState { const SizedBox(height: 36), PrimaryButton( buttonHeight: ButtonHeight.l, - label: "Preview send", + label: + ref.read(pSelectedMwcTransactionMethod) == + TransactionMethod.slatepack + ? "Create slatepack" + : "Preview send", enabled: ref.watch(pPreviewTxButtonEnabled(coin)), onPressed: ref.watch(pPreviewTxButtonEnabled(coin)) ? previewSend : null, From cf9cecd58c6e6fe275c5d31ae618a88282e96adc Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 09:47:49 -0600 Subject: [PATCH 41/91] pubspec lock --- pubspec.lock | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 33c6da0ebf..866a1e3310 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -813,11 +813,11 @@ packages: dependency: "direct main" description: path: "." - ref: f0b1300140d45c13e7722f8f8d20308efeba8449 - resolved-ref: f0b1300140d45c13e7722f8f8d20308efeba8449 + ref: "794ab2d7b88b34d64a89518f9b9f41dcc235aca1" + resolved-ref: "794ab2d7b88b34d64a89518f9b9f41dcc235aca1" url: "https://github.com/cypherstack/electrum_adapter.git" source: git - version: "3.0.0" + version: "3.0.2" emojis: dependency: "direct main" description: @@ -1126,8 +1126,8 @@ packages: dependency: "direct main" description: path: "." - ref: afaad488f5215a9c2c211e5e2f8460237eef60f1 - resolved-ref: afaad488f5215a9c2c211e5e2f8460237eef60f1 + ref: "540d0bc7dc27a97d45d63f412f26818a7f3b8b51" + resolved-ref: "540d0bc7dc27a97d45d63f412f26818a7f3b8b51" url: "https://github.com/cypherstack/fusiondart.git" source: git version: "1.0.0" @@ -1970,11 +1970,10 @@ packages: socks_socket: dependency: transitive description: - path: "." - ref: master - resolved-ref: e6232c53c1595469931ababa878759a067c02e94 - url: "https://github.com/cypherstack/socks_socket.git" - source: git + name: socks_socket + sha256: "53bc7eae40a3aa16ea810b0e9de3bb23ba7beb0b40d09357b89190f2f44374cc" + url: "https://pub.dev" + source: hosted version: "1.1.1" solana: dependency: "direct main" @@ -2207,8 +2206,8 @@ packages: dependency: "direct main" description: path: "." - ref: "752f054b65c500adb9cad578bf183a978e012502" - resolved-ref: "752f054b65c500adb9cad578bf183a978e012502" + ref: "16c9e709e984ec89e8715ce378b038c93ad7add3" + resolved-ref: "16c9e709e984ec89e8715ce378b038c93ad7add3" url: "https://github.com/cypherstack/tor.git" source: git version: "0.0.1" From eccd9b6fbbb9d83dfec4654bcc98339b445b85e5 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 09:48:14 -0600 Subject: [PATCH 42/91] remove duplicate desktop receive qr create button --- .../wallet_view/sub_widgets/desktop_receive.dart | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 359d25ff60..0e9f4e22bb 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -923,21 +923,6 @@ class _DesktopReceiveState extends ConsumerState { ).extension()!.accentColorBlue, ), ), - const SizedBox(width: 8), - Padding( - padding: const EdgeInsets.only(bottom: 2), - child: Text( - "Create new QR code", - style: STextStyles.desktopTextExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ).extension()!.accentColorBlue, - ), - ), - ), ], ), ), From 2f6d2a9ac3b08811d10575c1df07f6493fda9aac Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 10:31:13 -0600 Subject: [PATCH 43/91] no need for extra string variables when there is an enum already... --- .../desktop_mwc_txs_method_toggle.dart | 4 ++-- .../wallet_view/sub_widgets/desktop_receive.dart | 15 +++++++-------- lib/utilities/enums/txs_method_mwc_enum.dart | 10 +++++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart index 3817d139f6..4f157c88d4 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart @@ -62,9 +62,9 @@ class MwcTxsMethodToggle extends ConsumerWidget { ), ), onIcon: Assets.svg.gear, - onText: "Slatepack", + onText: TxsMethodMwcType.slatepack.value, offIcon: Assets.svg.radioSyncing, - offText: "Automatic", + offText: TxsMethodMwcType.automatic.value, ); } } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 0e9f4e22bb..1629f14c81 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -92,7 +92,7 @@ class _DesktopReceiveState extends ConsumerState { final _addressFocusNode = FocusNode(); int _currentIndex = 0; - String? _selectedMethodMwc; // Variable to store selected dropdown value + late TxsMethodMwcType _selectedMethodMwc; String? _note; final List _walletAddressTypes = []; @@ -308,7 +308,7 @@ class _DesktopReceiveState extends ConsumerState { isMimblewimblecoin = wallet is MimblewimblecoinWallet; if (isMimblewimblecoin) { - _selectedMethodMwc = "Slatepack"; + _selectedMethodMwc = TxsMethodMwcType.slatepack; } debugPrint("Address generated: $isMimblewimblecoin"); @@ -495,10 +495,7 @@ class _DesktopReceiveState extends ConsumerState { child: MwcTxsMethodToggle( onChanged: (TxsMethodMwcType type) { setState(() { - _selectedMethodMwc = - type == TxsMethodMwcType.slatepack - ? 'Slatepack' - : 'Automatic'; + _selectedMethodMwc = type; }); }, ), @@ -681,7 +678,8 @@ class _DesktopReceiveState extends ConsumerState { label: "Generate new address", ), const SizedBox(height: 20), - if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') + if (isMimblewimblecoin && + _selectedMethodMwc == TxsMethodMwcType.slatepack) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -816,7 +814,8 @@ class _DesktopReceiveState extends ConsumerState { // TODO: create transparent button class to account for hover // Conditional logic for 'Submit' button or QR code - if (isMimblewimblecoin && _selectedMethodMwc == 'Slatepack') + if (isMimblewimblecoin && + _selectedMethodMwc == TxsMethodMwcType.slatepack) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: PrimaryButton( diff --git a/lib/utilities/enums/txs_method_mwc_enum.dart b/lib/utilities/enums/txs_method_mwc_enum.dart index a77e08492e..23c04ab08a 100644 --- a/lib/utilities/enums/txs_method_mwc_enum.dart +++ b/lib/utilities/enums/txs_method_mwc_enum.dart @@ -8,7 +8,11 @@ * */ -enum TxsMethodMwcType { - slatepack, - automatic +enum TxsMethodMwcType { + slatepack("Slatepack"), + automatic("Automatic"); + + final String value; + + const TxsMethodMwcType(this.value); } From 3849f8ab5cf3f7dc238b798a565d8e1de3f6cae7 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 10:35:34 -0600 Subject: [PATCH 44/91] fix scripts --- scripts/ios/build_all.sh | 2 -- scripts/ios/build_all_campfire.sh | 2 -- scripts/ios/build_all_duo.sh | 2 -- scripts/linux/build_all.sh | 2 -- scripts/linux/build_all_campfire.sh | 2 -- scripts/linux/build_all_duo.sh | 2 -- scripts/macos/build_all.sh | 2 -- scripts/macos/build_all_campfire.sh | 2 -- scripts/macos/build_all_duo.sh | 2 -- scripts/rust_version.sh | 18 ------------------ scripts/windows/build_all.sh | 5 ----- scripts/windows/build_all_campfire.sh | 5 ----- scripts/windows/build_all_duo.sh | 5 ----- 13 files changed, 51 deletions(-) diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index 0bc5777015..83177db5c2 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -14,9 +14,7 @@ rustup target add x86_64-apple-ios source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/ios/build_all_campfire.sh b/scripts/ios/build_all_campfire.sh index 0bc5777015..83177db5c2 100755 --- a/scripts/ios/build_all_campfire.sh +++ b/scripts/ios/build_all_campfire.sh @@ -14,9 +14,7 @@ rustup target add x86_64-apple-ios source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/ios/build_all_duo.sh b/scripts/ios/build_all_duo.sh index 48c1335fef..0b560202b6 100755 --- a/scripts/ios/build_all_duo.sh +++ b/scripts/ios/build_all_duo.sh @@ -16,9 +16,7 @@ rustup target add x86_64-apple-ios source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/ios/ && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/linux/build_all.sh b/scripts/linux/build_all.sh index ca6ae2d2a5..50490b1979 100755 --- a/scripts/linux/build_all.sh +++ b/scripts/linux/build_all.sh @@ -13,9 +13,7 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/linux/build_all_campfire.sh b/scripts/linux/build_all_campfire.sh index ca6ae2d2a5..50490b1979 100755 --- a/scripts/linux/build_all_campfire.sh +++ b/scripts/linux/build_all_campfire.sh @@ -13,9 +13,7 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/linux/build_all_duo.sh b/scripts/linux/build_all_duo.sh index 5a98fe3814..a29947f4f6 100755 --- a/scripts/linux/build_all_duo.sh +++ b/scripts/linux/build_all_duo.sh @@ -16,9 +16,7 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/macos/build_all.sh b/scripts/macos/build_all.sh index f8dabc3d3f..de9b79efaa 100755 --- a/scripts/macos/build_all.sh +++ b/scripts/macos/build_all.sh @@ -7,9 +7,7 @@ set -x -e source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/macos/build_all_campfire.sh b/scripts/macos/build_all_campfire.sh index f727c4268b..2bc4aaf4bb 100755 --- a/scripts/macos/build_all_campfire.sh +++ b/scripts/macos/build_all_campfire.sh @@ -7,9 +7,7 @@ set -x -e source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/macos/build_all_duo.sh b/scripts/macos/build_all_duo.sh index d817a02e2a..29a0d82420 100755 --- a/scripts/macos/build_all_duo.sh +++ b/scripts/macos/build_all_duo.sh @@ -9,9 +9,7 @@ set -x -e source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/macos && ./build_all.sh ) -set_rust_to_1720 # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/rust_version.sh b/scripts/rust_version.sh index 3f658fead0..65bf911f49 100755 --- a/scripts/rust_version.sh +++ b/scripts/rust_version.sh @@ -18,21 +18,3 @@ set_rust_version_for_libepiccash() { exit 1 fi } - -set_rust_to_1810() { - if rustup toolchain list | grep -q "1.81.0"; then - rustup default 1.81.0 - else - echo "Rust version 1.81.0 is not installed. Please install it using 'rustup install 1.81.0'." >&2 - exit 1 - fi -} - -set_rust_to_1720() { - if rustup toolchain list | grep -q "1.72.0"; then - rustup default 1.72.0 - else - echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2 - exit 1 - fi -} \ No newline at end of file diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index 178868a8de..6d7395bbf3 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -8,11 +8,6 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) - -mkdir -p build -(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/windows/build_all_campfire.sh b/scripts/windows/build_all_campfire.sh index 178868a8de..6d7395bbf3 100755 --- a/scripts/windows/build_all_campfire.sh +++ b/scripts/windows/build_all_campfire.sh @@ -8,11 +8,6 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) - -mkdir -p build -(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/windows/build_all_duo.sh b/scripts/windows/build_all_duo.sh index 9e27fd8e03..6a19b94f52 100755 --- a/scripts/windows/build_all_duo.sh +++ b/scripts/windows/build_all_duo.sh @@ -10,11 +10,6 @@ mkdir -p build source ../rust_version.sh set_rust_version_for_libepiccash (cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) - -mkdir -p build -(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) -(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) -set_rust_to_1810 (cd ../../crypto_plugins/flutter_libmwc/scripts/windows && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else From acbce8ad0cf174321fec84a729dcec067cd53fb5 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 16 Sep 2025 13:14:58 -0500 Subject: [PATCH 45/91] fix: prevent UI crash on MWC error (hack) --- .../wallet_view/sub_widgets/desktop_send.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 3dee26b7cc..d7ad101951 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -170,6 +170,8 @@ class _DesktopSendState extends ConsumerState { Future _handleDesktopSlatepackCreation( MimblewimblecoinWallet wallet, ) async { + bool buildingDialogDismissed = false; + try { final amount = ref.read(pSendAmount)!; @@ -199,6 +201,7 @@ class _DesktopSendState extends ConsumerState { // Close building dialog. if (mounted) { Navigator.of(context, rootNavigator: true).pop(); + buildingDialogDismissed = true; } if (!slatepackResult.success || slatepackResult.slatepack == null) { @@ -224,8 +227,8 @@ class _DesktopSendState extends ConsumerState { } catch (e, s) { Logging.instance.e('Failed to create MWC slatepack on desktop: $e\n$s'); - // Close building dialog if still open. - if (mounted) { + // Close building dialog if still open and not already dismissed. + if (mounted && !buildingDialogDismissed) { Navigator.of(context, rootNavigator: true).pop(); } From da33a5d12f2920e2b4f7b4536693189a772f1210 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 16 Sep 2025 13:18:55 -0500 Subject: [PATCH 46/91] fix: prevent UI crash on MWC error with no hack --- .../wallet_view/sub_widgets/desktop_send.dart | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index d7ad101951..c6834bfdb0 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -170,8 +170,6 @@ class _DesktopSendState extends ConsumerState { Future _handleDesktopSlatepackCreation( MimblewimblecoinWallet wallet, ) async { - bool buildingDialogDismissed = false; - try { final amount = ref.read(pSendAmount)!; @@ -183,8 +181,11 @@ class _DesktopSendState extends ConsumerState { builder: (context) => BuildingTransactionDialog( coin: coin, - onCancel: - () => Navigator.of(context, rootNavigator: true).pop(), + onCancel: () { + // Use maybePop so we never accidentally pop the base route + // if the dialog hasn't fully mounted yet. + Navigator.of(context, rootNavigator: true).maybePop(); + }, isSpark: false, ), ), @@ -198,10 +199,10 @@ class _DesktopSendState extends ConsumerState { encrypt: false, // No encryption without recipient address. ); - // Close building dialog. + // Close building dialog if present. Use maybePop to avoid popping the + // underlying page route in case the dialog hasn't mounted yet. if (mounted) { - Navigator.of(context, rootNavigator: true).pop(); - buildingDialogDismissed = true; + await Navigator.of(context, rootNavigator: true).maybePop(); } if (!slatepackResult.success || slatepackResult.slatepack == null) { @@ -227,9 +228,10 @@ class _DesktopSendState extends ConsumerState { } catch (e, s) { Logging.instance.e('Failed to create MWC slatepack on desktop: $e\n$s'); - // Close building dialog if still open and not already dismissed. - if (mounted && !buildingDialogDismissed) { - Navigator.of(context, rootNavigator: true).pop(); + // Close building dialog if still open. maybePop prevents popping the + // page route if the dialog wasn't pushed yet. + if (mounted) { + await Navigator.of(context, rootNavigator: true).maybePop(); } if (mounted) { @@ -618,8 +620,8 @@ class _DesktopSendState extends ConsumerState { txData = txData.copyWith(noteOnChain: _onChainNote ?? ""); } } - // pop building dialog - Navigator.of(context, rootNavigator: true).pop(); + // Close building dialog if present without risking popping the page. + await Navigator.of(context, rootNavigator: true).maybePop(); unawaited( showDialog( @@ -642,8 +644,8 @@ class _DesktopSendState extends ConsumerState { } catch (e, s) { Logging.instance.e("Desktop send: ", error: e, stackTrace: s); if (mounted) { - // pop building dialog - Navigator.of(context, rootNavigator: true).pop(); + // Close building dialog if present without risking popping the page. + await Navigator.of(context, rootNavigator: true).maybePop(); unawaited( showDialog( From 7ef8320fb1fa3b74985c2e5a3069833c0b386d22 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 12:28:22 -0600 Subject: [PATCH 47/91] fix slatepack/auto type toggle on desktop --- lib/models/mwc_transaction_method.dart | 70 ------------------- lib/pages/send_view/send_view.dart | 38 +++++----- .../mwc_transaction_method_selector.dart | 38 +++++----- .../desktop_mwc_txs_method_toggle.dart | 29 ++++---- .../sub_widgets/desktop_receive.dart | 31 ++++---- .../wallet_view/sub_widgets/desktop_send.dart | 33 +++------ .../ui/preview_tx_button_state_provider.dart | 9 +-- .../enums/mwc_transaction_method.dart | 70 +++++++++++++++++++ lib/utilities/enums/txs_method_mwc_enum.dart | 18 ----- .../coins/mimblewimblecoin.dart | 15 ++-- 10 files changed, 159 insertions(+), 192 deletions(-) delete mode 100644 lib/models/mwc_transaction_method.dart create mode 100644 lib/utilities/enums/mwc_transaction_method.dart delete mode 100644 lib/utilities/enums/txs_method_mwc_enum.dart diff --git a/lib/models/mwc_transaction_method.dart b/lib/models/mwc_transaction_method.dart deleted file mode 100644 index 1663d834c6..0000000000 --- a/lib/models/mwc_transaction_method.dart +++ /dev/null @@ -1,70 +0,0 @@ -/// Enum to represent different MWC transaction methods. -enum TransactionMethod { - /// Manual slatepack exchange (copy/paste, QR codes, files). - slatepack, - - /// Automatic transaction via MWCMQS. - mwcmqs, - - /// Direct HTTP/HTTPS to recipient's wallet. - http, - - /// Unknown or unsupported method. - unknown; - - /// Human readable name for the transaction method. - String get displayName { - switch (this) { - case TransactionMethod.slatepack: - return 'Slatepack'; - case TransactionMethod.mwcmqs: - return 'MWCMQS'; - case TransactionMethod.http: - return 'HTTP'; - case TransactionMethod.unknown: - return 'Unknown'; - } - } - - /// Description of how the transaction method works. - String get description { - switch (this) { - case TransactionMethod.slatepack: - return 'Manual exchange via text, QR codes, or files'; - case TransactionMethod.mwcmqs: - return 'Automatic exchange via MWCMQS messaging'; - case TransactionMethod.http: - return 'Direct connection to recipient wallet'; - case TransactionMethod.unknown: - return 'Unsupported transaction method'; - } - } - - /// Whether this method requires manual intervention. - bool get isManual { - switch (this) { - case TransactionMethod.slatepack: - return true; - case TransactionMethod.mwcmqs: - return false; - case TransactionMethod.http: - return false; - case TransactionMethod.unknown: - return true; - } - } - - /// Whether this method works offline. - bool get worksOffline { - switch (this) { - case TransactionMethod.slatepack: - return true; - case TransactionMethod.mwcmqs: - return false; - case TransactionMethod.http: - return false; - case TransactionMethod.unknown: - return false; - } - } -} diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index e4a206ce96..96a64f6550 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -22,7 +22,6 @@ import 'package:tuple/tuple.dart'; import '../../models/input.dart'; import '../../models/isar/models/isar_models.dart'; -import '../../models/mwc_transaction_method.dart'; import '../../models/paynym/paynym_account_lite.dart'; import '../../models/send_view_auto_fill_data.dart'; import '../../providers/providers.dart'; @@ -43,6 +42,7 @@ import '../../utilities/barcode_scanner_interface.dart'; import '../../utilities/clipboard_interface.dart'; import '../../utilities/constants.dart'; import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../utilities/enums/mwc_transaction_method.dart'; import '../../utilities/eth_commons.dart'; import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; @@ -149,7 +149,7 @@ class _SendViewState extends ConsumerState { Set selectedUTXOs = {}; // MWC transaction method selection. - TransactionMethod? _selectedTransactionMethod; + MwcTransactionMethod? _selectedTransactionMethod; void _applyUri(PaymentUriData paymentData) { try { @@ -860,21 +860,21 @@ class _SendViewState extends ConsumerState { if (_selectedTransactionMethod != null) { switch (_selectedTransactionMethod!) { - case TransactionMethod.slatepack: + case MwcTransactionMethod.slatepack: transactionMethod = 'slatepack'; break; - case TransactionMethod.mwcmqs: + case MwcTransactionMethod.mwcmqs: transactionMethod = 'mwcmqs'; break; - case TransactionMethod.http: - transactionMethod = 'http'; - break; - case TransactionMethod.unknown: - // Auto-detect from address format. - final mwcCoin = coin as Mimblewimblecoin; - final method = mwcCoin.getTransactionMethod(_address!); - transactionMethod = method.toString().split('.').last; - break; + // case MwcTransactionMethod.http: + // transactionMethod = 'http'; + // break; + // case MwcTransactionMethod.unknown: + // // Auto-detect from address format. + // final mwcCoin = coin as Mimblewimblecoin; + // final method = mwcCoin.getTransactionMethod(_address!); + // transactionMethod = method.toString().split('.').last; + // break; } } else { // Auto-detect from address format if no method selected. @@ -1136,11 +1136,11 @@ class _SendViewState extends ConsumerState { ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); - // Initialize MWC transaction method to default (non-slatepack). - if (coin is Mimblewimblecoin) { - ref.read(pSelectedMwcTransactionMethod.notifier).state = - null; // No method selected initially. - } + // // Initialize MWC transaction method to default (non-slatepack). + // if (coin is Mimblewimblecoin) { + // ref.read(pSelectedMwcTransactionMethod.notifier).state = + // null; // No method selected initially. + // } }); isCustomFee.addListener(() { if (!isCustomFee.value) { @@ -1650,7 +1650,7 @@ class _SendViewState extends ConsumerState { decoration: standardInputDecoration( coin is Mimblewimblecoin && _selectedTransactionMethod == - TransactionMethod + MwcTransactionMethod .slatepack || _selectedTransactionMethod == null ? "Enter ${coin.ticker} address (optional)" diff --git a/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart b/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart index 33cbcf5123..123656faf5 100644 --- a/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart +++ b/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import '../../../models/mwc_transaction_method.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; +import '../../../utilities/enums/mwc_transaction_method.dart'; import '../../../utilities/text_styles.dart'; import '../../../widgets/rounded_white_container.dart'; @@ -16,8 +16,8 @@ class MwcTransactionMethodSelector extends ConsumerStatefulWidget { this.addressText, }); - final void Function(TransactionMethod) onMethodSelected; - final TransactionMethod? selectedMethod; + final void Function(MwcTransactionMethod) onMethodSelected; + final MwcTransactionMethod? selectedMethod; final String? addressText; @override @@ -27,7 +27,7 @@ class MwcTransactionMethodSelector extends ConsumerStatefulWidget { class _MwcTransactionMethodSelectorState extends ConsumerState { - TransactionMethod? _selectedMethod; + MwcTransactionMethod? _selectedMethod; @override void initState() { @@ -35,7 +35,7 @@ class _MwcTransactionMethodSelectorState _selectedMethod = widget.selectedMethod; } - void _selectMethod(TransactionMethod method) { + void _selectMethod(MwcTransactionMethod method) { setState(() { _selectedMethod = method; }); @@ -43,7 +43,7 @@ class _MwcTransactionMethodSelectorState } Widget _buildMethodTile({ - required TransactionMethod method, + required MwcTransactionMethod method, required String title, required String subtitle, required IconData icon, @@ -197,7 +197,7 @@ class _MwcTransactionMethodSelectorState children: [ // Slatepack method. _buildMethodTile( - method: TransactionMethod.slatepack, + method: MwcTransactionMethod.slatepack, title: "Slatepack (Manual)", subtitle: "Copy/paste, QR codes, or files", icon: Icons.qr_code, @@ -207,24 +207,24 @@ class _MwcTransactionMethodSelectorState // MWCMQS method. _buildMethodTile( - method: TransactionMethod.mwcmqs, + method: MwcTransactionMethod.mwcmqs, title: "MWCMQS (Automatic)", subtitle: "Direct messaging to recipient", icon: Icons.message, available: true, unavailableReason: "Requires MWCMQS address", ), - const SizedBox(height: 12), - - // HTTP method. - _buildMethodTile( - method: TransactionMethod.http, - title: "HTTP (Direct)", - subtitle: "Direct connection to wallet", - icon: Icons.http, - available: true, - unavailableReason: "Requires HTTP address", - ), + // const SizedBox(height: 12), + // + // // HTTP method. + // _buildMethodTile( + // method: MwcTransactionMethod.http, + // title: "HTTP (Direct)", + // subtitle: "Direct connection to wallet", + // icon: Icons.http, + // available: true, + // unavailableReason: "Requires HTTP address", + // ), ], ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart index 4f157c88d4..1257d464cc 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart @@ -11,17 +11,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../providers/ui/preview_tx_button_state_provider.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/enums/mwc_transaction_method.dart'; import '../../../../utilities/util.dart'; import '../../../../widgets/toggle.dart'; class MwcTxsMethodToggle extends ConsumerWidget { - const MwcTxsMethodToggle({super.key, this.onChanged}); - - final void Function(TxsMethodMwcType)? onChanged; + const MwcTxsMethodToggle({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -33,13 +32,19 @@ class MwcTxsMethodToggle extends ConsumerWidget { // Align visual labels with callback semantics: // When the toggle shows "Slatepack" (on), emit slatepack. // When it shows "Automatic" (off), emit automatic. - if (value) { - onChanged?.call(TxsMethodMwcType.slatepack); - } else { - onChanged?.call(TxsMethodMwcType.automatic); - } + // if (value) { + // onChanged?.call(TxsMethodMwcType.slatepack); + // } else { + // onChanged?.call(TxsMethodMwcType.automatic); + // } + ref.read(pSelectedMwcTransactionMethod.notifier).state = + value + ? MwcTransactionMethod.mwcmqs + : MwcTransactionMethod.slatepack; }, - isOn: false, + isOn: + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.mwcmqs, onColor: isDesktop ? Theme.of( @@ -62,9 +67,9 @@ class MwcTxsMethodToggle extends ConsumerWidget { ), ), onIcon: Assets.svg.gear, - onText: TxsMethodMwcType.slatepack.value, + onText: "Slatepack", offIcon: Assets.svg.radioSyncing, - offText: TxsMethodMwcType.automatic.value, + offText: "Automatic", ); } } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 1629f14c81..b907489d95 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -24,6 +24,7 @@ import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; import '../../../../providers/providers.dart'; +import '../../../../providers/ui/preview_tx_button_state_provider.dart'; import '../../../../route_generator.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; @@ -31,7 +32,7 @@ import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/derive_path_type_enum.dart'; -import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/enums/mwc_transaction_method.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; @@ -92,7 +93,6 @@ class _DesktopReceiveState extends ConsumerState { final _addressFocusNode = FocusNode(); int _currentIndex = 0; - late TxsMethodMwcType _selectedMethodMwc; String? _note; final List _walletAddressTypes = []; @@ -116,7 +116,10 @@ class _DesktopReceiveState extends ConsumerState { paymentData.coin?.uriScheme == coin.uriScheme) { _address = paymentData.address; receiveSlateController.text = - _selectedMethodMwc == "Slatepack" ? "" : _address!; + ref.read(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack + ? "" + : _address!; setState(() { _addressToggleFlag = receiveSlateController.text.isNotEmpty; }); @@ -307,10 +310,6 @@ class _DesktopReceiveState extends ConsumerState { wallet.info.isMwebEnabled; isMimblewimblecoin = wallet is MimblewimblecoinWallet; - if (isMimblewimblecoin) { - _selectedMethodMwc = TxsMethodMwcType.slatepack; - } - debugPrint("Address generated: $isMimblewimblecoin"); if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { showMultiType = false; @@ -489,16 +488,10 @@ class _DesktopReceiveState extends ConsumerState { width: 1, ), ), - child: SizedBox( + child: const SizedBox( height: - 50, // Provide an explicit height to avoid infinite constraints - child: MwcTxsMethodToggle( - onChanged: (TxsMethodMwcType type) { - setState(() { - _selectedMethodMwc = type; - }); - }, - ), + 60, // Provide an explicit height to avoid infinite constraints + child: MwcTxsMethodToggle(), ), ), ), @@ -679,7 +672,8 @@ class _DesktopReceiveState extends ConsumerState { ), const SizedBox(height: 20), if (isMimblewimblecoin && - _selectedMethodMwc == TxsMethodMwcType.slatepack) + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -815,7 +809,8 @@ class _DesktopReceiveState extends ConsumerState { // TODO: create transparent button class to account for hover // Conditional logic for 'Submit' button or QR code if (isMimblewimblecoin && - _selectedMethodMwc == TxsMethodMwcType.slatepack) + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: PrimaryButton( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index c6834bfdb0..732d0e05e4 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -20,7 +20,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../../models/isar/models/blockchain_data/address.dart'; import '../../../../models/isar/models/blockchain_data/utxo.dart'; import '../../../../models/isar/models/contact_entry.dart'; -import '../../../../models/mwc_transaction_method.dart'; import '../../../../models/paynym/paynym_account_lite.dart'; import '../../../../models/send_view_auto_fill_data.dart'; import '../../../../pages/send_view/confirm_transaction_view.dart'; @@ -42,7 +41,7 @@ import '../../../../utilities/amount/amount_unit.dart'; import '../../../../utilities/assets.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/txs_method_mwc_enum.dart'; +import '../../../../utilities/enums/mwc_transaction_method.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; import '../../../../utilities/text_styles.dart'; @@ -297,7 +296,7 @@ class _DesktopSendState extends ConsumerState { // Handle MWC slatepack transactions directly. if (isMimblewimblecoin && ref.read(pSelectedMwcTransactionMethod) == - TransactionMethod.slatepack) { + MwcTransactionMethod.slatepack) { await _handleDesktopSlatepackCreation(wallet as MimblewimblecoinWallet); return; } @@ -1082,13 +1081,6 @@ class _DesktopSendState extends ConsumerState { isStellar = coin is Stellar; isMimblewimblecoin = coin is Mimblewimblecoin; - if (isMimblewimblecoin) { - // Initialize provider to slatepack method. - WidgetsBinding.instance.addPostFrameCallback((_) { - ref.read(pSelectedMwcTransactionMethod.notifier).state = - TransactionMethod.slatepack; - }); - } sendToController = TextEditingController(); cryptoAmountController = TextEditingController(); @@ -1234,7 +1226,7 @@ class _DesktopSendState extends ConsumerState { if (showPrivateBalance) const SizedBox(height: 4), if (isMimblewimblecoin) Padding( - padding: const EdgeInsets.all(0), + padding: const EdgeInsets.only(bottom: 16), child: Container( decoration: BoxDecoration( color: @@ -1252,19 +1244,10 @@ class _DesktopSendState extends ConsumerState { width: 1, ), ), - child: SizedBox( + child: const SizedBox( height: - 50, // Provide an explicit height to avoid infinite constraints - child: MwcTxsMethodToggle( - onChanged: (TxsMethodMwcType type) { - // Update the provider directly. - ref.read(pSelectedMwcTransactionMethod.notifier).state = - type == TxsMethodMwcType.slatepack - ? TransactionMethod.mwcmqs - : TransactionMethod.slatepack; - setState(() {}); // Trigger rebuild - }, - ), + 60, // Provide an explicit height to avoid infinite constraints + child: MwcTxsMethodToggle(), ), ), ), @@ -1639,7 +1622,7 @@ class _DesktopSendState extends ConsumerState { ), decoration: standardInputDecoration( ref.watch(pSelectedMwcTransactionMethod) == - TransactionMethod.slatepack + MwcTransactionMethod.slatepack ? "Enter ${coin.ticker} address (optional)" : "Enter ${coin.ticker} address", _addressFocusNode, @@ -1942,7 +1925,7 @@ class _DesktopSendState extends ConsumerState { buttonHeight: ButtonHeight.l, label: ref.read(pSelectedMwcTransactionMethod) == - TransactionMethod.slatepack + MwcTransactionMethod.slatepack ? "Create slatepack" : "Preview send", enabled: ref.watch(pPreviewTxButtonEnabled(coin)), diff --git a/lib/providers/ui/preview_tx_button_state_provider.dart b/lib/providers/ui/preview_tx_button_state_provider.dart index f8955f8888..de20131e11 100644 --- a/lib/providers/ui/preview_tx_button_state_provider.dart +++ b/lib/providers/ui/preview_tx_button_state_provider.dart @@ -10,8 +10,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../models/mwc_transaction_method.dart'; import '../../utilities/amount/amount.dart'; +import '../../utilities/enums/mwc_transaction_method.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; import '../wallet/public_private_balance_state_provider.dart'; @@ -22,8 +22,9 @@ final pValidSparkSendToAddress = StateProvider.autoDispose((_) => false); final pIsExchangeAddress = StateProvider((_) => false); // MWC Transaction Method Provider. -final pSelectedMwcTransactionMethod = - StateProvider.autoDispose((_) => null); +final pSelectedMwcTransactionMethod = StateProvider( + (_) => MwcTransactionMethod.slatepack, +); final pPreviewTxButtonEnabled = Provider.autoDispose .family((ref, coin) { @@ -32,7 +33,7 @@ final pPreviewTxButtonEnabled = Provider.autoDispose // For MWC slatepack transactions, address validation is not required. if (coin is Mimblewimblecoin) { final selectedMethod = ref.watch(pSelectedMwcTransactionMethod); - if (selectedMethod == TransactionMethod.slatepack) { + if (selectedMethod == MwcTransactionMethod.slatepack) { return amount > Amount.zero; } } diff --git a/lib/utilities/enums/mwc_transaction_method.dart b/lib/utilities/enums/mwc_transaction_method.dart new file mode 100644 index 0000000000..4b24ad37f7 --- /dev/null +++ b/lib/utilities/enums/mwc_transaction_method.dart @@ -0,0 +1,70 @@ +/// Enum to represent different MWC transaction methods. +enum MwcTransactionMethod { + /// Manual slatepack exchange (copy/paste, QR codes, files). + slatepack, + + /// Automatic transaction via MWCMQS. + mwcmqs; + + // /// Direct HTTP/HTTPS to recipient's wallet. + // http, + // + // /// Unknown or unsupported method. + // unknown; + + /// Human readable name for the transaction method. + String get displayName { + switch (this) { + case MwcTransactionMethod.slatepack: + return 'Slatepack'; + case MwcTransactionMethod.mwcmqs: + return 'MWCMQS'; + // case MwcTransactionMethod.http: + // return 'HTTP'; + // case MwcTransactionMethod.unknown: + // return 'Unknown'; + } + } + + /// Description of how the transaction method works. + String get description { + switch (this) { + case MwcTransactionMethod.slatepack: + return 'Manual exchange via text, QR codes, or files'; + case MwcTransactionMethod.mwcmqs: + return 'Automatic exchange via MWCMQS messaging'; + // case MwcTransactionMethod.http: + // return 'Direct connection to recipient wallet'; + // case MwcTransactionMethod.unknown: + // return 'Unsupported transaction method'; + } + } + + /// Whether this method requires manual intervention. + bool get isManual { + switch (this) { + case MwcTransactionMethod.slatepack: + return true; + case MwcTransactionMethod.mwcmqs: + return false; + // case MwcTransactionMethod.http: + // return false; + // case MwcTransactionMethod.unknown: + // return true; + } + } + + /// Whether this method works offline. + bool get worksOffline { + switch (this) { + case MwcTransactionMethod.slatepack: + return true; + case MwcTransactionMethod.mwcmqs: + return false; + // case MwcTransactionMethod.http: + // return false; + // case MwcTransactionMethod.unknown: + // return false; + } + } +} diff --git a/lib/utilities/enums/txs_method_mwc_enum.dart b/lib/utilities/enums/txs_method_mwc_enum.dart deleted file mode 100644 index 23c04ab08a..0000000000 --- a/lib/utilities/enums/txs_method_mwc_enum.dart +++ /dev/null @@ -1,18 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -enum TxsMethodMwcType { - slatepack("Slatepack"), - automatic("Automatic"); - - final String value; - - const TxsMethodMwcType(this.value); -} diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 9e9f962e05..c495d58f0f 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -1,10 +1,10 @@ import 'package:flutter_libmwc/lib.dart' as mimblewimblecoin; import '../../../models/isar/models/blockchain_data/address.dart'; -import '../../../models/mwc_transaction_method.dart'; import '../../../models/node_model.dart'; import '../../../utilities/default_nodes.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../../../utilities/enums/mwc_transaction_method.dart'; import '../crypto_currency.dart'; import '../intermediate/bip39_currency.dart'; @@ -92,15 +92,16 @@ class Mimblewimblecoin extends Bip39Currency { } /// Detect transaction type based on address/data format. - TransactionMethod getTransactionMethod(String addressOrData) { + MwcTransactionMethod getTransactionMethod(String addressOrData) { if (isSlatepack(addressOrData)) { - return TransactionMethod.slatepack; + return MwcTransactionMethod.slatepack; } else if (isMwcmqsAddress(addressOrData)) { - return TransactionMethod.mwcmqs; - } else if (isHttpAddress(addressOrData)) { - return TransactionMethod.http; + return MwcTransactionMethod.mwcmqs; + // } else if (isHttpAddress(addressOrData)) { + // return MwcTransactionMethod.http; } else { - return TransactionMethod.unknown; + throw Exception("Unknown MwcTransactionMethod found!"); + // return MwcTransactionMethod.unknown; } } From e85accbeaea5b6c58e979a89eb3e4e4c0c09f5a9 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 14:05:57 -0600 Subject: [PATCH 48/91] WIP partial refactor --- .../mwc_slatepack_import_dialog.dart | 340 +++++++++--------- .../sub_widgets/desktop_receive.dart | 14 +- .../custom_buttons/simple_paste_button.dart | 60 ++++ 3 files changed, 228 insertions(+), 186 deletions(-) create mode 100644 lib/widgets/custom_buttons/simple_paste_button.dart diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index ba7cf88278..a8c6b3920b 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -1,19 +1,21 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import '../../../notifications/show_flush_bar.dart'; import '../../../models/mwc_slatepack_models.dart'; +import '../../../notifications/show_flush_bar.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/constants.dart'; import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; import '../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/custom_buttons/simple_paste_button.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/rounded_white_container.dart'; @@ -58,11 +60,10 @@ class _MwcSlatepackImportDialogState super.dispose(); } - void _pasteFromClipboard() async { + void _pasteFromClipboard(String? content) async { try { - final data = await widget.clipboard.getData(Clipboard.kTextPlain); - if (data?.text != null) { - slatepackController.text = data!.text!; + if (content != null) { + slatepackController.text = content; _validateSlatepack(); } } catch (e) { @@ -106,7 +107,7 @@ class _MwcSlatepackImportDialogState final String slatepackType = switch (analysis.status) { 'S1' => "S1 (Initial Send)", - 'S2' => "S2 (Response)", + 'S2' => "S2 (Response)", 'S3' => "S3 (Finalized)", _ => _determineSlatepackType(decoded), // Fallback. }; @@ -218,211 +219,190 @@ class _MwcSlatepackImportDialogState @override Widget build(BuildContext context) { - final isDesktop = - Platform.isLinux || Platform.isMacOS || Platform.isWindows; + final isDesktop = Util.isDesktop; final canProcess = _decodedSlatepack != null && !_isProcessing; - return StackDialogBase( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Import Slatepack", + style: STextStyles.pageTitleH2(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: () async { + if (!_isProcessing) { + Navigator.of(context).pop(); + } + }, + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text("Import Slatepack", style: STextStyles.pageTitleH2(context)), - AppBarIconButton( - size: 36, - color: Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.x, - color: - Theme.of( - context, - ).extension()!.topNavIconPrimary, - width: 24, - height: 24, - ), - onPressed: - _isProcessing ? null : () => Navigator.of(context).pop(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Enter a slatepack to process the transaction.", + style: STextStyles.subtitle(context), + textAlign: TextAlign.center, + ), + SimplePasteButton( + onPaste: _pasteFromClipboard, + clipboard: widget.clipboard, + ), + ], ), - ], - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Paste or enter a slatepack to process the transaction.", - style: STextStyles.subtitle(context), - textAlign: TextAlign.center, + const SizedBox(height: 24), + + // Slatepack input field. + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - const SizedBox(height: 24), + child: TextField( + controller: slatepackController, + focusNode: slatepackFocusNode, + maxLines: 8, + onChanged: (_) => _validateSlatepack(), + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.all(16), + hintStyle: STextStyles.fieldLabel(context), + hintText: "BEGINSLATEPACK...", + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ), - // Slatepack input field. - Container( - decoration: BoxDecoration( - color: - Theme.of( - context, - ).extension()!.textFieldDefaultBG, - borderRadius: BorderRadius.circular(8), - border: Border.all( + // Validation status. + if (_validationError != null) ...[ + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.error_outline, + size: 16, color: - Theme.of(context) - .extension()! - .textFieldDefaultSearchIconLeft, + Theme.of(context).extension()!.textError, ), - ), - child: TextField( - controller: slatepackController, - focusNode: slatepackFocusNode, - maxLines: 8, - onChanged: (_) => _validateSlatepack(), - decoration: InputDecoration( - hintText: "BEGINSLATEPACK...", - labelText: "Slatepack", - border: InputBorder.none, - contentPadding: const EdgeInsets.all(12), - suffixIcon: IconButton( - onPressed: _pasteFromClipboard, - icon: SvgPicture.asset( - Assets.svg.clipboard, - width: 16, - height: 16, + const SizedBox(width: 8), + Expanded( + child: Text( + _validationError!, + style: STextStyles.w400_14(context).copyWith( color: - Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + Theme.of( + context, + ).extension()!.textError, ), ), ), - ), + ], ), - - // Validation status. - if (_validationError != null) ...[ - const SizedBox(height: 8), - Row( + ] else if (_decodedSlatepack != null) ...[ + const SizedBox(height: 8), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - Icons.error_outline, - size: 16, - color: - Theme.of( - context, - ).extension()!.textError, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - _validationError!, - style: STextStyles.w400_14(context).copyWith( + Row( + children: [ + Icon( + Icons.check_circle_outline, + size: 16, color: Theme.of( context, - ).extension()!.textError, + ).extension()!.accentColorGreen, ), - ), - ), - ], - ), - ] else if (_decodedSlatepack != null) ...[ - const SizedBox(height: 8), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.check_circle_outline, - size: 16, + const SizedBox(width: 8), + Text( + "Valid Slatepack", + style: STextStyles.label(context).copyWith( color: Theme.of( context, ).extension()!.accentColorGreen, ), - const SizedBox(width: 8), - Text( - "Valid Slatepack", - style: STextStyles.label(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorGreen, - ), - ), - ], + ), + ], + ), + const SizedBox(height: 8), + Text( + "Type: $_slatepackType", + style: STextStyles.w400_14(context), + ), + if (_decodedSlatepack!.wasEncrypted == true) + Text( + "Encrypted: Yes", + style: STextStyles.w400_14(context), ), - const SizedBox(height: 8), + if (_decodedSlatepack!.senderAddress != null) Text( - "Type: $_slatepackType", + "From: ${_decodedSlatepack!.senderAddress}", style: STextStyles.w400_14(context), ), - if (_decodedSlatepack!.wasEncrypted == true) - Text( - "Encrypted: Yes", - style: STextStyles.w400_14(context), - ), - if (_decodedSlatepack!.senderAddress != null) - Text( - "From: ${_decodedSlatepack!.senderAddress}", - style: STextStyles.w400_14(context), - ), - ], - ), + ], ), - ], - - const SizedBox(height: 24), + ), + ], - // Action buttons. - if (isDesktop) ...[ - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - onPressed: - _isProcessing - ? null - : () => Navigator.of(context).pop(), - ), + const SizedBox(height: 24), + + // Action buttons. + if (isDesktop) ...[ + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: + _isProcessing + ? null + : () => Navigator.of(context).pop(), ), - const SizedBox(width: 12), - Expanded( - child: PrimaryButton( - label: _isProcessing ? "Processing..." : "Process", - onPressed: canProcess ? _processSlatepack : null, - ), + ), + const SizedBox(width: 12), + Expanded( + child: PrimaryButton( + label: _isProcessing ? "Processing..." : "Process", + onPressed: canProcess ? _processSlatepack : null, ), - ], - ), - ] else ...[ - PrimaryButton( - label: - _isProcessing ? "Processing..." : "Process Slatepack", - onPressed: canProcess ? _processSlatepack : null, - ), - const SizedBox(height: 12), - SecondaryButton( - label: "Cancel", - onPressed: - _isProcessing - ? null - : () => Navigator.of(context).pop(), - ), - ], + ), + ], + ), + ] else ...[ + PrimaryButton( + label: _isProcessing ? "Processing..." : "Process Slatepack", + onPressed: canProcess ? _processSlatepack : null, + ), + const SizedBox(height: 12), + SecondaryButton( + label: "Cancel", + onPressed: + _isProcessing ? null : () => Navigator.of(context).pop(), + ), ], - ), + ], ), - ], - ), + ), + const SizedBox(height: 32), + ], ); } } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index b907489d95..93dfdf56ae 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -55,6 +55,7 @@ import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/dialogs/s_dialog.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/qr.dart'; @@ -822,12 +823,13 @@ class _DesktopReceiveState extends ConsumerState { await showDialog( context: context, builder: - (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 700, - child: MwcSlatepackImportDialog( - wallet: wallet as MimblewimblecoinWallet, - clipboard: widget.clipboard, + (context) => SDialog( + child: SizedBox( + width: 700, + child: MwcSlatepackImportDialog( + wallet: wallet as MimblewimblecoinWallet, + clipboard: widget.clipboard, + ), ), ), ); diff --git a/lib/widgets/custom_buttons/simple_paste_button.dart b/lib/widgets/custom_buttons/simple_paste_button.dart new file mode 100644 index 0000000000..4dfdf3c7b4 --- /dev/null +++ b/lib/widgets/custom_buttons/simple_paste_button.dart @@ -0,0 +1,60 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2025 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2025-09-16 + * + */ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/clipboard_interface.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../conditional_parent.dart'; + +class SimplePasteButton extends StatelessWidget { + const SimplePasteButton({ + super.key, + required this.onPaste, + this.clipboard = const ClipboardWrapper(), + }); + + final void Function(String?) onPaste; + final ClipboardInterface clipboard; + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: Util.isDesktop, + builder: + (child) => + MouseRegion(cursor: SystemMouseCursors.click, child: child), + child: GestureDetector( + onTap: () async { + // TODO handle async better here!!!!!!!!!!!!!!!!!!!!!!!! + final data = await clipboard.getData(Clipboard.kTextPlain); + onPaste(data?.text); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.clipboard, + width: 10, + height: 10, + color: Theme.of(context).extension()!.infoItemIcons, + ), + const SizedBox(width: 4), + Text("Paste", style: STextStyles.link2(context)), + ], + ), + ), + ); + } +} From 8dec49d4238e018b6f345f82504693649a51108d Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 16 Sep 2025 15:36:36 -0500 Subject: [PATCH 49/91] fix(mwc): hide "Send to" field when Slatepack tx method selected it currently does nothing. MWC supports encrypted slatepacks when supplied with an address, but this isn't implemented yet. --- .../wallet_view/sub_widgets/desktop_send.dart | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 732d0e05e4..aef8fa2e1a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1551,7 +1551,10 @@ class _DesktopSendState extends ConsumerState { ), ), const SizedBox(height: 20), - if (!isPaynymSend) + if (!isPaynymSend && + !(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) Text( "Send to", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -1562,8 +1565,15 @@ class _DesktopSendState extends ConsumerState { ), textAlign: TextAlign.left, ), - if (!isPaynymSend) const SizedBox(height: 10), - if (!isPaynymSend) + if (!isPaynymSend && + !(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) + const SizedBox(height: 10), + if (!isPaynymSend && + !(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1742,7 +1752,10 @@ class _DesktopSendState extends ConsumerState { ), ), ), - if (!isPaynymSend) + if (!isPaynymSend && + !(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) Builder( builder: (_) { final String? error; @@ -1761,6 +1774,12 @@ class _DesktopSendState extends ConsumerState { } else { if (_data != null && _data.contactLabel == _address) { error = null; + } else if (coin is Mimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack) { + // For MWC slatepack transactions, address validation is not required. + // TODO: When implementing encrypted slatepacks, address validation will be required. + error = null; } else if (!ref.watch(pValidSendToAddress)) { error = "Invalid address"; } else { From c7cfe6b23fda5821372fa8e4a7c7f7fbb6adf64b Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 16 Sep 2025 14:51:53 -0600 Subject: [PATCH 50/91] clean up bad copy pasted code --- .../sub_widgets/desktop_receive.dart | 137 ++++++------------ 1 file changed, 47 insertions(+), 90 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 93dfdf56ae..382b1a3a96 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -33,7 +33,6 @@ import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/derive_path_type_enum.dart'; import '../../../../utilities/enums/mwc_transaction_method.dart'; -import '../../../../utilities/logger.dart'; import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; @@ -88,69 +87,25 @@ class _DesktopReceiveState extends ConsumerState { late bool supportsMweb; late final bool showMultiType; late final bool isMimblewimblecoin; - late TextEditingController receiveSlateController; - String? _address; - bool _addressToggleFlag = false; - final _addressFocusNode = FocusNode(); + late TextEditingController _receiveSlateController; + String? _slate; + bool _slateToggleFlag = false; + final _slateFocusNode = FocusNode(); int _currentIndex = 0; - String? _note; final List _walletAddressTypes = []; final Map _addressMap = {}; final Map> _addressSubMap = {}; - Future pasteAddress() async { + Future _pasteSlatepack() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { - String content = data.text!.trim(); - if (content.contains("\n")) { - content = content.substring(0, content.indexOf("\n")); - } - - try { - final paymentData = AddressUtils.parsePaymentUri( - content, - logging: Logging.instance, - ); - if (paymentData != null && - paymentData.coin?.uriScheme == coin.uriScheme) { - _address = paymentData.address; - receiveSlateController.text = - ref.read(pSelectedMwcTransactionMethod) == - MwcTransactionMethod.slatepack - ? "" - : _address!; - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } else { - content = content.split("\n").first.trim(); - if (coin is Mimblewimblecoin) { - content = AddressUtils().formatAddressMwc(content); - } - - receiveSlateController.text = content; - _address = content; - - //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } - } catch (e) { - if (coin is Mimblewimblecoin) { - // strip http:// and https:// if content contains @ - content = AddressUtils().formatAddressMwc(content); - } - receiveSlateController.text = content; - _address = content; - // Trigger validation after pasting. - //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } + _slate = data.text; + _receiveSlateController.text = _slate!; + setState(() { + _slateToggleFlag = _receiveSlateController.text.isNotEmpty; + }); } } @@ -299,7 +254,7 @@ class _DesktopReceiveState extends ConsumerState { @override void initState() { - receiveSlateController = TextEditingController(); + _receiveSlateController = TextEditingController(); walletId = widget.walletId; coin = ref.read(pWalletInfo(walletId)).coin; clipboard = widget.clipboard; @@ -383,6 +338,7 @@ class _DesktopReceiveState extends ConsumerState { @override void dispose() { + _receiveSlateController.dispose(); for (final subscription in _addressSubMap.values) { subscription.cancel(); } @@ -697,8 +653,8 @@ class _DesktopReceiveState extends ConsumerState { child: TextField( minLines: 1, maxLines: 5, - key: const Key("sendViewAddressFieldKey"), - controller: receiveSlateController, + key: const Key("receiveViewSlatepackFieldKey"), + controller: _receiveSlateController, readOnly: false, autocorrect: false, enableSuggestions: false, @@ -709,14 +665,12 @@ class _DesktopReceiveState extends ConsumerState { selectAll: false, ), onChanged: (newValue) { - _address = newValue; - //_setValidAddressProviders(_address); - + _slate = newValue; setState(() { - _addressToggleFlag = newValue.isNotEmpty; + _slateToggleFlag = newValue.isNotEmpty; }); }, - focusNode: _addressFocusNode, + focusNode: _slateFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of( @@ -726,7 +680,7 @@ class _DesktopReceiveState extends ConsumerState { ), decoration: standardInputDecoration( "Enter Slatepack Message", - _addressFocusNode, + _slateFocusNode, context, desktopMed: true, ).copyWith( @@ -737,34 +691,34 @@ class _DesktopReceiveState extends ConsumerState { ), suffixIcon: Padding( padding: - receiveSlateController.text.isEmpty + _receiveSlateController.text.isEmpty ? const EdgeInsets.only(right: 8) : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _addressToggleFlag + _slateToggleFlag ? TextFieldIconButton( key: const Key( - "sendViewClearAddressFieldButtonKey", + "receiveViewClearSlatepackFieldButtonKey", ), onTap: () { - receiveSlateController.text = ""; - _address = ""; + _receiveSlateController.text = ""; + _slate = ""; setState(() { - _addressToggleFlag = false; + _slateToggleFlag = false; }); }, child: const XIcon(), ) : TextFieldIconButton( key: const Key( - "sendViewPasteAddressFieldButtonKey", + "receiveViewPasteSlatepackFieldButtonKey", ), - onTap: pasteAddress, + onTap: _pasteSlatepack, child: - receiveSlateController.text.isEmpty + _receiveSlateController.text.isEmpty ? const ClipboardIcon() : const XIcon(), ), @@ -817,23 +771,26 @@ class _DesktopReceiveState extends ConsumerState { child: PrimaryButton( buttonHeight: ButtonHeight.l, label: "Receive Slatepack", - enabled: true, - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); - await showDialog( - context: context, - builder: - (context) => SDialog( - child: SizedBox( - width: 700, - child: MwcSlatepackImportDialog( - wallet: wallet as MimblewimblecoinWallet, - clipboard: widget.clipboard, - ), - ), - ), - ); - }, + enabled: _slateToggleFlag, + onPressed: + _slateToggleFlag + ? () async { + final wallet = ref.read(pWallets).getWallet(walletId); + await showDialog( + context: context, + builder: + (context) => SDialog( + child: SizedBox( + width: 700, + child: MwcSlatepackImportDialog( + wallet: wallet as MimblewimblecoinWallet, + clipboard: widget.clipboard, + ), + ), + ), + ); + } + : null, ), ) else From 6ce184128bf7f612e4f44835f44b81bd67a039da Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 10:09:07 -0600 Subject: [PATCH 51/91] add detail divider widget --- lib/widgets/detail_item.dart | 133 +++++++++++++++++------------------ 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/lib/widgets/detail_item.dart b/lib/widgets/detail_item.dart index 13e8975d31..91a662538d 100644 --- a/lib/widgets/detail_item.dart +++ b/lib/widgets/detail_item.dart @@ -35,9 +35,9 @@ class DetailItem extends StatelessWidget { TextStyle detailStyle = STextStyles.w500_14(context); String _detail = detail; if (overrideDetailTextColor != null) { - detailStyle = STextStyles.w500_14(context).copyWith( - color: overrideDetailTextColor, - ); + detailStyle = STextStyles.w500_14( + context, + ).copyWith(color: overrideDetailTextColor); } if (detail.isEmpty && showEmptyDetail) { @@ -51,24 +51,14 @@ class DetailItem extends StatelessWidget { horizontal: horizontal, borderColor: borderColor, expandDetail: expandDetail, - title: disableSelectableText - ? Text( - title, - style: STextStyles.itemSubtitle(context), - ) - : SelectableText( - title, - style: STextStyles.itemSubtitle(context), - ), - detail: disableSelectableText - ? Text( - _detail, - style: detailStyle, - ) - : SelectableText( - _detail, - style: detailStyle, - ), + title: + disableSelectableText + ? Text(title, style: STextStyles.itemSubtitle(context)) + : SelectableText(title, style: STextStyles.itemSubtitle(context)), + detail: + disableSelectableText + ? Text(_detail, style: detailStyle) + : SelectableText(_detail, style: detailStyle), ); } } @@ -95,56 +85,65 @@ class DetailItemBase extends StatelessWidget { Widget build(BuildContext context) { return ConditionalParent( condition: !Util.isDesktop || borderColor != null, - builder: (child) => RoundedWhiteContainer( - padding: Util.isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), - borderColor: borderColor, - child: child, - ), + builder: + (child) => RoundedWhiteContainer( + padding: + Util.isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + borderColor: borderColor, + child: child, + ), child: ConditionalParent( condition: Util.isDesktop && borderColor == null, - builder: (child) => Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - child: horizontal - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - title, - if (expandDetail) - const SizedBox( - width: 16, + builder: + (child) => Padding(padding: const EdgeInsets.all(16), child: child), + child: + horizontal + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + title, + if (expandDetail) const SizedBox(width: 16), + ConditionalParent( + condition: expandDetail, + builder: (child) => Expanded(child: child), + child: detail, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [title, button ?? Container()], ), - ConditionalParent( - condition: expandDetail, - builder: (child) => Expanded(child: child), - child: detail, - ), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - title, - button ?? Container(), - ], - ), - const SizedBox( - height: 5, - ), - ConditionalParent( - condition: expandDetail, - builder: (child) => Expanded(child: child), - child: detail, - ), - ], - ), + const SizedBox(height: 5), + ConditionalParent( + condition: expandDetail, + builder: (child) => Expanded(child: child), + child: detail, + ), + ], + ), ), ); } } + +class DetailDivider extends StatelessWidget { + const DetailDivider({super.key}); + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Container( + height: 1, + color: Theme.of(context).extension()!.backgroundAppBar, + ); + } else { + return const SizedBox(height: 12); + } + } +} From a57a72c7b9bea970efcbe9cbc3660cc5357bdb90 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 10:09:40 -0600 Subject: [PATCH 52/91] add toString functions to mwc slate response objects --- lib/models/mwc_slatepack_models.dart | 48 +++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/models/mwc_slatepack_models.dart b/lib/models/mwc_slatepack_models.dart index f8a30fb3f5..8b5c05b832 100644 --- a/lib/models/mwc_slatepack_models.dart +++ b/lib/models/mwc_slatepack_models.dart @@ -14,6 +14,18 @@ class SlatepackResult { this.wasEncrypted, this.recipientAddress, }); + + @override + String toString() { + return "SlatepackResult(" + "success: $success, " + "error: $error, " + "slatepack: $slatepack, " + "slateJson: $slateJson, " + "wasEncrypted: $wasEncrypted, " + "recipientAddress: $recipientAddress" + ")"; + } } class SlatepackDecodeResult { @@ -32,6 +44,18 @@ class SlatepackDecodeResult { this.senderAddress, this.recipientAddress, }); + + @override + String toString() { + return "SlatepackDecodeResult(" + "success: $success, " + "error: $error, " + "slateJson: $slateJson, " + "wasEncrypted: $wasEncrypted, " + "senderAddress: $senderAddress, " + "recipientAddress: $recipientAddress" + ")"; + } } class ReceiveResult { @@ -52,6 +76,19 @@ class ReceiveResult { this.wasEncrypted, this.recipientAddress, }); + + @override + String toString() { + return "ReceiveResult(" + "success: $success, " + "error: $error, " + "slateId: $slateId, " + "commitId: $commitId, " + "responseSlatepack: $responseSlatepack, " + "wasEncrypted: $wasEncrypted, " + "recipientAddress: $recipientAddress" + ")"; + } } class FinalizeResult { @@ -66,5 +103,14 @@ class FinalizeResult { this.slateId, this.commitId, }); -} + @override + String toString() { + return "FinalizeResult(" + "success: $success, " + "error: $error, " + "slateId: $slateId, " + "commitId: $commitId" + ")"; + } +} From 63b1ac76fb390925fa76628ca70edc29e14e2137 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 10:11:03 -0600 Subject: [PATCH 53/91] desktop receive slatepack ui updates --- .../mwc_slatepack_import_dialog.dart | 416 +++++------------- .../sub_widgets/desktop_receive.dart | 404 ++++++++++------- 2 files changed, 352 insertions(+), 468 deletions(-) diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index a8c6b3920b..161f46d8e4 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -5,30 +5,35 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../models/mwc_slatepack_models.dart'; import '../../../notifications/show_flush_bar.dart'; +import '../../../providers/global/wallets_provider.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; -import '../../../utilities/constants.dart'; +import '../../../utilities/show_loading.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; -import '../../../wallets/crypto_currency/coins/mimblewimblecoin.dart'; import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../../widgets/conditional_parent.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; -import '../../../widgets/custom_buttons/simple_paste_button.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/detail_item.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/stack_dialog.dart'; class MwcSlatepackImportDialog extends ConsumerStatefulWidget { const MwcSlatepackImportDialog({ super.key, - required this.wallet, + required this.walletId, + required this.decoded, + required this.slatepackType, this.clipboard = const ClipboardWrapper(), }); - final MimblewimblecoinWallet wallet; + final String walletId; + final SlatepackDecodeResult decoded; + final String slatepackType; final ClipboardInterface clipboard; @override @@ -38,189 +43,78 @@ class MwcSlatepackImportDialog extends ConsumerStatefulWidget { class _MwcSlatepackImportDialogState extends ConsumerState { - late final TextEditingController slatepackController; - late final FocusNode slatepackFocusNode; + Future<({String responseSlatepack, bool wasEncrypted})> + _processSlatepack() async { + // add delay for showloading exception catching hack fix + await Future.delayed(const Duration(seconds: 1)); - bool _isProcessing = false; - String? _validationError; - SlatepackDecodeResult? _decodedSlatepack; - String? _slatepackType; + final slatepackText = widget.decoded.slateJson!; - @override - void initState() { - super.initState(); - slatepackController = TextEditingController(); - slatepackFocusNode = FocusNode(); - } - - @override - void dispose() { - slatepackController.dispose(); - slatepackFocusNode.dispose(); - super.dispose(); - } - - void _pasteFromClipboard(String? content) async { - try { - if (content != null) { - slatepackController.text = content; - _validateSlatepack(); - } - } catch (e) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to paste from clipboard", - context: context, - ); - } - } - - void _validateSlatepack() async { - final text = slatepackController.text.trim(); + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; - if (text.isEmpty) { - setState(() { - _validationError = null; - _decodedSlatepack = null; - _slatepackType = null; - }); - return; - } - - // Basic format validation. - final coin = widget.wallet.cryptoCurrency as Mimblewimblecoin; - if (!coin.isSlatepack(text)) { - setState(() { - _validationError = "Invalid slatepack format"; - _decodedSlatepack = null; - _slatepackType = null; - }); - return; - } - - try { - // Attempt to decode. - final decoded = await widget.wallet.decodeSlatepack(text); + // Determine action based on slatepack type. + if (widget.slatepackType.contains("S1")) { + // This is an initial slatepack - receive it and create response. + final result = await wallet.receiveSlatepack(slatepackText); - if (decoded.success) { - final analysis = await widget.wallet.analyzeSlatepack(text); - - final String slatepackType = switch (analysis.status) { - 'S1' => "S1 (Initial Send)", - 'S2' => "S2 (Response)", - 'S3' => "S3 (Finalized)", - _ => _determineSlatepackType(decoded), // Fallback. - }; - - setState(() { - _validationError = null; - _decodedSlatepack = decoded; - _slatepackType = slatepackType; - }); + if (result.success && result.responseSlatepack != null) { + return ( + responseSlatepack: result.responseSlatepack!, + wasEncrypted: result.wasEncrypted ?? false, + ); } else { - setState(() { - _validationError = decoded.error ?? "Failed to decode slatepack"; - _decodedSlatepack = null; - _slatepackType = null; - }); + throw Exception(result.error ?? 'Failed to process slatepack'); } - } catch (e) { - setState(() { - _validationError = "Error decoding slatepack: $e"; - _decodedSlatepack = null; - _slatepackType = null; - }); - } - } - - String _determineSlatepackType(SlatepackDecodeResult decoded) { - // Fallback analysis based on sender/recipient addresses. - if (decoded.senderAddress != null && decoded.recipientAddress != null) { - return "S2 (Response)"; - } else if (decoded.senderAddress != null) { - return "S1 (Initial)"; } else { - return "Unknown"; + throw Exception('Unsupported slatepack type: ${widget.slatepackType}'); } } - void _processSlatepack() async { - if (_decodedSlatepack == null || slatepackController.text.trim().isEmpty) { - return; - } - - setState(() { - _isProcessing = true; - }); - - try { - final slatepackText = slatepackController.text.trim(); - - // Determine action based on slatepack type. - if (_slatepackType?.contains("S1") == true) { - // This is an initial slatepack - receive it and create response. - final result = await widget.wallet.receiveSlatepack(slatepackText); - - if (result.success && result.responseSlatepack != null) { - // Show response slatepack. - if (mounted) { - Navigator.of(context).pop(); // Close this dialog. - - await showDialog( - context: context, - barrierDismissible: false, - builder: - (context) => _SlatepackResponseDialog( - responseSlatepack: result.responseSlatepack!, - wasEncrypted: result.wasEncrypted ?? false, - clipboard: widget.clipboard, - ), - ); - } - } else { - throw Exception(result.error ?? 'Failed to process slatepack'); - } - } else if (_slatepackType?.contains("S2") == true) { - // This is a response slatepack - finalize it. - final result = await widget.wallet.finalizeSlatepack(slatepackText); - - if (result.success) { - if (mounted) { - Navigator.of(context).pop(); // Close this dialog. + Future _processPressed() async { + Exception? ex; + final result = await showLoading( + whileFuture: _processSlatepack(), + context: context, + message: "Processing slatepack...", + onException: (e) => ex = e, + ); - showFloatingFlushBar( - type: FlushBarType.success, - message: "Transaction finalized and broadcast successfully!", - context: context, - ); - } - } else { - throw Exception(result.error ?? 'Failed to finalize slatepack'); - } - } else { - throw Exception('Unsupported slatepack type: $_slatepackType'); - } - } catch (e) { + if (result == null || ex != null) { if (mounted) { - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Failed to process slatepack: $e", + await showDialog( context: context, + useRootNavigator: true, + builder: + (context) => StackOkDialog( + desktopPopRootNavigator: true, + maxWidth: Util.isDesktop ? 400 : null, + title: "Slatepack receive error", + message: + ex?.toString() ?? "Unexpected result without exception", + ), ); } - } finally { - if (mounted) { - setState(() { - _isProcessing = false; - }); - } + return; + } + + if (mounted) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => _SlatepackResponseDialog( + responseSlatepack: result.responseSlatepack, + wasEncrypted: result.wasEncrypted, + clipboard: widget.clipboard, + ), + ); } } @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; - final canProcess = _decodedSlatepack != null && !_isProcessing; return Column( mainAxisSize: MainAxisSize.min, @@ -236,13 +130,7 @@ class _MwcSlatepackImportDialogState style: STextStyles.pageTitleH2(context), ), ), - DesktopDialogCloseButton( - onPressedOverride: () async { - if (!_isProcessing) { - Navigator.of(context).pop(); - } - }, - ), + const DesktopDialogCloseButton(), ], ), Padding( @@ -251,153 +139,63 @@ class _MwcSlatepackImportDialogState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Enter a slatepack to process the transaction.", - style: STextStyles.subtitle(context), - textAlign: TextAlign.center, - ), - SimplePasteButton( - onPaste: _pasteFromClipboard, - clipboard: widget.clipboard, - ), - ], - ), - const SizedBox(height: 24), - - // Slatepack input field. - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - controller: slatepackController, - focusNode: slatepackFocusNode, - maxLines: 8, - onChanged: (_) => _validateSlatepack(), - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.all(16), - hintStyle: STextStyles.fieldLabel(context), - hintText: "BEGINSLATEPACK...", - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - ), - ), - ), - - // Validation status. - if (_validationError != null) ...[ - const SizedBox(height: 8), - Row( + ConditionalParent( + condition: isDesktop, + builder: (child) => RoundedWhiteContainer(child: child), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Icon( - Icons.error_outline, - size: 16, - color: - Theme.of(context).extension()!.textError, + DetailItem(title: "Type", detail: widget.slatepackType), + const DetailDivider(), + DetailItem( + title: "Encrypted", + detail: (widget.decoded.wasEncrypted ?? false).toString(), ), - const SizedBox(width: 8), - Expanded( - child: Text( - _validationError!, - style: STextStyles.w400_14(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textError, - ), + if (widget.decoded.senderAddress != null) + const DetailDivider(), + if (widget.decoded.senderAddress != null) + DetailItem( + title: "From", + detail: widget.decoded.senderAddress!, + ), + if (widget.decoded.recipientAddress != null) + const DetailDivider(), + if (widget.decoded.recipientAddress != null) + DetailItem( + title: "To", + detail: widget.decoded.recipientAddress!, ), + const DetailDivider(), + DetailItem( + title: "Slatepack", + detail: widget.decoded.slateJson!, ), ], ), - ] else if (_decodedSlatepack != null) ...[ - const SizedBox(height: 8), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.check_circle_outline, - size: 16, - color: - Theme.of( - context, - ).extension()!.accentColorGreen, - ), - const SizedBox(width: 8), - Text( - "Valid Slatepack", - style: STextStyles.label(context).copyWith( - color: - Theme.of( - context, - ).extension()!.accentColorGreen, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - "Type: $_slatepackType", - style: STextStyles.w400_14(context), - ), - if (_decodedSlatepack!.wasEncrypted == true) - Text( - "Encrypted: Yes", - style: STextStyles.w400_14(context), - ), - if (_decodedSlatepack!.senderAddress != null) - Text( - "From: ${_decodedSlatepack!.senderAddress}", - style: STextStyles.w400_14(context), - ), - ], - ), - ), - ], - + ), const SizedBox(height: 24), - - // Action buttons. - if (isDesktop) ...[ - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Cancel", - onPressed: - _isProcessing - ? null - : () => Navigator.of(context).pop(), - ), - ), - const SizedBox(width: 12), - Expanded( - child: PrimaryButton( - label: _isProcessing ? "Processing..." : "Process", - onPressed: canProcess ? _processSlatepack : null, - ), + ConditionalParent( + condition: isDesktop, + builder: + (child) => Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [child], ), - ], - ), - ] else ...[ - PrimaryButton( - label: _isProcessing ? "Processing..." : "Process Slatepack", - onPressed: canProcess ? _processSlatepack : null, + child: PrimaryButton( + width: isDesktop ? 220 : null, + + buttonHeight: isDesktop ? ButtonHeight.l : null, + label: "Process", + onPressed: _processPressed, ), - const SizedBox(height: 12), + ), + if (!isDesktop) const SizedBox(height: 12), + if (!isDesktop) SecondaryButton( label: "Cancel", - onPressed: - _isProcessing ? null : () => Navigator.of(context).pop(), + onPressed: Navigator.of(context).pop, ), - ], ], ), ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 382b1a3a96..80b358206a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -20,6 +20,7 @@ import 'package:tuple/tuple.dart'; import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/keys/view_only_wallet_data.dart'; +import '../../../../models/mwc_slatepack_models.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; @@ -59,6 +60,7 @@ import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; +import '../../../../widgets/stack_dialog.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; import 'desktop_mwc_txs_method_toggle.dart'; @@ -109,6 +111,102 @@ class _DesktopReceiveState extends ConsumerState { } } + Future<({SlatepackDecodeResult result, String type})?> + _decodeSlatepack() async { + // add delay for showloading exception catching hack fix + await Future.delayed(const Duration(seconds: 1)); + + final wallet = + ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet; + final text = _receiveSlateController.text.trim(); + + if (text.isEmpty) { + return null; + } + + // Basic format validation. + final coin = wallet.cryptoCurrency as Mimblewimblecoin; + if (!coin.isSlatepack(text)) { + throw Exception("Invalid slatepack format"); + } + + // Attempt to decode. + final decoded = await wallet.decodeSlatepack(text); + + if (decoded.success) { + final analysis = await wallet.analyzeSlatepack(text); + + String _determineSlatepackType(SlatepackDecodeResult decoded) { + // Fallback analysis based on sender/recipient addresses. + if (decoded.senderAddress != null && decoded.recipientAddress != null) { + return "S2 (Response)"; + } else if (decoded.senderAddress != null) { + return "S1 (Initial)"; + } else { + return "Unknown"; + } + } + + final String slatepackType = switch (analysis.status) { + 'S1' => "S1 (Initial Send)", + 'S2' => "S2 (Response)", + 'S3' => "S3 (Finalized)", + _ => _determineSlatepackType(decoded), // Fallback. + }; + + return (result: decoded, type: slatepackType); + } else { + throw Exception(decoded.error ?? "Failed to decode slatepack"); + } + } + + Future _onReceiveSlatePressed() async { + Exception? ex; + final result = await showLoading( + whileFuture: _decodeSlatepack(), + context: context, + message: "Decoding slatepack...", + rootNavigator: Util.isDesktop, + onException: (e) => ex = e, + ); + + if (result == null || ex != null) { + if (mounted) { + await showDialog( + context: context, + useRootNavigator: true, + builder: + (context) => StackOkDialog( + desktopPopRootNavigator: true, + title: "Slatepack receive error", + message: + ex?.toString() ?? "Unexpected result without exception", + maxWidth: 400, + ), + ); + } + return; + } + + if (mounted) { + await showDialog( + context: context, + builder: + (context) => SDialog( + child: SizedBox( + width: 700, + child: MwcSlatepackImportDialog( + walletId: widget.walletId, + clipboard: widget.clipboard, + decoded: result.result, + slatepackType: result.type, + ), + ), + ), + ); + } + } + Future generateNewAddress() async { final wallet = ref.read(pWallets).getWallet(walletId); if (wallet is MultiAddressInterface) { @@ -452,165 +550,171 @@ class _DesktopReceiveState extends ConsumerState { ), ), ), - const SizedBox(height: 20), - ConditionalParent( - condition: showMultiType, - builder: - (child) => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - DropdownButtonHideUnderline( - child: DropdownButton2( - value: _currentIndex, - items: [ - for (int i = 0; i < _walletAddressTypes.length; i++) - DropdownMenuItem( - value: i, - child: Text( - supportsSpark && - _walletAddressTypes[i] == - AddressType.p2pkh - ? "Transparent address" - : "${_walletAddressTypes[i].readableName} address", - style: STextStyles.w500_14(context), + if (!(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) + const SizedBox(height: 20), + if (!(isMimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack)) + ConditionalParent( + condition: showMultiType, + builder: + (child) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + DropdownButtonHideUnderline( + child: DropdownButton2( + value: _currentIndex, + items: [ + for (int i = 0; i < _walletAddressTypes.length; i++) + DropdownMenuItem( + value: i, + child: Text( + supportsSpark && + _walletAddressTypes[i] == + AddressType.p2pkh + ? "Transparent address" + : "${_walletAddressTypes[i].readableName} address", + style: STextStyles.w500_14(context), + ), + ), + ], + onChanged: (value) { + if (value != null && value != _currentIndex) { + setState(() { + _currentIndex = value; + }); + } + }, + isExpanded: true, + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, ), ), - ], - onChanged: (value) { - if (value != null && value != _currentIndex) { - setState(() { - _currentIndex = value; - }); - } - }, - isExpanded: true, - iconStyleData: IconStyleData( - icon: Padding( - padding: const EdgeInsets.only(right: 10), - child: SvgPicture.asset( - Assets.svg.chevronDown, - width: 12, - height: 6, + ), + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( color: - Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), ), - ), - buttonStyleData: ButtonStyleData( - decoration: BoxDecoration( - color: - Theme.of( - context, - ).extension()!.textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + decoration: BoxDecoration( + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), ), - ), - dropdownStyleData: DropdownStyleData( - offset: const Offset(0, -10), - elevation: 0, - decoration: BoxDecoration( - color: - Theme.of( - context, - ).extension()!.textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, ), ), ), - menuItemStyleData: const MenuItemStyleData( - padding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - ), ), - ), - const SizedBox(height: 12), - child, - ], - ), - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - clipboard.setData(ClipboardData(text: address)); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: - Theme.of( - context, - ).extension()!.backgroundAppBar, - width: 1, - ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + const SizedBox(height: 12), + child, + ], ), - child: RoundedWhiteContainer( - child: Column( - children: [ - Row( - children: [ - Text( - "Your ${widget.contractAddress == null ? coin.ticker : ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract.symbol))} address", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 15, - height: 15, - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - const SizedBox(width: 4), - Text("Copy", style: STextStyles.link2(context)), - ], - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - Expanded( - child: Text( - address, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ).extension()!.textDark, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + clipboard.setData(ClipboardData(text: address)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: + Theme.of( + context, + ).extension()!.backgroundAppBar, + width: 1, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: RoundedWhiteContainer( + child: Column( + children: [ + Row( + children: [ + Text( + "Your ${widget.contractAddress == null ? coin.ticker : ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract.symbol))} address", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 15, + height: 15, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Text( + address, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ), ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), ), ), ), ), - ), if (canGen) const SizedBox(height: 20), @@ -772,25 +876,7 @@ class _DesktopReceiveState extends ConsumerState { buttonHeight: ButtonHeight.l, label: "Receive Slatepack", enabled: _slateToggleFlag, - onPressed: - _slateToggleFlag - ? () async { - final wallet = ref.read(pWallets).getWallet(walletId); - await showDialog( - context: context, - builder: - (context) => SDialog( - child: SizedBox( - width: 700, - child: MwcSlatepackImportDialog( - wallet: wallet as MimblewimblecoinWallet, - clipboard: widget.clipboard, - ), - ), - ), - ); - } - : null, + onPressed: _slateToggleFlag ? _onReceiveSlatePressed : null, ), ) else From 382f6b50afece5d3df418d956a70e816a6974087 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 10:45:29 -0600 Subject: [PATCH 54/91] desktop finalize slatepack ui updates and garbage clean up --- .../sub_widgets/desktop_finalize.dart | 268 ++++++------------ 1 file changed, 83 insertions(+), 185 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart index c425f79e7a..78de9654bd 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart @@ -13,29 +13,20 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:isar/isar.dart'; -import '../../../../models/isar/models/isar_models.dart'; -import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; +import '../../../../notifications/show_flush_bar.dart'; import '../../../../providers/providers.dart'; import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/address_utils.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; -import '../../../../utilities/logger.dart'; +import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; -import '../../../../wallets/crypto_currency/crypto_currency.dart'; -import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/impl/bitcoin_wallet.dart'; +import '../../../../utilities/util.dart'; import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; -import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; -import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/stack_dialog.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; @@ -43,12 +34,10 @@ class DesktopFinalize extends ConsumerStatefulWidget { const DesktopFinalize({ super.key, required this.walletId, - this.contractAddress, this.clipboard = const ClipboardWrapper(), }); final String walletId; - final String? contractAddress; final ClipboardInterface clipboard; @override @@ -56,156 +45,94 @@ class DesktopFinalize extends ConsumerStatefulWidget { } class _DesktopFinalizeState extends ConsumerState { - late final CryptoCurrency coin; - late final String walletId; - late final ClipboardInterface clipboard; - late final bool supportsSpark; - late final bool showMultiType; - late final bool isMimblewimblecoin; - late TextEditingController receiveSlateController; - String? _address; - bool _addressToggleFlag = false; - final _addressFocusNode = FocusNode(); + late TextEditingController _slateController; + final _slateFocusNode = FocusNode(); + bool _slateToggleFlag = false; - int _currentIndex = 0; - String? _selectedMethodMwc; // Variable to store selected dropdown value - String? _note; - - final List _walletAddressTypes = []; - final Map _addressMap = {}; - final Map> _addressSubMap = {}; - - Future pasteAddress() async { - final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + Future _pasteSlatepack() async { + final ClipboardData? data = await widget.clipboard.getData( + Clipboard.kTextPlain, + ); if (data?.text != null && data!.text!.isNotEmpty) { - String content = data.text!.trim(); - if (content.contains("\n")) { - content = content.substring(0, content.indexOf("\n")); - } - - try { - final paymentData = AddressUtils.parsePaymentUri( - content, - logging: Logging.instance, - ); - if (paymentData != null && - paymentData.coin?.uriScheme == coin.uriScheme) { - _address = paymentData.address; - receiveSlateController.text = _address!; - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } else { - content = content.split("\n").first.trim(); - if (coin is Mimblewimblecoin) { - content = AddressUtils().formatAddressMwc(content); - } - - receiveSlateController.text = content; - _address = content; - - //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } - } catch (e) { - if (coin is Mimblewimblecoin) { - // strip http:// and https:// if content contains @ - content = AddressUtils().formatAddressMwc(content); - } - receiveSlateController.text = content; - _address = content; - // Trigger validation after pasting. - //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = receiveSlateController.text.isNotEmpty; - }); - } + _slateController.text = data.text!; + setState(() { + _slateToggleFlag = _slateController.text.isNotEmpty; + }); } } - @override - void initState() { - receiveSlateController = TextEditingController(); - walletId = widget.walletId; - coin = ref.read(pWalletInfo(walletId)).coin; - clipboard = widget.clipboard; - final wallet = ref.read(pWallets).getWallet(walletId); - supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface; + Future _finalize() async { + // add delay for showloading exception catching hack fix + await Future.delayed(const Duration(seconds: 1)); - isMimblewimblecoin = wallet is MimblewimblecoinWallet; - if (isMimblewimblecoin) { - _selectedMethodMwc = "Slatepack"; - } - debugPrint("Address generated: $isMimblewimblecoin"); + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; - if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) { - showMultiType = false; - } else { - showMultiType = - supportsSpark || - (wallet is! BCashInterface && - wallet is Bip39HDWallet && - wallet.supportedAddressTypes.length > 1); + final decoded = await wallet.decodeSlatepack(_slateController.text); + if (!decoded.success) { + throw Exception(decoded.error ?? "Failed to decode slatepack"); } - _walletAddressTypes.add(wallet.info.mainAddressType); - - if (showMultiType) { - if (supportsSpark) { - _walletAddressTypes.insert(0, AddressType.spark); - } else { - _walletAddressTypes.addAll( - (wallet as Bip39HDWallet).supportedAddressTypes.where( - (e) => e != wallet.info.mainAddressType, - ), - ); - } + final analysis = await wallet.analyzeSlatepack(_slateController.text); + if (analysis.status != "S2") { + throw Exception("Invalid slatepack type: ${analysis.status}"); } - if (_walletAddressTypes.length > 1 && wallet is BitcoinWallet) { - _walletAddressTypes.removeWhere((e) => e == AddressType.p2pkh); + final result = await wallet.finalizeSlatepack(_slateController.text); + + if (!result.success) { + throw Exception( + result.error ?? "Finalize failed without providing an error???", + ); } + } - _addressMap[_walletAddressTypes[_currentIndex]] = ref.read( - pWalletReceivingAddress(walletId), + Future _finalizePressed() async { + Exception? ex; + await showLoading( + whileFuture: _finalize(), + context: context, + message: "Finalizing slatepack...", + rootNavigator: Util.isDesktop, + onException: (e) => ex = e, ); - if (showMultiType) { - for (final type in _walletAddressTypes) { - _addressSubMap[type] = ref - .read(mainDBProvider) - .isar - .addresses - .where() - .walletIdEqualTo(walletId) - .filter() - .typeEqualTo(type) - .sortByDerivationIndexDesc() - .findFirst() - .asStream() - .listen((event) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() { - _addressMap[type] = - event?.value ?? _addressMap[type] ?? "[No address yet]"; - }); - } - }); - }); + if (mounted) { + if (ex != null) { + await showDialog( + context: context, + useRootNavigator: true, + builder: + (context) => StackOkDialog( + desktopPopRootNavigator: true, + title: "Slatepack finalize error", + message: + ex?.toString() ?? "Unexpected result without exception", + maxWidth: 400, + ), + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Transaction finalized and broadcast successfully!", + context: context, + ), + ); } } + } + @override + void initState() { + _slateController = TextEditingController(); super.initState(); } @override void dispose() { - for (final subscription in _addressSubMap.values) { - subscription.cancel(); - } + _slateController.dispose(); + _slateFocusNode.dispose(); super.dispose(); } @@ -213,17 +140,6 @@ class _DesktopFinalizeState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final String address; - if (showMultiType) { - address = _addressMap[_walletAddressTypes[_currentIndex]]!; - } else { - address = ref.watch(pWalletReceivingAddress(walletId)); - } - - final wallet = ref.watch( - pWallets.select((value) => value.getWallet(walletId)), - ); - return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -232,7 +148,6 @@ class _DesktopFinalizeState extends ConsumerState { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Label Text Text( "Finalize Slatepack", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -251,8 +166,8 @@ class _DesktopFinalizeState extends ConsumerState { child: TextField( minLines: 1, maxLines: 5, - key: const Key("sendViewAddressFieldKey"), - controller: receiveSlateController, + key: const Key("finalizeSlatepackFieldKey"), + controller: _slateController, readOnly: false, autocorrect: false, enableSuggestions: false, @@ -263,14 +178,11 @@ class _DesktopFinalizeState extends ConsumerState { selectAll: false, ), onChanged: (newValue) { - _address = newValue; - //_setValidAddressProviders(_address); - setState(() { - _addressToggleFlag = newValue.isNotEmpty; + _slateToggleFlag = newValue.isNotEmpty; }); }, - focusNode: _addressFocusNode, + focusNode: _slateFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of( @@ -280,7 +192,7 @@ class _DesktopFinalizeState extends ConsumerState { ), decoration: standardInputDecoration( "Enter Final Slatepack Message", - _addressFocusNode, + _slateFocusNode, context, desktopMed: true, ).copyWith( @@ -291,34 +203,33 @@ class _DesktopFinalizeState extends ConsumerState { ), suffixIcon: Padding( padding: - receiveSlateController.text.isEmpty + _slateController.text.isEmpty ? const EdgeInsets.only(right: 8) : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _addressToggleFlag + _slateToggleFlag ? TextFieldIconButton( key: const Key( - "sendViewClearAddressFieldButtonKey", + "slateFinalizeClearFieldButtonKey", ), onTap: () { - receiveSlateController.text = ""; - _address = ""; + _slateController.text = ""; setState(() { - _addressToggleFlag = false; + _slateToggleFlag = false; }); }, child: const XIcon(), ) : TextFieldIconButton( key: const Key( - "sendViewPasteAddressFieldButtonKey", + "slateFinalizePasteFieldButtonKey", ), - onTap: pasteAddress, + onTap: _pasteSlatepack, child: - receiveSlateController.text.isEmpty + _slateController.text.isEmpty ? const ClipboardIcon() : const XIcon(), ), @@ -338,20 +249,7 @@ class _DesktopFinalizeState extends ConsumerState { buttonHeight: ButtonHeight.l, label: "Finalize Slatepack", enabled: true, - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); - await showDialog( - context: context, - builder: - (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 700, - child: MwcSlatepackImportDialog( - wallet: wallet as MimblewimblecoinWallet, - ), - ), - ); - }, + onPressed: _finalizePressed, ), ), ], From faa7b70ecd7e7bce8d5024f831486aa09942a5fd Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 12:59:49 -0600 Subject: [PATCH 55/91] fix cancelling progress dialog width on desktop --- ...ancelling_transaction_progress_dialog.dart | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart b/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart index f1824d17e6..da9c45a68f 100644 --- a/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart +++ b/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart @@ -9,7 +9,10 @@ */ import 'package:flutter/material.dart'; + +import '../../../../utilities/util.dart'; import '../../../../widgets/animated_widgets/rotating_arrows.dart'; +import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/stack_dialog.dart'; class CancellingTransactionProgressDialog extends StatefulWidget { @@ -28,28 +31,33 @@ class _CancellingTransactionProgressDialogState onWillPop: () async { return false; }, - child: const StackDialog( - title: "Cancelling transaction", - message: "This may take a while. Please do not exit this screen.", - icon: RotatingArrows( - width: 24, - height: 24, + child: ConditionalParent( + condition: Util.isDesktop, + builder: + (child) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [SizedBox(width: 400, child: child)], + ), + child: const StackDialog( + title: "Cancelling transaction", + message: "This may take a while. Please do not exit this screen.", + icon: RotatingArrows(width: 24, height: 24), + // rightButton: TextButton( + // style: Theme.of(context).textButtonTheme.style?.copyWith( + // backgroundColor: MaterialStateProperty.all( + // CFColors.buttonGray, + // ), + // ), + // child: Text( + // "Cancel", + // style: STextStyles.itemSubtitle12(context), + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // onCancel.call(); + // }, + // ), ), - // rightButton: TextButton( - // style: Theme.of(context).textButtonTheme.style?.copyWith( - // backgroundColor: MaterialStateProperty.all( - // CFColors.buttonGray, - // ), - // ), - // child: Text( - // "Cancel", - // style: STextStyles.itemSubtitle12(context), - // ), - // onPressed: () { - // Navigator.of(context).pop(); - // onCancel.call(); - // }, - // ), ), ); } From 8a9816adc87a8806f47c754e60b5b008412c7c68 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:40:36 -0600 Subject: [PATCH 56/91] add border styling for desktop --- .../sub_widgets/mwc_slatepack_import_dialog.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 161f46d8e4..6dd7289512 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -141,7 +141,17 @@ class _MwcSlatepackImportDialogState children: [ ConditionalParent( condition: isDesktop, - builder: (child) => RoundedWhiteContainer(child: child), + builder: + (child) => RoundedWhiteContainer( + borderColor: + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, + padding: const EdgeInsets.all(0), + child: child, + ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, From 23cbfb38500da46fbeb63c8630a75bb53b2feab1 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:40:56 -0600 Subject: [PATCH 57/91] selectable message text --- lib/widgets/stack_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/stack_dialog.dart b/lib/widgets/stack_dialog.dart index f601330b6f..2c56aa7c03 100644 --- a/lib/widgets/stack_dialog.dart +++ b/lib/widgets/stack_dialog.dart @@ -180,7 +180,7 @@ class StackOkDialog extends StatelessWidget { child: Row( children: [ Flexible( - child: Text( + child: SelectableText( message!, style: STextStyles.smallMed14(context), ), From 63509e62080960b5977ecfda589385b711c3f9a9 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:41:26 -0600 Subject: [PATCH 58/91] refactor desktop send slate creation --- .../wallet_view/sub_widgets/desktop_send.dart | 87 ++++++++++--------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index aef8fa2e1a..6ae236aa19 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -20,6 +20,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import '../../../../models/isar/models/blockchain_data/address.dart'; import '../../../../models/isar/models/blockchain_data/utxo.dart'; import '../../../../models/isar/models/contact_entry.dart'; +import '../../../../models/mwc_slatepack_models.dart'; import '../../../../models/paynym/paynym_account_lite.dart'; import '../../../../models/send_view_auto_fill_data.dart'; import '../../../../pages/send_view/confirm_transaction_view.dart'; @@ -44,6 +45,7 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/enums/mwc_transaction_method.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; +import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; @@ -172,41 +174,46 @@ class _DesktopSendState extends ConsumerState { try { final amount = ref.read(pSendAmount)!; - // Show building dialog. - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: - (context) => BuildingTransactionDialog( - coin: coin, - onCancel: () { - // Use maybePop so we never accidentally pop the base route - // if the dialog hasn't fully mounted yet. - Navigator.of(context, rootNavigator: true).maybePop(); - }, - isSpark: false, - ), - ), - ); + Future wrappedFutureWithDelay() async { + await Future.delayed(const Duration(seconds: 1)); + return wallet.createSlatepack( + amount: amount, + recipientAddress: null, // No specific recipient for manual slatepack. + message: _onChainNote?.isNotEmpty == true ? _onChainNote : null, + encrypt: false, // No encryption without recipient address. + ); + } // Create slatepack. - final slatepackResult = await wallet.createSlatepack( - amount: amount, - recipientAddress: null, // No specific recipient for manual slatepack. - message: _onChainNote?.isNotEmpty == true ? _onChainNote : null, - encrypt: false, // No encryption without recipient address. + Exception? ex; + final slatepackResult = await showLoading( + whileFuture: wrappedFutureWithDelay(), + context: context, + rootNavigator: true, + message: "Building slatepack...", + delay: const Duration(seconds: 2), + onException: (e) => ex = e, ); - // Close building dialog if present. Use maybePop to avoid popping the - // underlying page route in case the dialog hasn't mounted yet. - if (mounted) { - await Navigator.of(context, rootNavigator: true).maybePop(); + if (slatepackResult == null || + !slatepackResult.success || + slatepackResult.slatepack == null || + ex != null) { + String error = + ex?.toString() ?? + slatepackResult?.error ?? + 'Failed to create slatepack'; + if (error.startsWith("Exception:")) { + error = error.replaceFirst("Exception:", "").trim(); + } + throw Exception(error); } - if (!slatepackResult.success || slatepackResult.slatepack == null) { - throw Exception(slatepackResult.error ?? 'Failed to create slatepack'); - } + // refresh asap to show the pending slate tx in history + unawaited(() async { + await Future.delayed(Duration.zero); + await wallet.refresh(); + }()); // Show slatepack dialog. if (mounted) { @@ -215,7 +222,7 @@ class _DesktopSendState extends ConsumerState { barrierDismissible: false, builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, + maxHeight: double.infinity, maxWidth: 700, child: MwcSlatepackDialog(slatepackResult: slatepackResult), ), @@ -225,13 +232,11 @@ class _DesktopSendState extends ConsumerState { clearSendForm(); } } catch (e, s) { - Logging.instance.e('Failed to create MWC slatepack on desktop: $e\n$s'); - - // Close building dialog if still open. maybePop prevents popping the - // page route if the dialog wasn't pushed yet. - if (mounted) { - await Navigator.of(context, rootNavigator: true).maybePop(); - } + Logging.instance.e( + 'Failed to create MWC slatepack on desktop', + error: e, + stackTrace: s, + ); if (mounted) { await showDialog( @@ -619,8 +624,8 @@ class _DesktopSendState extends ConsumerState { txData = txData.copyWith(noteOnChain: _onChainNote ?? ""); } } - // Close building dialog if present without risking popping the page. - await Navigator.of(context, rootNavigator: true).maybePop(); + // pop building dialog + Navigator.of(context, rootNavigator: true).pop(); unawaited( showDialog( @@ -643,8 +648,8 @@ class _DesktopSendState extends ConsumerState { } catch (e, s) { Logging.instance.e("Desktop send: ", error: e, stackTrace: s); if (mounted) { - // Close building dialog if present without risking popping the page. - await Navigator.of(context, rootNavigator: true).maybePop(); + // pop building dialog + Navigator.of(context, rootNavigator: true).pop(); unawaited( showDialog( From 16afdfcefa636042b6edfdd5a2b55f7d82e050a8 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:41:53 -0600 Subject: [PATCH 59/91] fix ok dialog width on desktop --- .../transaction_details_view.dart | 14 +++++++++++--- .../tx_v2/transaction_v2_details_view.dart | 12 ++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index ca997e70b6..e1b2c89f0e 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1587,6 +1587,10 @@ class _TransactionDetailsViewState "Could not open in block explorer", message: "Failed to open \"${uri.toString()}\"", + maxWidth: + Util.isDesktop + ? 400 + : null, ), ), ); @@ -1820,6 +1824,7 @@ class _TransactionDetailsViewState final result = await wallet .cancelPendingTransactionAndPost(id); + if (context.mounted) { // pop progress dialog Navigator.of(context).pop(); @@ -1830,8 +1835,8 @@ class _TransactionDetailsViewState builder: (_) => StackOkDialog( title: "Transaction cancelled", + maxWidth: Util.isDesktop ? 400 : null, onOkPressed: (_) { - wallet.refresh(); Navigator.of(context).popUntil( ModalRoute.withName( WalletView.routeName, @@ -1847,6 +1852,7 @@ class _TransactionDetailsViewState (_) => StackOkDialog( title: "Failed to cancel transaction", message: result, + maxWidth: Util.isDesktop ? 400 : null, ), ); } @@ -1876,7 +1882,8 @@ class _TransactionDetailsViewState final result = await wallet .cancelPendingTransactionAndPost(id); - if (mounted) { + + if (context.mounted) { // pop progress dialog Navigator.of(context).pop(); @@ -1886,8 +1893,8 @@ class _TransactionDetailsViewState builder: (_) => StackOkDialog( title: "Transaction cancelled", + maxWidth: Util.isDesktop ? 400 : null, onOkPressed: (_) { - wallet.refresh(); Navigator.of(context).popUntil( ModalRoute.withName( WalletView.routeName, @@ -1903,6 +1910,7 @@ class _TransactionDetailsViewState (_) => StackOkDialog( title: "Failed to cancel transaction", message: result, + maxWidth: Util.isDesktop ? 400 : null, ), ); } 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 c7ef4cacb6..c971e53438 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 @@ -2157,6 +2157,10 @@ class _TransactionV2DetailsViewState "Could not open in block explorer", message: "Failed to open \"${uri.toString()}\"", + maxWidth: + Util.isDesktop + ? 400 + : null, ), ), ); @@ -2414,15 +2418,16 @@ class _TransactionV2DetailsViewState (_) => StackOkDialog( title: "Transaction cancelled", onOkPressed: (_) { - wallet.refresh(); Navigator.of(context).popUntil( ModalRoute.withName( WalletView.routeName, ), ); }, + maxWidth: Util.isDesktop ? 400 : null, ), ); + unawaited(wallet.refresh()); } else { await showDialog( context: context, @@ -2430,6 +2435,7 @@ class _TransactionV2DetailsViewState (_) => StackOkDialog( title: "Failed to cancel transaction", message: result, + maxWidth: Util.isDesktop ? 400 : null, ), ); } @@ -2470,15 +2476,16 @@ class _TransactionV2DetailsViewState (_) => StackOkDialog( title: "Transaction cancelled", onOkPressed: (_) { - wallet.refresh(); Navigator.of(context).popUntil( ModalRoute.withName( WalletView.routeName, ), ); }, + maxWidth: Util.isDesktop ? 400 : null, ), ); + unawaited(wallet.refresh()); } else { await showDialog( context: context, @@ -2486,6 +2493,7 @@ class _TransactionV2DetailsViewState (_) => StackOkDialog( title: "Failed to cancel transaction", message: result, + maxWidth: Util.isDesktop ? 400 : null, ), ); } From 8ea3e6c541c73d6decba9a289478b567c8fc3caf Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:42:17 -0600 Subject: [PATCH 60/91] clean up slatepack dialog a bit --- .../sub_widgets/mwc_slatepack_dialog.dart | 396 +++++++----------- 1 file changed, 152 insertions(+), 244 deletions(-) diff --git a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart index 1941e7483c..1ae4f9d600 100644 --- a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart +++ b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart @@ -1,22 +1,17 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import '../../../notifications/show_flush_bar.dart'; import '../../../models/mwc_slatepack_models.dart'; +import '../../../notifications/show_flush_bar.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; import '../../../utilities/text_styles.dart'; -import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; -import '../../../widgets/desktop/primary_button.dart'; -import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/rounded_white_container.dart'; -import '../../../widgets/stack_dialog.dart'; class MwcSlatepackDialog extends ConsumerStatefulWidget { const MwcSlatepackDialog({ @@ -33,8 +28,6 @@ class MwcSlatepackDialog extends ConsumerStatefulWidget { } class _MwcSlatepackDialogState extends ConsumerState { - int _currentView = 0; // 0: Slatepack text, 1: QR Code. - void _copySlatepack() { widget.clipboard.setData( ClipboardData(text: widget.slatepackResult.slatepack!), @@ -58,266 +51,181 @@ class _MwcSlatepackDialogState extends ConsumerState { @override Widget build(BuildContext context) { - final isDesktop = - Platform.isLinux || Platform.isMacOS || Platform.isWindows; - - return StackDialogBase( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("Send Slatepack", style: STextStyles.pageTitleH2(context)), - AppBarIconButton( - size: 36, - color: Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.x, - color: - Theme.of( - context, - ).extension()!.topNavIconPrimary, - width: 24, - height: 24, - ), - onPressed: () => Navigator.of(context).pop(), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Send Slatepack", + style: STextStyles.pageTitleH2(context), ), - ], - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Info text. - Text( - "Share this slatepack with the recipient to complete the transaction.", - style: STextStyles.subtitle(context), - textAlign: TextAlign.center, + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Instructions. + Container( + padding: const EdgeInsets.all(12), + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.infoItemIcons.withOpacity(0.05), + borderRadius: BorderRadius.circular(8), ), - const SizedBox(height: 16), - - // Encryption status. - if (widget.slatepackResult.wasEncrypted == true) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Next Steps:", + style: STextStyles.label( + context, + ).copyWith(fontWeight: FontWeight.w600), ), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .infoItemIcons - .withOpacity(0.1), - borderRadius: BorderRadius.circular(8), + const SizedBox(height: 8), + Text( + "1. Share this slatepack with the recipient\n" + "2. Wait for them to return the response slatepack\n" + "3. Import their response to finalize the transaction", + style: STextStyles.w400_14(context), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.lock, - size: 16, - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - const SizedBox(width: 8), - Text( - "Encrypted for recipient", - style: STextStyles.label(context).copyWith( - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - ), - ], - ), - ), - const SizedBox(height: 16), + ], + ), + ), - // View toggle buttons. - Row( + // Encryption status. + // we don't encrypt so ignore for now + // if (widget.slatepackResult.wasEncrypted == true) + // Container( + // padding: const EdgeInsets.symmetric( + // horizontal: 12, + // vertical: 8, + // ), + // decoration: BoxDecoration( + // color: Theme.of( + // context, + // ).extension()!.infoItemIcons.withOpacity(0.1), + // borderRadius: BorderRadius.circular(8), + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon( + // Icons.lock, + // size: 16, + // color: + // Theme.of( + // context, + // ).extension()!.infoItemIcons, + // ), + // const SizedBox(width: 8), + // Text( + // "Encrypted for recipient", + // style: STextStyles.label(context).copyWith( + // color: + // Theme.of( + // context, + // ).extension()!.infoItemIcons, + // ), + // ), + // ], + // ), + // ), + const SizedBox(height: 24), + + // QR Code view. + RoundedWhiteContainer( + child: Column( children: [ - Expanded( - child: SecondaryButton( - label: "Text", - buttonHeight: ButtonHeight.m, - onPressed: - _currentView == 0 - ? null - : () => setState(() => _currentView = 0), - ), + Text( + "Slatepack QR Code", + style: STextStyles.itemSubtitle(context), ), - const SizedBox(width: 12), - Expanded( - child: SecondaryButton( - label: "QR Code", - buttonHeight: ButtonHeight.m, - onPressed: - _currentView == 1 - ? null - : () => setState(() => _currentView = 1), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: QrImageView( + data: widget.slatepackResult.slatepack!, + size: 200, + backgroundColor: Colors.white, + foregroundColor: Colors.black, + errorCorrectionLevel: QrErrorCorrectLevel.M, ), ), ], ), - const SizedBox(height: 16), + ), - // Content display. - if (_currentView == 0) ...[ - // Slatepack text view. - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text( - "Slatepack", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - GestureDetector( - onTap: _copySlatepack, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 10, - height: 10, - color: - Theme.of(context) - .extension()! - .infoItemIcons, - ), - const SizedBox(width: 4), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 8), - Container( - constraints: const BoxConstraints( - maxHeight: 200, - minHeight: 100, - ), - child: SingleChildScrollView( - child: SelectableText( - widget.slatepackResult.slatepack!, - style: STextStyles.w400_14( - context, - ).copyWith(fontFamily: 'monospace'), - ), - ), - ), - ], - ), - ), - ] else ...[ - // QR Code view. - RoundedWhiteContainer( - child: Column( + const SizedBox(height: 12), + + // Slatepack text view. + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( children: [ Text( - "Scan QR Code", + "Slatepack", style: STextStyles.itemSubtitle(context), ), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: QrImageView( - data: widget.slatepackResult.slatepack!, - size: 200, - backgroundColor: Colors.white, - foregroundColor: Colors.black, - errorCorrectionLevel: QrErrorCorrectLevel.M, + const Spacer(), + GestureDetector( + onTap: _copySlatepack, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, + ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], ), ), ], ), - ), - ], - - const SizedBox(height: 24), - - // Action buttons. - if (isDesktop) ...[ - Row( - children: [ - Expanded( - child: SecondaryButton( - label: "Share File", - onPressed: _shareSlatepack, - ), + const SizedBox(height: 8), + Container( + constraints: const BoxConstraints( + maxHeight: 200, + minHeight: 100, ), - const SizedBox(width: 12), - Expanded( - child: PrimaryButton( - label: "Copy Text", - onPressed: _copySlatepack, + child: SingleChildScrollView( + child: SelectableText( + widget.slatepackResult.slatepack!, + style: STextStyles.w400_14( + context, + ).copyWith(fontFamily: 'monospace'), ), ), - ], - ), - ] else ...[ - PrimaryButton( - label: "Copy Slatepack", - onPressed: _copySlatepack, - ), - const SizedBox(height: 12), - SecondaryButton(label: "Share", onPressed: _shareSlatepack), - ], - - const SizedBox(height: 16), - - // Instructions. - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of( - context, - ).extension()!.infoItemIcons.withOpacity(0.05), - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Next Steps:", - style: STextStyles.label( - context, - ).copyWith(fontWeight: FontWeight.w600), - ), - const SizedBox(height: 8), - Text( - "1. Share this slatepack with the recipient\n" - "2. Wait for them to return the response slatepack\n" - "3. Import their response to finalize the transaction", - style: STextStyles.w400_14(context), - ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), - ], - ), + ), + ], ); } } From 5efc4e95d200e18f9dda5ba0851c5cd98e494665 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 13:45:47 -0600 Subject: [PATCH 61/91] WIP starting mobile mwc... --- lib/pages/receive_view/receive_view.dart | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index e461978e2b..9e476ad98d 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -52,7 +52,6 @@ import '../../widgets/qr.dart'; import '../../widgets/rounded_white_container.dart'; import 'addresses/wallet_addresses_view.dart'; import 'generate_receiving_uri_qr_code_view.dart'; -import 'sub_widgets/mwc_slatepack_import_dialog.dart'; class ReceiveView extends ConsumerStatefulWidget { const ReceiveView({ @@ -702,14 +701,14 @@ class _ReceiveViewState extends ConsumerState { onPressed: () async { final wallet = ref.read(pWallets).getWallet(walletId); if (wallet is MimblewimblecoinWallet) { - await showDialog( - context: context, - builder: - (context) => MwcSlatepackImportDialog( - wallet: wallet, - clipboard: clipboard, - ), - ); + // await showDialog( + // context: context, + // builder: + // (context) => MwcSlatepackImportDialog( + // wallet: wallet, + // clipboard: clipboard, + // ), + // ); } }, ), From 31184a2af4d95e5b73bdbda9fd6d94b8302b3705 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 Sep 2025 15:39:59 -0600 Subject: [PATCH 62/91] fix my refactoring mistake --- .../sub_widgets/mwc_slatepack_import_dialog.dart | 6 +++--- .../wallet_view/sub_widgets/desktop_receive.dart | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 6dd7289512..90080feb5a 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -26,12 +26,14 @@ class MwcSlatepackImportDialog extends ConsumerStatefulWidget { const MwcSlatepackImportDialog({ super.key, required this.walletId, + required this.rawSlatepack, required this.decoded, required this.slatepackType, this.clipboard = const ClipboardWrapper(), }); final String walletId; + final String rawSlatepack; final SlatepackDecodeResult decoded; final String slatepackType; final ClipboardInterface clipboard; @@ -48,15 +50,13 @@ class _MwcSlatepackImportDialogState // add delay for showloading exception catching hack fix await Future.delayed(const Duration(seconds: 1)); - final slatepackText = widget.decoded.slateJson!; - final wallet = ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; // Determine action based on slatepack type. if (widget.slatepackType.contains("S1")) { // This is an initial slatepack - receive it and create response. - final result = await wallet.receiveSlatepack(slatepackText); + final result = await wallet.receiveSlatepack(widget.rawSlatepack); if (result.success && result.responseSlatepack != null) { return ( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 80b358206a..cd4f9c2ff0 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -111,7 +111,7 @@ class _DesktopReceiveState extends ConsumerState { } } - Future<({SlatepackDecodeResult result, String type})?> + Future<({SlatepackDecodeResult result, String type, String raw})?> _decodeSlatepack() async { // add delay for showloading exception catching hack fix await Future.delayed(const Duration(seconds: 1)); @@ -154,7 +154,7 @@ class _DesktopReceiveState extends ConsumerState { _ => _determineSlatepackType(decoded), // Fallback. }; - return (result: decoded, type: slatepackType); + return (result: decoded, type: slatepackType, raw: text); } else { throw Exception(decoded.error ?? "Failed to decode slatepack"); } @@ -198,6 +198,7 @@ class _DesktopReceiveState extends ConsumerState { child: MwcSlatepackImportDialog( walletId: widget.walletId, clipboard: widget.clipboard, + rawSlatepack: result.raw, decoded: result.result, slatepackType: result.type, ), From 0b80d51f294ff9801d6cd23bad6cad211c7f2648 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 09:02:00 -0600 Subject: [PATCH 63/91] fix missing android mwc build calls --- scripts/android/build_all.sh | 1 + scripts/android/build_all_campfire.sh | 1 + scripts/android/build_all_duo.sh | 1 + 3 files changed, 3 insertions(+) diff --git a/scripts/android/build_all.sh b/scripts/android/build_all.sh index d791b933be..dc904e95dd 100755 --- a/scripts/android/build_all.sh +++ b/scripts/android/build_all.sh @@ -11,6 +11,7 @@ PLUGINS_DIR=../../crypto_plugins source ../rust_version.sh set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) +(cd "${PLUGINS_DIR}"/flutter_libmwc/scripts/android && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/android/build_all_campfire.sh b/scripts/android/build_all_campfire.sh index d791b933be..dc904e95dd 100755 --- a/scripts/android/build_all_campfire.sh +++ b/scripts/android/build_all_campfire.sh @@ -11,6 +11,7 @@ PLUGINS_DIR=../../crypto_plugins source ../rust_version.sh set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) +(cd "${PLUGINS_DIR}"/flutter_libmwc/scripts/android && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else diff --git a/scripts/android/build_all_duo.sh b/scripts/android/build_all_duo.sh index 39579d2381..1a0d6058f9 100755 --- a/scripts/android/build_all_duo.sh +++ b/scripts/android/build_all_duo.sh @@ -13,6 +13,7 @@ PLUGINS_DIR=../../crypto_plugins source ../rust_version.sh set_rust_version_for_libepiccash (cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) +(cd "${PLUGINS_DIR}"/flutter_libmwc/scripts/android && ./build_all.sh ) # set rust (back) to a more recent stable release after building epiccash set_rust_to_everything_else From 69ac018bd8f5c2bd22b6f3690ab022baf43bac88 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 09:51:08 -0600 Subject: [PATCH 64/91] mwc send view tweaks --- lib/pages/send_view/send_view.dart | 111 +++---- .../mwc_transaction_method_selector.dart | 272 ------------------ .../sub_widgets/desktop_receive.dart | 2 +- .../wallet_view/sub_widgets/desktop_send.dart | 4 +- .../desktop_mwc_txs_method_toggle.dart | 22 +- 5 files changed, 53 insertions(+), 358 deletions(-) delete mode 100644 lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart rename lib/{pages_desktop_specific/my_stack_view/wallet_view/sub_widgets => widgets}/desktop_mwc_txs_method_toggle.dart (70%) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 96a64f6550..7844162a7d 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -62,6 +62,7 @@ import '../../widgets/animated_text.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; +import '../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../widgets/eth_fee_form.dart'; import '../../widgets/fee_slider.dart'; @@ -78,7 +79,6 @@ import '../coin_control/coin_control_view.dart'; import 'confirm_transaction_view.dart'; import 'sub_widgets/building_transaction_dialog.dart'; import 'sub_widgets/dual_balance_selection_sheet.dart'; -import 'sub_widgets/mwc_transaction_method_selector.dart'; import 'sub_widgets/transaction_fee_selection_sheet.dart'; class SendView extends ConsumerStatefulWidget { @@ -1135,12 +1135,6 @@ class _SendViewState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) { ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); - - // // Initialize MWC transaction method to default (non-slatepack). - // if (coin is Mimblewimblecoin) { - // ref.read(pSelectedMwcTransactionMethod.notifier).state = - // null; // No method selected initially. - // } }); isCustomFee.addListener(() { if (!isCustomFee.value) { @@ -1342,21 +1336,10 @@ class _SendViewState extends ConsumerState { ); } - // Probably not be needed. - // if (coin is Mimblewimblecoin) { - // sendToController.addListener(() { - // _address = sendToController.text.trim(); - // - // if (_address != null && _address!.isNotEmpty) { - // _address = _address!.trim(); - // if (_address!.contains("\n")) { - // _address = _address!.substring(0, _address!.indexOf("\n")); - // } - // - // sendToController.text = AddressUtils().formatAddressMwc(_address!); - // } - // }); - // } + final isMwcSlatepack = + coin is Mimblewimblecoin && + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack; return Background( child: Scaffold( @@ -1536,54 +1519,43 @@ class _SendViewState extends ConsumerState { // MWC Transaction Method Selector. if (coin is Mimblewimblecoin) ...[ - MwcTransactionMethodSelector( - onMethodSelected: (method) { - setState(() { - _selectedTransactionMethod = method; - }); - // Update the provider as well. - ref - .read( - pSelectedMwcTransactionMethod.notifier, - ) - .state = method; - }, - selectedMethod: _selectedTransactionMethod, - addressText: sendToController.text.trim(), + const SizedBox( + height: 40, + child: MwcTxsMethodToggle(), ), const SizedBox(height: 16), ], - // "Send to" field - optional for MWC slatepack transactions. - // Always show the field, but make it optional for slatepack. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - isPaynymSend - ? "Send to PayNym address" - : "Send to", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - // if (coin is Monero) - // CustomTextButton( - // text: "Use OpenAlias", - // onTap: () async { - // await showModalBottomSheet( - // context: context, - // builder: (context) => - // OpenAliasBottomSheet( - // onSelected: (address) { - // sendToController.text = address; - // }, - // ), - // ); - // }, - // ), - ], - ), - const SizedBox(height: 8), + if (!isMwcSlatepack) + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + isPaynymSend + ? "Send to PayNym address" + : "Send to", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + // if (coin is Monero) + // CustomTextButton( + // text: "Use OpenAlias", + // onTap: () async { + // await showModalBottomSheet( + // context: context, + // builder: (context) => + // OpenAliasBottomSheet( + // onSelected: (address) { + // sendToController.text = address; + // }, + // ), + // ); + // }, + // ), + ], + ), + if (!isMwcSlatepack) const SizedBox(height: 8), if (isPaynymSend) TextField( key: const Key("sendViewPaynymAddressFieldKey"), @@ -1592,7 +1564,7 @@ class _SendViewState extends ConsumerState { readOnly: true, style: STextStyles.fieldLabel(context), ), - if (!isPaynymSend) + if (!isPaynymSend && !isMwcSlatepack) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -2641,7 +2613,10 @@ class _SendViewState extends ConsumerState { context, ), child: Text( - "Preview", + ref.watch(pSelectedMwcTransactionMethod) == + MwcTransactionMethod.slatepack + ? "Create slatepack" + : "Preview", style: STextStyles.button(context), ), ), diff --git a/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart b/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart deleted file mode 100644 index 123656faf5..0000000000 --- a/lib/pages/send_view/sub_widgets/mwc_transaction_method_selector.dart +++ /dev/null @@ -1,272 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -import '../../../themes/stack_colors.dart'; -import '../../../utilities/assets.dart'; -import '../../../utilities/enums/mwc_transaction_method.dart'; -import '../../../utilities/text_styles.dart'; -import '../../../widgets/rounded_white_container.dart'; - -class MwcTransactionMethodSelector extends ConsumerStatefulWidget { - const MwcTransactionMethodSelector({ - super.key, - required this.onMethodSelected, - this.selectedMethod, - this.addressText, - }); - - final void Function(MwcTransactionMethod) onMethodSelected; - final MwcTransactionMethod? selectedMethod; - final String? addressText; - - @override - ConsumerState createState() => - _MwcTransactionMethodSelectorState(); -} - -class _MwcTransactionMethodSelectorState - extends ConsumerState { - MwcTransactionMethod? _selectedMethod; - - @override - void initState() { - super.initState(); - _selectedMethod = widget.selectedMethod; - } - - void _selectMethod(MwcTransactionMethod method) { - setState(() { - _selectedMethod = method; - }); - widget.onMethodSelected(method); - } - - Widget _buildMethodTile({ - required MwcTransactionMethod method, - required String title, - required String subtitle, - required IconData icon, - required bool available, - String? unavailableReason, - }) { - final isSelected = _selectedMethod == method; - final canSelect = available && widget.addressText?.isNotEmpty != true; - - return GestureDetector( - onTap: canSelect ? () => _selectMethod(method) : null, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - color: - isSelected - ? Theme.of(context) - .extension()! - .infoItemIcons - .withValues(alpha: 0.1) - : Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: - isSelected - ? Theme.of(context).extension()!.infoItemIcons - : Theme.of(context).extension()!.background, - width: 2, - ), - ), - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: - available - ? Theme.of(context) - .extension()! - .infoItemIcons - .withValues(alpha: 0.1) - : Theme.of( - context, - ).extension()!.textFieldDefaultBG, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - icon, - size: 20, - color: - available - ? Theme.of( - context, - ).extension()!.infoItemIcons - : Theme.of( - context, - ).extension()!.textSubtitle2, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: STextStyles.w600_14(context).copyWith( - color: - available - ? Theme.of( - context, - ).extension()!.textDark - : Theme.of( - context, - ).extension()!.textSubtitle2, - ), - ), - const SizedBox(height: 2), - Text( - available ? subtitle : (unavailableReason ?? subtitle), - style: STextStyles.w400_14(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textSubtitle2, - ), - ), - ], - ), - ), - if (isSelected) - Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - shape: BoxShape.circle, - ), - child: Icon( - Icons.check, - size: 12, - color: Theme.of(context).extension()!.popupBG, - ), - ), - ], - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - final hasAddressInput = widget.addressText?.isNotEmpty == true; - - return RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - SvgPicture.asset( - Assets.svg.swap, - width: 16, - height: 16, - colorFilter: ColorFilter.mode( - Theme.of(context).extension()!.infoItemIcons, - BlendMode.srcIn, - ), - ), - const SizedBox(width: 8), - Text("Transaction Method", style: STextStyles.w600_14(context)), - ], - ), - const SizedBox(height: 4), - Text( - hasAddressInput - ? "Method detected from address format" - : "Choose how to send this transaction", - style: STextStyles.w400_14(context).copyWith( - color: Theme.of(context).extension()!.textSubtitle2, - ), - ), - const SizedBox(height: 16), - - Column( - children: [ - // Slatepack method. - _buildMethodTile( - method: MwcTransactionMethod.slatepack, - title: "Slatepack (Manual)", - subtitle: "Copy/paste, QR codes, or files", - icon: Icons.qr_code, - available: true, - ), - const SizedBox(height: 12), - - // MWCMQS method. - _buildMethodTile( - method: MwcTransactionMethod.mwcmqs, - title: "MWCMQS (Automatic)", - subtitle: "Direct messaging to recipient", - icon: Icons.message, - available: true, - unavailableReason: "Requires MWCMQS address", - ), - // const SizedBox(height: 12), - // - // // HTTP method. - // _buildMethodTile( - // method: MwcTransactionMethod.http, - // title: "HTTP (Direct)", - // subtitle: "Direct connection to wallet", - // icon: Icons.http, - // available: true, - // unavailableReason: "Requires HTTP address", - // ), - ], - ), - - if (hasAddressInput) ...[ - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .infoItemIcons - .withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - "Transaction method is automatically detected from the address format. Clear the address field to manually select a method.", - style: STextStyles.w400_14(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textSubtitle2, - ), - ), - ), - ], - ), - ), - ], - ], - ), - ); - } -} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index cd4f9c2ff0..254c922488 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -55,6 +55,7 @@ import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../../../widgets/dialogs/s_dialog.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; @@ -63,7 +64,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 'desktop_mwc_txs_method_toggle.dart'; class DesktopReceive extends ConsumerStatefulWidget { const DesktopReceive({ diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 6ae236aa19..32e602d0b8 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -64,6 +64,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/qr_code_scanner_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart'; @@ -76,7 +77,6 @@ import '../../../../widgets/textfield_icon_button.dart'; import '../../../coin_control/desktop_coin_control_use_dialog.dart'; import '../../../desktop_home_view.dart'; import 'address_book_address_chooser/address_book_address_chooser.dart'; -import 'desktop_mwc_txs_method_toggle.dart'; import 'desktop_send_fee_form.dart'; class DesktopSend extends ConsumerStatefulWidget { @@ -1948,7 +1948,7 @@ class _DesktopSendState extends ConsumerState { PrimaryButton( buttonHeight: ButtonHeight.l, label: - ref.read(pSelectedMwcTransactionMethod) == + ref.watch(pSelectedMwcTransactionMethod) == MwcTransactionMethod.slatepack ? "Create slatepack" : "Preview send", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart b/lib/widgets/desktop_mwc_txs_method_toggle.dart similarity index 70% rename from lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart rename to lib/widgets/desktop_mwc_txs_method_toggle.dart index 1257d464cc..4381d6e236 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_mwc_txs_method_toggle.dart +++ b/lib/widgets/desktop_mwc_txs_method_toggle.dart @@ -11,13 +11,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../providers/ui/preview_tx_button_state_provider.dart'; -import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/assets.dart'; -import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/mwc_transaction_method.dart'; -import '../../../../utilities/util.dart'; -import '../../../../widgets/toggle.dart'; +import '../providers/ui/preview_tx_button_state_provider.dart'; +import '../themes/stack_colors.dart'; +import '../utilities/assets.dart'; +import '../utilities/constants.dart'; +import '../utilities/enums/mwc_transaction_method.dart'; +import '../utilities/util.dart'; +import 'toggle.dart'; class MwcTxsMethodToggle extends ConsumerWidget { const MwcTxsMethodToggle({super.key}); @@ -29,14 +29,6 @@ class MwcTxsMethodToggle extends ConsumerWidget { return Toggle( onValueChanged: (value) { - // Align visual labels with callback semantics: - // When the toggle shows "Slatepack" (on), emit slatepack. - // When it shows "Automatic" (off), emit automatic. - // if (value) { - // onChanged?.call(TxsMethodMwcType.slatepack); - // } else { - // onChanged?.call(TxsMethodMwcType.automatic); - // } ref.read(pSelectedMwcTransactionMethod.notifier).state = value ? MwcTransactionMethod.mwcmqs From c26fe0d018add156f1e73b0d1ed077c7af59085c Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 09:51:51 -0600 Subject: [PATCH 65/91] rename file appropriately --- lib/pages/send_view/send_view.dart | 2 +- .../my_stack_view/wallet_view/sub_widgets/desktop_receive.dart | 2 +- .../my_stack_view/wallet_view/sub_widgets/desktop_send.dart | 2 +- ...op_mwc_txs_method_toggle.dart => mwc_txs_method_toggle.dart} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename lib/widgets/{desktop_mwc_txs_method_toggle.dart => mwc_txs_method_toggle.dart} (100%) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 7844162a7d..084277a9c2 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -62,7 +62,6 @@ import '../../widgets/animated_text.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; -import '../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../widgets/eth_fee_form.dart'; import '../../widgets/fee_slider.dart'; @@ -70,6 +69,7 @@ import '../../widgets/icon_widgets/addressbook_icon.dart'; import '../../widgets/icon_widgets/clipboard_icon.dart'; import '../../widgets/icon_widgets/qrcode_icon.dart'; import '../../widgets/icon_widgets/x_icon.dart'; +import '../../widgets/mwc_txs_method_toggle.dart'; import '../../widgets/rounded_white_container.dart'; import '../../widgets/stack_dialog.dart'; import '../../widgets/stack_text_field.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 254c922488..3fe9f4ed53 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -55,10 +55,10 @@ import '../../../../widgets/custom_loading_overlay.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; -import '../../../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../../../widgets/dialogs/s_dialog.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/mwc_txs_method_toggle.dart'; import '../../../../widgets/qr.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_dialog.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 32e602d0b8..ac30df24ea 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -64,13 +64,13 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/qr_code_scanner_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; -import '../../../../widgets/desktop_mwc_txs_method_toggle.dart'; import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart'; import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/qrcode_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; +import '../../../../widgets/mwc_txs_method_toggle.dart'; import '../../../../widgets/rounded_container.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; diff --git a/lib/widgets/desktop_mwc_txs_method_toggle.dart b/lib/widgets/mwc_txs_method_toggle.dart similarity index 100% rename from lib/widgets/desktop_mwc_txs_method_toggle.dart rename to lib/widgets/mwc_txs_method_toggle.dart From 480619c9493547b1f079cf5767a1cdd8afc309ee Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 10:06:09 -0600 Subject: [PATCH 66/91] remove unused functions --- .../coins/mimblewimblecoin.dart | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index c495d58f0f..552bcc24fa 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -105,52 +105,6 @@ class Mimblewimblecoin extends Bip39Currency { } } - /// Validate slatepack format. - bool validateSlatepack(String slatepack) { - try { - final trimmed = slatepack.trim(); - if (!isSlatepack(trimmed)) { - return false; - } - - // Basic structure validation. - final lines = trimmed.split('\n'); - if (lines.length < 3) { - return false; - } - - // Should have header, content, and footer. - return lines.first.startsWith('BEGINSLATEPACK.') && - lines.last.endsWith('.ENDSLATEPACK') && - lines.length > 2; - } catch (e) { - return false; - } - } - - /// Get expected slatepack type from content (S1, S2, S3). - String? getSlatepackType(String slatepack) { - if (!validateSlatepack(slatepack)) { - return null; - } - - try { - // This is a simplified approach - in reality you'd need to decode - // the slatepack content to determine the exact type. - final lines = slatepack.trim().split('\n'); - final header = lines.first; - - // Basic heuristic based on header format. - if (header.contains('BEGINSLATEPACK.')) { - return 'unknown'; // Would need proper decoding to determine S1/S2/S3. - } - - return null; - } catch (e) { - return null; - } - } - @override NodeModel defaultNode({required bool isPrimary}) { switch (network) { From 408fac30304ccf31fda7e2a9cbe5419d0065aecd Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 10:07:43 -0600 Subject: [PATCH 67/91] validateAddress is reserved for spending to. A slatepack cannot be spent to and is not a valid address. This commit will probably break things --- lib/wallets/crypto_currency/coins/mimblewimblecoin.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index 552bcc24fa..b7fe060432 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -53,11 +53,6 @@ class Mimblewimblecoin extends Bip39Currency { @override bool validateAddress(String address) { - // Check if it's a slatepack first. - if (isSlatepack(address)) { - return true; - } - // Check URI schemes (HTTP, HTTPS, MWCMQS). final Uri? uri = Uri.tryParse(address); if (uri != null && From 33961b6836d541a2ad36761824cd02b8a63bf034 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 10:11:11 -0600 Subject: [PATCH 68/91] MWC does not do http sends --- .../crypto_currency/coins/mimblewimblecoin.dart | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index b7fe060432..e2779e6ed4 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -55,15 +55,12 @@ class Mimblewimblecoin extends Bip39Currency { bool validateAddress(String address) { // Check URI schemes (HTTP, HTTPS, MWCMQS). final Uri? uri = Uri.tryParse(address); - if (uri != null && - (uri.scheme == "http" || - uri.scheme == "https" || - uri.scheme == "mwcmqs") && - uri.host.isNotEmpty) { - return true; + if (uri != null && uri.scheme == "mwcmqs" && uri.host.isNotEmpty) { + // Use libmwc for address validation. + return mimblewimblecoin.Libmwc.validateSendAddress(address: uri.host); } - // Use libmwc for other address validation. + // Use libmwc for address validation. return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } @@ -81,11 +78,6 @@ class Mimblewimblecoin extends Bip39Currency { return address.startsWith('mwcmqs://'); } - /// Check if address is HTTP format. - bool isHttpAddress(String address) { - return address.startsWith('http://') || address.startsWith('https://'); - } - /// Detect transaction type based on address/data format. MwcTransactionMethod getTransactionMethod(String addressOrData) { if (isSlatepack(addressOrData)) { From ae89d1f0c8bf81e54cba5e20f30209ff493b355d Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 11:05:26 -0600 Subject: [PATCH 69/91] share finalize view base with desktop --- lib/pages/finalize_view/finalize_view.dart | 337 ++++++++++++++++++ lib/pages/wallet_view/wallet_view.dart | 18 + .../sub_widgets/desktop_finalize.dart | 258 -------------- .../wallet_view/sub_widgets/my_wallet.dart | 125 +++---- lib/route_generator.dart | 11 + .../components/icons/finalize_nav_icon.dart | 40 +++ 6 files changed, 459 insertions(+), 330 deletions(-) create mode 100644 lib/pages/finalize_view/finalize_view.dart delete mode 100644 lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart create mode 100644 lib/widgets/wallet_navigation_bar/components/icons/finalize_nav_icon.dart diff --git a/lib/pages/finalize_view/finalize_view.dart b/lib/pages/finalize_view/finalize_view.dart new file mode 100644 index 0000000000..fa69aae52f --- /dev/null +++ b/lib/pages/finalize_view/finalize_view.dart @@ -0,0 +1,337 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../notifications/show_flush_bar.dart'; +import '../../providers/global/barcode_scanner_provider.dart'; +import '../../providers/global/wallets_provider.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/barcode_scanner_interface.dart'; +import '../../utilities/clipboard_interface.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/show_loading.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../widgets/icon_widgets/qrcode_icon.dart'; +import '../../widgets/icon_widgets/x_icon.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/stack_text_field.dart'; +import '../../widgets/textfield_icon_button.dart'; + +class FinalizeView extends ConsumerStatefulWidget { + const FinalizeView({ + super.key, + required this.walletId, + this.clipboard = const ClipboardWrapper(), + }); + + static const String routeName = "/finalizeView"; + + final String walletId; + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => _FinalizeViewState(); +} + +class _FinalizeViewState extends ConsumerState { + late final TextEditingController _slateController; + late final FocusNode _slateFocusNode; + + bool _slateToggleFlag = false; + + Future _pasteSlatepack() async { + final ClipboardData? data = await widget.clipboard.getData( + Clipboard.kTextPlain, + ); + if (data?.text != null && data!.text!.isNotEmpty) { + _slateController.text = data.text!; + setState(() { + _slateToggleFlag = _slateController.text.isNotEmpty; + }); + } + } + + Future _scanQr() async { + try { + if (!Util.isDesktop && _slateFocusNode.hasFocus) { + _slateFocusNode.unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + if (mounted) { + final qrResult = await ref.read(pBarcodeScanner).scan(context: context); + if (qrResult.rawContent.isNotEmpty && qrResult.rawContent != "null") { + _slateController.text = qrResult.rawContent; + setState(() { + _slateToggleFlag = _slateController.text.isNotEmpty; + }); + } + } + } on PlatformException catch (e, s) { + if (mounted) { + try { + await checkCamPermDeniedMobileAndOpenAppSettings( + context, + logging: Logging.instance, + ); + } catch (e, s) { + Logging.instance.e( + "Failed to check cam permissions", + error: e, + stackTrace: s, + ); + } + } else { + Logging.instance.e( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, + ); + } + } + } + + Future _finalize() async { + // add delay for showloading exception catching hack fix + await Future.delayed(const Duration(seconds: 1)); + + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; + + final decoded = await wallet.decodeSlatepack(_slateController.text); + if (!decoded.success) { + throw Exception(decoded.error ?? "Failed to decode slatepack"); + } + + final analysis = await wallet.analyzeSlatepack(_slateController.text); + if (analysis.status != "S2") { + throw Exception("Invalid slatepack type: ${analysis.status}"); + } + + final result = await wallet.finalizeSlatepack(_slateController.text); + + if (!result.success) { + throw Exception( + result.error ?? "Finalize failed without providing an error???", + ); + } + } + + Future _finalizePressed() async { + if (!Util.isDesktop && _slateFocusNode.hasFocus) { + _slateFocusNode.unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Exception? ex; + await showLoading( + whileFuture: _finalize(), + context: context, + message: "Finalizing slatepack...", + rootNavigator: Util.isDesktop, + onException: (e) => ex = e, + ); + + if (mounted) { + if (ex != null) { + await showDialog( + context: context, + builder: + (context) => StackOkDialog( + desktopPopRootNavigator: Util.isDesktop, + title: "Slatepack finalize error", + message: + ex?.toString() ?? "Unexpected result without exception", + maxWidth: Util.isDesktop ? 400 : null, + ), + ); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Transaction finalized and broadcast successfully!", + context: context, + ), + ); + } + } + } + } + + @override + void initState() { + super.initState(); + _slateController = TextEditingController(); + _slateFocusNode = FocusNode(); + } + + @override + void dispose() { + _slateController.dispose(); + _slateFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return ConditionalParent( + condition: !Util.isDesktop, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Finalize slatepack", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: Constants.size.standardPadding, + ), + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("finalizeSlatepackFieldKey"), + controller: _slateController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + setState(() { + _slateToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _slateFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter Final Slatepack Message", + _slateFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, // Adjust vertical padding for better alignment + ), + suffixIcon: Padding( + padding: + _slateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _slateToggleFlag + ? TextFieldIconButton( + key: const Key( + "slateFinalizeClearFieldButtonKey", + ), + onTap: () { + _slateController.text = ""; + setState(() { + _slateToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "slateFinalizePasteFieldButtonKey", + ), + onTap: _pasteSlatepack, + child: + _slateController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_slateController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key("sendViewScanQrButtonKey"), + onTap: _scanQr, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + Util.isDesktop ? const SizedBox(height: 24) : const Spacer(), + PrimaryButton( + label: "Finalize Slatepack", + enabled: _slateToggleFlag, + onPressed: _slateToggleFlag ? _finalizePressed : null, + ), + + if (!Util.isDesktop) SizedBox(height: Constants.size.standardPadding), + ], + ), + ); + } +} diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index a479ae4eb7..c61340a4b5 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -51,6 +51,7 @@ import '../../wallets/crypto_currency/intermediate/frost_currency.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; 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/lib_salvium_wallet.dart'; @@ -75,6 +76,7 @@ import '../../widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/churn_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart'; +import '../../widgets/wallet_navigation_bar/components/icons/finalize_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/frost_sign_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart'; import '../../widgets/wallet_navigation_bar/components/icons/ordinals_nav_icon.dart'; @@ -88,6 +90,7 @@ import '../cashfusion/cashfusion_view.dart'; import '../churning/churning_view.dart'; import '../coin_control/coin_control_view.dart'; import '../exchange_view/wallet_initiated_exchange_view.dart'; +import '../finalize_view/finalize_view.dart'; import '../monkey/monkey_view.dart'; import '../namecoin_names/namecoin_names_home_view.dart'; import '../notification_views/notifications_view.dart'; @@ -1015,6 +1018,21 @@ class _WalletViewState extends ConsumerState { } }, ), + if (wallet is MimblewimblecoinWallet) + WalletNavigationBarItemData( + label: "Finalize", + icon: const FinalizeNavIcon(), + onTap: () { + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + FinalizeView.routeName, + arguments: walletId, + ), + ); + } + }, + ), if (ref.watch(pWalletCoin(walletId)) is FrostCurrency) WalletNavigationBarItemData( label: "Sign", diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart deleted file mode 100644 index 78de9654bd..0000000000 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_finalize.dart +++ /dev/null @@ -1,258 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../../../../notifications/show_flush_bar.dart'; -import '../../../../providers/providers.dart'; -import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/clipboard_interface.dart'; -import '../../../../utilities/constants.dart'; -import '../../../../utilities/show_loading.dart'; -import '../../../../utilities/text_styles.dart'; -import '../../../../utilities/util.dart'; -import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; -import '../../../../widgets/desktop/primary_button.dart'; -import '../../../../widgets/icon_widgets/clipboard_icon.dart'; -import '../../../../widgets/icon_widgets/x_icon.dart'; -import '../../../../widgets/stack_dialog.dart'; -import '../../../../widgets/stack_text_field.dart'; -import '../../../../widgets/textfield_icon_button.dart'; - -class DesktopFinalize extends ConsumerStatefulWidget { - const DesktopFinalize({ - super.key, - required this.walletId, - this.clipboard = const ClipboardWrapper(), - }); - - final String walletId; - final ClipboardInterface clipboard; - - @override - ConsumerState createState() => _DesktopFinalizeState(); -} - -class _DesktopFinalizeState extends ConsumerState { - late TextEditingController _slateController; - final _slateFocusNode = FocusNode(); - bool _slateToggleFlag = false; - - Future _pasteSlatepack() async { - final ClipboardData? data = await widget.clipboard.getData( - Clipboard.kTextPlain, - ); - if (data?.text != null && data!.text!.isNotEmpty) { - _slateController.text = data.text!; - setState(() { - _slateToggleFlag = _slateController.text.isNotEmpty; - }); - } - } - - Future _finalize() async { - // add delay for showloading exception catching hack fix - await Future.delayed(const Duration(seconds: 1)); - - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as MimblewimblecoinWallet; - - final decoded = await wallet.decodeSlatepack(_slateController.text); - if (!decoded.success) { - throw Exception(decoded.error ?? "Failed to decode slatepack"); - } - - final analysis = await wallet.analyzeSlatepack(_slateController.text); - if (analysis.status != "S2") { - throw Exception("Invalid slatepack type: ${analysis.status}"); - } - - final result = await wallet.finalizeSlatepack(_slateController.text); - - if (!result.success) { - throw Exception( - result.error ?? "Finalize failed without providing an error???", - ); - } - } - - Future _finalizePressed() async { - Exception? ex; - await showLoading( - whileFuture: _finalize(), - context: context, - message: "Finalizing slatepack...", - rootNavigator: Util.isDesktop, - onException: (e) => ex = e, - ); - - if (mounted) { - if (ex != null) { - await showDialog( - context: context, - useRootNavigator: true, - builder: - (context) => StackOkDialog( - desktopPopRootNavigator: true, - title: "Slatepack finalize error", - message: - ex?.toString() ?? "Unexpected result without exception", - maxWidth: 400, - ), - ); - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "Transaction finalized and broadcast successfully!", - context: context, - ), - ); - } - } - } - - @override - void initState() { - _slateController = TextEditingController(); - super.initState(); - } - - @override - void dispose() { - _slateController.dispose(); - _slateFocusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - debugPrint("BUILD: $runtimeType"); - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 4), - const SizedBox(height: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Finalize Slatepack", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFieldActiveSearchIconRight, - ), - textAlign: TextAlign.left, - ), - const SizedBox(height: 8), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - minLines: 1, - maxLines: 5, - key: const Key("finalizeSlatepackFieldKey"), - controller: _slateController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - onChanged: (newValue) { - setState(() { - _slateToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _slateFocusNode, - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textFieldActiveText, - height: 1.8, - ), - decoration: standardInputDecoration( - "Enter Final Slatepack Message", - _slateFocusNode, - context, - desktopMed: true, - ).copyWith( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: - 12, // Adjust vertical padding for better alignment - ), - suffixIcon: Padding( - padding: - _slateController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _slateToggleFlag - ? TextFieldIconButton( - key: const Key( - "slateFinalizeClearFieldButtonKey", - ), - onTap: () { - _slateController.text = ""; - setState(() { - _slateToggleFlag = false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "slateFinalizePasteFieldButtonKey", - ), - onTap: _pasteSlatepack, - child: - _slateController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 32), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Finalize Slatepack", - enabled: true, - onPressed: _finalizePressed, - ), - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index a644a2175c..9a8d94da6f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -12,29 +12,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../frost_route_generator.dart'; +import '../../../../pages/finalize_view/finalize_view.dart'; import '../../../../pages/send_view/frost_ms/frost_send_view.dart'; import '../../../../pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart'; import '../../../../providers/global/wallets_provider.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart'; -import '../../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart'; import '../../../../widgets/custom_tab_view.dart'; import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/frost_scaffold.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../my_stack_view.dart'; -import 'desktop_finalize.dart'; import 'desktop_receive.dart'; import 'desktop_send.dart'; import 'desktop_token_send.dart'; class MyWallet extends ConsumerStatefulWidget { - const MyWallet({ - super.key, - required this.walletId, - this.contractAddress, - }); + const MyWallet({super.key, required this.walletId, this.contractAddress}); final String walletId; final String? contractAddress; @@ -44,10 +39,7 @@ class MyWallet extends ConsumerStatefulWidget { } class _MyWalletState extends ConsumerState { - final titles = [ - "Send", - "Receive", - ]; + final titles = ["Send", "Receive"]; late final bool isEth; late final CryptoCurrency coin; @@ -110,64 +102,57 @@ class _MyWalletState extends ConsumerState { widget.contractAddress == null ? isFrost ? Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: - const EdgeInsets.fromLTRB(0, 20, 0, 0), - child: SecondaryButton( - width: 200, - buttonHeight: ButtonHeight.l, - label: "Import sign config", - onPressed: () async { - final wallet = ref - .read(pWallets) - .getWallet(widget.walletId) - as BitcoinFrostWallet; - ref.read(pFrostScaffoldArgs.state).state = - ( - info: ( - walletName: wallet.info.name, - frostCurrency: wallet.cryptoCurrency, - ), - walletId: widget.walletId, - stepRoutes: FrostRouteGenerator - .signFrostTxStepRoutes, - parentNav: Navigator.of(context), - frostInterruptionDialogType: - FrostInterruptionDialogType - .transactionCreation, - callerRouteName: MyStackView.routeName, - ); - - await Navigator.of(context).pushNamed( - FrostStepScaffold.routeName, - ); - }, - ), + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), + child: SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Import sign config", + onPressed: () async { + final wallet = + ref + .read(pWallets) + .getWallet(widget.walletId) + as BitcoinFrostWallet; + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: widget.walletId, + stepRoutes: + FrostRouteGenerator + .signFrostTxStepRoutes, + parentNav: Navigator.of(context), + frostInterruptionDialogType: + FrostInterruptionDialogType + .transactionCreation, + callerRouteName: MyStackView.routeName, + ); + + await Navigator.of( + context, + ).pushNamed(FrostStepScaffold.routeName); + }, ), - ], - ), - FrostSendView( - walletId: widget.walletId, - coin: coin, - ), - ], - ) - : Padding( - padding: const EdgeInsets.all(20), - child: DesktopSend( - walletId: widget.walletId, + ), + ], ), - ) + FrostSendView(walletId: widget.walletId, coin: coin), + ], + ) + : Padding( + padding: const EdgeInsets.all(20), + child: DesktopSend(walletId: widget.walletId), + ) : Padding( - padding: const EdgeInsets.all(20), - child: DesktopTokenSend( - walletId: widget.walletId, - ), - ), + padding: const EdgeInsets.all(20), + child: DesktopTokenSend(walletId: widget.walletId), + ), Padding( padding: const EdgeInsets.all(20), child: DesktopReceive( @@ -178,9 +163,7 @@ class _MyWalletState extends ConsumerState { if (isMimblewimblecoin) Padding( padding: const EdgeInsets.all(20), - child: DesktopFinalize( - walletId: widget.walletId, - ), + child: FinalizeView(walletId: widget.walletId), ), if (isEth && widget.contractAddress == null) @@ -190,9 +173,7 @@ class _MyWalletState extends ConsumerState { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height - 362, ), - child: TransactionsV2List( - walletId: widget.walletId, - ), + child: TransactionsV2List(walletId: widget.walletId), ), ), ], diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 4c5e16ccfd..064e54df9a 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -68,6 +68,7 @@ import 'pages/exchange_view/exchange_step_views/step_4_view.dart'; import 'pages/exchange_view/send_from_view.dart'; import 'pages/exchange_view/trade_details_view.dart'; import 'pages/exchange_view/wallet_initiated_exchange_view.dart'; +import 'pages/finalize_view/finalize_view.dart'; import 'pages/generic/single_field_edit_view.dart'; import 'pages/home_view/home_view.dart'; import 'pages/intro_view.dart'; @@ -1712,6 +1713,16 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case FinalizeView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => FinalizeView(walletId: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case WalletAddressesView.routeName: if (args is String) { return getRoute( diff --git a/lib/widgets/wallet_navigation_bar/components/icons/finalize_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/finalize_nav_icon.dart new file mode 100644 index 0000000000..04b19d2135 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/finalize_nav_icon.dart @@ -0,0 +1,40 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2025 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2025-09-18 + * + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/assets.dart'; + +class FinalizeNavIcon extends StatelessWidget { + const FinalizeNavIcon({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.bottomNavIconIcon.withOpacity(0.4), + borderRadius: BorderRadius.circular(24), + ), + child: Padding( + padding: const EdgeInsets.all(6.0), + child: SvgPicture.asset( + Assets.svg.circleLock, + width: 12, + height: 12, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ), + ), + ); + } +} From 2004da14ac9dc56d78c2eaf3a5f4c5c0eb2248f1 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 11:10:45 -0600 Subject: [PATCH 70/91] fix hidden slate id on tx details view when cancel button is visible --- .../transaction_views/tx_v2/transaction_v2_details_view.dart | 4 ++++ 1 file changed, 4 insertions(+) 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 c971e53438..2f2bbf83b4 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 @@ -2352,6 +2352,10 @@ class _TransactionV2DetailsViewState ), ), ), + if ((coin is Epiccash || coin is Mimblewimblecoin) && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) + const SizedBox(height: 40), ], ), ), From cc581fc6cefa4ab4f5fb7aa5eb8523e72c30fac2 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 12:39:04 -0600 Subject: [PATCH 71/91] update mwc lib --- .gitmodules | 2 +- crypto_plugins/flutter_libmwc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0e00f52521..d0c1810aa1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,4 @@ url = https://github.com/cypherstack/frostdart [submodule "crypto_plugins/flutter_libmwc"] path = crypto_plugins/flutter_libmwc - url = https://github.com/vekamo/flutter_libmwc + url = https://github.com/cypherstack/flutter_libmwc diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 29c7ea5267..d34d8fb244 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 29c7ea526772a170d1f9488c4661333c0b2564fd +Subproject commit d34d8fb2447dd3bf732bb07b68bd3254732ba7b6 From c3ed4494271178554b9da3bad8e50ee9ef6c77d1 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 12:50:07 -0600 Subject: [PATCH 72/91] fix address validation --- lib/wallets/crypto_currency/coins/mimblewimblecoin.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart index e2779e6ed4..6d670d5ff6 100644 --- a/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart +++ b/lib/wallets/crypto_currency/coins/mimblewimblecoin.dart @@ -53,13 +53,6 @@ class Mimblewimblecoin extends Bip39Currency { @override bool validateAddress(String address) { - // Check URI schemes (HTTP, HTTPS, MWCMQS). - final Uri? uri = Uri.tryParse(address); - if (uri != null && uri.scheme == "mwcmqs" && uri.host.isNotEmpty) { - // Use libmwc for address validation. - return mimblewimblecoin.Libmwc.validateSendAddress(address: uri.host); - } - // Use libmwc for address validation. return mimblewimblecoin.Libmwc.validateSendAddress(address: address); } From fa45fcb9c7077ad93f05c8677fff1bc63212caea Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 18 Sep 2025 13:43:49 -0600 Subject: [PATCH 73/91] fix ref access issue --- lib/pages/home_view/home_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 4beb066630..223c3d9c14 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -57,6 +57,9 @@ class HomeView extends ConsumerStatefulWidget { class _HomeViewState extends ConsumerState { final GlobalKey _key = GlobalKey(); + // keep reference to be able to remove listener in dispose without requiring the riverpod ref + late final Prefs _prefs; + late final PageController _pageController; late final RotateIconController _rotateIconController; @@ -209,6 +212,7 @@ class _HomeViewState extends ConsumerState { @override void initState() { + _prefs = ref.read(prefsChangeNotifierProvider); _autoLockInfo = ref.read(prefsChangeNotifierProvider).autoLockInfo; if (_autoLockInfo.enabled) { _idleMonitor = IdleMonitor( @@ -242,7 +246,7 @@ class _HomeViewState extends ConsumerState { @override dispose() { - ref.read(prefsChangeNotifierProvider).removeListener(_prefsTimeoutListener); + _prefs.removeListener(_prefsTimeoutListener); _idleMonitor?.detach(); _pageController.dispose(); _rotateIconController.forward = null; From 45e677f2bca0ff50c9b2dff152f5332fde254c98 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 19 Sep 2025 08:06:25 -0600 Subject: [PATCH 74/91] mobile slatepack creation --- lib/pages/send_view/send_view.dart | 141 +++++--- .../sub_widgets/mwc_slatepack_dialog.dart | 303 +++++++++--------- 2 files changed, 244 insertions(+), 200 deletions(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 084277a9c2..77cbee8b63 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -9,7 +9,6 @@ */ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:cs_monero/cs_monero.dart' as lib_monero; @@ -22,6 +21,7 @@ import 'package:tuple/tuple.dart'; import '../../models/input.dart'; import '../../models/isar/models/isar_models.dart'; +import '../../models/mwc_slatepack_models.dart'; import '../../models/paynym/paynym_account_lite.dart'; import '../../models/send_view_auto_fill_data.dart'; import '../../providers/providers.dart'; @@ -47,6 +47,7 @@ import '../../utilities/eth_commons.dart'; import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; import '../../utilities/prefs.dart'; +import '../../utilities/show_loading.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; @@ -54,6 +55,7 @@ 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/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'; @@ -79,6 +81,7 @@ import '../coin_control/coin_control_view.dart'; import 'confirm_transaction_view.dart'; import 'sub_widgets/building_transaction_dialog.dart'; import 'sub_widgets/dual_balance_selection_sheet.dart'; +import 'sub_widgets/mwc_slatepack_dialog.dart'; import 'sub_widgets/transaction_fee_selection_sheet.dart'; class SendView extends ConsumerStatefulWidget { @@ -611,6 +614,97 @@ class _SendViewState extends ConsumerState { } } + Future _createSlatepack() async { + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 100)); + + try { + if (mounted) { + final wallet = + ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet; + + final amount = ref.read(pSendAmount)!; + + Future wrappedFutureWithDelay() async { + await Future.delayed(const Duration(seconds: 1)); + return wallet.createSlatepack( + amount: amount, + recipientAddress: null, + // No specific recipient for manual slatepack. + message: + onChainNoteController.text.isNotEmpty == true + ? onChainNoteController.text + : null, + encrypt: false, // No encryption without recipient address. + ); + } + + // Create slatepack. + Exception? ex; + final slatepackResult = await showLoading( + whileFuture: wrappedFutureWithDelay(), + context: context, + message: "Building slatepack...", + delay: const Duration(seconds: 2), + onException: (e) => ex = e, + ); + + if (slatepackResult == null || + !slatepackResult.success || + slatepackResult.slatepack == null || + ex != null) { + String error = + ex?.toString() ?? + slatepackResult?.error ?? + 'Failed to create slatepack'; + if (error.startsWith("Exception:")) { + error = error.replaceFirst("Exception:", "").trim(); + } + throw Exception(error); + } + + // refresh asap to show the pending slate tx in history + unawaited(() async { + await Future.delayed(Duration.zero); + await wallet.refresh(); + }()); + + // Show slatepack dialog. + if (mounted) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => StackDialogBase( + child: MwcSlatepackDialog(slatepackResult: slatepackResult), + ), + ); + + // Clear form after slatepack dialog is closed. + clearSendForm(); + } + } + } catch (e, s) { + Logging.instance.e( + 'Failed to create MWC slatepack on mobile', + error: e, + stackTrace: s, + ); + + if (mounted) { + await showDialog( + context: context, + builder: + (context) => StackOkDialog( + title: "Slatepack Creation Failed", + message: e.toString(), + ), + ); + } + } + } + Future _previewTransaction() async { // wait for keyboard to disappear FocusScope.of(context).unfocus(); @@ -852,40 +946,6 @@ class _SendViewState extends ConsumerState { ); } else { final memo = coin is Stellar ? memoController.text : null; - - // For MWC, determine transaction method and add it to otherData. - Map? otherData; - if (coin is Mimblewimblecoin) { - String transactionMethod = 'slatepack'; // Default to slatepack. - - if (_selectedTransactionMethod != null) { - switch (_selectedTransactionMethod!) { - case MwcTransactionMethod.slatepack: - transactionMethod = 'slatepack'; - break; - case MwcTransactionMethod.mwcmqs: - transactionMethod = 'mwcmqs'; - break; - // case MwcTransactionMethod.http: - // transactionMethod = 'http'; - // break; - // case MwcTransactionMethod.unknown: - // // Auto-detect from address format. - // final mwcCoin = coin as Mimblewimblecoin; - // final method = mwcCoin.getTransactionMethod(_address!); - // transactionMethod = method.toString().split('.').last; - // break; - } - } else { - // Auto-detect from address format if no method selected. - final mwcCoin = coin as Mimblewimblecoin; - final method = mwcCoin.getTransactionMethod(_address!); - transactionMethod = method.toString().split('.').last; - } - - otherData = {'transactionMethod': transactionMethod}; - } - txDataFuture = wallet.prepareSend( txData: TxData( recipients: [ @@ -900,10 +960,6 @@ class _SendViewState extends ConsumerState { feeRateType: ref.read(feeRateTypeMobileStateProvider), satsPerVByte: isCustomFee.value ? customFeeRate : null, ethEIP1559Fee: ethFee, - otherData: - otherData != null - ? jsonEncode(otherData) - : null, // Include MWC transaction method info. utxos: (wallet is CoinControlInterface && coinControlEnabled && @@ -2600,7 +2656,12 @@ class _SendViewState extends ConsumerState { TextButton( onPressed: ref.watch(pPreviewTxButtonEnabled(coin)) - ? _previewTransaction + ? ref.watch( + pSelectedMwcTransactionMethod, + ) == + MwcTransactionMethod.slatepack + ? _createSlatepack + : _previewTransaction : null, style: ref.watch(pPreviewTxButtonEnabled(coin)) diff --git a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart index 1ae4f9d600..ec57cf0a53 100644 --- a/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart +++ b/lib/pages/send_view/sub_widgets/mwc_slatepack_dialog.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../../models/mwc_slatepack_models.dart'; import '../../../notifications/show_flush_bar.dart'; @@ -10,7 +9,12 @@ import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../widgets/conditional_parent.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/qr.dart'; +import '../../../widgets/rounded_container.dart'; import '../../../widgets/rounded_white_container.dart'; class MwcSlatepackDialog extends ConsumerStatefulWidget { @@ -51,181 +55,160 @@ class _MwcSlatepackDialogState extends ConsumerState { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Send Slatepack", - style: STextStyles.pageTitleH2(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Padding( - padding: const EdgeInsets.all(32), - child: Column( + return ConditionalParent( + condition: Util.isDesktop, + builder: + (child) => Column( mainAxisSize: MainAxisSize.min, children: [ - // Instructions. - Container( - padding: const EdgeInsets.all(12), - width: double.infinity, - decoration: BoxDecoration( - color: Theme.of( + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Send Slatepack", + style: STextStyles.pageTitleH2(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding(padding: const EdgeInsets.all(32), child: child), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Instructions. + RoundedContainer( + color: + Theme.of(context).extension()!.textFieldDefaultBG, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Next Steps:", + style: STextStyles.label( context, - ).extension()!.infoItemIcons.withOpacity(0.05), - borderRadius: BorderRadius.circular(8), + ).copyWith(fontWeight: FontWeight.w600), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Next Steps:", - style: STextStyles.label( - context, - ).copyWith(fontWeight: FontWeight.w600), - ), - const SizedBox(height: 8), - Text( - "1. Share this slatepack with the recipient\n" - "2. Wait for them to return the response slatepack\n" - "3. Import their response to finalize the transaction", - style: STextStyles.w400_14(context), - ), - ], + const SizedBox(height: 8), + Text( + "1. Share this slatepack with the recipient\n" + "2. Wait for them to return the response slatepack\n" + "3. Import their response to finalize the transaction", + style: STextStyles.w400_14(context), ), - ), + ], + ), + ), - // Encryption status. - // we don't encrypt so ignore for now - // if (widget.slatepackResult.wasEncrypted == true) - // Container( - // padding: const EdgeInsets.symmetric( - // horizontal: 12, - // vertical: 8, - // ), - // decoration: BoxDecoration( - // color: Theme.of( - // context, - // ).extension()!.infoItemIcons.withOpacity(0.1), - // borderRadius: BorderRadius.circular(8), - // ), - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon( - // Icons.lock, - // size: 16, - // color: - // Theme.of( - // context, - // ).extension()!.infoItemIcons, - // ), - // const SizedBox(width: 8), - // Text( - // "Encrypted for recipient", - // style: STextStyles.label(context).copyWith( - // color: - // Theme.of( - // context, - // ).extension()!.infoItemIcons, - // ), - // ), - // ], - // ), - // ), - const SizedBox(height: 24), + // Encryption status. + // we don't encrypt so ignore for now + // if (widget.slatepackResult.wasEncrypted == true) + // Container( + // padding: const EdgeInsets.symmetric( + // horizontal: 12, + // vertical: 8, + // ), + // decoration: BoxDecoration( + // color: Theme.of( + // context, + // ).extension()!.infoItemIcons.withOpacity(0.1), + // borderRadius: BorderRadius.circular(8), + // ), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Icon( + // Icons.lock, + // size: 16, + // color: + // Theme.of( + // context, + // ).extension()!.infoItemIcons, + // ), + // const SizedBox(width: 8), + // Text( + // "Encrypted for recipient", + // style: STextStyles.label(context).copyWith( + // color: + // Theme.of( + // context, + // ).extension()!.infoItemIcons, + // ), + // ), + // ], + // ), + // ), + const SizedBox(height: 12), - // QR Code view. - RoundedWhiteContainer( - child: Column( - children: [ - Text( - "Slatepack QR Code", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: QrImageView( - data: widget.slatepackResult.slatepack!, - size: 200, - backgroundColor: Colors.white, - foregroundColor: Colors.black, - errorCorrectionLevel: QrErrorCorrectLevel.M, - ), - ), - ], - ), - ), + // QR Code view. + Center( + child: QR( + data: widget.slatepackResult.slatepack!, + size: 220, + // errorCorrectionLevel: QrErrorCorrectLevel.M, + ), + ), - const SizedBox(height: 12), + const SizedBox(height: 12), - // Slatepack text view. - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + // Slatepack text view. + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( children: [ - Row( - children: [ - Text( - "Slatepack", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - GestureDetector( - onTap: _copySlatepack, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 10, - height: 10, - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - const SizedBox(width: 4), - Text("Copy", style: STextStyles.link2(context)), - ], + Text("Slatepack", style: STextStyles.itemSubtitle(context)), + const Spacer(), + GestureDetector( + onTap: _copySlatepack, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: + Theme.of( + context, + ).extension()!.infoItemIcons, ), - ), - ], - ), - const SizedBox(height: 8), - Container( - constraints: const BoxConstraints( - maxHeight: 200, - minHeight: 100, - ), - child: SingleChildScrollView( - child: SelectableText( - widget.slatepackResult.slatepack!, - style: STextStyles.w400_14( - context, - ).copyWith(fontFamily: 'monospace'), - ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], ), ), ], ), - ), - ], + const SizedBox(height: 8), + Container( + constraints: const BoxConstraints( + maxHeight: 200, + minHeight: 100, + ), + child: SingleChildScrollView( + child: SelectableText( + widget.slatepackResult.slatepack!, + style: STextStyles.w400_14( + context, + ).copyWith(fontFamily: 'monospace'), + ), + ), + ), + ], + ), ), - ), - ], + + if (!Util.isDesktop) + PrimaryButton(label: "Done", onPressed: Navigator.of(context).pop), + ], + ), ); } } From 084a622c32ef5d654f943176b52fcf7ac059e26b Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 19 Sep 2025 08:07:10 -0600 Subject: [PATCH 75/91] update libmwc --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index d34d8fb244..715ab35eb0 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit d34d8fb2447dd3bf732bb07b68bd3254732ba7b6 +Subproject commit 715ab35eb00af7c96600e1df897fc4e4dd711b14 From 88add78be4ef790f446c82103b0276a75c83edc1 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 19 Sep 2025 08:32:58 -0600 Subject: [PATCH 76/91] dart format --- .../wallet/impl/mimblewimblecoin_wallet.dart | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 42e807fc2a..5566091c82 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -17,6 +17,7 @@ import '../../../models/isar/models/blockchain_data/transaction.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/mwc_slatepack_models.dart'; import '../../../models/mwcmqs_config_model.dart'; import '../../../models/node_model.dart'; import '../../../models/paymint/fee_object_model.dart'; @@ -26,7 +27,6 @@ import '../../../services/event_bus/events/global/node_connection_status_changed import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import '../../../services/event_bus/global_event_bus.dart'; -import '../../../models/mwc_slatepack_models.dart'; import '../../../utilities/amount/amount.dart'; import '../../../utilities/default_mwcmqs.dart'; import '../../../utilities/flutter_secure_storage_interface.dart'; @@ -76,26 +76,36 @@ class MimblewimblecoinWallet extends Bip39Wallet { key: '${walletId}_mwcmqsConfig', value: stringConfig, ); - + // Restart MWCMQS listener with new configuration if wallet has a handle. try { - final handle = await secureStorageInterface.read(key: '${walletId}_wallet'); + final handle = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); if (handle != null && handle.isNotEmpty) { await stopSlatepackListener(); await startSlatepackListener(); - Logging.instance.i('Restarted MWCMQS listener with new config: $host:$port'); + Logging.instance.i( + 'Restarted MWCMQS listener with new config: $host:$port', + ); } } catch (e, s) { - Logging.instance.e('Failed to restart MWCMQS listener after config update: $e\n$s'); + Logging.instance.e( + 'Failed to restart MWCMQS listener after config update: $e\n$s', + ); } } Future _ensureWalletOpen() async { - final existing = await secureStorageInterface.read(key: '${walletId}_wallet'); + final existing = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); if (existing != null && existing.isNotEmpty) return existing; final config = await _getRealConfig(); - final password = await secureStorageInterface.read(key: '${walletId}_password'); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); if (password == null) { throw Exception('Wallet password not found'); } @@ -103,7 +113,10 @@ class MimblewimblecoinWallet extends Bip39Wallet { config: config, password: password, ); - await secureStorageInterface.write(key: '${walletId}_wallet', value: opened); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: opened, + ); return opened; } @@ -133,15 +146,13 @@ class MimblewimblecoinWallet extends Bip39Wallet { if (customConfigJson != null) { try { - final customConfig = jsonDecode(customConfigJson) as Map; + final customConfig = + jsonDecode(customConfigJson) as Map; final host = customConfig['mwcmqs_domain'] as String?; final port = customConfig['mwcmqs_port'] as int?; - + if (host != null && port != null) { - return MwcMqsConfigModel( - host: host, - port: port, - ); + return MwcMqsConfigModel(host: host, port: port); } } catch (e) { Logging.instance.w('Failed to parse custom MWCMQS config: $e'); @@ -199,13 +210,18 @@ class MimblewimblecoinWallet extends Bip39Wallet { /// Decode a slatepack. Future decodeSlatepack(String slatepack) async { try { - final handle = await secureStorageInterface.read(key: '${walletId}_wallet'); - final result = handle != null - ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( - wallet: handle, - slatepack: slatepack, - ) - : await mimblewimblecoin.Libmwc.decodeSlatepack(slatepack: slatepack); + final handle = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); + final result = + handle != null + ? await mimblewimblecoin.Libmwc.decodeSlatepackWithWallet( + wallet: handle, + slatepack: slatepack, + ) + : await mimblewimblecoin.Libmwc.decodeSlatepack( + slatepack: slatepack, + ); return SlatepackDecodeResult( success: true, @@ -292,7 +308,9 @@ class MimblewimblecoinWallet extends Bip39Wallet { try { await _ensureWalletOpen(); final mwcmqsConfig = await getMwcMqsConfig(); - final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); mimblewimblecoin.Libmwc.startMwcMqsListener( wallet: wallet!, mwcmqsConfig: mwcmqsConfig.toString(), @@ -798,7 +816,6 @@ class MimblewimblecoinWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { - if (isRestore != true) { String? encodedWallet = await secureStorageInterface.read( key: "${walletId}_wallet", From cdcdafe35efbd79c4673599108329040a6afd5ba Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 19 Sep 2025 08:59:55 -0600 Subject: [PATCH 77/91] bare minimum but functional slatepack ui in both desktop and mobile UIs --- lib/pages/receive_view/receive_view.dart | 70 +++++- .../sub_widgets/slatepack_entry_dialog.dart | 223 ++++++++++++++++++ .../sub_widgets/desktop_receive.dart | 51 +--- .../wallet/impl/mimblewimblecoin_wallet.dart | 47 ++++ 4 files changed, 329 insertions(+), 62 deletions(-) create mode 100644 lib/pages/receive_view/sub_widgets/slatepack_entry_dialog.dart diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 9e476ad98d..693bed3082 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -48,10 +48,14 @@ import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/custom_loading_overlay.dart'; import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/dialogs/s_dialog.dart'; import '../../widgets/qr.dart'; import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; import 'addresses/wallet_addresses_view.dart'; import 'generate_receiving_uri_qr_code_view.dart'; +import 'sub_widgets/mwc_slatepack_import_dialog.dart'; +import 'sub_widgets/slatepack_entry_dialog.dart'; class ReceiveView extends ConsumerStatefulWidget { const ReceiveView({ @@ -85,6 +89,58 @@ class _ReceiveViewState extends ConsumerState { final Map _addressMap = {}; final Map> _addressSubMap = {}; + Future _importSlatepack() async { + final slatepackString = await showDialog( + context: context, + builder: (context) => const SlatepackEntryDialog(), + ); + + if (slatepackString == null) return; + if (mounted) { + final wallet = + ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet; + + Exception? ex; + final result = await showLoading( + whileFuture: wallet.fullDecodeSlatepack(slatepackString), + context: context, + message: "Decoding slatepack...", + onException: (e) => ex = e, + ); + + if (result == null || ex != null) { + if (mounted) { + await showDialog( + context: context, + builder: + (context) => StackOkDialog( + title: "Slatepack receive error", + message: + ex?.toString() ?? "Unexpected result without exception", + ), + ); + } + return; + } + + if (mounted) { + await showDialog( + context: context, + builder: + (context) => SDialog( + child: MwcSlatepackImportDialog( + walletId: widget.walletId, + clipboard: widget.clipboard, + rawSlatepack: result.raw, + decoded: result.result, + slatepackType: result.type, + ), + ), + ); + } + } + } + Future generateNewAddress() async { final wallet = ref.read(pWallets).getWallet(walletId); @@ -698,19 +754,7 @@ class _ReceiveViewState extends ConsumerState { const SizedBox(height: 12), SecondaryButton( label: "Import Slatepack", - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); - if (wallet is MimblewimblecoinWallet) { - // await showDialog( - // context: context, - // builder: - // (context) => MwcSlatepackImportDialog( - // wallet: wallet, - // clipboard: clipboard, - // ), - // ); - } - }, + onPressed: _importSlatepack, ), ], const SizedBox(height: 30), diff --git a/lib/pages/receive_view/sub_widgets/slatepack_entry_dialog.dart b/lib/pages/receive_view/sub_widgets/slatepack_entry_dialog.dart new file mode 100644 index 0000000000..ec9aee0c36 --- /dev/null +++ b/lib/pages/receive_view/sub_widgets/slatepack_entry_dialog.dart @@ -0,0 +1,223 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../providers/global/barcode_scanner_provider.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/barcode_scanner_interface.dart'; +import '../../../utilities/clipboard_interface.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/icon_widgets/clipboard_icon.dart'; +import '../../../widgets/icon_widgets/qrcode_icon.dart'; +import '../../../widgets/icon_widgets/x_icon.dart'; +import '../../../widgets/stack_dialog.dart'; +import '../../../widgets/stack_text_field.dart'; +import '../../../widgets/textfield_icon_button.dart'; + +class SlatepackEntryDialog extends ConsumerStatefulWidget { + const SlatepackEntryDialog({ + super.key, + this.clipboard = const ClipboardWrapper(), + }); + + final ClipboardInterface clipboard; + + @override + ConsumerState createState() => + _SlatepackEntryDialogState(); +} + +class _SlatepackEntryDialogState extends ConsumerState { + final _receiveSlateController = TextEditingController(); + final _slateFocusNode = FocusNode(); + + bool _slateToggleFlag = false; + + Future _pasteSlatepack() async { + final ClipboardData? data = await widget.clipboard.getData( + Clipboard.kTextPlain, + ); + if (data?.text != null && data!.text!.isNotEmpty) { + _receiveSlateController.text = data.text!; + setState(() { + _slateToggleFlag = _receiveSlateController.text.isNotEmpty; + }); + } + } + + Future _scanQr() async { + try { + if (_slateFocusNode.hasFocus) { + _slateFocusNode.unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + if (mounted) { + final qrResult = await ref.read(pBarcodeScanner).scan(context: context); + if (qrResult.rawContent.isNotEmpty && qrResult.rawContent != "null") { + _receiveSlateController.text = qrResult.rawContent; + setState(() { + _slateToggleFlag = _receiveSlateController.text.isNotEmpty; + }); + } + } + } on PlatformException catch (e, s) { + if (mounted) { + try { + await checkCamPermDeniedMobileAndOpenAppSettings( + context, + logging: Logging.instance, + ); + } catch (e, s) { + Logging.instance.e( + "Failed to check cam permissions", + error: e, + stackTrace: s, + ); + } + } else { + Logging.instance.e( + "Failed to get camera permissions while trying to scan qr code in SendView: ", + error: e, + stackTrace: s, + ); + } + } + } + + @override + void dispose() { + _receiveSlateController.dispose(); + _slateFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Receive Slatepack", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox(height: 12), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("receiveViewSlatepackFieldKey"), + controller: _receiveSlateController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + setState(() { + _slateToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _slateFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter Slatepack Message", + _slateFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, // Adjust vertical padding for better alignment + ), + suffixIcon: Padding( + padding: + _receiveSlateController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _slateToggleFlag + ? TextFieldIconButton( + key: const Key( + "receiveViewClearSlatepackFieldButtonKey", + ), + onTap: () { + _receiveSlateController.text = ""; + setState(() { + _slateToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "receiveViewPasteSlatepackFieldButtonKey", + ), + onTap: _pasteSlatepack, + child: + _receiveSlateController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_receiveSlateController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key("sendViewScanQrButtonKey"), + onTap: _scanQr, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox(height: 16), + PrimaryButton( + label: "Import", + enabled: _slateToggleFlag, + onPressed: + !_slateToggleFlag + ? null + : () => + Navigator.of(context).pop(_receiveSlateController.text), + ), + const SizedBox(height: 16), + SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 3fe9f4ed53..3e30ff8ca8 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -20,7 +20,6 @@ import 'package:tuple/tuple.dart'; import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/keys/view_only_wallet_data.dart'; -import '../../../../models/mwc_slatepack_models.dart'; import '../../../../notifications/show_flush_bar.dart'; import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import '../../../../pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart'; @@ -111,59 +110,13 @@ class _DesktopReceiveState extends ConsumerState { } } - Future<({SlatepackDecodeResult result, String type, String raw})?> - _decodeSlatepack() async { - // add delay for showloading exception catching hack fix - await Future.delayed(const Duration(seconds: 1)); - + Future _onReceiveSlatePressed() async { final wallet = ref.read(pWallets).getWallet(walletId) as MimblewimblecoinWallet; - final text = _receiveSlateController.text.trim(); - - if (text.isEmpty) { - return null; - } - - // Basic format validation. - final coin = wallet.cryptoCurrency as Mimblewimblecoin; - if (!coin.isSlatepack(text)) { - throw Exception("Invalid slatepack format"); - } - - // Attempt to decode. - final decoded = await wallet.decodeSlatepack(text); - - if (decoded.success) { - final analysis = await wallet.analyzeSlatepack(text); - - String _determineSlatepackType(SlatepackDecodeResult decoded) { - // Fallback analysis based on sender/recipient addresses. - if (decoded.senderAddress != null && decoded.recipientAddress != null) { - return "S2 (Response)"; - } else if (decoded.senderAddress != null) { - return "S1 (Initial)"; - } else { - return "Unknown"; - } - } - final String slatepackType = switch (analysis.status) { - 'S1' => "S1 (Initial Send)", - 'S2' => "S2 (Response)", - 'S3' => "S3 (Finalized)", - _ => _determineSlatepackType(decoded), // Fallback. - }; - - return (result: decoded, type: slatepackType, raw: text); - } else { - throw Exception(decoded.error ?? "Failed to decode slatepack"); - } - } - - Future _onReceiveSlatePressed() async { Exception? ex; final result = await showLoading( - whileFuture: _decodeSlatepack(), + whileFuture: wallet.fullDecodeSlatepack(_receiveSlateController.text), context: context, message: "Decoding slatepack...", rootNavigator: Util.isDesktop, diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart index 5566091c82..567908275e 100644 --- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart +++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart @@ -207,6 +207,53 @@ class MimblewimblecoinWallet extends Bip39Wallet { } } + // this isn't nearly the best way to handle this but better than copy pasting + // this function in two places (mobile and desktop widgets)... + Future<({SlatepackDecodeResult result, String type, String raw})?> + fullDecodeSlatepack(String slatepack) async { + // add delay for showloading exception catching hack fix + await Future.delayed(const Duration(seconds: 1)); + + if (slatepack.isEmpty) { + return null; + } + + // Basic format validation. + final coin = cryptoCurrency as Mimblewimblecoin; + if (!coin.isSlatepack(slatepack)) { + throw Exception("Invalid slatepack format"); + } + + // Attempt to decode. + final decoded = await decodeSlatepack(slatepack); + + if (decoded.success) { + final analysis = await analyzeSlatepack(slatepack); + + String _determineSlatepackType(SlatepackDecodeResult decoded) { + // Fallback analysis based on sender/recipient addresses. + if (decoded.senderAddress != null && decoded.recipientAddress != null) { + return "S2 (Response)"; + } else if (decoded.senderAddress != null) { + return "S1 (Initial)"; + } else { + return "Unknown"; + } + } + + final String slatepackType = switch (analysis.status) { + 'S1' => "S1 (Initial Send)", + 'S2' => "S2 (Response)", + 'S3' => "S3 (Finalized)", + _ => _determineSlatepackType(decoded), // Fallback. + }; + + return (result: decoded, type: slatepackType, raw: slatepack); + } else { + throw Exception(decoded.error ?? "Failed to decode slatepack"); + } + } + /// Decode a slatepack. Future decodeSlatepack(String slatepack) async { try { From f4e5e81947ee819bbd835d07b2bdbd6031a5d829 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 19 Sep 2025 13:43:47 -0600 Subject: [PATCH 78/91] update libmwc --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 715ab35eb0..8dc699c35f 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 715ab35eb00af7c96600e1df897fc4e4dd711b14 +Subproject commit 8dc699c35f1bae8337548a74b84e2fe4b2d1f2ba From 71d937b1b7fd14e8d1f5f17b282d5a6ec4587479 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Sun, 21 Sep 2025 21:06:03 -0500 Subject: [PATCH 79/91] fix(mwc): update flutter_libmwc macOS deployment target to 10.14 --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 8dc699c35f..7f721883d2 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 8dc699c35f1bae8337548a74b84e2fe4b2d1f2ba +Subproject commit 7f721883d22f892b49a1b58a162740aa425bc06f From 12a895ca9faf8b179412e9f11b4a2554651919c3 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 22 Sep 2025 12:16:58 -0600 Subject: [PATCH 80/91] fix: textfield hint text --- lib/pages/send_view/send_view.dart | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 77cbee8b63..1577e568e8 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -151,9 +151,6 @@ class _SendViewState extends ConsumerState { Set selectedUTXOs = {}; - // MWC transaction method selection. - MwcTransactionMethod? _selectedTransactionMethod; - void _applyUri(PaymentUriData paymentData) { try { // auto fill address @@ -1676,11 +1673,7 @@ class _SendViewState extends ConsumerState { focusNode: _addressFocusNode, style: STextStyles.field(context), decoration: standardInputDecoration( - coin is Mimblewimblecoin && - _selectedTransactionMethod == - MwcTransactionMethod - .slatepack || - _selectedTransactionMethod == null + isMwcSlatepack ? "Enter ${coin.ticker} address (optional)" : "Enter ${coin.ticker} address", _addressFocusNode, From f10021dc2a21fba43fafa6c587bc9df9876b618a Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 22 Sep 2025 12:19:16 -0600 Subject: [PATCH 81/91] use minimal libmwc changes branch --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 7f721883d2..964c362e8e 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 7f721883d22f892b49a1b58a162740aa425bc06f +Subproject commit 964c362e8efdd5719ad4618c9c5c42b454a0f4d9 From eaf3af94d8ced39ce701fcef520b87e2ecedbe2c Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 22 Sep 2025 13:34:33 -0600 Subject: [PATCH 82/91] clean up import receive mwc slatepack info dialog --- .../mwc_slatepack_import_dialog.dart | 139 +++++++++++++----- 1 file changed, 102 insertions(+), 37 deletions(-) diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 90080feb5a..18b6af7bd4 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,11 +9,14 @@ import '../../../models/mwc_slatepack_models.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../themes/stack_colors.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/amount/amount_formatter.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/clipboard_interface.dart'; import '../../../utilities/show_loading.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../widgets/conditional_parent.dart'; import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; @@ -112,6 +117,37 @@ class _MwcSlatepackImportDialogState } } + late final Amount? _amount; + + // late final Amount? _fee; + + @override + void initState() { + final map = jsonDecode(widget.decoded.slateJson!) as Map; + + final rawAmount = BigInt.tryParse(map["amount"].toString()); + _amount = + rawAmount == null + ? null + : Amount( + rawValue: rawAmount, + fractionDigits: + ref.read(pWalletCoin(widget.walletId)).fractionDigits, + ); + + // final rawFee = BigInt.tryParse(map["fee"].toString()); + // _fee = + // rawFee == null + // ? null + // : Amount( + // rawValue: rawFee, + // fractionDigits: + // ref.read(pWalletCoin(widget.walletId)).fractionDigits, + // ); + + super.initState(); + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; @@ -119,22 +155,23 @@ class _MwcSlatepackImportDialogState return Column( mainAxisSize: MainAxisSize.min, children: [ - // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Import Slatepack", - style: STextStyles.pageTitleH2(context), + if (isDesktop) + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Import Slatepack", + style: STextStyles.pageTitleH2(context), + ), ), - ), - const DesktopDialogCloseButton(), - ], - ), + const DesktopDialogCloseButton(), + ], + ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), + padding: EdgeInsets.symmetric(horizontal: isDesktop ? 32 : 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -156,31 +193,59 @@ class _MwcSlatepackImportDialogState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - DetailItem(title: "Type", detail: widget.slatepackType), - const DetailDivider(), - DetailItem( - title: "Encrypted", - detail: (widget.decoded.wasEncrypted ?? false).toString(), - ), - if (widget.decoded.senderAddress != null) - const DetailDivider(), - if (widget.decoded.senderAddress != null) - DetailItem( - title: "From", - detail: widget.decoded.senderAddress!, + if (!isDesktop) + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 24), + child: Text( + "Import slatepack", + style: STextStyles.pageTitleH2(context), + ), ), - if (widget.decoded.recipientAddress != null) - const DetailDivider(), - if (widget.decoded.recipientAddress != null) + + // DetailItem(title: "Type", detail: widget.slatepackType), + // const DetailDivider(), + // DetailItem( + // title: "Encrypted", + // detail: (widget.decoded.wasEncrypted ?? false).toString(), + // ), + // if (widget.decoded.senderAddress != null) + // const DetailDivider(), + // if (widget.decoded.senderAddress != null) + // DetailItem( + // title: "From", + // detail: widget.decoded.senderAddress!, + // ), + // if (widget.decoded.recipientAddress != null) + // const DetailDivider(), + // if (widget.decoded.recipientAddress != null) + // DetailItem( + // title: "To", + // detail: widget.decoded.recipientAddress!, + // ), + // if (_amount != null) const DetailDivider(), + if (_amount != null) DetailItem( - title: "To", - detail: widget.decoded.recipientAddress!, + title: "Amount", + detail: ref + .watch( + pAmountFormatter( + ref.watch(pWalletCoin(widget.walletId)), + ), + ) + .format(_amount), ), - const DetailDivider(), - DetailItem( - title: "Slatepack", - detail: widget.decoded.slateJson!, - ), + // if (_fee != null) const DetailDivider(), + // if (_fee != null) + // DetailItem( + // title: "Fee", + // detail: ref + // .watch( + // pAmountFormatter( + // ref.watch(pWalletCoin(widget.walletId)), + // ), + // ) + // .format(_fee), + // ), ], ), ), @@ -209,7 +274,7 @@ class _MwcSlatepackImportDialogState ], ), ), - const SizedBox(height: 32), + isDesktop ? const SizedBox(height: 32) : const SizedBox(height: 16), ], ); } From 905031cc4e61997efdee365bfd680af7234fa3f3 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 22 Sep 2025 13:34:33 -0600 Subject: [PATCH 83/91] clean up import receive mwc slatepack response dialog --- .../mwc_slatepack_import_dialog.dart | 321 +++++++++--------- 1 file changed, 157 insertions(+), 164 deletions(-) diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 90080feb5a..105c7e8504 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -1,24 +1,26 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import '../../../models/mwc_slatepack_models.dart'; -import '../../../notifications/show_flush_bar.dart'; import '../../../providers/global/wallets_provider.dart'; import '../../../themes/stack_colors.dart'; -import '../../../utilities/assets.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/amount/amount_formatter.dart'; import '../../../utilities/clipboard_interface.dart'; import '../../../utilities/show_loading.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; +import '../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../wallets/wallet/impl/mimblewimblecoin_wallet.dart'; import '../../../widgets/conditional_parent.dart'; -import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../../widgets/custom_buttons/simple_copy_button.dart'; import '../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/detail_item.dart'; +import '../../../widgets/dialogs/s_dialog.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../../widgets/stack_dialog.dart'; @@ -106,12 +108,42 @@ class _MwcSlatepackImportDialogState (context) => _SlatepackResponseDialog( responseSlatepack: result.responseSlatepack, wasEncrypted: result.wasEncrypted, - clipboard: widget.clipboard, ), ); } } + late final Amount? _amount; + + // late final Amount? _fee; + + @override + void initState() { + final map = jsonDecode(widget.decoded.slateJson!) as Map; + + final rawAmount = BigInt.tryParse(map["amount"].toString()); + _amount = + rawAmount == null + ? null + : Amount( + rawValue: rawAmount, + fractionDigits: + ref.read(pWalletCoin(widget.walletId)).fractionDigits, + ); + + // final rawFee = BigInt.tryParse(map["fee"].toString()); + // _fee = + // rawFee == null + // ? null + // : Amount( + // rawValue: rawFee, + // fractionDigits: + // ref.read(pWalletCoin(widget.walletId)).fractionDigits, + // ); + + super.initState(); + } + @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; @@ -119,22 +151,23 @@ class _MwcSlatepackImportDialogState return Column( mainAxisSize: MainAxisSize.min, children: [ - // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Import Slatepack", - style: STextStyles.pageTitleH2(context), + if (isDesktop) + // Header with title and close button. + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Import Slatepack", + style: STextStyles.pageTitleH2(context), + ), ), - ), - const DesktopDialogCloseButton(), - ], - ), + const DesktopDialogCloseButton(), + ], + ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), + padding: EdgeInsets.symmetric(horizontal: isDesktop ? 32 : 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -156,31 +189,59 @@ class _MwcSlatepackImportDialogState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - DetailItem(title: "Type", detail: widget.slatepackType), - const DetailDivider(), - DetailItem( - title: "Encrypted", - detail: (widget.decoded.wasEncrypted ?? false).toString(), - ), - if (widget.decoded.senderAddress != null) - const DetailDivider(), - if (widget.decoded.senderAddress != null) - DetailItem( - title: "From", - detail: widget.decoded.senderAddress!, + if (!isDesktop) + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 24), + child: Text( + "Import slatepack", + style: STextStyles.pageTitleH2(context), + ), ), - if (widget.decoded.recipientAddress != null) - const DetailDivider(), - if (widget.decoded.recipientAddress != null) + + // DetailItem(title: "Type", detail: widget.slatepackType), + // const DetailDivider(), + // DetailItem( + // title: "Encrypted", + // detail: (widget.decoded.wasEncrypted ?? false).toString(), + // ), + // if (widget.decoded.senderAddress != null) + // const DetailDivider(), + // if (widget.decoded.senderAddress != null) + // DetailItem( + // title: "From", + // detail: widget.decoded.senderAddress!, + // ), + // if (widget.decoded.recipientAddress != null) + // const DetailDivider(), + // if (widget.decoded.recipientAddress != null) + // DetailItem( + // title: "To", + // detail: widget.decoded.recipientAddress!, + // ), + // if (_amount != null) const DetailDivider(), + if (_amount != null) DetailItem( - title: "To", - detail: widget.decoded.recipientAddress!, + title: "Amount", + detail: ref + .watch( + pAmountFormatter( + ref.watch(pWalletCoin(widget.walletId)), + ), + ) + .format(_amount), ), - const DetailDivider(), - DetailItem( - title: "Slatepack", - detail: widget.decoded.slateJson!, - ), + // if (_fee != null) const DetailDivider(), + // if (_fee != null) + // DetailItem( + // title: "Fee", + // detail: ref + // .watch( + // pAmountFormatter( + // ref.watch(pWalletCoin(widget.walletId)), + // ), + // ) + // .format(_fee), + // ), ], ), ), @@ -209,7 +270,7 @@ class _MwcSlatepackImportDialogState ], ), ), - const SizedBox(height: 32), + isDesktop ? const SizedBox(height: 32) : const SizedBox(height: 16), ], ); } @@ -219,151 +280,83 @@ class _SlatepackResponseDialog extends StatelessWidget { const _SlatepackResponseDialog({ required this.responseSlatepack, required this.wasEncrypted, - required this.clipboard, }); final String responseSlatepack; final bool wasEncrypted; - final ClipboardInterface clipboard; - - void _copySlatepack(BuildContext context) { - clipboard.setData(ClipboardData(text: responseSlatepack)); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Response slatepack copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - } @override Widget build(BuildContext context) { - return StackDialogBase( + return SDialog( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header with title and close button. - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Response Slatepack", - style: STextStyles.pageTitleH2(context), - ), - AppBarIconButton( - size: 36, - color: Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.x, - color: - Theme.of( - context, - ).extension()!.topNavIconPrimary, - width: 24, - height: 24, - ), - onPressed: () => Navigator.of(context).pop(), + if (Util.isDesktop) + Padding( + padding: const EdgeInsets.only(left: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Response Slatepack", + style: STextStyles.pageTitleH2(context), + ), + const DesktopDialogCloseButton(), + ], ), - ], - ), - const SizedBox(height: 16), + ), Padding( - padding: const EdgeInsets.all(24), + padding: + Util.isDesktop + ? const EdgeInsets.only(left: 32, right: 32, bottom: 32) + : const EdgeInsets.only(left: 24, right: 24, bottom: 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ + if (!Util.isDesktop) const SizedBox(height: 24), Text( "Return this slatepack to the sender to complete the transaction.", - style: STextStyles.subtitle(context), - textAlign: TextAlign.center, + style: STextStyles.pageTitleH2(context), ), - const SizedBox(height: 16), - - if (wasEncrypted) - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .infoItemIcons - .withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.lock, - size: 16, - color: - Theme.of( - context, - ).extension()!.infoItemIcons, - ), - const SizedBox(width: 8), - Text( - "Encrypted Response", - style: STextStyles.label(context), - ), - ], + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Response slatepack", + style: STextStyles.itemSubtitle(context), ), - ), - const SizedBox(height: 16), - - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text( - "Response Slatepack", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - GestureDetector( - onTap: () => _copySlatepack(context), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 10, - height: 10, - ), - const SizedBox(width: 4), - Text("Copy", style: STextStyles.link2(context)), - ], - ), - ), - ], - ), - const SizedBox(height: 8), - Container( - constraints: const BoxConstraints( - maxHeight: 200, - minHeight: 100, - ), - child: SingleChildScrollView( - child: SelectableText( - responseSlatepack, - style: STextStyles.w400_14( - context, - ).copyWith(fontFamily: 'monospace'), - ), - ), + SimpleCopyButton(data: responseSlatepack), + ], + ), + const SizedBox(height: 8), + ConditionalParent( + condition: !Util.isDesktop, + builder: + (child) => SizedBox( + height: 220, + child: SingleChildScrollView(child: child), ), - ], + child: SelectableText( + responseSlatepack, + style: STextStyles.w500_14(context), ), ), - const SizedBox(height: 24), - PrimaryButton( - label: "Copy Response", - onPressed: () => _copySlatepack(context), + ConditionalParent( + condition: Util.isDesktop, + builder: + (child) => Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [child], + ), + child: PrimaryButton( + label: "Done", + width: Util.isDesktop ? 220 : null, + buttonHeight: Util.isDesktop ? ButtonHeight.l : null, + onPressed: Navigator.of(context).pop, + ), ), ], ), From 072f6aeb70e7cc8d124ca8910121dcc3711c6ea7 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 22 Sep 2025 19:27:15 -0500 Subject: [PATCH 84/91] fix(mwc): post (broadcast) finalized tx --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 964c362e8e..da3248981f 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 964c362e8efdd5719ad4618c9c5c42b454a0f4d9 +Subproject commit da3248981f1540322660d85a1dbe8a3328fc3816 From 6bc47344cb47aec4dae48df0a29716fbf074664c Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 23 Sep 2025 14:01:23 -0600 Subject: [PATCH 85/91] disable mwc on macos --- lib/app_config.dart | 3 +++ scripts/app_config/configure_stack_wallet.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/app_config.dart b/lib/app_config.dart index 09eeae26f7..5f9b37ab77 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -1,3 +1,6 @@ +// ignore: unused_import +import 'dart:io'; + import 'wallets/crypto_currency/crypto_currency.dart'; import 'wallets/crypto_currency/intermediate/frost_currency.dart'; diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 175d950388..b407b7d473 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -62,7 +62,7 @@ final List _supportedCoins = List.unmodifiable([ Dogecoin(CryptoCurrencyNetwork.main), Ecash(CryptoCurrencyNetwork.main), Epiccash(CryptoCurrencyNetwork.main), - Mimblewimblecoin(CryptoCurrencyNetwork.main), + if (!Platform.isMacOS) Mimblewimblecoin(CryptoCurrencyNetwork.main), Ethereum(CryptoCurrencyNetwork.main), Fact0rn(CryptoCurrencyNetwork.main), Firo(CryptoCurrencyNetwork.main), From 9dcb9cac22914c2305ba4552f3e57d4accc2cf37 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 Sep 2025 08:10:42 -0600 Subject: [PATCH 86/91] update libmwc with openWallet error checking --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index da3248981f..0581d81b5a 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit da3248981f1540322660d85a1dbe8a3328fc3816 +Subproject commit 0581d81b5abb9dbdee81a95b4b74958d81c3ae9b From 62c28caf3bf38e1bdf2c359c39f1cd9901bb81a4 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 Sep 2025 09:22:17 -0600 Subject: [PATCH 87/91] clear finalize field on success --- lib/pages/finalize_view/finalize_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/finalize_view/finalize_view.dart b/lib/pages/finalize_view/finalize_view.dart index fa69aae52f..b2538bb5c1 100644 --- a/lib/pages/finalize_view/finalize_view.dart +++ b/lib/pages/finalize_view/finalize_view.dart @@ -166,6 +166,7 @@ class _FinalizeViewState extends ConsumerState { ), ); } else { + _slateController.text = ""; unawaited( showFloatingFlushBar( type: FlushBarType.success, From 8930f3742228af4405b034382b2a76f8f7e311a1 Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 24 Sep 2025 09:52:22 -0600 Subject: [PATCH 88/91] mwc ui tweaks --- lib/pages/finalize_view/finalize_view.dart | 4 +- lib/pages/receive_view/receive_view.dart | 37 +++++++++++------ .../mwc_slatepack_import_dialog.dart | 21 ++++------ .../sub_widgets/desktop_receive.dart | 41 ++++++++++++------- 4 files changed, 62 insertions(+), 41 deletions(-) diff --git a/lib/pages/finalize_view/finalize_view.dart b/lib/pages/finalize_view/finalize_view.dart index b2538bb5c1..e0a3415907 100644 --- a/lib/pages/finalize_view/finalize_view.dart +++ b/lib/pages/finalize_view/finalize_view.dart @@ -166,7 +166,9 @@ class _FinalizeViewState extends ConsumerState { ), ); } else { - _slateController.text = ""; + setState(() { + _slateController.text = ""; + }); unawaited( showFloatingFlushBar( type: FlushBarType.success, diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 693bed3082..3cd473b489 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -124,19 +124,32 @@ class _ReceiveViewState extends ConsumerState { } if (mounted) { - await showDialog( - context: context, - builder: - (context) => SDialog( - child: MwcSlatepackImportDialog( - walletId: widget.walletId, - clipboard: widget.clipboard, - rawSlatepack: result.raw, - decoded: result.result, - slatepackType: result.type, + final response = + await showDialog<({String responseSlatepack, bool wasEncrypted})>( + context: context, + builder: + (context) => SDialog( + child: MwcSlatepackImportDialog( + walletId: widget.walletId, + clipboard: widget.clipboard, + rawSlatepack: result.raw, + decoded: result.result, + slatepackType: result.type, + ), + ), + ); + + if (mounted && response != null) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => SlatepackResponseDialog( + responseSlatepack: response.responseSlatepack, + wasEncrypted: response.wasEncrypted, ), - ), - ); + ); + } } } } diff --git a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart index 105c7e8504..6b1bc060d8 100644 --- a/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart +++ b/lib/pages/receive_view/sub_widgets/mwc_slatepack_import_dialog.dart @@ -101,15 +101,7 @@ class _MwcSlatepackImportDialogState } if (mounted) { - await showDialog( - context: context, - barrierDismissible: false, - builder: - (context) => _SlatepackResponseDialog( - responseSlatepack: result.responseSlatepack, - wasEncrypted: result.wasEncrypted, - ), - ); + Navigator.of(context).pop(result); } } @@ -167,7 +159,7 @@ class _MwcSlatepackImportDialogState ], ), Padding( - padding: EdgeInsets.symmetric(horizontal: isDesktop ? 32 : 16), + padding: EdgeInsets.symmetric(horizontal: isDesktop ? 32 : 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -191,7 +183,7 @@ class _MwcSlatepackImportDialogState children: [ if (!isDesktop) Padding( - padding: const EdgeInsets.only(top: 16, bottom: 24), + padding: const EdgeInsets.only(top: 24, bottom: 24), child: Text( "Import slatepack", style: STextStyles.pageTitleH2(context), @@ -270,14 +262,15 @@ class _MwcSlatepackImportDialogState ], ), ), - isDesktop ? const SizedBox(height: 32) : const SizedBox(height: 16), + isDesktop ? const SizedBox(height: 32) : const SizedBox(height: 24), ], ); } } -class _SlatepackResponseDialog extends StatelessWidget { - const _SlatepackResponseDialog({ +class SlatepackResponseDialog extends StatelessWidget { + const SlatepackResponseDialog({ + super.key, required this.responseSlatepack, required this.wasEncrypted, }); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 3e30ff8ca8..f926cce6e8 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -142,22 +142,35 @@ class _DesktopReceiveState extends ConsumerState { } if (mounted) { - await showDialog( - context: context, - builder: - (context) => SDialog( - child: SizedBox( - width: 700, - child: MwcSlatepackImportDialog( - walletId: widget.walletId, - clipboard: widget.clipboard, - rawSlatepack: result.raw, - decoded: result.result, - slatepackType: result.type, + final response = + await showDialog<({String responseSlatepack, bool wasEncrypted})>( + context: context, + builder: + (context) => SDialog( + child: SizedBox( + width: 700, + child: MwcSlatepackImportDialog( + walletId: widget.walletId, + clipboard: widget.clipboard, + rawSlatepack: result.raw, + decoded: result.result, + slatepackType: result.type, + ), + ), ), + ); + + if (mounted && response != null) { + await showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => SlatepackResponseDialog( + responseSlatepack: response.responseSlatepack, + wasEncrypted: response.wasEncrypted, ), - ), - ); + ); + } } } From 515ca7c012eb048b544a3a7242cf74459276bb9f Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 24 Sep 2025 14:42:34 -0500 Subject: [PATCH 89/91] chore: silence print --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 0581d81b5a..162177f85b 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 0581d81b5abb9dbdee81a95b4b74958d81c3ae9b +Subproject commit 162177f85b2373877985cf853f420bdd2f1433e3 From ccbab5b99aaa2dd8b3e3e6ad5e83c169d4e75c75 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 25 Sep 2025 16:15:12 -0500 Subject: [PATCH 90/91] chore(mwc): ndk 27b->28 --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 162177f85b..5fb49ff075 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 162177f85b2373877985cf853f420bdd2f1433e3 +Subproject commit 5fb49ff075bf0111d4a238bbcd09256b632812aa From 1a7c8010663f85a78248e0eb458b5dfb68c85d2e Mon Sep 17 00:00:00 2001 From: sneurlax Date: Wed, 1 Oct 2025 10:42:21 -0500 Subject: [PATCH 91/91] fix(mwc): silence seed/mnemonic print --- crypto_plugins/flutter_libmwc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmwc b/crypto_plugins/flutter_libmwc index 5fb49ff075..a692b7d685 160000 --- a/crypto_plugins/flutter_libmwc +++ b/crypto_plugins/flutter_libmwc @@ -1 +1 @@ -Subproject commit 5fb49ff075bf0111d4a238bbcd09256b632812aa +Subproject commit a692b7d6859445791f6ba4270e4cf7e897506d78