From cb7e63ba3b7470f79a7aefcbafb0679fbb2262db Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 9 Jan 2024 10:56:05 -0600 Subject: [PATCH 1/4] rough epic cash refactor --- .../blockchain_data/v2/transaction_v2.dart | 74 +- .../wallet_settings_view.dart | 6 +- .../transaction_details_view.dart | 8 +- .../tx_v2/transaction_v2_details_view.dart | 158 +- lib/services/coins/coin_service.dart | 9 +- .../coins/epiccash/epiccash_wallet.dart | 1426 ----------------- lib/services/wallets.dart | 2 +- lib/services/wallets_service.dart | 2 +- lib/wallets/wallet/impl/epiccash_wallet.dart | 1118 ++++++++++++- .../epiccash_wallet_info_extension.dart | 13 + 10 files changed, 1309 insertions(+), 1507 deletions(-) delete mode 100644 lib/services/coins/epiccash/epiccash_wallet.dart 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 5d6bfe5c5..e00d8952a 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -57,6 +57,14 @@ class TransactionV2 { required this.otherData, }); + bool get isEpiccashTransaction => + _getFromOtherData(key: "isEpiccashTransaction") == true; + int? get numberOfMessages => + _getFromOtherData(key: "numberOfMessages") as int?; + String? get slateId => _getFromOtherData(key: "slateId") as String?; + String? get onChainNote => _getFromOtherData(key: "onChainNote") as String?; + bool get isCancelled => _getFromOtherData(key: "isCancelled") == true; + int getConfirmations(int currentChainHeight) { if (height == null || height! <= 0) return 0; return max(0, currentChainHeight - (height! - 1)); @@ -146,35 +154,35 @@ class TransactionV2 { } } - // if (coin == Coin.epicCash) { - // if (_transaction.isCancelled) { - // return "Cancelled"; - // } else if (type == TransactionType.incoming) { - // if (isConfirmed(height, minConfirms)) { - // return "Received"; - // } else { - // if (_transaction.numberOfMessages == 1) { - // return "Receiving (waiting for sender)"; - // } else if ((_transaction.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"; - // } - // } - // } else if (type == TransactionType.outgoing) { - // if (isConfirmed(height, minConfirms)) { - // return "Sent (confirmed)"; - // } else { - // if (_transaction.numberOfMessages == 1) { - // return "Sending (waiting for receiver)"; - // } else if ((_transaction.numberOfMessages ?? 0) > 1) { - // return "Sending (waiting for confirmations)"; - // } else { - // return "Sending"; - // } - // } - // } - // } + if (isEpiccashTransaction) { + 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"; + } + } + } 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"; + } + } + } + } if (type == TransactionType.incoming) { // if (_transaction.isMinting) { @@ -198,6 +206,14 @@ class TransactionV2 { } } + dynamic _getFromOtherData({required dynamic key}) { + if (otherData == null) { + return null; + } + final map = jsonDecode(otherData!); + return map[key]; + } + @override String toString() { return 'TransactionV2(\n' 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 282e64a00..726010c67 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 @@ -30,7 +30,6 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/route_generator.dart'; -import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; @@ -40,6 +39,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -470,11 +470,11 @@ class _EpiBoxInfoFormState extends ConsumerState { final hostController = TextEditingController(); final portController = TextEditingController(); - late EpicCashWallet wallet; + late EpiccashWallet wallet; @override void initState() { - wallet = ref.read(pWallets).getWallet(widget.walletId) as EpicCashWallet; + wallet = ref.read(pWallets).getWallet(widget.walletId) as EpiccashWallet; wallet.getEpicBoxConfig().then((EpicBoxConfigModel epicBoxConfig) { hostController.text = epicBoxConfig.host; 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 aebcc86a3..82a29b087 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -26,7 +26,6 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount_formatter.dart'; @@ -39,6 +38,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -1592,7 +1592,7 @@ class _TransactionDetailsViewState onPressed: () async { final wallet = ref.read(pWallets).getWallet(walletId); - if (wallet is EpicCashWallet) { + if (wallet is EpiccashWallet) { final String? id = _transaction.slateId; if (id == null) { unawaited(showFloatingFlushBar( @@ -1610,8 +1610,8 @@ class _TransactionDetailsViewState const CancellingTransactionProgressDialog(), )); - final result = await (wallet as EpicCashWallet) - .cancelPendingTransactionAndPost(id); + final result = + await wallet.cancelPendingTransactionAndPost(id); if (mounted) { // pop progress dialog Navigator.of(context).pop(); 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 42dd20c7a..58ab3f713 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 @@ -19,7 +19,9 @@ import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart' import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/themes/stack_colors.dart'; @@ -34,6 +36,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -1448,7 +1451,68 @@ class _TransactionV2DetailsViewState // ], // ), // ), - + if (coin == Coin.epicCash) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (coin == Coin.epicCash) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Slate ID", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), + // Flexible( + // child: FittedBox( + // fit: BoxFit.scaleDown, + // child: + SelectableText( + _transaction.slateId ?? "Unknown", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context), + ), + // ), + // ), + ], + ), + if (isDesktop) + const SizedBox( + width: 12, + ), + if (isDesktop) + IconCopyButton( + data: _transaction.slateId ?? "Unknown", + ), + ], + ), + ), if (!isDesktop) const SizedBox( height: 12, @@ -1463,6 +1527,98 @@ class _TransactionV2DetailsViewState ], ), ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: (coin == Coin.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, + )); + 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", + context: context, + )); + return; + } + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), + ), + ), + ) + : null, ), ); } diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 17322de2d..959a3a395 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -14,7 +14,6 @@ import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; @@ -101,13 +100,7 @@ abstract class CoinServiceAPI { throw UnimplementedError("moved"); case Coin.epicCash: - return EpicCashWallet( - walletId: walletId, - walletName: walletName, - coin: coin, - secureStore: secureStorageInterface, - // tracker: tracker, - ); + throw UnimplementedError("moved"); case Coin.ethereum: return EthereumWallet( diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart deleted file mode 100644 index e2c2d9d84..000000000 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ /dev/null @@ -1,1426 +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 'dart:convert'; -import 'dart:ffi'; -import 'dart:io'; -import 'dart:isolate'; - -import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_libepiccash/lib.dart' as epiccash; -import 'package:flutter_libepiccash/models/transaction.dart'; -import 'package:isar/isar.dart'; -import 'package:mutex/mutex.dart'; -import 'package:stack_wallet_backup/generate_password.dart'; -import 'package:stackwallet/db/isar/main_db.dart'; -import 'package:stackwallet/models/balance.dart'; -import 'package:stackwallet/models/epicbox_config_model.dart'; -import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; -import 'package:stackwallet/models/node_model.dart'; -import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; -import 'package:stackwallet/services/coins/coin_service.dart'; -import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/mixins/epic_cash_hive.dart'; -import 'package:stackwallet/services/mixins/wallet_cache.dart'; -import 'package:stackwallet/services/mixins/wallet_db.dart'; -import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/default_epicboxes.dart'; -import 'package:stackwallet/utilities/default_nodes.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/utilities/stack_file_system.dart'; -import 'package:stackwallet/utilities/test_epic_box_connection.dart'; -import 'package:tuple/tuple.dart'; -import 'package:websocket_universal/websocket_universal.dart'; - -const int MINIMUM_CONFIRMATIONS = 3; - -const String GENESIS_HASH_MAINNET = ""; -const String GENESIS_HASH_TESTNET = ""; - -Future deleteEpicWallet({ - 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) { - Directory appDir = await StackFileSystem.applicationRootDirectory(); - - final path = "${appDir.path}/epiccash"; - final String name = walletId.trim(); - final walletDir = '$path/$name'; - - var editConfig = jsonDecode(config as String); - - editConfig["wallet_dir"] = walletDir; - config = jsonEncode(editConfig); - } - - if (wallet == null) { - return "Tried to delete non existent epic wallet file with walletId=$walletId"; - } else { - try { - return epiccash.LibEpiccash.deleteWallet( - wallet: wallet, - config: config!, - ); - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - return "deleteEpicWallet($walletId) failed..."; - } - } -} - -class EpicCashWallet extends CoinServiceAPI - with WalletCache, WalletDB, EpicCashHive { - EpicCashWallet({ - required String walletId, - required String walletName, - required Coin coin, - required SecureStorageInterface secureStore, - MainDB? mockableOverride, - }) { - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _secureStore = secureStore; - initCache(walletId, coin); - initEpicCashHive(walletId); - initWalletDB(mockableOverride: mockableOverride); - } - - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final syncMutex = Mutex(); - - final _prefs = Prefs.instance; - - NodeModel? _epicNode; - - @override - Future updateNode(bool shouldRefresh) async { - _epicNode = NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - // TODO notify ui/ fire event for node changed? - - String stringConfig = await getConfig(); - await _secureStore.write(key: '${_walletId}_config', value: stringConfig); - - if (shouldRefresh) { - unawaited(refresh()); - } - } - - @override - set isFavorite(bool markFavorite) { - _isFavorite = markFavorite; - updateCachedIsFavorite(markFavorite); - } - - @override - bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); - - bool? _isFavorite; - - late ReceivePort receivePort; - - Future startSync() async { - Logging.instance.log("request start sync", level: LogLevel.Info); - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - const int refreshFromNode = 1; - if (!syncMutex.isLocked) { - await epiccash.LibEpiccash.getWalletBalances( - wallet: wallet!, - refreshFromNode: refreshFromNode, - minimumConfirmations: 10, - ); - } else { - Logging.instance.log("request start sync denied", level: LogLevel.Info); - } - return ""; - } - - Future< - ({ - double awaitingFinalization, - double pending, - double spendable, - double total - })> allWalletBalances() async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - const refreshFromNode = 0; - return await epiccash.LibEpiccash.getWalletBalances( - wallet: wallet!, - refreshFromNode: refreshFromNode, - minimumConfirmations: MINIMUM_CONFIRMATIONS, - ); - } - - Timer? timer; - late final Coin _coin; - - @override - Coin get coin => _coin; - - late SecureStorageInterface _secureStore; - - /// returns an empty String on success, error message on failure - Future cancelPendingTransactionAndPost(String txSlateId) async { - try { - final String wallet = (await _secureStore.read( - key: '${_walletId}_wallet', - ))!; - - final result = await epiccash.LibEpiccash.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(); - } - } - - @override - Future confirmSend({required Map txData}) async { - try { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - - // TODO determine whether it is worth sending change to a change address. - String slateId; - String receiverAddress = txData['addresss'] as String; - - if (!receiverAddress.startsWith("http://") || - !receiverAddress.startsWith("https://")) { - bool isEpicboxConnected = await testEpicboxServer( - epicboxConfig.host, epicboxConfig.port ?? 443); - if (!isEpicboxConnected) { - throw Exception("Failed to send TX : Unable to reach epicbox server"); - } - } - - ({String commitId, String slateId}) transaction; - - if (receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://")) { - transaction = await epiccash.LibEpiccash.txHttpSend( - wallet: wallet!, - selectionStrategyIsAll: 0, - minimumConfirmations: MINIMUM_CONFIRMATIONS, - message: txData['onChainNote'] as String, - amount: (txData['recipientAmt'] as Amount).raw.toInt(), - address: txData['addresss'] as String); - } else { - transaction = await epiccash.LibEpiccash.createTransaction( - wallet: wallet!, - amount: (txData['recipientAmt'] as Amount).raw.toInt(), - address: txData['addresss'] as String, - secretKeyIndex: 0, - epicboxConfig: epicboxConfig.toString(), - minimumConfirmations: MINIMUM_CONFIRMATIONS, - note: txData['onChainNote'] as String); - } - - Map txAddressInfo = {}; - txAddressInfo['from'] = await currentReceivingAddress; - txAddressInfo['to'] = txData['addresss'] as String; - await putSendToAddresses(transaction, txAddressInfo); - - slateId = transaction.slateId; - return slateId; - } catch (e, s) { - Logging.instance.log("Error sending $e - $s", level: LogLevel.Error); - rethrow; - } - // return ""; - } - - Future _getReceivingAddressForIndex( - int index, - ) async { - isar_models.Address? address = await db - .getAddresses(walletId) - .filter() - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .and() - .typeEqualTo(isar_models.AddressType.mimbleWimble) - .and() - .derivationIndexEqualTo(index) - .findFirst(); - - if (address == null) { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - - String? walletAddress = await epiccash.LibEpiccash.getAddressInfo( - wallet: wallet!, - index: index, - epicboxConfig: epicboxConfig.toString(), - ); - - Logging.instance - .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); - - address = isar_models.Address( - walletId: walletId, - value: walletAddress!, - derivationIndex: index, - derivationPath: null, - type: isar_models.AddressType.mimbleWimble, - subType: isar_models.AddressSubType.receiving, - publicKey: [], // ?? - ); - - await db.updateOrPutAddresses([address]); - } - - return address; - } - - @override - Future get currentReceivingAddress async => - (await _currentReceivingAddress)?.value ?? - (await _getReceivingAddressForIndex(0)).value; - - Future get _currentReceivingAddress => db - .getAddresses(walletId) - .filter() - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .and() - .typeEqualTo(isar_models.AddressType.mimbleWimble) - .sortByDerivationIndexDesc() - .findFirst(); - - @override - Future exit() async { - _hasCalledExit = true; - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - Logging.instance.log("EpicCash_wallet exit finished", level: LogLevel.Info); - } - - bool _hasCalledExit = false; - - @override - bool get hasCalledExit => _hasCalledExit; - - Future _getFees() async { - // TODO: implement _getFees - return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 10, - numberOfBlocksSlow: 10, - fast: 1, - medium: 1, - slow: 1); - } - - @override - Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; - - @override - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - refreshMutex = true; - try { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - // clear blockchain info - await db.deleteWalletBlockchainData(walletId); - - await epicUpdateLastScannedBlock(await getRestoreHeight()); - - await _startScans(); - - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - } catch (e, s) { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - - Logging.instance.log( - "Exception rethrown from fullRescan(): $e\n$s", - level: LogLevel.Error, - printFullLength: true, - ); - rethrow; - } finally { - refreshMutex = false; - } - } - - @override - Future initializeExisting() async { - Logging.instance.log("initializeExisting() ${coin.prettyName} wallet", - level: LogLevel.Info); - - final config = await getRealConfig(); - final password = await _secureStore.read(key: '${_walletId}_password'); - - final walletOpen = await epiccash.LibEpiccash.openWallet( - config: config, password: password!); - await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); - - if (getCachedId() == null) { - //todo: check if print needed - // debugPrint("Exception was thrown"); - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - await _prefs.init(); - await updateNode(false); - await _refreshBalance(); - // TODO: is there anything else that should be set up here whenever this wallet is first loaded again? - } - - Future storeEpicboxInfo() async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - int index = 0; - - Logging.instance.log("This index is $index", level: LogLevel.Info); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - - String? walletAddress = await epiccash.LibEpiccash.getAddressInfo( - wallet: wallet!, - index: index, - epicboxConfig: epicboxConfig.toString(), - ); - - Logging.instance - .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); - await _secureStore.write( - key: '${_walletId}_address_info', value: walletAddress); - } - - // TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index - int calculateRestoreHeightFrom({required DateTime date}) { - int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; - const int epicCashFirstBlock = 1565370278; - const double overestimateSecondsPerBlock = 61; - int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; - int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; - //todo: check if print needed - // debugPrint( - // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); - int height = approximateHeight; - if (height < 0) { - height = 0; - } - return height; - } - - @override - Future initializeNew( - ({String mnemonicPassphrase, int wordCount})? data, - ) async { - await _prefs.init(); - await updateNode(false); - final mnemonic = await _getMnemonicList(); - final String mnemonicString = mnemonic.join(" "); - - final String password = generatePassword(); - String stringConfig = await getConfig(); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonicString); - await _secureStore.write(key: '${_walletId}_config', value: stringConfig); - await _secureStore.write(key: '${_walletId}_password', value: password); - await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); - - String name = _walletId; - - await epiccash.LibEpiccash.initializeNewWallet( - config: stringConfig, - mnemonic: mnemonicString, - password: password, - name: name, - ); - - //Open wallet - final walletOpen = await epiccash.LibEpiccash.openWallet( - config: stringConfig, password: password); - await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); - - //Store Epic box address info - await storeEpicboxInfo(); - - // subtract a couple days to ensure we have a buffer for SWB - final bufferedCreateHeight = calculateRestoreHeightFrom( - date: DateTime.now().subtract(const Duration(days: 2))); - - await Future.wait([ - epicUpdateRestoreHeight(bufferedCreateHeight), - updateCachedIsFavorite(false), - updateCachedId(walletId), - epicUpdateReceivingIndex(0), - epicUpdateChangeIndex(0), - ]); - - final initialReceivingAddress = await _getReceivingAddressForIndex(0); - - await db.putAddress(initialReceivingAddress); - } - - bool refreshMutex = false; - - @override - bool get isRefreshing => refreshMutex; - - @override - // unused for epic - Future get maxFee => throw UnimplementedError(); - - Future> _getMnemonicList() async { - String? _mnemonicString = await mnemonicString; - if (_mnemonicString != null) { - final List data = _mnemonicString.split(' '); - return data; - } else { - _mnemonicString = epiccash.LibEpiccash.getMnemonic(); - await _secureStore.write( - key: '${_walletId}_mnemonic', value: _mnemonicString); - final List data = _mnemonicString.split(' '); - return data; - } - } - - @override - Future> get mnemonic => _getMnemonicList(); - - @override - Future get mnemonicString => - _secureStore.read(key: '${_walletId}_mnemonic'); - - @override - Future get mnemonicPassphrase => _secureStore.read( - key: '${_walletId}_mnemonicPassphrase', - ); - - @override - Future> prepareSend({ - required String address, - required Amount amount, - Map? args, - }) async { - try { - int satAmount = amount.raw.toInt(); - int realfee = await nativeFee(satAmount); - - if (balance.spendable == amount) { - satAmount = balance.spendable.raw.toInt() - realfee; - } - - Map txData = { - "fee": realfee, - "addresss": address, - "recipientAmt": Amount( - rawValue: BigInt.from(satAmount), - fractionDigits: coin.decimals, - ), - }; - - Logging.instance.log("prepare send: $txData", level: LogLevel.Info); - return txData; - } catch (e, s) { - Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); - rethrow; - } - } - - Future nativeFee(int satoshiAmount, - {bool ifErrorEstimateFee = false}) async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - try { - final available = balance.spendable.raw.toInt(); - - var transactionFees = await epiccash.LibEpiccash.getTransactionFees( - wallet: wallet!, - amount: satoshiAmount, - minimumConfirmations: MINIMUM_CONFIRMATIONS, - 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 currentWalletDirPath() async { - Directory appDir = await StackFileSystem.applicationRootDirectory(); - - final path = "${appDir.path}/epiccash"; - final String name = _walletId.trim(); - return '$path/$name'; - } - - Future getConfig() async { - if (_epicNode == null) { - await updateNode(false); - } - final NodeModel node = _epicNode!; - 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, ""); - String stringConfig = json.encode(config); - return stringConfig; - } - - Future testEpicboxServer(String host, int port) async { - // TODO use an EpicBoxServerModel as the only param - final websocketConnectionUri = 'wss://$host:$port'; - const connectionOptions = SocketConnectionOptions( - pingIntervalMs: 3000, - timeoutConnectionMs: 4000, - - /// see ping/pong messages in [logEventStream] stream - skipPingMessages: true, - - /// Set this attribute to `true` if do not need any ping/pong - /// messages and ping measurement. Default is `false` - pingRestrictionForce: true, - ); - - final IMessageProcessor textSocketProcessor = - SocketSimpleTextProcessor(); - final textSocketHandler = IWebSocketHandler.createClient( - websocketConnectionUri, - textSocketProcessor, - connectionOptions: connectionOptions, - ); - - // Listening to server responses: - bool isConnected = true; - textSocketHandler.incomingMessagesStream.listen((inMsg) { - Logging.instance.log( - '> webSocket got text message from server: "$inMsg" ' - '[ping: ${textSocketHandler.pingDelayMs}]', - level: LogLevel.Info); - }); - - // Connecting to server: - final isTextSocketConnected = await textSocketHandler.connect(); - if (!isTextSocketConnected) { - // ignore: avoid_print - Logging.instance.log( - 'Connection to [$websocketConnectionUri] failed for some reason!', - level: LogLevel.Error); - isConnected = false; - } - return isConnected; - } - - Future getEpicBoxConfig() async { - EpicBoxConfigModel? _epicBoxConfig; - // read epicbox config from secure store - String? storedConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); - - // we should move to storing the primary server model like we do with nodes, and build the config from that (see epic-mobile) - // EpicBoxServerModel? _epicBox = epicBox ?? - // DB.instance.get( - // boxName: DB.boxNamePrimaryEpicBox, key: 'primary'); - // Logging.instance.log( - // "Read primary Epic Box config: ${jsonEncode(_epicBox)}", - // level: LogLevel.Info); - - if (storedConfig == null) { - // if no config stored, use the default epicbox server as config - _epicBoxConfig = - EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer); - } else { - // if a config is stored, test it - - _epicBoxConfig = EpicBoxConfigModel.fromString( - storedConfig); // fromString handles checking old config formats - } - - bool isEpicboxConnected = await testEpicboxServer( - _epicBoxConfig.host, _epicBoxConfig.port ?? 443); - - if (!isEpicboxConnected) { - // default Epicbox is not connected, default to Europe - _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 _epicBoxConfig; - } - - Future getRealConfig() async { - String? config = await _secureStore.read(key: '${_walletId}_config'); - if (Platform.isIOS) { - final walletDir = await currentWalletDirPath(); - var editConfig = jsonDecode(config as String); - - editConfig["wallet_dir"] = walletDir; - config = jsonEncode(editConfig); - } - return config!; - } - - Future updateEpicboxConfig(String host, int port) async { - String stringConfig = jsonEncode({ - "epicbox_domain": host, - "epicbox_port": port, - "epicbox_protocol_unsecure": false, - "epicbox_address_index": 0, - }); - await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: stringConfig); - // TODO: refresh anything that needs to be refreshed/updated due to epicbox info changed - } - - Future _startScans() async { - try { - //First stop the current listener - epiccash.LibEpiccash.stopEpicboxListener(); - final wallet = await _secureStore.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 = - epicGetLastScannedBlock() ?? await getRestoreHeight(); - - // loop while scanning in chain in chunks (of blocks?) - while (lastScannedBlock < chainHeight) { - Logging.instance.log( - "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", - level: LogLevel.Info, - ); - - int nextScannedBlock = await epiccash.LibEpiccash.scanOutputs( - wallet: wallet!, - startHeight: lastScannedBlock, - numberOfBlocks: scanChunkSize, - ); - - // update local cache - await epicUpdateLastScannedBlock(nextScannedBlock); - - // 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 listenToEpicbox(); - } catch (e, s) { - Logging.instance.log( - "_startScans failed: $e\n$s", - level: LogLevel.Error, - ); - rethrow; - } - } - - Future get getSyncPercent async { - int lastScannedBlock = epicGetLastScannedBlock() ?? 0; - final _chainHeight = await chainHeight; - 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; - } - - double highestPercent = 0; - - @override - Future recoverFromMnemonic({ - required String mnemonic, - String? mnemonicPassphrase, // unused in epic - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - try { - await _prefs.init(); - await updateNode(false); - final String password = generatePassword(); - - String stringConfig = await getConfig(); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - final String name = _walletName.trim(); - - await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); - await _secureStore.write(key: '${_walletId}_config', value: stringConfig); - await _secureStore.write(key: '${_walletId}_password', value: password); - - await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); - - await epiccash.LibEpiccash.recoverWallet( - config: stringConfig, - password: password, - mnemonic: mnemonic, - name: name, - ); - - await Future.wait([ - epicUpdateRestoreHeight(height), - updateCachedId(walletId), - epicUpdateReceivingIndex(0), - epicUpdateChangeIndex(0), - updateCachedIsFavorite(false), - ]); - - //Open Wallet - final walletOpen = await epiccash.LibEpiccash.openWallet( - config: stringConfig, password: password); - await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); - - //Store Epic box address info - await storeEpicboxInfo(); - } catch (e, s) { - Logging.instance - .log("Error recovering wallet $e\n$s", level: LogLevel.Error); - rethrow; - } - } - - Future listenToEpicbox() async { - Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - epiccash.LibEpiccash.startEpicboxListener( - wallet: wallet!, epicboxConfig: epicboxConfig.toString()); - } - - Future getRestoreHeight() async { - return epicGetRestoreHeight() ?? epicGetCreationHeight()!; - } - - Future get chainHeight async { - try { - final config = await getRealConfig(); - int? latestHeight = - await epiccash.LibEpiccash.getChainHeight(config: config); - - await updateCachedChainHeight(latestHeight); - if (latestHeight > storedChainHeight) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Updated current chain height in $walletId $walletName!", - walletId, - ), - ); - } - return latestHeight; - } catch (e, s) { - Logging.instance.log("Exception caught in chainHeight: $e\n$s", - level: LogLevel.Error); - return storedChainHeight; - } - } - - @override - int get storedChainHeight => getCachedChainHeight(); - - bool _shouldAutoSync = true; - - @override - bool get shouldAutoSync => _shouldAutoSync; - - @override - set shouldAutoSync(bool shouldAutoSync) { - if (_shouldAutoSync != shouldAutoSync) { - _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - Logging.instance.log("Should autosync", level: LogLevel.Info); - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - refresh(); - } - } - } - - Future setCurrentIndex() async { - try { - final int receivingIndex = epicGetReceivingIndex()!; - // 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> getSlatesToCommits() async { - try { - var slatesToCommits = epicGetSlatesToCommits(); - if (slatesToCommits == null) { - slatesToCommits = {}; - } else { - slatesToCommits = slatesToCommits; - } - return slatesToCommits; - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return {}; - } - } - - Future putSendToAddresses(({String slateId, String commitId}) slateData, - Map txAddressInfo) async { - try { - var slatesToCommits = await getSlatesToCommits(); - final from = txAddressInfo['from']; - final to = txAddressInfo['to']; - slatesToCommits[slateData.slateId] = { - "commitId": slateData.commitId, - "from": from, - "to": to, - }; - - await epicUpdateSlatesToCommits(slatesToCommits); - return true; - } catch (e, s) { - Logging.instance - .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); - return false; - } - } - - Future putSlatesToCommits(String slateMessage, String encoded) async { - try { - var slatesToCommits = await getSlatesToCommits(); - final slate = jsonDecode(slateMessage); - final part1 = jsonDecode(slate[0] as String); - final part2 = jsonDecode(slate[1] as String); - final slateId = part1[0]['tx_slate_id']; - if (slatesToCommits[slateId] != null && - (slatesToCommits[slateId] as Map).isNotEmpty) { - // This happens when the sender receives the response. - return true; - } - final commitId = part2['tx']['body']['outputs'][0]['commit']; - - final toFromInfoString = jsonDecode(encoded); - final toFromInfo = jsonDecode(toFromInfoString[0] as String); - final from = toFromInfo['from']; - final to = toFromInfo['to']; - slatesToCommits[slateId] = { - "commitId": commitId, - "from": from, - "to": to, - }; - await epicUpdateSlatesToCommits(slatesToCommits); - return true; - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return false; - } - } - - /// Refreshes display data for the wallet - @override - Future refresh() async { - Logging.instance - .log("$walletId $walletName Calling refresh", level: LogLevel.Info); - if (refreshMutex) { - Logging.instance.log("$walletId $walletName refreshMutex denied", - level: LogLevel.Info); - return; - } else { - refreshMutex = true; - } - - try { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - if (epicGetCreationHeight() == null) { - await epicUpdateCreationHeight(await chainHeight); - } - - final int curAdd = await setCurrentIndex(); - await _getReceivingAddressForIndex(curAdd); - - await _startScans(); - - unawaited(startSync()); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); - - final currentHeight = await chainHeight; - const storedHeight = 1; //await storedChainHeight; - - Logging.instance.log("chain height in refresh function: $currentHeight", - level: LogLevel.Info); - Logging.instance.log("cached height in refresh function: $storedHeight", - level: LogLevel.Info); - - // TODO: implement refresh - // TODO: check if it needs a refresh and if so get all of the most recent data. - if (currentHeight != storedHeight) { - await _refreshTransactions(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.50, walletId)); - - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletName in background!", walletId)); - } - - await _refreshBalance(); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - refreshMutex = false; - if (shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async { - Logging.instance.log( - "Periodic refresh check for $walletId $walletName in object instance: $hashCode", - level: LogLevel.Info); - // chain height check currently broken - // if ((await chainHeight) != (await storedChainHeight)) { - if (await refreshIfThereIsNewData()) { - await refresh(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - // } - }); - } - } catch (error, strace) { - refreshMutex = false; - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - coin, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Warning); - } - } - - Future refreshIfThereIsNewData() async { - if (_hasCalledExit) return false; - // TODO returning true here signals this class to call refresh() after which it will fire an event that notifies the UI that new data has been fetched/found for this wallet - return true; - // TODO: do a quick check to see if there is any new data that would require a refresh - } - - @override - Future testNetworkConnection() async { - try { - // force unwrap optional as we want connection test to fail if wallet - // wasn't initialized or epicbox node was set to null - return await testEpicNodeConnection( - NodeFormData() - ..host = _epicNode!.host - ..useSSL = _epicNode!.useSSL - ..port = _epicNode!.port, - ) != - null; - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Warning); - return false; - } - } - - Timer? _networkAliveTimer; - - void startNetworkAlivePinging() { - // call once on start right away - _periodicPingCheck(); - - // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); - } - - void _periodicPingCheck() async { - bool hasNetwork = await testNetworkConnection(); - - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - - _isConnected = hasNetwork; - if (hasNetwork) { - unawaited(refresh()); - } - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - bool _isConnected = false; - - @override - bool get isConnected => _isConnected; - - Future _refreshTransactions() async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - const refreshFromNode = 1; - - var transactions = await epiccash.LibEpiccash.getTransactions( - wallet: wallet!, refreshFromNode: refreshFromNode); - - final List> txnsData = - []; - - final slatesToCommits = await getSlatesToCommits(); - - for (var tx in transactions) { - Logging.instance.log("tx: $tx", level: LogLevel.Info); - // // TODO: does "confirmed" mean finalized? If so please remove this todo - final isConfirmed = tx.confirmed; - - int amt = 0; - if (tx.txType == TransactionType.TxReceived || - tx.txType == TransactionType.TxReceivedCancelled) { - amt = int.parse(tx.amountCredited); - } else { - int debit = int.parse(tx.amountDebited); - int credit = int.parse(tx.amountCredited); - int fee = int.parse((tx.fee ?? "0")); //TODO -double check this - amt = debit - credit - fee; - } - - DateTime dt = DateTime.parse(tx.creationTs); - - String? slateId = tx.txSlateId; - String address = slatesToCommits[slateId] - ?[tx.txType == TransactionType.TxReceived ? "from" : "to"] - as String? ?? - ""; - String? commitId = slatesToCommits[slateId]?['commitId'] as String?; - int? numberOfMessages = tx.messages?.messages.length; - String? onChainNote = tx.messages?.messages[0].message; - - int? height; - - if (isConfirmed) { - height = tx.kernelLookupMinHeight ?? 1; - } else { - height = null; - } - - final isIncoming = (tx.txType == TransactionType.TxReceived || - tx.txType == TransactionType.TxReceivedCancelled); - final txn = isar_models.Transaction( - walletId: walletId, - txid: commitId ?? tx.id.toString(), - timestamp: (dt.millisecondsSinceEpoch ~/ 1000), - type: isIncoming - ? isar_models.TransactionType.incoming - : isar_models.TransactionType.outgoing, - subType: isar_models.TransactionSubType.none, - amount: amt, - amountString: Amount( - rawValue: BigInt.from(amt), - fractionDigits: coin.decimals, - ).toJsonString(), - fee: (tx.fee == "null") ? 0 : int.parse(tx.fee!), - height: height, - isCancelled: tx.txType == TransactionType.TxSentCancelled || - tx.txType == TransactionType.TxReceivedCancelled, - isLelantus: false, - slateId: slateId, - nonce: null, - otherData: onChainNote, - inputs: [], - outputs: [], - numberOfMessages: numberOfMessages, - ); - - // txn.address = - // ""; // for this when you send a transaction you will just need to save in a hashmap in hive with the key being the txid, and the value being the address it was sent to. then you can look this value up right here in your hashmap. - isar_models.Address? transactionAddress = await db - .getAddresses(walletId) - .filter() - .valueEqualTo(address) - .findFirst(); - - if (transactionAddress == null || transactionAddress!.value.isEmpty) { - if (isIncoming) { - //Use current receiving address as address - String receivingAddress = await currentReceivingAddress; - transactionAddress = isar_models.Address( - walletId: walletId, - value: receivingAddress, - publicKey: [], - derivationIndex: 0, - derivationPath: null, - type: isar_models.AddressType.mimbleWimble, - subType: isar_models.AddressSubType.receiving, - ); - } else { - final myRcvAddr = await currentReceivingAddress; - final isSentToSelf = myRcvAddr == address; - - transactionAddress = isar_models.Address( - walletId: walletId, - value: address, - publicKey: [], - derivationIndex: isSentToSelf ? 0 : -1, - derivationPath: null, - type: isSentToSelf - ? isar_models.AddressType.mimbleWimble - : isar_models.AddressType.nonWallet, - subType: isSentToSelf - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.nonWallet, - ); - } - } - - txnsData.add(Tuple2(txn, transactionAddress)); - } - - await db.addNewTransactionData(txnsData, walletId); - - // quick hack to notify manager to call notifyListeners if - // transactions changed - if (txnsData.isNotEmpty) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Transactions updated/added for: $walletId $walletName ", - walletId, - ), - ); - } - } - - @override - Future updateSentCachedTxData(Map txData) async { - // not used in epic - } - - @override - bool validateAddress(String address) { - // Invalid address that contains HTTP and epicbox 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 epiccash.LibEpiccash.validateSendAddress(address: address); - } - - @override - String get walletId => _walletId; - late final String _walletId; - - @override - String get walletName => _walletName; - late String _walletName; - - @override - set walletName(String newName) => _walletName = newName; - - @override - void Function(bool)? get onIsActiveWalletChanged => (isActive) async { - timer?.cancel(); - timer = null; - if (isActive) { - unawaited(startSync()); - } - this.isActive = isActive; - }; - - bool isActive = false; - - @override - Future estimateFeeFor(Amount amount, int feeRate) async { - int currentFee = - await nativeFee(amount.raw.toInt(), ifErrorEstimateFee: true); - return Amount( - rawValue: BigInt.from(currentFee), - fractionDigits: coin.decimals, - ); - } - - // not used in epic currently - @override - Future generateNewAddress() async { - try { - return true; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from generateNewAddress(): $e\n$s", - level: LogLevel.Error); - return false; - } - } - - Future _refreshBalance() async { - var balances = await allWalletBalances(); - _balance = Balance( - total: Amount.fromDecimal( - Decimal.parse(balances.total.toString()) + - Decimal.parse(balances.awaitingFinalization.toString()), - fractionDigits: coin.decimals, - ), - spendable: Amount.fromDecimal( - Decimal.parse(balances.spendable.toString()), - fractionDigits: coin.decimals, - ), - blockedTotal: Amount( - rawValue: BigInt.zero, - fractionDigits: coin.decimals, - ), - pendingSpendable: Amount.fromDecimal( - Decimal.parse(balances.pending.toString()), - fractionDigits: coin.decimals, - ), - ); - await updateCachedBalance(_balance!); - } - - @override - Balance get balance => _balance ??= getCachedBalance(); - Balance? _balance; - - @override - Future> get utxos => throw UnimplementedError(); - - @override - Future> get transactions => - db.getTransactions(walletId).findAll(); -} diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index cdeccb439..c1124b05f 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -13,7 +13,6 @@ import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/db/isar/main_db.dart'; -import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_service.dart'; import 'package:stackwallet/services/trade_sent_from_stack_service.dart'; @@ -24,6 +23,7 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; class Wallets { diff --git a/lib/services/wallets_service.dart b/lib/services/wallets_service.dart index 17a0221d6..27bc4b95f 100644 --- a/lib/services/wallets_service.dart +++ b/lib/services/wallets_service.dart @@ -15,12 +15,12 @@ import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/db/isar/main_db.dart'; -import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/notifications_service.dart'; import 'package:stackwallet/services/trade_sent_from_stack_service.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; import 'package:uuid/uuid.dart'; class WalletInfo { diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 19f949ec4..e532b6195 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -1,17 +1,497 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_libepiccash/lib.dart' as epiccash; +import 'package:flutter_libepiccash/models/transaction.dart' as epic_models; import 'package:isar/isar.dart'; +import 'package:mutex/mutex.dart'; +import 'package:stack_wallet_backup/generate_password.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/epicbox_config_model.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/default_epicboxes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/epiccash.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart'; +import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; +import 'package:websocket_universal/websocket_universal.dart'; +// +// refactor of https://github.com/cypherstack/stack_wallet/blob/1d9fb4cd069f22492ece690ac788e05b8f8b1209/lib/services/coins/epiccash/epiccash_wallet.dart +// class EpiccashWallet extends Bip39Wallet { EpiccashWallet(CryptoCurrencyNetwork network) : super(Epiccash(network)); + final syncMutex = Mutex(); + NodeModel? _epicNode; + Timer? timer; + + double highestPercent = 0; + Future get getSyncPercent async { + int lastScannedBlock = info.epicData?.lastScannedBlock ?? 0; + final _chainHeight = await chainHeight; + 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 { + 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 epiccash.LibEpiccash.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 getEpicBoxConfig() async { + EpicBoxConfigModel? _epicBoxConfig; + // read epicbox config from secure store + String? storedConfig = + await secureStorageInterface.read(key: '${walletId}_epicboxConfig'); + + // we should move to storing the primary server model like we do with nodes, and build the config from that (see epic-mobile) + // EpicBoxServerModel? _epicBox = epicBox ?? + // DB.instance.get( + // boxName: DB.boxNamePrimaryEpicBox, key: 'primary'); + // Logging.instance.log( + // "Read primary Epic Box config: ${jsonEncode(_epicBox)}", + // level: LogLevel.Info); + + if (storedConfig == null) { + // if no config stored, use the default epicbox server as config + _epicBoxConfig = + EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer); + } else { + // if a config is stored, test it + + _epicBoxConfig = EpicBoxConfigModel.fromString( + storedConfig); // fromString handles checking old config formats + } + + bool isEpicboxConnected = await _testEpicboxServer( + _epicBoxConfig.host, _epicBoxConfig.port ?? 443); + + if (!isEpicboxConnected) { + // default Epicbox is not connected, default to Europe + _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 _epicBoxConfig; + } + + // ================= Private ================================================= + + Future _getConfig() async { + if (_epicNode == null) { + await updateNode(); + } + final NodeModel node = _epicNode!; + 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, ""); + String stringConfig = jsonEncode(config); + return stringConfig; + } + + Future _currentWalletDirPath() async { + Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/epiccash"; + 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(); + + var transactionFees = await epiccash.LibEpiccash.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 epiccash.LibEpiccash.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 epiccash.LibEpiccash.getWalletBalances( + wallet: wallet!, + refreshFromNode: refreshFromNode, + minimumConfirmations: cryptoCurrency.minConfirms, + ); + } + + Future _testEpicboxServer(String host, int port) async { + // TODO use an EpicBoxServerModel as the only param + final websocketConnectionUri = 'wss://$host:$port'; + const connectionOptions = SocketConnectionOptions( + pingIntervalMs: 3000, + timeoutConnectionMs: 4000, + + /// see ping/pong messages in [logEventStream] stream + skipPingMessages: true, + + /// Set this attribute to `true` if do not need any ping/pong + /// messages and ping measurement. Default is `false` + pingRestrictionForce: true, + ); + + final IMessageProcessor textSocketProcessor = + SocketSimpleTextProcessor(); + final textSocketHandler = IWebSocketHandler.createClient( + websocketConnectionUri, + textSocketProcessor, + connectionOptions: connectionOptions, + ); + + // Listening to server responses: + bool isConnected = true; + textSocketHandler.incomingMessagesStream.listen((inMsg) { + Logging.instance.log( + '> webSocket got text message from server: "$inMsg" ' + '[ping: ${textSocketHandler.pingDelayMs}]', + level: LogLevel.Info); + }); + + // Connecting to server: + final isTextSocketConnected = await textSocketHandler.connect(); + if (!isTextSocketConnected) { + // ignore: avoid_print + Logging.instance.log( + 'Connection to [$websocketConnectionUri] failed for some reason!', + level: LogLevel.Error); + isConnected = false; + } + return isConnected; + } + + Future _putSendToAddresses( + ({String slateId, String commitId}) slateData, + Map txAddressInfo, + ) async { + try { + var slatesToCommits = await _getSlatesToCommits(); + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; + slatesToCommits[slateData.slateId] = { + "commitId": slateData.commitId, + "from": from, + "to": to, + }; + await info.updateExtraEpiccashWalletInfo( + epicData: info.epicData!.copyWith( + slatesToCommits: slatesToCommits, + ), + isar: mainDB.isar, + ); + return true; + } catch (e, s) { + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); + return false; + } + } + + // TODO: [prio=high] this isn't needed. Condense to `info.epicData?.slatesToCommits ?? {}` where needed + Future> _getSlatesToCommits() async { + try { + var slatesToCommits = info.epicData?.slatesToCommits; + if (slatesToCommits == null) { + slatesToCommits = {}; + } else { + slatesToCommits = slatesToCommits; + } + return slatesToCommits; + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return {}; + } + } + + Future _getCurrentIndex() async { + try { + final int receivingIndex = info.epicData!.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 wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + final walletAddress = await epiccash.LibEpiccash.getAddressInfo( + wallet: wallet!, + index: index, + epicboxConfig: epicboxConfig.toString(), + ); + + Logging.instance.log( + "WALLET_ADDRESS_IS $walletAddress", + level: LogLevel.Info, + ); + + 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 + epiccash.LibEpiccash.stopEpicboxListener(); + 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.epicData!.lastScannedBlock; + + // loop while scanning in chain in chunks (of blocks?) + while (lastScannedBlock < chainHeight) { + Logging.instance.log( + "chainHeight: $chainHeight, lastScannedBlock: $lastScannedBlock", + level: LogLevel.Info, + ); + + int nextScannedBlock = await epiccash.LibEpiccash.scanOutputs( + wallet: wallet!, + startHeight: lastScannedBlock, + numberOfBlocks: scanChunkSize, + ); + + // update local cache + await info.updateExtraEpiccashWalletInfo( + epicData: info.epicData!.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 _listenToEpicbox(); + } catch (e, s) { + Logging.instance.log( + "_startScans failed: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future _listenToEpicbox() async { + Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + epiccash.LibEpiccash.startEpicboxListener( + wallet: wallet!, + epicboxConfig: epicboxConfig.toString(), + ); + } + +// TODO: [prio=high] what is the point of this??? + Future _getRealConfig() async { + String? config = await secureStorageInterface.read( + key: '${walletId}_config', + ); + if (Platform.isIOS) { + final walletDir = await _currentWalletDirPath(); + var 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}) { + int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000; + const int epicCashFirstBlock = 1565370278; + const double overestimateSecondsPerBlock = 61; + int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock; + int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock; + //todo: check if print needed + // debugPrint( + // "approximate height: $approximateHeight chosen_seconds: $chosenSeconds"); + int height = approximateHeight; + if (height < 0) { + height = 0; + } + return height; + } + + // ============== Overrides ================================================== + + @override + int get isarTransactionVersion => 2; + @override FilterOperation? get changeAddressFilterOperation => FilterGroup.and(standardChangeAddressFilters); @@ -21,51 +501,555 @@ class EpiccashWallet extends Bip39Wallet { FilterGroup.and(standardReceivingAddressFilters); @override - Future confirmSend({required TxData txData}) { - // TODO: implement confirmSend - throw UnimplementedError(); + Future init() async { + 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 EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', value: stringConfig); + await secureStorageInterface.write( + key: '${walletId}_password', value: password); + await secureStorageInterface.write( + key: '${walletId}_epicboxConfig', value: epicboxConfig.toString()); + + String name = walletId; + + await epiccash.LibEpiccash.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); + + //Open wallet + encodedWallet = await epiccash.LibEpiccash.openWallet( + config: stringConfig, password: password); + await secureStorageInterface.write( + key: '${walletId}_wallet', value: encodedWallet); + + //Store Epic box 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 epicData = ExtraEpiccashWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: bufferedCreateHeight, + restoreHeight: bufferedCreateHeight, + creationHeight: bufferedCreateHeight, + ); + + await info.updateExtraEpiccashWalletInfo( + epicData: epicData, + isar: mainDB.isar, + ); + } else { + Logging.instance.log( + "initializeExisting() ${cryptoCurrency.coin.prettyName} wallet", + level: LogLevel.Info); + + final config = await _getRealConfig(); + final password = + await secureStorageInterface.read(key: '${walletId}_password'); + + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: config, password: password!); + await secureStorageInterface.write( + key: '${walletId}_wallet', value: walletOpen); + + await updateNode(); + await updateBalance(); + // TODO: is there anything else that should be set up here whenever this wallet is first loaded again? + } + + return await super.init(); } @override - Future prepareSend({required TxData txData}) { - // TODO: implement prepareSend - throw UnimplementedError(); + Future confirmSend({required TxData txData}) async { + try { + final wallet = + await secureStorageInterface.read(key: '${walletId}_wallet'); + final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + // 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://")) { + bool isEpicboxConnected = await _testEpicboxServer( + epicboxConfig.host, epicboxConfig.port ?? 443); + if (!isEpicboxConnected) { + throw Exception("Failed to send TX : Unable to reach epicbox server"); + } + } + + ({String commitId, String slateId}) transaction; + + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { + transaction = await epiccash.LibEpiccash.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 epiccash.LibEpiccash.createTransaction( + wallet: wallet!, + amount: txData.recipients!.first.amount.raw.toInt(), + address: txData.recipients!.first.address, + secretKeyIndex: 0, + epicboxConfig: epicboxConfig.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( + "Epic cash confirmSend: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } } @override - Future recover({required bool isRescan}) { - // TODO: implement recover - throw UnimplementedError(); + Future prepareSend({required TxData txData}) async { + try { + if (txData.recipients?.length != 1) { + throw Exception("Epic cash 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( + "Epic cash 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("Epic cash prepareSend: $e\n$s", level: LogLevel.Error); + rethrow; + } } @override - Future refresh() { - // TODO: implement refresh - throw UnimplementedError(); + Future recover({required bool isRescan}) async { + try { + await refreshMutex.protect(() async { + if (isRescan) { + // clear blockchain info + await mainDB.deleteWalletBlockchainData(walletId); + + await info.updateExtraEpiccashWalletInfo( + epicData: info.epicData!.copyWith( + lastScannedBlock: info.epicData!.restoreHeight, + ), + isar: mainDB.isar, + ); + + await _startScans(); + } else { + await updateNode(); + final String password = generatePassword(); + + final String stringConfig = await _getConfig(); + final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + await secureStorageInterface.write( + key: '${walletId}_config', + value: stringConfig, + ); + await secureStorageInterface.write( + key: '${walletId}_password', + value: password, + ); + + await secureStorageInterface.write( + key: '${walletId}_epicboxConfig', + value: epicboxConfig.toString(), + ); + + await epiccash.LibEpiccash.recoverWallet( + config: stringConfig, + password: password, + mnemonic: await getMnemonic(), + name: info.walletId, + ); + + final epicData = ExtraEpiccashWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: info.restoreHeight, + restoreHeight: info.restoreHeight, + creationHeight: info.epicData?.creationHeight ?? info.restoreHeight, + ); + + await info.updateExtraEpiccashWalletInfo( + epicData: epicData, + isar: mainDB.isar, + ); + + //Open Wallet + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: walletOpen, + ); + + await _generateAndStoreReceivingAddressForIndex( + epicData.receivingIndex); + } + }); + + await refresh(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from electrumx_mixin recover(): $e\n$s", + level: LogLevel.Info); + + rethrow; + } } @override - Future updateBalance() { - // TODO: implement updateBalance - throw UnimplementedError(); + 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.coin, + ), + ); + + // 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.coin, + ), + ); + + 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.coin, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + cryptoCurrency.coin, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error, + ); + } finally { + refreshMutex.release(); + } } @override - Future updateTransactions() { - // TODO: implement updateTransactions - throw UnimplementedError(); + 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( + "Epic cash wallet failed to update balance: $e\n$s", + level: LogLevel.Warning, + ); + } } @override - Future updateUTXOs() { - // TODO: implement updateUTXOs - throw UnimplementedError(); + 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 epiccash.LibEpiccash.getTransactions( + wallet: wallet!, + refreshFromNode: refreshFromNode, + ); + + final List txns = []; + + final slatesToCommits = await _getSlatesToCommits(); + + for (final tx in transactions) { + // Logging.instance.log("tx: $tx", level: LogLevel.Info); + + // unsure if needed + // final isIncoming = + // tx.txType == epic_models.TransactionType.TxReceived || + // tx.txType == epic_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?; + + // hack epic tx data into inputs and outputs + final List outputs = []; + final List inputs = []; + // TODO: [prio=high] should addressFrom and addressTo be swapped?? + final addressFromIsMine = myAddressesSet.contains(addressFrom); + final addressToIsMine = myAddressesSet.contains(addressTo); + outputs.add( + OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: tx.amountCredited, + addresses: [if (addressFrom != null) addressFrom], + walletOwns: addressFromIsMine, + ), + ); + inputs.add( + InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + sequence: null, + outpoint: null, + addresses: [if (addressTo != null) addressTo], + valueStringSats: tx.amountDebited, + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: addressToIsMine, + ), + ); + + final TransactionType txType; + if (addressFromIsMine && addressToIsMine) { + txType = TransactionType.sentToSelf; + } else if (addressFromIsMine) { + txType = TransactionType.incoming; + } else { + txType = TransactionType.outgoing; + } + + final otherData = { + "isEpiccashTransaction": true, + "numberOfMessages": numberOfMessages, + "slateId": slateId, + "onChainNote": onChainNote, + "isCancelled": + tx.txType == epic_models.TransactionType.TxSentCancelled || + tx.txType == epic_models.TransactionType.TxReceivedCancelled, + }; + + 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.updateOrPutTransactionV2s(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 updateNode() { - // TODO: implement updateNode - throw UnimplementedError(); + Future updateUTXOs() async { + // not used for epiccash + return false; + } + + @override + Future updateNode() async { + _epicNode = 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, + ); + + await refresh(); } @override @@ -90,20 +1074,86 @@ class EpiccashWallet extends Bip39Wallet { @override Future updateChainHeight() async { - // final height = await fetchChainHeight(); - // await walletInfo.updateCachedChainHeight( - // newHeight: height, - // isar: mainDB.isar, - // ); + final config = await _getRealConfig(); + final latestHeight = + await epiccash.LibEpiccash.getChainHeight(config: config); + await info.updateCachedChainHeight( + newHeight: latestHeight, + isar: mainDB.isar, + ); } @override - Future estimateFeeFor(Amount amount, int feeRate) { - // TODO: implement estimateFeeFor - throw UnimplementedError(); + Future estimateFeeFor(Amount amount, int feeRate) async { + // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? + int currentFee = await _nativeFee( + amount.raw.toInt(), + ifErrorEstimateFee: true, + ); + return Amount( + rawValue: BigInt.from(currentFee), + fractionDigits: cryptoCurrency.fractionDigits, + ); } @override - // TODO: implement fees - Future get fees => throw UnimplementedError(); + 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("EpicCash_wallet exit finished", level: LogLevel.Info); + } +} + +Future deleteEpicWallet({ + 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) { + Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/epiccash"; + final String name = walletId.trim(); + final walletDir = '$path/$name'; + + var editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } + + if (wallet == null) { + return "Tried to delete non existent epic wallet file with walletId=$walletId"; + } else { + try { + return epiccash.LibEpiccash.deleteWallet( + wallet: wallet, + config: config!, + ); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + return "deleteEpicWallet($walletId) failed..."; + } + } } diff --git a/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart b/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart index 1b46eb21e..8d71c655f 100644 --- a/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart +++ b/lib/wallets/wallet/supporting/epiccash_wallet_info_extension.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:isar/isar.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; @@ -23,6 +24,18 @@ extension EpiccashWalletInfoExtension on WalletInfo { return null; } } + + Future updateExtraEpiccashWalletInfo({ + required ExtraEpiccashWalletInfo epicData, + required Isar isar, + }) async { + await updateOtherData( + newEntries: { + WalletInfoKeys.epiccashData: jsonEncode(epicData.toMap()), + }, + isar: isar, + ); + } } /// Holds data previously stored in hive From 68754e3329838fe305b8fbac6a801423b9ee54db Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 9 Jan 2024 11:04:02 -0600 Subject: [PATCH 2/4] build runner --- .../blockchain_data/v2/transaction_v2.g.dart | 715 ++++++++- .../coins/firo/firo_wallet_test.mocks.dart | 1334 ----------------- 2 files changed, 684 insertions(+), 1365 deletions(-) delete mode 100644 test/services/coins/firo/firo_wallet_test.mocks.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 c9182bc0a..814ec9d94 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 @@ -38,46 +38,71 @@ const TransactionV2Schema = CollectionSchema( type: IsarType.objectList, target: r'InputV2', ), - r'otherData': PropertySchema( + r'isCancelled': PropertySchema( id: 4, + name: r'isCancelled', + type: IsarType.bool, + ), + r'isEpiccashTransaction': PropertySchema( + id: 5, + name: r'isEpiccashTransaction', + type: IsarType.bool, + ), + r'numberOfMessages': PropertySchema( + id: 6, + name: r'numberOfMessages', + type: IsarType.long, + ), + r'onChainNote': PropertySchema( + id: 7, + name: r'onChainNote', + type: IsarType.string, + ), + r'otherData': PropertySchema( + id: 8, name: r'otherData', type: IsarType.string, ), r'outputs': PropertySchema( - id: 5, + id: 9, name: r'outputs', type: IsarType.objectList, target: r'OutputV2', ), + r'slateId': PropertySchema( + id: 10, + name: r'slateId', + type: IsarType.string, + ), r'subType': PropertySchema( - id: 6, + id: 11, name: r'subType', type: IsarType.byte, enumMap: _TransactionV2subTypeEnumValueMap, ), r'timestamp': PropertySchema( - id: 7, + id: 12, name: r'timestamp', type: IsarType.long, ), r'txid': PropertySchema( - id: 8, + id: 13, name: r'txid', type: IsarType.string, ), r'type': PropertySchema( - id: 9, + id: 14, name: r'type', type: IsarType.byte, enumMap: _TransactionV2typeEnumValueMap, ), r'version': PropertySchema( - id: 10, + id: 15, name: r'version', type: IsarType.long, ), r'walletId': PropertySchema( - id: 11, + id: 16, name: r'walletId', type: IsarType.string, ) @@ -166,6 +191,12 @@ int _transactionV2EstimateSize( bytesCount += InputV2Schema.estimateSize(value, offsets, allOffsets); } } + { + final value = object.onChainNote; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } { final value = object.otherData; if (value != null) { @@ -180,6 +211,12 @@ int _transactionV2EstimateSize( bytesCount += OutputV2Schema.estimateSize(value, offsets, allOffsets); } } + { + final value = object.slateId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } bytesCount += 3 + object.txid.length * 3; bytesCount += 3 + object.walletId.length * 3; return bytesCount; @@ -200,19 +237,24 @@ void _transactionV2Serialize( InputV2Schema.serialize, object.inputs, ); - writer.writeString(offsets[4], object.otherData); + writer.writeBool(offsets[4], object.isCancelled); + writer.writeBool(offsets[5], object.isEpiccashTransaction); + writer.writeLong(offsets[6], object.numberOfMessages); + writer.writeString(offsets[7], object.onChainNote); + writer.writeString(offsets[8], object.otherData); writer.writeObjectList( - offsets[5], + offsets[9], allOffsets, OutputV2Schema.serialize, object.outputs, ); - writer.writeByte(offsets[6], object.subType.index); - writer.writeLong(offsets[7], object.timestamp); - writer.writeString(offsets[8], object.txid); - writer.writeByte(offsets[9], object.type.index); - writer.writeLong(offsets[10], object.version); - writer.writeString(offsets[11], object.walletId); + writer.writeString(offsets[10], object.slateId); + writer.writeByte(offsets[11], object.subType.index); + writer.writeLong(offsets[12], object.timestamp); + writer.writeString(offsets[13], object.txid); + writer.writeByte(offsets[14], object.type.index); + writer.writeLong(offsets[15], object.version); + writer.writeString(offsets[16], object.walletId); } TransactionV2 _transactionV2Deserialize( @@ -232,23 +274,23 @@ TransactionV2 _transactionV2Deserialize( InputV2(), ) ?? [], - otherData: reader.readStringOrNull(offsets[4]), + otherData: reader.readStringOrNull(offsets[8]), outputs: reader.readObjectList( - offsets[5], + offsets[9], OutputV2Schema.deserialize, allOffsets, OutputV2(), ) ?? [], subType: - _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[6])] ?? + _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[11])] ?? TransactionSubType.none, - timestamp: reader.readLong(offsets[7]), - txid: reader.readString(offsets[8]), - type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[9])] ?? + timestamp: reader.readLong(offsets[12]), + txid: reader.readString(offsets[13]), + type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? TransactionType.outgoing, - version: reader.readLong(offsets[10]), - walletId: reader.readString(offsets[11]), + version: reader.readLong(offsets[15]), + walletId: reader.readString(offsets[16]), ); object.id = id; return object; @@ -276,8 +318,16 @@ P _transactionV2DeserializeProp

( ) ?? []) as P; case 4: - return (reader.readStringOrNull(offset)) as P; + return (reader.readBool(offset)) as P; case 5: + return (reader.readBool(offset)) as P; + case 6: + return (reader.readLongOrNull(offset)) as P; + case 7: + return (reader.readStringOrNull(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: return (reader.readObjectList( offset, OutputV2Schema.deserialize, @@ -285,20 +335,22 @@ P _transactionV2DeserializeProp

( OutputV2(), ) ?? []) as P; - case 6: + case 10: + return (reader.readStringOrNull(offset)) as P; + case 11: return (_TransactionV2subTypeValueEnumMap[ reader.readByteOrNull(offset)] ?? TransactionSubType.none) as P; - case 7: + case 12: return (reader.readLong(offset)) as P; - case 8: + case 13: return (reader.readString(offset)) as P; - case 9: + case 14: return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? TransactionType.outgoing) as P; - case 10: + case 15: return (reader.readLong(offset)) as P; - case 11: + case 16: return (reader.readString(offset)) as P; default: throw IsarError('Unknown property with id $propertyId'); @@ -1263,6 +1315,254 @@ extension TransactionV2QueryFilter }); } + QueryBuilder + isCancelledEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isCancelled', + value: value, + )); + }); + } + + QueryBuilder + isEpiccashTransactionEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isEpiccashTransaction', + value: value, + )); + }); + } + + QueryBuilder + numberOfMessagesIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'numberOfMessages', + )); + }); + } + + QueryBuilder + numberOfMessagesIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'numberOfMessages', + )); + }); + } + + QueryBuilder + numberOfMessagesEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'numberOfMessages', + value: value, + )); + }); + } + + QueryBuilder + numberOfMessagesGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'numberOfMessages', + value: value, + )); + }); + } + + QueryBuilder + numberOfMessagesLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'numberOfMessages', + value: value, + )); + }); + } + + QueryBuilder + numberOfMessagesBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'numberOfMessages', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + onChainNoteIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'onChainNote', + )); + }); + } + + QueryBuilder + onChainNoteIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'onChainNote', + )); + }); + } + + QueryBuilder + onChainNoteEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteBetween( + 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'onChainNote', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'onChainNote', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'onChainNote', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + onChainNoteIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'onChainNote', + value: '', + )); + }); + } + + QueryBuilder + onChainNoteIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'onChainNote', + value: '', + )); + }); + } + QueryBuilder otherDataIsNull() { return QueryBuilder.apply(this, (query) { @@ -1506,6 +1806,160 @@ extension TransactionV2QueryFilter }); } + QueryBuilder + slateIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'slateId', + )); + }); + } + + QueryBuilder + slateIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'slateId', + )); + }); + } + + QueryBuilder + slateIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdBetween( + 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'slateId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'slateId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'slateId', + value: '', + )); + }); + } + + QueryBuilder + slateIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'slateId', + value: '', + )); + }); + } + QueryBuilder subTypeEqualTo(TransactionSubType value) { return QueryBuilder.apply(this, (query) { @@ -2060,6 +2514,60 @@ extension TransactionV2QuerySortBy }); } + QueryBuilder sortByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.asc); + }); + } + + QueryBuilder + sortByIsCancelledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.desc); + }); + } + + QueryBuilder + sortByIsEpiccashTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isEpiccashTransaction', Sort.asc); + }); + } + + QueryBuilder + sortByIsEpiccashTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isEpiccashTransaction', Sort.desc); + }); + } + + QueryBuilder + sortByNumberOfMessages() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberOfMessages', Sort.asc); + }); + } + + QueryBuilder + sortByNumberOfMessagesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberOfMessages', Sort.desc); + }); + } + + QueryBuilder sortByOnChainNote() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'onChainNote', Sort.asc); + }); + } + + QueryBuilder + sortByOnChainNoteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'onChainNote', Sort.desc); + }); + } + QueryBuilder sortByOtherData() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'otherData', Sort.asc); @@ -2073,6 +2581,18 @@ extension TransactionV2QuerySortBy }); } + QueryBuilder sortBySlateId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.asc); + }); + } + + QueryBuilder sortBySlateIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.desc); + }); + } + QueryBuilder sortBySubType() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'subType', Sort.asc); @@ -2199,6 +2719,60 @@ extension TransactionV2QuerySortThenBy }); } + QueryBuilder thenByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.asc); + }); + } + + QueryBuilder + thenByIsCancelledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.desc); + }); + } + + QueryBuilder + thenByIsEpiccashTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isEpiccashTransaction', Sort.asc); + }); + } + + QueryBuilder + thenByIsEpiccashTransactionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isEpiccashTransaction', Sort.desc); + }); + } + + QueryBuilder + thenByNumberOfMessages() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberOfMessages', Sort.asc); + }); + } + + QueryBuilder + thenByNumberOfMessagesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberOfMessages', Sort.desc); + }); + } + + QueryBuilder thenByOnChainNote() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'onChainNote', Sort.asc); + }); + } + + QueryBuilder + thenByOnChainNoteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'onChainNote', Sort.desc); + }); + } + QueryBuilder thenByOtherData() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'otherData', Sort.asc); @@ -2212,6 +2786,18 @@ extension TransactionV2QuerySortThenBy }); } + QueryBuilder thenBySlateId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.asc); + }); + } + + QueryBuilder thenBySlateIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.desc); + }); + } + QueryBuilder thenBySubType() { return QueryBuilder.apply(this, (query) { return query.addSortBy(r'subType', Sort.asc); @@ -2309,6 +2895,34 @@ extension TransactionV2QueryWhereDistinct }); } + QueryBuilder + distinctByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isCancelled'); + }); + } + + QueryBuilder + distinctByIsEpiccashTransaction() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isEpiccashTransaction'); + }); + } + + QueryBuilder + distinctByNumberOfMessages() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'numberOfMessages'); + }); + } + + QueryBuilder distinctByOnChainNote( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'onChainNote', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctByOtherData( {bool caseSensitive = true}) { return QueryBuilder.apply(this, (query) { @@ -2316,6 +2930,13 @@ extension TransactionV2QueryWhereDistinct }); } + QueryBuilder distinctBySlateId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'slateId', caseSensitive: caseSensitive); + }); + } + QueryBuilder distinctBySubType() { return QueryBuilder.apply(this, (query) { return query.addDistinctBy(r'subType'); @@ -2388,6 +3009,32 @@ extension TransactionV2QueryProperty }); } + QueryBuilder isCancelledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isCancelled'); + }); + } + + QueryBuilder + isEpiccashTransactionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isEpiccashTransaction'); + }); + } + + QueryBuilder + numberOfMessagesProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'numberOfMessages'); + }); + } + + QueryBuilder onChainNoteProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'onChainNote'); + }); + } + QueryBuilder otherDataProperty() { return QueryBuilder.apply(this, (query) { return query.addPropertyName(r'otherData'); @@ -2401,6 +3048,12 @@ extension TransactionV2QueryProperty }); } + QueryBuilder slateIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'slateId'); + }); + } + QueryBuilder subTypeProperty() { return QueryBuilder.apply(this, (query) { diff --git a/test/services/coins/firo/firo_wallet_test.mocks.dart b/test/services/coins/firo/firo_wallet_test.mocks.dart deleted file mode 100644 index 213c594db..000000000 --- a/test/services/coins/firo/firo_wallet_test.mocks.dart +++ /dev/null @@ -1,1334 +0,0 @@ -// Mocks generated by Mockito 5.4.2 from annotations -// in stackwallet/test/services/coins/firo/firo_wallet_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; - -import 'package:decimal/decimal.dart' as _i2; -import 'package:isar/isar.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/db/isar/main_db.dart' as _i9; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart' as _i6; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart' as _i3; -import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i12; -import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart' - as _i15; -import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i11; -import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; -import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i8; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; -import 'package:stackwallet/wallets/isar/models/wallet_info.dart' as _i10; -import 'package:tuple/tuple.dart' as _i14; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeDuration_0 extends _i1.SmartFake implements Duration { - _FakeDuration_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeDecimal_1 extends _i1.SmartFake implements _i2.Decimal { - _FakeDecimal_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeElectrumXClient_2 extends _i1.SmartFake - implements _i3.ElectrumXClient { - _FakeElectrumXClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeIsar_3 extends _i1.SmartFake implements _i4.Isar { - _FakeIsar_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeQueryBuilder_4 extends _i1.SmartFake - implements _i4.QueryBuilder { - _FakeQueryBuilder_4( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [ElectrumXClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient { - MockElectrumXClient() { - _i1.throwOnMissingStub(this); - } - - @override - set failovers(List<_i3.ElectrumXNode>? _failovers) => super.noSuchMethod( - Invocation.setter( - #failovers, - _failovers, - ), - returnValueForMissingStub: null, - ); - @override - int get currentFailoverIndex => (super.noSuchMethod( - Invocation.getter(#currentFailoverIndex), - returnValue: 0, - ) as int); - @override - set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod( - Invocation.setter( - #currentFailoverIndex, - _currentFailoverIndex, - ), - returnValueForMissingStub: null, - ); - @override - Duration get connectionTimeoutForSpecialCaseJsonRPCClients => - (super.noSuchMethod( - Invocation.getter(#connectionTimeoutForSpecialCaseJsonRPCClients), - returnValue: _FakeDuration_0( - this, - Invocation.getter(#connectionTimeoutForSpecialCaseJsonRPCClients), - ), - ) as Duration); - @override - String get host => (super.noSuchMethod( - Invocation.getter(#host), - returnValue: '', - ) as String); - @override - int get port => (super.noSuchMethod( - Invocation.getter(#port), - returnValue: 0, - ) as int); - @override - bool get useSSL => (super.noSuchMethod( - Invocation.getter(#useSSL), - returnValue: false, - ) as bool); - @override - _i5.Future request({ - required String? command, - List? args = const [], - String? requestID, - int? retries = 2, - Duration? requestTimeout = const Duration(seconds: 60), - }) => - (super.noSuchMethod( - Invocation.method( - #request, - [], - { - #command: command, - #args: args, - #requestID: requestID, - #retries: retries, - #requestTimeout: requestTimeout, - }, - ), - returnValue: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future>> batchRequest({ - required String? command, - required Map>? args, - Duration? requestTimeout = const Duration(seconds: 60), - int? retries = 2, - }) => - (super.noSuchMethod( - Invocation.method( - #batchRequest, - [], - { - #command: command, - #args: args, - #requestTimeout: requestTimeout, - #retries: retries, - }, - ), - returnValue: _i5.Future>>.value( - >[]), - ) as _i5.Future>>); - @override - _i5.Future ping({ - String? requestID, - int? retryCount = 1, - }) => - (super.noSuchMethod( - Invocation.method( - #ping, - [], - { - #requestID: requestID, - #retryCount: retryCount, - }, - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i5.Future> getBlockHeadTip({String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getBlockHeadTip, - [], - {#requestID: requestID}, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future> getServerFeatures({String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getServerFeatures, - [], - {#requestID: requestID}, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future broadcastTransaction({ - required String? rawTx, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #broadcastTransaction, - [], - { - #rawTx: rawTx, - #requestID: requestID, - }, - ), - returnValue: _i5.Future.value(''), - ) as _i5.Future); - @override - _i5.Future> getBalance({ - required String? scripthash, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getBalance, - [], - { - #scripthash: scripthash, - #requestID: requestID, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future>> getHistory({ - required String? scripthash, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getHistory, - [], - { - #scripthash: scripthash, - #requestID: requestID, - }, - ), - returnValue: _i5.Future>>.value( - >[]), - ) as _i5.Future>>); - @override - _i5.Future>>> getBatchHistory( - {required Map>? args}) => - (super.noSuchMethod( - Invocation.method( - #getBatchHistory, - [], - {#args: args}, - ), - returnValue: _i5.Future>>>.value( - >>{}), - ) as _i5.Future>>>); - @override - _i5.Future>> getUTXOs({ - required String? scripthash, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getUTXOs, - [], - { - #scripthash: scripthash, - #requestID: requestID, - }, - ), - returnValue: _i5.Future>>.value( - >[]), - ) as _i5.Future>>); - @override - _i5.Future>>> getBatchUTXOs( - {required Map>? args}) => - (super.noSuchMethod( - Invocation.method( - #getBatchUTXOs, - [], - {#args: args}, - ), - returnValue: _i5.Future>>>.value( - >>{}), - ) as _i5.Future>>>); - @override - _i5.Future> getTransaction({ - required String? txHash, - bool? verbose = true, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getTransaction, - [], - { - #txHash: txHash, - #verbose: verbose, - #requestID: requestID, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future> getLelantusAnonymitySet({ - String? groupId = r'1', - String? blockhash = r'', - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getLelantusAnonymitySet, - [], - { - #groupId: groupId, - #blockhash: blockhash, - #requestID: requestID, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future getLelantusMintData({ - dynamic mints, - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getLelantusMintData, - [], - { - #mints: mints, - #requestID: requestID, - }, - ), - returnValue: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future> getLelantusUsedCoinSerials({ - String? requestID, - required int? startNumber, - }) => - (super.noSuchMethod( - Invocation.method( - #getLelantusUsedCoinSerials, - [], - { - #requestID: requestID, - #startNumber: startNumber, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future getLelantusLatestCoinId({String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getLelantusLatestCoinId, - [], - {#requestID: requestID}, - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future> getSparkAnonymitySet({ - String? coinGroupId = r'1', - String? startBlockHash = r'', - String? requestID, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkAnonymitySet, - [], - { - #coinGroupId: coinGroupId, - #startBlockHash: startBlockHash, - #requestID: requestID, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future> getSparkUsedCoinsTags({ - String? requestID, - required int? startNumber, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkUsedCoinsTags, - [], - { - #requestID: requestID, - #startNumber: startNumber, - }, - ), - returnValue: _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future>> getSparkMintMetaData({ - String? requestID, - required List? sparkCoinHashes, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkMintMetaData, - [], - { - #requestID: requestID, - #sparkCoinHashes: sparkCoinHashes, - }, - ), - returnValue: _i5.Future>>.value( - >[]), - ) as _i5.Future>>); - @override - _i5.Future getSparkLatestCoinId({String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getSparkLatestCoinId, - [], - {#requestID: requestID}, - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future> getFeeRate({String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getFeeRate, - [], - {#requestID: requestID}, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future<_i2.Decimal> estimateFee({ - String? requestID, - required int? blocks, - }) => - (super.noSuchMethod( - Invocation.method( - #estimateFee, - [], - { - #requestID: requestID, - #blocks: blocks, - }, - ), - returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1( - this, - Invocation.method( - #estimateFee, - [], - { - #requestID: requestID, - #blocks: blocks, - }, - ), - )), - ) as _i5.Future<_i2.Decimal>); - @override - _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( - Invocation.method( - #relayFee, - [], - {#requestID: requestID}, - ), - returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_1( - this, - Invocation.method( - #relayFee, - [], - {#requestID: requestID}, - ), - )), - ) as _i5.Future<_i2.Decimal>); -} - -/// A class which mocks [CachedElectrumXClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumXClient extends _i1.Mock - implements _i6.CachedElectrumXClient { - MockCachedElectrumXClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.ElectrumXClient get electrumXClient => (super.noSuchMethod( - Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumXClient_2( - this, - Invocation.getter(#electrumXClient), - ), - ) as _i3.ElectrumXClient); - @override - _i5.Future> getAnonymitySet({ - required String? groupId, - String? blockhash = r'', - required _i7.Coin? coin, - }) => - (super.noSuchMethod( - Invocation.method( - #getAnonymitySet, - [], - { - #groupId: groupId, - #blockhash: blockhash, - #coin: coin, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future> getSparkAnonymitySet({ - required String? groupId, - String? blockhash = r'', - required _i7.Coin? coin, - }) => - (super.noSuchMethod( - Invocation.method( - #getSparkAnonymitySet, - [], - { - #groupId: groupId, - #blockhash: blockhash, - #coin: coin, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - String base64ToHex(String? source) => (super.noSuchMethod( - Invocation.method( - #base64ToHex, - [source], - ), - returnValue: '', - ) as String); - @override - String base64ToReverseHex(String? source) => (super.noSuchMethod( - Invocation.method( - #base64ToReverseHex, - [source], - ), - returnValue: '', - ) as String); - @override - _i5.Future> getTransaction({ - required String? txHash, - required _i7.Coin? coin, - bool? verbose = true, - }) => - (super.noSuchMethod( - Invocation.method( - #getTransaction, - [], - { - #txHash: txHash, - #coin: coin, - #verbose: verbose, - }, - ), - returnValue: - _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future> getUsedCoinSerials({ - required _i7.Coin? coin, - int? startNumber = 0, - }) => - (super.noSuchMethod( - Invocation.method( - #getUsedCoinSerials, - [], - { - #coin: coin, - #startNumber: startNumber, - }, - ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); - @override - _i5.Future> getSparkUsedCoinsTags({required _i7.Coin? coin}) => - (super.noSuchMethod( - Invocation.method( - #getSparkUsedCoinsTags, - [], - {#coin: coin}, - ), - returnValue: _i5.Future>.value({}), - ) as _i5.Future>); - @override - _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => - (super.noSuchMethod( - Invocation.method( - #clearSharedTransactionCache, - [], - {#coin: coin}, - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); -} - -/// A class which mocks [TransactionNotificationTracker]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockTransactionNotificationTracker extends _i1.Mock - implements _i8.TransactionNotificationTracker { - MockTransactionNotificationTracker() { - _i1.throwOnMissingStub(this); - } - - @override - String get walletId => (super.noSuchMethod( - Invocation.getter(#walletId), - returnValue: '', - ) as String); - @override - List get pendings => (super.noSuchMethod( - Invocation.getter(#pendings), - returnValue: [], - ) as List); - @override - List get confirmeds => (super.noSuchMethod( - Invocation.getter(#confirmeds), - returnValue: [], - ) as List); - @override - bool wasNotifiedPending(String? txid) => (super.noSuchMethod( - Invocation.method( - #wasNotifiedPending, - [txid], - ), - returnValue: false, - ) as bool); - @override - _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( - Invocation.method( - #addNotifiedPending, - [txid], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( - Invocation.method( - #wasNotifiedConfirmed, - [txid], - ), - returnValue: false, - ) as bool); - @override - _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( - Invocation.method( - #addNotifiedConfirmed, - [txid], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( - Invocation.method( - #deleteTransaction, - [txid], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); -} - -/// A class which mocks [MainDB]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockMainDB extends _i1.Mock implements _i9.MainDB { - MockMainDB() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Isar get isar => (super.noSuchMethod( - Invocation.getter(#isar), - returnValue: _FakeIsar_3( - this, - Invocation.getter(#isar), - ), - ) as _i4.Isar); - @override - _i5.Future initMainDB({_i4.Isar? mock}) => (super.noSuchMethod( - Invocation.method( - #initMainDB, - [], - {#mock: mock}, - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i5.Future putWalletInfo(_i10.WalletInfo? walletInfo) => - (super.noSuchMethod( - Invocation.method( - #putWalletInfo, - [walletInfo], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future updateWalletInfo(_i10.WalletInfo? walletInfo) => - (super.noSuchMethod( - Invocation.method( - #updateWalletInfo, - [walletInfo], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - List<_i11.ContactEntry> getContactEntries() => (super.noSuchMethod( - Invocation.method( - #getContactEntries, - [], - ), - returnValue: <_i11.ContactEntry>[], - ) as List<_i11.ContactEntry>); - @override - _i5.Future deleteContactEntry({required String? id}) => - (super.noSuchMethod( - Invocation.method( - #deleteContactEntry, - [], - {#id: id}, - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i5.Future isContactEntryExists({required String? id}) => - (super.noSuchMethod( - Invocation.method( - #isContactEntryExists, - [], - {#id: id}, - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i11.ContactEntry? getContactEntry({required String? id}) => - (super.noSuchMethod(Invocation.method( - #getContactEntry, - [], - {#id: id}, - )) as _i11.ContactEntry?); - @override - _i5.Future putContactEntry( - {required _i11.ContactEntry? contactEntry}) => - (super.noSuchMethod( - Invocation.method( - #putContactEntry, - [], - {#contactEntry: contactEntry}, - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i12.TransactionBlockExplorer? getTransactionBlockExplorer( - {required _i7.Coin? coin}) => - (super.noSuchMethod(Invocation.method( - #getTransactionBlockExplorer, - [], - {#coin: coin}, - )) as _i12.TransactionBlockExplorer?); - @override - _i5.Future putTransactionBlockExplorer( - _i12.TransactionBlockExplorer? explorer) => - (super.noSuchMethod( - Invocation.method( - #putTransactionBlockExplorer, - [explorer], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i4.QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause> - getAddresses(String? walletId) => (super.noSuchMethod( - Invocation.method( - #getAddresses, - [walletId], - ), - returnValue: _FakeQueryBuilder_4<_i13.Address, _i13.Address, - _i4.QAfterWhereClause>( - this, - Invocation.method( - #getAddresses, - [walletId], - ), - ), - ) as _i4 - .QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause>); - @override - _i5.Future putAddress(_i13.Address? address) => (super.noSuchMethod( - Invocation.method( - #putAddress, - [address], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future> putAddresses(List<_i13.Address>? addresses) => - (super.noSuchMethod( - Invocation.method( - #putAddresses, - [addresses], - ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); - @override - _i5.Future> updateOrPutAddresses(List<_i13.Address>? addresses) => - (super.noSuchMethod( - Invocation.method( - #updateOrPutAddresses, - [addresses], - ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); - @override - _i5.Future<_i13.Address?> getAddress( - String? walletId, - String? address, - ) => - (super.noSuchMethod( - Invocation.method( - #getAddress, - [ - walletId, - address, - ], - ), - returnValue: _i5.Future<_i13.Address?>.value(), - ) as _i5.Future<_i13.Address?>); - @override - _i5.Future updateAddress( - _i13.Address? oldAddress, - _i13.Address? newAddress, - ) => - (super.noSuchMethod( - Invocation.method( - #updateAddress, - [ - oldAddress, - newAddress, - ], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, _i4.QAfterWhereClause> - getTransactions(String? walletId) => (super.noSuchMethod( - Invocation.method( - #getTransactions, - [walletId], - ), - returnValue: _FakeQueryBuilder_4<_i13.Transaction, _i13.Transaction, - _i4.QAfterWhereClause>( - this, - Invocation.method( - #getTransactions, - [walletId], - ), - ), - ) as _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, - _i4.QAfterWhereClause>); - @override - _i5.Future putTransaction(_i13.Transaction? transaction) => - (super.noSuchMethod( - Invocation.method( - #putTransaction, - [transaction], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future> putTransactions(List<_i13.Transaction>? transactions) => - (super.noSuchMethod( - Invocation.method( - #putTransactions, - [transactions], - ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); - @override - _i5.Future<_i13.Transaction?> getTransaction( - String? walletId, - String? txid, - ) => - (super.noSuchMethod( - Invocation.method( - #getTransaction, - [ - walletId, - txid, - ], - ), - returnValue: _i5.Future<_i13.Transaction?>.value(), - ) as _i5.Future<_i13.Transaction?>); - @override - _i5.Stream<_i13.Transaction?> watchTransaction({ - required int? id, - bool? fireImmediately = false, - }) => - (super.noSuchMethod( - Invocation.method( - #watchTransaction, - [], - { - #id: id, - #fireImmediately: fireImmediately, - }, - ), - returnValue: _i5.Stream<_i13.Transaction?>.empty(), - ) as _i5.Stream<_i13.Transaction?>); - @override - _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause> getUTXOs( - String? walletId) => - (super.noSuchMethod( - Invocation.method( - #getUTXOs, - [walletId], - ), - returnValue: - _FakeQueryBuilder_4<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>( - this, - Invocation.method( - #getUTXOs, - [walletId], - ), - ), - ) as _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>); - @override - _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterFilterCondition> - getUTXOsByAddress( - String? walletId, - String? address, - ) => - (super.noSuchMethod( - Invocation.method( - #getUTXOsByAddress, - [ - walletId, - address, - ], - ), - returnValue: _FakeQueryBuilder_4<_i13.UTXO, _i13.UTXO, - _i4.QAfterFilterCondition>( - this, - Invocation.method( - #getUTXOsByAddress, - [ - walletId, - address, - ], - ), - ), - ) as _i4 - .QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterFilterCondition>); - @override - _i5.Future putUTXO(_i13.UTXO? utxo) => (super.noSuchMethod( - Invocation.method( - #putUTXO, - [utxo], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future putUTXOs(List<_i13.UTXO>? utxos) => (super.noSuchMethod( - Invocation.method( - #putUTXOs, - [utxos], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future updateUTXOs( - String? walletId, - List<_i13.UTXO>? utxos, - ) => - (super.noSuchMethod( - Invocation.method( - #updateUTXOs, - [ - walletId, - utxos, - ], - ), - returnValue: _i5.Future.value(false), - ) as _i5.Future); - @override - _i5.Stream<_i13.UTXO?> watchUTXO({ - required int? id, - bool? fireImmediately = false, - }) => - (super.noSuchMethod( - Invocation.method( - #watchUTXO, - [], - { - #id: id, - #fireImmediately: fireImmediately, - }, - ), - returnValue: _i5.Stream<_i13.UTXO?>.empty(), - ) as _i5.Stream<_i13.UTXO?>); - @override - _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, - _i4.QAfterWhereClause> getTransactionNotes( - String? walletId) => - (super.noSuchMethod( - Invocation.method( - #getTransactionNotes, - [walletId], - ), - returnValue: _FakeQueryBuilder_4<_i13.TransactionNote, - _i13.TransactionNote, _i4.QAfterWhereClause>( - this, - Invocation.method( - #getTransactionNotes, - [walletId], - ), - ), - ) as _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, - _i4.QAfterWhereClause>); - @override - _i5.Future putTransactionNote(_i13.TransactionNote? transactionNote) => - (super.noSuchMethod( - Invocation.method( - #putTransactionNote, - [transactionNote], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future putTransactionNotes( - List<_i13.TransactionNote>? transactionNotes) => - (super.noSuchMethod( - Invocation.method( - #putTransactionNotes, - [transactionNotes], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future<_i13.TransactionNote?> getTransactionNote( - String? walletId, - String? txid, - ) => - (super.noSuchMethod( - Invocation.method( - #getTransactionNote, - [ - walletId, - txid, - ], - ), - returnValue: _i5.Future<_i13.TransactionNote?>.value(), - ) as _i5.Future<_i13.TransactionNote?>); - @override - _i5.Stream<_i13.TransactionNote?> watchTransactionNote({ - required int? id, - bool? fireImmediately = false, - }) => - (super.noSuchMethod( - Invocation.method( - #watchTransactionNote, - [], - { - #id: id, - #fireImmediately: fireImmediately, - }, - ), - returnValue: _i5.Stream<_i13.TransactionNote?>.empty(), - ) as _i5.Stream<_i13.TransactionNote?>); - @override - _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, _i4.QAfterWhereClause> - getAddressLabels(String? walletId) => (super.noSuchMethod( - Invocation.method( - #getAddressLabels, - [walletId], - ), - returnValue: _FakeQueryBuilder_4<_i13.AddressLabel, - _i13.AddressLabel, _i4.QAfterWhereClause>( - this, - Invocation.method( - #getAddressLabels, - [walletId], - ), - ), - ) as _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, - _i4.QAfterWhereClause>); - @override - _i5.Future putAddressLabel(_i13.AddressLabel? addressLabel) => - (super.noSuchMethod( - Invocation.method( - #putAddressLabel, - [addressLabel], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - int putAddressLabelSync(_i13.AddressLabel? addressLabel) => - (super.noSuchMethod( - Invocation.method( - #putAddressLabelSync, - [addressLabel], - ), - returnValue: 0, - ) as int); - @override - _i5.Future putAddressLabels(List<_i13.AddressLabel>? addressLabels) => - (super.noSuchMethod( - Invocation.method( - #putAddressLabels, - [addressLabels], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future<_i13.AddressLabel?> getAddressLabel( - String? walletId, - String? addressString, - ) => - (super.noSuchMethod( - Invocation.method( - #getAddressLabel, - [ - walletId, - addressString, - ], - ), - returnValue: _i5.Future<_i13.AddressLabel?>.value(), - ) as _i5.Future<_i13.AddressLabel?>); - @override - _i13.AddressLabel? getAddressLabelSync( - String? walletId, - String? addressString, - ) => - (super.noSuchMethod(Invocation.method( - #getAddressLabelSync, - [ - walletId, - addressString, - ], - )) as _i13.AddressLabel?); - @override - _i5.Stream<_i13.AddressLabel?> watchAddressLabel({ - required int? id, - bool? fireImmediately = false, - }) => - (super.noSuchMethod( - Invocation.method( - #watchAddressLabel, - [], - { - #id: id, - #fireImmediately: fireImmediately, - }, - ), - returnValue: _i5.Stream<_i13.AddressLabel?>.empty(), - ) as _i5.Stream<_i13.AddressLabel?>); - @override - _i5.Future updateAddressLabel(_i13.AddressLabel? addressLabel) => - (super.noSuchMethod( - Invocation.method( - #updateAddressLabel, - [addressLabel], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future deleteWalletBlockchainData(String? walletId) => - (super.noSuchMethod( - Invocation.method( - #deleteWalletBlockchainData, - [walletId], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future deleteAddressLabels(String? walletId) => (super.noSuchMethod( - Invocation.method( - #deleteAddressLabels, - [walletId], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future deleteTransactionNotes(String? walletId) => - (super.noSuchMethod( - Invocation.method( - #deleteTransactionNotes, - [walletId], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future addNewTransactionData( - List<_i14.Tuple2<_i13.Transaction, _i13.Address?>>? transactionsData, - String? walletId, - ) => - (super.noSuchMethod( - Invocation.method( - #addNewTransactionData, - [ - transactionsData, - walletId, - ], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future> updateOrPutTransactionV2s( - List<_i15.TransactionV2>? transactions) => - (super.noSuchMethod( - Invocation.method( - #updateOrPutTransactionV2s, - [transactions], - ), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); - @override - _i4.QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere> - getEthContracts() => (super.noSuchMethod( - Invocation.method( - #getEthContracts, - [], - ), - returnValue: _FakeQueryBuilder_4<_i13.EthContract, _i13.EthContract, - _i4.QWhere>( - this, - Invocation.method( - #getEthContracts, - [], - ), - ), - ) as _i4 - .QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere>); - @override - _i5.Future<_i13.EthContract?> getEthContract(String? contractAddress) => - (super.noSuchMethod( - Invocation.method( - #getEthContract, - [contractAddress], - ), - returnValue: _i5.Future<_i13.EthContract?>.value(), - ) as _i5.Future<_i13.EthContract?>); - @override - _i13.EthContract? getEthContractSync(String? contractAddress) => - (super.noSuchMethod(Invocation.method( - #getEthContractSync, - [contractAddress], - )) as _i13.EthContract?); - @override - _i5.Future putEthContract(_i13.EthContract? contract) => - (super.noSuchMethod( - Invocation.method( - #putEthContract, - [contract], - ), - returnValue: _i5.Future.value(0), - ) as _i5.Future); - @override - _i5.Future putEthContracts(List<_i13.EthContract>? contracts) => - (super.noSuchMethod( - Invocation.method( - #putEthContracts, - [contracts], - ), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - @override - _i5.Future getHighestUsedMintIndex({required String? walletId}) => - (super.noSuchMethod( - Invocation.method( - #getHighestUsedMintIndex, - [], - {#walletId: walletId}, - ), - returnValue: _i5.Future.value(), - ) as _i5.Future); -} From c4a3874bf02205ac5b3d6a04efaaf00b584a6c66 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 9 Jan 2024 14:43:58 -0600 Subject: [PATCH 3/4] various epiccash tweaks and fixes --- .../blockchain_data/v2/transaction_v2.dart | 4 + .../restore_wallet_view.dart | 29 ++- .../tx_v2/transaction_v2_details_view.dart | 20 +- lib/services/mixins/epic_cash_hive.dart | 122 ----------- lib/utilities/constants.dart | 2 +- lib/wallets/isar/models/wallet_info.dart | 1 - lib/wallets/wallet/impl/epiccash_wallet.dart | 205 ++++++++++-------- 7 files changed, 167 insertions(+), 216 deletions(-) delete mode 100644 lib/services/mixins/epic_cash_hive.dart 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 e00d8952a..fe46f9a82 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -155,6 +155,10 @@ class TransactionV2 { } if (isEpiccashTransaction) { + if (slateId == null) { + return "Restored Funds"; + } + if (isCancelled) { return "Cancelled"; } else if (type == TransactionType.incoming) { 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 74b1e517d..f2eb0d779 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 @@ -10,6 +10,7 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:convert'; import 'dart:io'; import 'dart:math'; @@ -50,6 +51,8 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; +import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; +import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -202,6 +205,7 @@ class _RestoreWalletViewState extends ConsumerState { mnemonic = mnemonic.trim(); int height = 0; + String? otherDataJsonString; if (widget.coin == Coin.monero) { height = monero.getHeigthByDate(date: widget.restoreFromDate); @@ -228,6 +232,22 @@ class _RestoreWalletViewState extends ConsumerState { if (height < 0) { height = 0; } + + otherDataJsonString = jsonEncode( + { + WalletInfoKeys.epiccashData: jsonEncode( + ExtraEpiccashWalletInfo( + 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 @@ -244,6 +264,8 @@ class _RestoreWalletViewState extends ConsumerState { final info = WalletInfo.createNew( coin: widget.coin, name: widget.walletName, + restoreHeight: height, + otherDataJsonString: otherDataJsonString, ); bool isRestoring = true; @@ -292,7 +314,12 @@ class _RestoreWalletViewState extends ConsumerState { mnemonic: mnemonic, ); - await wallet.init(); + if (wallet is EpiccashWallet) { + await wallet.init(isRestore: true); + } else { + await wallet.init(); + } + await wallet.recover(isRescan: false); // check if state is still active before continuing 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 58ab3f713..74b016893 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 @@ -110,7 +110,25 @@ class _TransactionV2DetailsViewState unit = coin.ticker; - if (_transaction.subType == TransactionSubType.cashFusion) { + if (_transaction.isEpiccashTransaction) { + switch (_transaction.type) { + case TransactionType.outgoing: + case TransactionType.unknown: + amount = _transaction.getAmountSentFromThisWallet(coin: coin); + break; + + case TransactionType.incoming: + case TransactionType.sentToSelf: + amount = _transaction.getAmountReceivedInThisWallet(coin: coin); + break; + } + data = _transaction.outputs + .map((e) => ( + addresses: e.addresses, + amount: Amount(rawValue: e.value, fractionDigits: coin.decimals) + )) + .toList(); + } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet(coin: coin); data = _transaction.outputs .where((e) => e.walletOwns) diff --git a/lib/services/mixins/epic_cash_hive.dart b/lib/services/mixins/epic_cash_hive.dart deleted file mode 100644 index 014b9e02d..000000000 --- a/lib/services/mixins/epic_cash_hive.dart +++ /dev/null @@ -1,122 +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 'package:stackwallet/db/hive/db.dart'; - -mixin EpicCashHive { - late final String _walletId; - - void initEpicCashHive(String walletId) { - _walletId = walletId; - } - - // receiving index - int? epicGetReceivingIndex() { - return DB.instance.get(boxName: _walletId, key: "receivingIndex") - as int?; - } - - Future epicUpdateReceivingIndex(int index) async { - await DB.instance.put( - boxName: _walletId, - key: "receivingIndex", - value: index, - ); - } - - // change index - int? epicGetChangeIndex() { - return DB.instance.get(boxName: _walletId, key: "changeIndex") - as int?; - } - - Future epicUpdateChangeIndex(int index) async { - await DB.instance.put( - boxName: _walletId, - key: "changeIndex", - value: index, - ); - } - - // slateToAddresses - Map epicGetSlatesToAddresses() { - return DB.instance.get( - boxName: _walletId, - key: "slate_to_address", - ) as Map? ?? - {}; - } - - Future epicUpdateSlatesToAddresses(Map map) async { - await DB.instance.put( - boxName: _walletId, - key: "slate_to_address", - value: map, - ); - } - - // slatesToCommits - Map? epicGetSlatesToCommits() { - return DB.instance.get( - boxName: _walletId, - key: "slatesToCommits", - ) as Map?; - } - - Future epicUpdateSlatesToCommits(Map map) async { - await DB.instance.put( - boxName: _walletId, - key: "slatesToCommits", - value: map, - ); - } - - // last scanned block - int? epicGetLastScannedBlock() { - return DB.instance.get(boxName: _walletId, key: "lastScannedBlock") - as int?; - } - - Future epicUpdateLastScannedBlock(int blockHeight) async { - await DB.instance.put( - boxName: _walletId, - key: "lastScannedBlock", - value: blockHeight, - ); - } - - // epic restore height - int? epicGetRestoreHeight() { - return DB.instance.get(boxName: _walletId, key: "restoreHeight") - as int?; - } - - Future epicUpdateRestoreHeight(int height) async { - await DB.instance.put( - boxName: _walletId, - key: "restoreHeight", - value: height, - ); - } - - // epic creation height - int? epicGetCreationHeight() { - return DB.instance.get(boxName: _walletId, key: "creationHeight") - as int?; - } - - Future epicUpdateCreationHeight(int height) async { - await DB.instance.put( - boxName: _walletId, - key: "creationHeight", - value: height, - ); - } -} diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index b8b74a5d3..db0543044 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -259,7 +259,6 @@ abstract class Constants { case Coin.litecoinTestNet: case Coin.firo: case Coin.firoTestNet: - case Coin.epicCash: case Coin.namecoin: case Coin.particl: case Coin.ethereum: @@ -270,6 +269,7 @@ abstract class Constants { case Coin.nano: case Coin.banano: + case Coin.epicCash: case Coin.stellar: case Coin.stellarTestnet: case Coin.tezos: diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index 405728d4c..e18cba80d 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -353,7 +353,6 @@ class WalletInfo implements IsarId { int favouriteOrderIndex = -1, int cachedChainHeight = 0, int restoreHeight = 0, - bool isMnemonicVerified = false, String? cachedBalanceString, String? cachedBalanceSecondaryString, String? cachedBalanceTertiaryString, diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index e532b6195..ef3cd1966 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -501,79 +501,87 @@ class EpiccashWallet extends Bip39Wallet { FilterGroup.and(standardReceivingAddressFilters); @override - Future init() async { - String? encodedWallet = - await secureStorageInterface.read(key: "${walletId}_wallet"); + 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(); + // 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 EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + final String password = generatePassword(); + final String stringConfig = await _getConfig(); + final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - await secureStorageInterface.write( - key: '${walletId}_config', value: stringConfig); - await secureStorageInterface.write( - key: '${walletId}_password', value: password); - await secureStorageInterface.write( - key: '${walletId}_epicboxConfig', value: epicboxConfig.toString()); + await secureStorageInterface.write( + key: '${walletId}_config', value: stringConfig); + await secureStorageInterface.write( + key: '${walletId}_password', value: password); + await secureStorageInterface.write( + key: '${walletId}_epicboxConfig', value: epicboxConfig.toString()); - String name = walletId; + String name = walletId; - await epiccash.LibEpiccash.initializeNewWallet( - config: stringConfig, - mnemonic: mnemonicString, - password: password, - name: name, - ); + await epiccash.LibEpiccash.initializeNewWallet( + config: stringConfig, + mnemonic: mnemonicString, + password: password, + name: name, + ); - //Open wallet - encodedWallet = await epiccash.LibEpiccash.openWallet( - config: stringConfig, password: password); - await secureStorageInterface.write( - key: '${walletId}_wallet', value: encodedWallet); + //Open wallet + encodedWallet = await epiccash.LibEpiccash.openWallet( + config: stringConfig, + password: password, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', + value: encodedWallet, + ); - //Store Epic box address info - await _generateAndStoreReceivingAddressForIndex(0); + //Store Epic box 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))); + // subtract a couple days to ensure we have a buffer for SWB + final bufferedCreateHeight = _calculateRestoreHeightFrom( + date: DateTime.now().subtract(const Duration(days: 2))); - final epicData = ExtraEpiccashWalletInfo( - receivingIndex: 0, - changeIndex: 0, - slatesToAddresses: {}, - slatesToCommits: {}, - lastScannedBlock: bufferedCreateHeight, - restoreHeight: bufferedCreateHeight, - creationHeight: bufferedCreateHeight, - ); + final epicData = ExtraEpiccashWalletInfo( + receivingIndex: 0, + changeIndex: 0, + slatesToAddresses: {}, + slatesToCommits: {}, + lastScannedBlock: bufferedCreateHeight, + restoreHeight: bufferedCreateHeight, + creationHeight: bufferedCreateHeight, + ); - await info.updateExtraEpiccashWalletInfo( - epicData: epicData, - isar: mainDB.isar, - ); - } else { - Logging.instance.log( - "initializeExisting() ${cryptoCurrency.coin.prettyName} wallet", - level: LogLevel.Info); + await info.updateExtraEpiccashWalletInfo( + epicData: epicData, + isar: mainDB.isar, + ); + } else { + Logging.instance.log( + "initializeExisting() ${cryptoCurrency.coin.prettyName} wallet", + level: LogLevel.Info); - final config = await _getRealConfig(); - final password = - await secureStorageInterface.read(key: '${walletId}_password'); + final config = await _getRealConfig(); + final password = + await secureStorageInterface.read(key: '${walletId}_password'); - final walletOpen = await epiccash.LibEpiccash.openWallet( - config: config, password: password!); - await secureStorageInterface.write( - key: '${walletId}_wallet', value: walletOpen); + final walletOpen = await epiccash.LibEpiccash.openWallet( + config: config, + password: password!, + ); + await secureStorageInterface.write( + key: '${walletId}_wallet', value: walletOpen); - await updateNode(); - await updateBalance(); - // TODO: is there anything else that should be set up here whenever this wallet is first loaded again? + await updateNode(); + await updateBalance(); + // TODO: is there anything else that should be set up here whenever this wallet is first loaded again? + } } return await super.init(); @@ -695,7 +703,7 @@ class EpiccashWallet extends Bip39Wallet { isar: mainDB.isar, ); - await _startScans(); + unawaited(_startScans()); } else { await updateNode(); final String password = generatePassword(); @@ -754,7 +762,7 @@ class EpiccashWallet extends Bip39Wallet { } }); - await refresh(); + unawaited(refresh()); } catch (e, s) { Logging.instance.log( "Exception rethrown from electrumx_mixin recover(): $e\n$s", @@ -945,10 +953,9 @@ class EpiccashWallet extends Bip39Wallet { for (final tx in transactions) { // Logging.instance.log("tx: $tx", level: LogLevel.Info); - // unsure if needed - // final isIncoming = - // tx.txType == epic_models.TransactionType.TxReceived || - // tx.txType == epic_models.TransactionType.TxReceivedCancelled; + final isIncoming = + tx.txType == epic_models.TransactionType.TxReceived || + tx.txType == epic_models.TransactionType.TxReceivedCancelled; final slateId = tx.txSlateId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; final numberOfMessages = tx.messages?.messages.length; @@ -956,43 +963,57 @@ class EpiccashWallet extends Bip39Wallet { 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 epic tx data into inputs and outputs final List outputs = []; final List inputs = []; - // TODO: [prio=high] should addressFrom and addressTo be swapped?? final addressFromIsMine = myAddressesSet.contains(addressFrom); final addressToIsMine = myAddressesSet.contains(addressTo); - outputs.add( - OutputV2.isarCantDoRequiredInDefaultConstructor( - scriptPubKeyHex: "00", - valueStringSats: tx.amountCredited, - addresses: [if (addressFrom != null) addressFrom], - walletOwns: addressFromIsMine, - ), + + OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: credit.toString(), + addresses: [ + if (addressFrom != null) addressFrom, + ], + walletOwns: true, ); - inputs.add( - InputV2.isarCantDoRequiredInDefaultConstructor( - scriptSigHex: null, - sequence: null, - outpoint: null, - addresses: [if (addressTo != null) addressTo], - valueStringSats: tx.amountDebited, - witness: null, - innerRedeemScriptAsm: null, - coinbase: null, - walletOwns: addressToIsMine, - ), + InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: 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 (addressFromIsMine && addressToIsMine) { - txType = TransactionType.sentToSelf; - } else if (addressFromIsMine) { - txType = TransactionType.incoming; + 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 = { "isEpiccashTransaction": true, "numberOfMessages": numberOfMessages, @@ -1001,6 +1022,10 @@ class EpiccashWallet extends Bip39Wallet { "isCancelled": tx.txType == epic_models.TransactionType.TxSentCancelled || tx.txType == epic_models.TransactionType.TxReceivedCancelled, + "anonFees": Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), }; final txn = TransactionV2( From 228444141b893052496c76d4f743a64dc38920bc Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 9 Jan 2024 14:44:16 -0600 Subject: [PATCH 4/4] migration fix for mnemonic verified flag changes --- lib/db/migrate_wallets_to_isar.dart | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/db/migrate_wallets_to_isar.dart b/lib/db/migrate_wallets_to_isar.dart index 893a85094..50ead2110 100644 --- a/lib/db/migrate_wallets_to_isar.dart +++ b/lib/db/migrate_wallets_to_isar.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/models/isar/models/transaction_note.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; +import 'package:stackwallet/wallets/isar/models/wallet_info_meta.dart'; import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_extension.dart'; Future migrateWalletsToIsar({ @@ -47,7 +48,7 @@ Future migrateWalletsToIsar({ final List favourites = (await Hive.openBox(DB.boxNameFavoriteWallets)).values.toList(); - final List newInfo = []; + final List<(WalletInfo, WalletInfoMeta)> newInfo = []; final List migratedNotes = []; // @@ -113,15 +114,19 @@ Future migrateWalletsToIsar({ // otherData.removeWhere((key, value) => value == null); + final infoMeta = WalletInfoMeta( + walletId: old.walletId, + isMnemonicVerified: allWalletsBox + .get("${old.walletId}_mnemonicHasBeenVerified") as bool? ?? + false, + ); + final info = WalletInfo( coinName: old.coin.name, walletId: old.walletId, name: old.name, mainAddressType: old.coin.primaryAddressType, favouriteOrderIndex: favourites.indexOf(old.walletId), - isMnemonicVerified: allWalletsBox - .get("${old.walletId}_mnemonicHasBeenVerified") as bool? ?? - false, cachedChainHeight: walletBox.get( DBKeys.storedChainHeight, ) as int? ?? @@ -135,7 +140,7 @@ Future migrateWalletsToIsar({ otherDataJsonString: jsonEncode(otherData), ); - newInfo.add(info); + newInfo.add((info, infoMeta)); } if (migratedNotes.isNotEmpty) { @@ -145,10 +150,14 @@ Future migrateWalletsToIsar({ } await MainDB.instance.isar.writeTxn(() async { - await MainDB.instance.isar.walletInfo.putAll(newInfo); + await MainDB.instance.isar.walletInfo + .putAll(newInfo.map((e) => e.$1).toList()); + await MainDB.instance.isar.walletInfoMeta + .putAll(newInfo.map((e) => e.$2).toList()); }); - await _cleanupOnSuccess(walletIds: newInfo.map((e) => e.walletId).toList()); + await _cleanupOnSuccess( + walletIds: newInfo.map((e) => e.$1.walletId).toList()); } Future _cleanupOnSuccess({required List walletIds}) async {