diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index de29931da..d636f16aa 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit de29931dacc9aefaf42a9ca139a8754a42adc40d +Subproject commit d636f16aa0079c7ef47f0c8547fba1635e5eb33b diff --git a/dockerfile.linux b/dockerfile.linux index 0853741d9..4a3867008 100644 --- a/dockerfile.linux +++ b/dockerfile.linux @@ -2,10 +2,10 @@ FROM ubuntu:20.04 as base COPY . /stack_wallet WORKDIR /stack_wallet/scripts/linux RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y git=1:2.25.1-1ubuntu3.6 make=4.2.1-1.2 curl=7.68.0-1ubuntu2.14 cargo=0.62.0ubuntu0libgit2-0ubuntu0.20.04.1 \ - file=1:5.38-4 ca-certificates=20211016~20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 libclang-dev=1:10.0-50~exp1 \ - unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 \ - valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 libgtk-3-dev=3.24.20-0ubuntu1.1 \ - libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \ + file=1:5.38-4 ca-certificates=20211016ubuntu0.20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 \ + libclang-dev=1:10.0-50~exp1 unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 \ + libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 \ + libgtk-3-dev=3.24.20-0ubuntu1.1 libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 && cd .. && ./prebuild.sh && cd linux && ./build_all.sh diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index b159fb9aa..97dc91361 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -19,6 +19,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; @@ -105,8 +106,25 @@ class _NewWalletRecoveryPhraseWarningViewState ) ], ), - body: Padding( - padding: EdgeInsets.all(isDesktop ? 0 : 16), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ); + }, + ), child: Column( crossAxisAlignment: isDesktop ? CrossAxisAlignment.center @@ -315,9 +333,11 @@ class _NewWalletRecoveryPhraseWarningViewState const SizedBox( width: 20, ), - Text( - "Do not show them to anyone.", - style: STextStyles.navBarTitle(context), + Expanded( + child: Text( + "Do not show them to anyone.", + style: STextStyles.navBarTitle(context), + ), ), ], ), @@ -327,6 +347,10 @@ class _NewWalletRecoveryPhraseWarningViewState ), ), if (!isDesktop) const Spacer(), + if (!isDesktop) + const SizedBox( + height: 16, + ), if (isDesktop) const SizedBox( height: 32, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index dac2af028..6b03d06fb 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -23,8 +23,6 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart' - as datePicker; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -154,10 +152,46 @@ class _RestoreOptionsViewState extends ConsumerState { await Future.delayed(const Duration(milliseconds: 125)); } - final date = await datePicker.showRoundedDatePicker( + final date = await showRoundedDatePicker( context: context, initialDate: DateTime.now(), - height: height * 0.5, + height: height / 3.0, + theme: ThemeData( + primarySwatch: Util.createMaterialColor(fetchedColor), + ), + //TODO pick a better initial date + // 2007 chosen as that is just before bitcoin launched + firstDate: DateTime(2007), + lastDate: DateTime.now(), + borderRadius: Constants.size.circularBorderRadius * 2, + + textPositiveButton: "SELECT", + + styleDatePicker: _buildDatePickerStyle(), + styleYearPicker: _buildYearPickerStyle(), + ); + if (date != null) { + _restoreFromDate = date; + _dateController.text = Format.formatDate(date); + } + } + + Future chooseDesktopDate() async { + final height = MediaQuery.of(context).size.height; + final fetchedColor = + Theme.of(context).extension()!.accentColorDark; + // check and hide keyboard + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 125)); + } + + final now = DateTime.now(); + + final date = await showRoundedDatePicker( + context: context, + initialDate: DateTime.now(), + height: height / 3.0, theme: ThemeData( primarySwatch: Util.createMaterialColor(fetchedColor), ), @@ -283,15 +317,22 @@ class _RestoreOptionsViewState extends ConsumerState { (coin == Coin.wownero && ref.watch(mnemonicWordCountStateProvider.state).state == 25)) - - // if (!isDesktop) - RestoreFromDatePicker( - onTap: chooseDate, - controller: _dateController, - ), - - // if (isDesktop) - // // TODO desktop date picker + if (!isDesktop) + RestoreFromDatePicker( + onTap: chooseDate, + controller: _dateController, + ), + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) + if (isDesktop) + // TODO desktop date picker + RestoreFromDatePicker( + onTap: chooseDesktopDate, + controller: _dateController, + ), if (coin == Coin.monero || coin == Coin.epicCash || (coin == Coin.wownero && @@ -412,7 +453,6 @@ class _RestoreOptionsViewState extends ConsumerState { isDesktop: isDesktop, onPressed: _nextEnabled ? nextPressed : null, ), - if (isDesktop) const Spacer( flex: 15, 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 7a21a6f72..bb2628192 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 @@ -736,6 +736,8 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key( @@ -831,6 +833,8 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key( @@ -954,6 +958,8 @@ class _RestoreWalletViewState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 4), child: TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key("restoreMnemonicFormField_$i"), diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 27bc7b151..3c61681f0 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -409,8 +409,9 @@ class _GenerateUriQrCodeViewState extends State { height: 1.8, ) : STextStyles.field(context), - keyboardType: - const TextInputType.numberWithOptions(decimal: true), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => setState(() {}), decoration: standardInputDecoration( "Amount", diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index b129c4467..e34b9d8ad 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1111,10 +1111,12 @@ class _SendViewState extends ConsumerState { const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a crypto amount with 8 decimal places @@ -1168,11 +1170,12 @@ class _SendViewState extends ConsumerState { const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: - const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a fiat amount with 2 decimal places 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 92b712111..f27f57312 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 @@ -391,7 +391,8 @@ class _EpiBoxInfoFormState extends ConsumerState { enableSuggestions: Util.isDesktop ? false : true, controller: portController, decoration: const InputDecoration(hintText: "Port"), - keyboardType: const TextInputType.numberWithOptions(), + keyboardType: + Util.isDesktop ? null : const TextInputType.numberWithOptions(), ), const SizedBox( height: 8, diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index 7e1b53cbb..d0e013d83 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -739,10 +739,12 @@ class _TransactionSearchViewState controller: _amountTextEditingController, focusNode: amountTextFieldFocusNode, onChanged: (_) => setState(() {}), - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), inputFormatters: [ // regex to validate a crypto amount with 8 decimal places TextInputFormatter.withFunction((oldValue, newValue) => diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart index d7bfefb1f..1277c5c0f 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -42,6 +42,9 @@ class _ContactListItemState extends ConsumerState { final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); + // hack fix until we use a proper database (not Hive) + int i = 0; + return RoundedWhiteContainer( padding: const EdgeInsets.all(0), borderColor: Theme.of(context).extension()!.background, @@ -70,7 +73,8 @@ class _ContactListItemState extends ConsumerState { filterByCoin != null ? e.coin == filterByCoin! : true) .map( (e) => Column( - key: Key("contactAddress_${e.address}_${e.label}_key"), + key: Key( + "contactAddress_${e.address}_${e.label}_${++i}_key"), mainAxisSize: MainAxisSize.min, children: [ Container( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index f1b967471..c5d682c60 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -140,17 +140,21 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 40, ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Ok", - onPressed: () { - Navigator.of(context).pop(); - }, - ), + Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox( + width: 32, + ), + ], ), ], ), @@ -319,13 +323,13 @@ class _DesktopSendState extends ConsumerState { } if (!wasCancelled && mounted) { + txData["note"] = _note ?? ""; + txData["address"] = _address; // pop building dialog Navigator.of( context, rootNavigator: true, ).pop(); - txData["note"] = _note; - txData["address"] = _address; unawaited( showDialog( @@ -394,22 +398,24 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 40, ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: Expanded( - child: SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Yes", - onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(); - }, + Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), ), - ), + const SizedBox( + width: 32, + ), + ], ), ], ), @@ -1002,10 +1008,12 @@ class _DesktopSendState extends ConsumerState { key: const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a crypto amount with 8 decimal places @@ -1056,10 +1064,12 @@ class _DesktopSendState extends ConsumerState { key: const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a fiat amount with 2 decimal places diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index c5c3f22b1..c9499b054 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -2666,8 +2666,8 @@ class BitcoinCashWallet extends CoinServiceAPI { ], // dust limit is the minimum amount a change output should be ))["vSize"] as int; //todo: check if print needed - debugPrint("vSizeForOneOutput $vSizeForOneOutput"); - debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); + // debugPrint("vSizeForOneOutput $vSizeForOneOutput"); + // debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); // Assume 1 output, only for recipient and no change var feeForOneOutput = estimateTxFee( diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 41dbed42b..ceb9e7663 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -178,7 +178,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, - secureStore: secureStorageInterface, + secureStorage: secureStorageInterface, // tracker: tracker, ); diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 91a6c5ad3..2f72e0dc2 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -13,12 +13,10 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; -import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/pending_monero_transaction.dart'; -import 'package:dart_numerics/dart_numerics.dart'; import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/monero/monero.dart'; @@ -32,7 +30,6 @@ import 'package:stackwallet/models/paymint/transactions_model.dart'; import 'package:stackwallet/models/paymint/utxo_model.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'; @@ -51,46 +48,65 @@ import 'package:stackwallet/utilities/stack_file_system.dart'; const int MINIMUM_CONFIRMATIONS = 10; -//https://github.com/monero-project/monero/blob/8361d60aef6e17908658128284899e3a11d808d4/src/cryptonote_config.h#L162 -const String GENESIS_HASH_MAINNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; -const String GENESIS_HASH_TESTNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; - class MoneroWallet extends CoinServiceAPI { - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final _prefs = Prefs.instance; - - Timer? timer; - Timer? moneroAutosaveTimer; - late Coin _coin; - - late SecureStorageInterface _secureStore; - - late PriceAPI _priceAPI; - - Future getCurrentNode() async { - return NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - } - - MoneroWallet( - {required String walletId, - required String walletName, - required Coin coin, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore}) { - _walletId = walletId; - _walletName = walletName; - _coin = coin; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } + final String _walletId; + final Coin _coin; + final PriceAPI _priceAPI; + final SecureStorageInterface _secureStorage; + final Prefs _prefs; + String _walletName; bool _shouldAutoSync = false; + bool _isConnected = false; + bool _hasCalledExit = false; + bool refreshMutex = false; + bool longMutex = false; + + WalletService? walletService; + KeyService? keysStorage; + MoneroWalletBase? walletBase; + WalletCreationService? _walletCreationService; + Timer? _autoSaveTimer; + + Future? _currentReceivingAddress; + Future? _feeObject; + Future? _transactionData; + + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); + + MoneroWallet({ + required String walletId, + required String walletName, + required Coin coin, + required SecureStorageInterface secureStorage, + PriceAPI? priceAPI, + Prefs? prefs, + }) : _walletId = walletId, + _walletName = walletName, + _coin = coin, + _priceAPI = priceAPI ?? PriceAPI(Client()), + _secureStorage = secureStorage, + _prefs = prefs ?? Prefs.instance; + + @override + bool get isFavorite { + try { + return DB.instance.get(boxName: walletId, key: "isFavorite") + as bool; + } catch (e, s) { + Logging.instance.log( + "isFavorite fetch failed (returning false by default): $e\n$s", + level: LogLevel.Error); + return false; + } + } + + @override + set isFavorite(bool markFavorite) { + DB.instance.put( + boxName: walletId, key: "isFavorite", value: markFavorite); + } @override bool get shouldAutoSync => _shouldAutoSync; @@ -99,591 +115,235 @@ class MoneroWallet extends CoinServiceAPI { set shouldAutoSync(bool shouldAutoSync) { if (_shouldAutoSync != shouldAutoSync) { _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - moneroAutosaveTimer?.cancel(); - timer = null; - moneroAutosaveTimer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - // Walletbase needs to be open for this to work - refresh(); - } + // xmr wallets cannot be open at the same time + // leave following commented out for now + + // if (!shouldAutoSync) { + // timer?.cancel(); + // moneroAutosaveTimer?.cancel(); + // timer = null; + // moneroAutosaveTimer = null; + // stopNetworkAlivePinging(); + // } else { + // startNetworkAlivePinging(); + // // Walletbase needs to be open for this to work + // refresh(); + // } } } @override - Future updateNode(bool shouldRefresh) async { - final node = await getCurrentNode(); + String get walletName => _walletName; - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; - // TODO: is this sync call needed? Do we need to notify ui here? - await walletBase?.startSync(); + @override + // not used for monero + Future> get allOwnAddresses async => []; - if (shouldRefresh) { - await refresh(); + @override + Future get availableBalance async { + int runningBalance = 0; + for (final entry in walletBase!.balance!.entries) { + runningBalance += entry.value.unlockedBalance; } - } - - Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { - return []; - } - final List data = mnemonicString.split(' '); - return data; + return Format.satoshisToAmount(runningBalance, coin: coin); } @override - Future> get mnemonic => _getMnemonicList(); - - Future get currentNodeHeight async { - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - return await walletBase!.getNodeHeight(); - } - } catch (e, s) {} - int _height = -1; - try { - _height = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - int blocksRemaining = -1; - - try { - blocksRemaining = - (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - int currentHeight = _height + blocksRemaining; - if (_height == -1 || blocksRemaining == -1) { - currentHeight = int64MaxValue; - } - final cachedHeight = DB.instance - .get(boxName: walletId, key: "storedNodeHeight") as int? ?? - 0; - - if (currentHeight > cachedHeight && currentHeight != int64MaxValue) { - await DB.instance.put( - boxName: walletId, key: "storedNodeHeight", value: currentHeight); - return currentHeight; - } else { - return cachedHeight; - } - } - - Future get currentSyncingHeight async { - //TODO return the tip of the monero blockchain - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - // Logging.instance - // .log("currentSyncingHeight lol", level: LogLevel.Warning); - return getSyncingHeight(); - } - } catch (e, s) {} - int syncingHeight = -1; - try { - syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - final cachedHeight = - DB.instance.get(boxName: walletId, key: "storedSyncingHeight") - as int? ?? - 0; - - if (syncingHeight > cachedHeight) { - await DB.instance.put( - boxName: walletId, key: "storedSyncingHeight", value: syncingHeight); - return syncingHeight; - } else { - return cachedHeight; - } - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } - - int get storedChainHeight { - return DB.instance.get(boxName: walletId, key: "storedChainHeight") - as int? ?? - 0; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain(int chain) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - Future _checkCurrentReceivingAddressesForTransactions() async { - try { - await _checkReceivingAddressForTransactions(); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkReceivingAddressForTransactions() async { - try { - int highestIndex = -1; - for (var element - in walletBase!.transactionHistory!.transactions!.entries) { - if (element.value.direction == TransactionDirection.incoming) { - int curAddressIndex = - element.value.additionalInfo!['addressIndex'] as int; - if (curAddressIndex > highestIndex) { - highestIndex = curAddressIndex; - } - } - } - - // Check the new receiving index - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - if (highestIndex >= curIndex) { - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - - // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); - - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); - - // Set the new receiving address that the service - - _currentReceivingAddress = Future(() => newReceivingAddress); - } - } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", - level: LogLevel.Error); - return; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } + // not used + Future get balanceMinusMaxFee => throw UnimplementedError(); @override - bool get isRefreshing => refreshMutex; - - bool refreshMutex = false; - - Timer? syncPercentTimer; - - Mutex syncHeightMutex = Mutex(); - Future stopSyncPercentTimer() async { - syncPercentTimer?.cancel(); - syncPercentTimer = null; - } - - Future startSyncPercentTimer() async { - if (syncPercentTimer != null) { - return; - } - syncPercentTimer?.cancel(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercentCached, walletId)); - syncPercentTimer = Timer.periodic(const Duration(seconds: 30), (_) async { - if (syncHeightMutex.isLocked) { - return; - } - await syncHeightMutex.protect(() async { - // int restoreheight = walletBase!.walletInfo.restoreHeight ?? 0; - int _height = await currentSyncingHeight; - int _currentHeight = await currentNodeHeight; - double progress = 0; - try { - progress = walletBase!.syncStatus!.progress(); - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - final int blocksRemaining = _currentHeight - _height; - - GlobalEventBus.instance - .fire(BlocksRemainingEvent(blocksRemaining, walletId)); - - if (progress == 1 && _currentHeight > 0 && _height > 0) { - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - return; - } - - // for some reason this can be 0 which screws up the percent calculation - // int64MaxValue is NOT the best value to use here - if (_currentHeight < 1) { - _currentHeight = int64MaxValue; - } - - if (_height < 1) { - _height = 1; - } - - double restorePercent = progress; - double highestPercent = highestPercentCached; + Coin get coin => _coin; + @override + Future confirmSend({required Map txData}) async { + try { + Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + final pendingMoneroTransaction = + txData['pendingMoneroTransaction'] as PendingMoneroTransaction; + try { + await pendingMoneroTransaction.commit(); Logging.instance.log( - "currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached", + "transaction ${pendingMoneroTransaction.id} has been sent", level: LogLevel.Info); - - if (restorePercent > 0 && restorePercent <= 1) { - // if (restorePercent > highestPercent) { - highestPercent = restorePercent; - highestPercentCached = restorePercent; - // } - } - - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercent, walletId)); - }); - }); - } - - double get highestPercentCached => - DB.instance.get(boxName: walletId, key: "highestPercentCached") - as double? ?? - 0; - set highestPercentCached(double value) => DB.instance.put( - boxName: walletId, - key: "highestPercentCached", - value: value, - ); - - /// Refreshes display data for the wallet - @override - Future refresh() async { - if (refreshMutex) { - Logging.instance.log("$walletId $walletName refreshMutex denied", - level: LogLevel.Info); - return; - } else { - refreshMutex = true; - } - - if (walletBase == null) { - throw Exception("Tried to call refresh() in monero without walletBase!"); - } - - try { - await startSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - final int _currentSyncingHeight = await currentSyncingHeight; - final int storedHeight = storedChainHeight; - int _currentNodeHeight = await currentNodeHeight; - - double progress = 0; - try { - progress = (walletBase!.syncStatus!).progress(); + return pendingMoneroTransaction.id; } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - await _fetchTransactionData(); - - bool stillSyncing = false; - Logging.instance.log( - "storedHeight: $storedHeight, _currentSyncingHeight: $_currentSyncingHeight, _currentNodeHeight: $_currentNodeHeight, progress: $progress, issynced: ${await walletBase!.isConnected()}", - level: LogLevel.Info); - - if (progress < 1.0) { - stillSyncing = true; - } - - if (_currentSyncingHeight > storedHeight) { - // 0 is returned from monero as I assume an error????? - if (_currentSyncingHeight > 0) { - // 0 failed to fetch current height??? - await updateStoredChainHeight(newHeight: _currentSyncingHeight); - } - } - - await _checkCurrentReceivingAddressesForTransactions(); - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - try { - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - _currentReceivingAddress = Future(() => newReceivingAddress); - } catch (e, s) { - Logging.instance.log( - "Failed to call _generateAddressForChain(0, $curIndex): $e\n$s", + Logging.instance.log("$walletName monero confirmSend: $e\n$s", level: LogLevel.Error); + rethrow; } - final newTxData = await _fetchTransactionData(); - _transactionData = Future(() => newTxData); - - if (isActive || shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async { - //todo: check if print needed - // debugPrint("run timer"); - //TODO: check for new data and refresh if needed. if monero even needs this - // 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)); - // } - // } - }); - moneroAutosaveTimer ??= - Timer.periodic(const Duration(seconds: 93), (timer) async { - //todo: check if print needed - // debugPrint("run monero timer"); - if (isActive) { - await walletBase?.save(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - }); - } - - if (stillSyncing) { - debugPrint("still syncing"); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - refreshMutex = false; - return; - } - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - refreshMutex = false; - } catch (error, strace) { - refreshMutex = false; - await stopSyncPercentTimer(); - 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.Error); + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Info); + rethrow; } } - @override - // TODO: implement allOwnAddresses - Future> get allOwnAddresses { - return Future(() => []); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); - @override Future get currentReceivingAddress => _currentReceivingAddress ??= _getCurrentAddressForChain(0); @override - Future exit() async { - _hasCalledExit = true; - stopNetworkAlivePinging(); - moneroAutosaveTimer?.cancel(); - moneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - await stopSyncPercentTimer(); - await walletBase?.save(prioritySave: true); - walletBase?.close(); - isActive = false; + Future estimateFeeFor(int satoshiAmount, int feeRate) async { + MoneroTransactionPriority priority; + + switch (feeRate) { + case 1: + priority = MoneroTransactionPriority.regular; + break; + case 2: + priority = MoneroTransactionPriority.medium; + break; + case 3: + priority = MoneroTransactionPriority.fast; + break; + case 4: + priority = MoneroTransactionPriority.fastest; + break; + case 0: + default: + priority = MoneroTransactionPriority.slow; + break; + } + + final fee = walletBase!.calculateEstimatedFee(priority, satoshiAmount); + + return fee; } - bool _hasCalledExit = false; - @override - bool get hasCalledExit => _hasCalledExit; - - Future? _currentReceivingAddress; - - Future _getFees() async { - // TODO: not use random hard coded values here - return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 15, - numberOfBlocksSlow: 20, - fast: MoneroTransactionPriority.fast.raw!, - medium: MoneroTransactionPriority.regular.raw!, - slow: MoneroTransactionPriority.slow.raw!, - ); + Future exit() async { + if (!_hasCalledExit) { + walletBase?.onNewBlock = null; + walletBase?.onNewTransaction = null; + walletBase?.syncStatusChanged = null; + _hasCalledExit = true; + _autoSaveTimer?.cancel(); + await walletBase?.save(prioritySave: true); + walletBase?.close(); + } } @override Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; @override - // TODO: implement fullRescan Future fullRescan( int maxUnusedAddressGap, int maxNumberOfIndexesToCheck, ) async { var restoreHeight = walletBase?.walletInfo.restoreHeight; + highestPercentCached = 0; await walletBase?.rescan(height: restoreHeight); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - return; } - Future _generateAddressForChain(int chain, int index) async { - // - String address = walletBase!.getTransactionAddress(chain, index); + @override + Future generateNewAddress() async { + try { + const String indexKey = "receivingIndex"; + // First increment the receiving index + await _incrementAddressIndexForChain(0); + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; - return address; - } + // Use new index to derive a new receiving address + final newReceivingAddress = + await _generateAddressForChain(0, newReceivingIndex); - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain(String address, int chain) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } + // Add that new receiving address to the array of receiving addresses + await _addToAddressesArrayForChain(newReceivingAddress, 0); - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { + // Set the new receiving address that the service + + _currentReceivingAddress = Future(() => newReceivingAddress); + + return true; + } catch (e, s) { Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; } } - /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] - /// and - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _getCurrentAddressForChain(int chain) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; - final internalChainArray = (DB.instance - .get(boxName: walletId, key: arrayKey)) as List; - return internalChainArray.last as String; - } + @override + bool get hasCalledExit => _hasCalledExit; - //TODO: take in the default language when creating wallet. - Future _generateNewWallet() async { - Logging.instance - .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - // TODO: ping monero server and make sure the genesis hash matches - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } + @override + Future initializeExisting() async { + Logging.instance.log( + "Opening existing ${coin.prettyName} wallet $walletName...", + level: LogLevel.Info, + ); + + if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + walletService = + monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + + await _prefs.init(); + // final data = + // DB.instance.get(boxName: walletId, key: "latest_tx_model") + // as TransactionData?; + // if (data != null) { + // _transactionData = Future(() => data); // } + String password; + try { + password = await keysStorage!.getWalletPassword(walletName: _walletId); + } catch (_) { + throw Exception("Monero password not found for $walletName"); + } + walletBase = (await walletService!.openWallet(_walletId, password)) + as MoneroWalletBase; + // walletBase!.onNewBlock = onNewBlock; + // walletBase!.onNewTransaction = onNewTransaction; + // walletBase!.syncStatusChanged = syncStatusChanged; + Logging.instance.log( + "Opened existing ${coin.prettyName} wallet $walletName", + level: LogLevel.Info, + ); + // Wallet already exists, triggers for a returning user + + String indexKey = "receivingIndex"; + final curIndex = + await DB.instance.get(boxName: walletId, key: indexKey) as int; + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain(0, curIndex); + Logging.instance.log("xmr address in init existing: $newReceivingAddress", + level: LogLevel.Info); + _currentReceivingAddress = Future(() => newReceivingAddress); + } + + @override + Future initializeNew() async { + await _prefs.init(); + // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await _secureStorage.read(key: '${_walletId}_mnemonic')) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } walletService = monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); + keysStorage = KeyService(_secureStorage); WalletInfo walletInfo; WalletCredentials credentials; try { String name = _walletId; final dirPath = - await pathForWalletDir(name: name, type: WalletType.monero); - final path = await pathForWallet(name: name, type: WalletType.monero); + await _pathForWalletDir(name: name, type: WalletType.monero); + final path = await _pathForWallet(name: name, type: WalletType.monero); credentials = monero.createMoneroNewWalletCredentials( name: name, language: "English", @@ -710,7 +370,7 @@ class MoneroWallet extends CoinServiceAPI { credentials.walletInfo = walletInfo; _walletCreationService = WalletCreationService( - secureStorage: _secureStore, + secureStorage: _secureStorage, walletService: walletService, keyService: keysStorage, ); @@ -718,23 +378,28 @@ class MoneroWallet extends CoinServiceAPI { // To restore from a seed final wallet = await _walletCreationService?.create(credentials); - await _secureStore.write( + await _secureStorage.write( key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); walletInfo.address = wallet?.walletAddresses.address; await DB.instance .add(boxName: WalletInfo.boxName, value: walletInfo); walletBase?.close(); walletBase = wallet as MoneroWalletBase; + // walletBase!.onNewBlock = onNewBlock; + // walletBase!.onNewTransaction = onNewTransaction; + // walletBase!.syncStatusChanged = syncStatusChanged; } catch (e, s) { //todo: come back to this + debugPrint("some nice searchable string thing"); debugPrint(e.toString()); debugPrint(s.toString()); + walletBase?.close(); } - final node = await getCurrentNode(); + final node = await _getCurrentNode(); final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( + await walletBase!.connectToNode( node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.startSync(); + await walletBase!.startSync(); await DB.instance .put(boxName: walletId, key: "id", value: _walletId); @@ -771,663 +436,42 @@ class MoneroWallet extends CoinServiceAPI { .put(boxName: walletId, key: "receivingIndex", value: 0); _currentReceivingAddress = Future(() => initialReceivingAddress); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + walletBase?.close(); + Logging.instance + .log("initializeNew for $walletName $walletId", level: LogLevel.Info); } - @override - // TODO: implement initializeWallet - Future initializeNew() async { - await _prefs.init(); - // TODO: ping actual monero network - // try { - // final hasNetwork = await _electrumXClient.ping(); - // if (!hasNetwork) { - // return false; - // } - // } catch (e, s) { - // Logging.instance.log("Caught in initializeWallet(): $e\n$s"); - // return false; - // } - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _generateNewWallet(); - // var password; - // try { - // password = - // await keysStorage?.getWalletPassword(walletName: this._walletId); - // } catch (e, s) { - // Logging.instance.log("$e $s"); - // Logging.instance.log("Generating new ${coin.ticker} wallet."); - // // Triggers for new users automatically. Generates new wallet - // await _generateNewWallet(wallet); - // await wallet.put("id", this._walletId); - // return true; - // } - // walletBase = (await walletService?.openWallet(this._walletId, password)) - // as MoneroWalletBase; - // Logging.instance.log("Opening existing ${coin.ticker} wallet."); - // // Wallet already exists, triggers for a returning user - // final currentAddress = awaicurrentHeightt _getCurrentAddressForChain(0); - // this._currentReceivingAddress = Future(() => currentAddress); - // - // await walletBase?.connectToNode( - // node: Node( - // uri: "xmr-node.cakewallet.com:18081", type: WalletType.monero)); - // walletBase?.startSync(); - - return true; - } - - @override - Future initializeExisting() async { - Logging.instance.log( - "Opening existing ${coin.prettyName} wallet $walletName...", - level: LogLevel.Info); - - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { - //todo: check if print is needed - // debugPrint("Exception was thrown"); - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } - - String? password; - try { - password = await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not found $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as MoneroWalletBase; - debugPrint("walletBase $walletBase"); - Logging.instance.log( - "Opened existing ${coin.prettyName} wallet $walletName", - level: LogLevel.Info); - // Wallet already exists, triggers for a returning user - - String indexKey = "receivingIndex"; - final curIndex = - await DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - Logging.instance.log("xmr address in init existing: $newReceivingAddress", - level: LogLevel.Info); - _currentReceivingAddress = Future(() => newReceivingAddress); - } - - @override - Future get maxFee async { - var bal = await availableBalance; - var fee = walletBase!.calculateEstimatedFee( - monero.getDefaultTransactionPriority(), - Format.decimalAmountToSatoshis(bal, coin), - ); - - return fee; - } - - @override - // TODO: implement pendingBalance - Future get pendingBalance => throw UnimplementedError(); - - bool longMutex = false; - - // TODO: are these needed? - - WalletService? walletService; - KeyService? keysStorage; - MoneroWalletBase? walletBase; - WalletCreationService? _walletCreationService; - - String toStringForinfo(WalletInfo info) { - return "id: ${info.id} name: ${info.name} type: ${info.type} recovery: ${info.isRecovery}" - " restoreheight: ${info.restoreHeight} timestamp: ${info.timestamp} dirPath: ${info.dirPath} " - "path: ${info.path} address: ${info.address} addresses: ${info.addresses}"; - } - - Future pathForWalletDir({ - required String name, - required WalletType type, - }) async { - Directory root = await StackFileSystem.applicationRootDirectory(); - - final prefix = walletTypeToString(type).toLowerCase(); - final walletsDir = Directory('${root.path}/wallets'); - final walletDire = Directory('${walletsDir.path}/$prefix/$name'); - - if (!walletDire.existsSync()) { - walletDire.createSync(recursive: true); - } - - return walletDire.path; - } - - Future pathForWallet({ - required String name, - required WalletType type, - }) async => - await pathForWalletDir(name: name, type: type) - .then((path) => '$path/$name'); - - // TODO: take in a dynamic height - @override - Future recoverFromMnemonic({ - required String mnemonic, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - await _prefs.init(); - longMutex = true; - final start = DateTime.now(); - try { - // Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag"); - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } - // } - // check to make sure we aren't overwriting a mnemonic - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - longMutex = false; - throw Exception("Attempted to overwrite mnemonic on restore!"); - } - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); - - await DB.instance - .put(boxName: walletId, key: "restoreHeight", value: height); - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.monero); - final path = await pathForWallet(name: name, type: WalletType.monero); - credentials = monero.createMoneroRestoreWalletFromSeedCredentials( - name: name, - height: height, - mnemonic: mnemonic.trim(), - ); - try { - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.monero), - name: name, - type: WalletType.monero, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - dirPath: dirPath, - // TODO: find out what to put for address - address: ''); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: _secureStore, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService!.changeWalletType(); - // To restore from a seed - final wallet = - await _walletCreationService!.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - await DB.instance - .add(boxName: WalletInfo.boxName, value: walletInfo); - walletBase?.close(); - walletBase = wallet as MoneroWalletBase; - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [walletInfo.address!]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - } - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.rescan(height: credentials.height); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverFromMnemonic(): $e\n$s", - level: LogLevel.Error); - longMutex = false; - rethrow; - } - longMutex = false; - - final end = DateTime.now(); - Logging.instance.log( - "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", - level: LogLevel.Info); - } - - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - - @override - Future testNetworkConnection() async { - return await walletBase?.isConnected() ?? 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(); - _isConnected = hasNetwork; - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - bool _isConnected = false; - @override bool get isConnected => _isConnected; @override - Future get totalBalance async { - var transactions = walletBase?.transactionHistory!.transactions; - int transactionBalance = 0; - for (var tx in transactions!.entries) { - if (tx.value.direction == TransactionDirection.incoming) { - transactionBalance += tx.value.amount!; - } else { - transactionBalance += -tx.value.amount! - tx.value.fee!; - } - } + bool get isRefreshing => refreshMutex; - // TODO: grab total balance - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.fullBalance; - } - //todo: check if print needed - // debugPrint("balances: $transactionBalance $bal"); - if (isActive) { - String am = moneroAmountToString(amount: bal); + @override + // not used in xmr + Future get maxFee => throw UnimplementedError(); - return Decimal.parse(am); - } else { - String am = moneroAmountToString(amount: transactionBalance); - - return Decimal.parse(am); + @override + Future> get mnemonic async { + final mnemonicString = + await _secureStorage.read(key: '${_walletId}_mnemonic'); + if (mnemonicString == null) { + return []; } + final List data = mnemonicString.split(' '); + return data; } @override - // TODO: implement onIsActiveWalletChanged - void Function(bool)? get onIsActiveWalletChanged => (isActive) async { - await walletBase?.save(); - walletBase?.close(); - moneroAutosaveTimer?.cancel(); - moneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - await stopSyncPercentTimer(); - if (isActive) { - String? password; - try { - password = - await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not found $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as MoneroWalletBase?; - if (!(await walletBase!.isConnected())) { - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.startSync(); - } - await refresh(); - } - this.isActive = isActive; - }; - - bool isActive = false; + // not used in xmr + Future get pendingBalance => throw UnimplementedError(); @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - // not used in monero - TransactionData? cachedTxData; - - @override - Future updateSentCachedTxData(Map txData) async { - // not used in monero - } - - Future _fetchTransactionData() async { - final transactions = walletBase?.transactionHistory!.transactions; - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final txidsList = DB.instance - .get(boxName: walletId, key: "cachedTxids") as List? ?? - []; - - final Set cachedTxids = Set.from(txidsList); - - // TODO: filter to skip cached + confirmed txn processing in next step - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - - // sort thing stuff - // change to get Monero price - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - - if (transactions != null) { - for (var tx in transactions.entries) { - cachedTxids.add(tx.value.id); - Logging.instance.log( - "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " - "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " - "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" - " ${tx.value.keyIndex}", - level: LogLevel.Info); - String am = moneroAmountToString(amount: tx.value.amount!); - final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2); - Map midSortedTx = {}; - // // create final tx map - midSortedTx["txid"] = tx.value.id; - midSortedTx["confirmed_status"] = !tx.value.isPending && - tx.value.confirmations! >= MINIMUM_CONFIRMATIONS; - midSortedTx["confirmations"] = tx.value.confirmations ?? 0; - midSortedTx["timestamp"] = - (tx.value.date.millisecondsSinceEpoch ~/ 1000); - midSortedTx["txType"] = - tx.value.direction == TransactionDirection.incoming - ? "Received" - : "Sent"; - midSortedTx["amount"] = tx.value.amount; - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - midSortedTx["fees"] = tx.value.fee; - // TODO: shouldn't monero have an address I can grab - if (tx.value.direction == TransactionDirection.incoming) { - final addressInfo = tx.value.additionalInfo; - - midSortedTx["address"] = walletBase?.getTransactionAddress( - addressInfo!['accountIndex'] as int, - addressInfo['addressIndex'] as int, - ); - } else { - midSortedTx["address"] = ""; - } - - final int txHeight = tx.value.height ?? 0; - midSortedTx["height"] = txHeight; - if (txHeight >= latestTxnBlockHeight) { - latestTxnBlockHeight = txHeight; - } - - midSortedTx["aliens"] = []; - midSortedTx["inputSize"] = 0; - midSortedTx["outputSize"] = 0; - midSortedTx["inputs"] = []; - midSortedTx["outputs"] = []; - midSortedArray.add(midSortedTx); - } - } - - // sort by date ---- - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - Logging.instance.log(midSortedArray, level: LogLevel.Info); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } - } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - await DB.instance.put( - boxName: walletId, - key: 'cachedTxids', - value: cachedTxids.toList(growable: false)); - - return txModel; - } - - @override - // TODO: implement unspentOutputs - Future> get unspentOutputs => throw UnimplementedError(); - - @override - bool validateAddress(String address) { - bool valid = walletBase!.validateAddress(address); - return valid; - } - - @override - String get walletId => _walletId; - late String _walletId; - - @override - String get walletName => _walletName; - late String _walletName; - - // setter for updating on rename - @override - set walletName(String newName) => _walletName = newName; - - @override - set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); - } - - @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } - - @override - Future get availableBalance async { - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.unlockedBalance; - } - String am = moneroAmountToString(amount: bal); - - return Decimal.parse(am); - } - - @override - Coin get coin => _coin; - - @override - Future confirmSend({required Map txData}) async { - try { - Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); - final pendingMoneroTransaction = - txData['pendingMoneroTransaction'] as PendingMoneroTransaction; - try { - await pendingMoneroTransaction.commit(); - Logging.instance.log( - "transaction ${pendingMoneroTransaction.id} has been sent", - level: LogLevel.Info); - return pendingMoneroTransaction.id; - } catch (e, s) { - Logging.instance.log("$walletName monero confirmSend: $e\n$s", - level: LogLevel.Error); - rethrow; - } - } catch (e, s) { - Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Info); - rethrow; - } - } - - // TODO: fix the double free memory crash error. - @override - Future> prepareSend( - {required String address, - required int satoshiAmount, - Map? args}) async { - int amount = satoshiAmount; + Future> prepareSend({ + required String address, + required int satoshiAmount, + Map? args, + }) async { String toAddress = address; try { final feeRate = args?["feeRate"]; @@ -1450,16 +494,17 @@ class MoneroWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; final balance = await availableBalance; - final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal()); + final satInDecimal = + Format.satoshisToAmount(satoshiAmount, coin: coin); if (satInDecimal == balance) { isSendAll = true; } Logging.instance - .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = moneroAmountToString(amount: amount); - Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); + .log("$toAddress $satoshiAmount $args", level: LogLevel.Info); + String amountToSend = satInDecimal + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + Logging.instance + .log("$satoshiAmount $amountToSend", level: LogLevel.Info); monero_output.Output output = monero_output.Output(walletBase!); output.address = toAddress; @@ -1510,89 +555,719 @@ class MoneroWallet extends CoinServiceAPI { } } - Mutex prepareSendMutex = Mutex(); - Mutex estimateFeeMutex = Mutex(); - @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - MoneroTransactionPriority priority; - FeeRateType feeRateType; - - switch (feeRate) { - case 1: - priority = MoneroTransactionPriority.regular; - feeRateType = FeeRateType.average; - break; - case 2: - priority = MoneroTransactionPriority.medium; - feeRateType = FeeRateType.average; - break; - case 3: - priority = MoneroTransactionPriority.fast; - feeRateType = FeeRateType.fast; - break; - case 4: - priority = MoneroTransactionPriority.fastest; - feeRateType = FeeRateType.fast; - break; - case 0: - default: - priority = MoneroTransactionPriority.slow; - feeRateType = FeeRateType.slow; - break; - } - // int? aprox; - - // corrupted size vs. prev_size occurs but not sure if related to fees or just generating monero transactions in general - - // await estimateFeeMutex.protect(() async { - // { - // try { - // aprox = (await prepareSend( - // // This address is only used for getting an approximate fee, never for sending - // address: - // "8347huhmj6Ggzr1BpZPJAD5oa96ob5Fe8GtQdGZDYVVYVsCgtUNH3pEEzExDuaAVZdC16D4FkAb24J6wUfsKkcZtC8EPXB7", - // satoshiAmount: satoshiAmount, - // args: {"feeRate": feeRateType}))['fee'] as int?; - // await Future.delayed(const Duration(milliseconds: 1000)); - // } catch (e, s) { - // Logging.instance.log("$feeRateType $e $s", level: LogLevel.Error); - final aprox = walletBase!.calculateEstimatedFee(priority, satoshiAmount); - // } - // } - // }); - - print("this is the aprox fee $aprox for $satoshiAmount"); - final fee = aprox; - return fee; - } - - @override - Future generateNewAddress() async { + Future recoverFromMnemonic({ + required String mnemonic, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + await _prefs.init(); + longMutex = true; + final start = DateTime.now(); try { - const String indexKey = "receivingIndex"; - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + // Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag"); + // if (!integrationTestFlag) { + // final features = await electrumXClient.getServerFeatures(); + // Logging.instance.log("features: $features"); + // if (_networkType == BasicNetworkType.main) { + // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + // throw Exception("genesis hash does not match main net!"); + // } + // } else if (_networkType == BasicNetworkType.test) { + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // } + // } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await _secureStorage.read(key: '${_walletId}_mnemonic')) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStorage.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); - // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); + await DB.instance + .put(boxName: walletId, key: "restoreHeight", value: height); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); + walletService = + monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.monero); + final path = await _pathForWallet(name: name, type: WalletType.monero); + credentials = monero.createMoneroRestoreWalletFromSeedCredentials( + name: name, + height: height, + mnemonic: mnemonic.trim(), + ); + try { + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + dirPath: dirPath, + // TODO: find out what to put for address + address: ''); + credentials.walletInfo = walletInfo; - // Set the new receiving address that the service - - _currentReceivingAddress = Future(() => newReceivingAddress); - - return true; + _walletCreationService = WalletCreationService( + secureStorage: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService!.changeWalletType(); + // To restore from a seed + final wallet = + await _walletCreationService!.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + await DB.instance + .add(boxName: WalletInfo.boxName, value: walletInfo); + walletBase?.close(); + walletBase = wallet as MoneroWalletBase; + // walletBase!.onNewBlock = onNewBlock; + // walletBase!.onNewTransaction = onNewTransaction; + // walletBase!.syncStatusChanged = syncStatusChanged; + await DB.instance.put( + boxName: walletId, + key: 'receivingAddresses', + value: [walletInfo.address!]); + await DB.instance + .put(boxName: walletId, key: "receivingIndex", value: 0); + await DB.instance + .put(boxName: walletId, key: "id", value: _walletId); + await DB.instance + .put(boxName: walletId, key: "changeIndex", value: 0); + await DB.instance.put( + boxName: walletId, + key: 'blocked_tx_hashes', + value: ["0xdefault"], + ); // A list of transaction hashes to represent frozen utxos in wallet + // initialize address book entries + await DB.instance.put( + boxName: walletId, + key: 'addressBookEntries', + value: {}); + await DB.instance + .put(boxName: walletId, key: "isFavorite", value: false); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase!.connectToNode( + node: Node(uri: "$host:${node.port}", type: WalletType.monero)); + await walletBase!.rescan(height: credentials.height); + walletBase!.close(); } catch (e, s) { Logging.instance.log( - "Exception rethrown from generateNewAddress(): $e\n$s", + "Exception rethrown from recoverFromMnemonic(): $e\n$s", level: LogLevel.Error); - return false; + longMutex = false; + rethrow; + } + longMutex = false; + + final end = DateTime.now(); + Logging.instance.log( + "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + final newTxData = await _fetchTransactionData(); + _transactionData = Future(() => newTxData); + + await _checkCurrentReceivingAddressesForTransactions(); + String indexKey = "receivingIndex"; + final curIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + // Use new index to derive a new receiving address + try { + final newReceivingAddress = await _generateAddressForChain(0, curIndex); + _currentReceivingAddress = Future(() => newReceivingAddress); + } catch (e, s) { + Logging.instance.log( + "Failed to call _generateAddressForChain(0, $curIndex): $e\n$s", + level: LogLevel.Error); + } + + if (walletBase?.syncStatus is SyncedSyncStatus) { + refreshMutex = false; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); } } + + @override + Future send({ + required String toAddress, + required int amount, + Map args = const {}, + }) { + // not used for xmr + throw UnimplementedError(); + } + + @override + Future testNetworkConnection() async { + return await walletBase?.isConnected() ?? false; + } + + bool _isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => (isActive) async { + if (_isActive == isActive) { + return; + } + _isActive = isActive; + + if (isActive) { + _hasCalledExit = false; + String? password; + try { + password = + await keysStorage?.getWalletPassword(walletName: _walletId); + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + walletBase = (await walletService?.openWallet(_walletId, password!)) + as MoneroWalletBase?; + + walletBase!.onNewBlock = onNewBlock; + walletBase!.onNewTransaction = onNewTransaction; + walletBase!.syncStatusChanged = syncStatusChanged; + + if (!(await walletBase!.isConnected())) { + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node(uri: "$host:${node.port}", type: WalletType.monero)); + } + await walletBase?.startSync(); + await refresh(); + _autoSaveTimer?.cancel(); + _autoSaveTimer = Timer.periodic( + const Duration(seconds: 93), + (_) async => await walletBase?.save(), + ); + } else { + await exit(); + // _autoSaveTimer?.cancel(); + // await walletBase?.save(prioritySave: true); + // walletBase?.close(); + } + }; + + @override + Future get totalBalance async { + final balanceEntries = walletBase?.balance?.entries; + if (balanceEntries != null) { + int bal = 0; + for (var element in balanceEntries) { + bal = bal + element.value.fullBalance; + } + return Format.satoshisToAmount(bal, coin: coin); + } else { + final transactions = walletBase!.transactionHistory!.transactions; + int transactionBalance = 0; + for (var tx in transactions!.entries) { + if (tx.value.direction == TransactionDirection.incoming) { + transactionBalance += tx.value.amount!; + } else { + transactionBalance += -tx.value.amount! - tx.value.fee!; + } + } + + return Format.satoshisToAmount(transactionBalance, coin: coin); + } + } + + @override + Future get transactionData => + _transactionData ??= _fetchTransactionData(); + + @override + // not used for xmr + Future> get unspentOutputs => throw UnimplementedError(); + + @override + Future updateNode(bool shouldRefresh) async { + final node = await _getCurrentNode(); + + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node(uri: "$host:${node.port}", type: WalletType.monero)); + + // TODO: is this sync call needed? Do we need to notify ui here? + await walletBase?.startSync(); + + if (shouldRefresh) { + await refresh(); + } + } + + @override + Future updateSentCachedTxData(Map txData) async { + // not used for xmr + return; + } + + @override + bool validateAddress(String address) => walletBase!.validateAddress(address); + + @override + String get walletId => _walletId; + + /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] + /// and + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _getCurrentAddressForChain(int chain) async { + // Here, we assume that chain == 1 if it isn't 0 + String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + final internalChainArray = (DB.instance + .get(boxName: walletId, key: arrayKey)) as List; + return internalChainArray.last as String; + } + + /// Increases the index for either the internal or external chain, depending on [chain]. + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _incrementAddressIndexForChain(int chain) async { + // Here we assume chain == 1 if it isn't 0 + String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; + + final newIndex = + (DB.instance.get(boxName: walletId, key: indexKey)) + 1; + await DB.instance + .put(boxName: walletId, key: indexKey, value: newIndex); + } + + Future _generateAddressForChain(int chain, int index) async { + // + String address = walletBase!.getTransactionAddress(chain, index); + + return address; + } + + /// Adds [address] to the relevant chain's address array, which is determined by [chain]. + /// [address] - Expects a standard native segwit address + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _addToAddressesArrayForChain(String address, int chain) async { + String chainArray = ''; + if (chain == 0) { + chainArray = 'receivingAddresses'; + } else { + chainArray = 'changeAddresses'; + } + + final addressArray = + DB.instance.get(boxName: walletId, key: chainArray); + if (addressArray == null) { + Logging.instance.log( + 'Attempting to add the following to $chainArray array for chain $chain:${[ + address + ]}', + level: LogLevel.Info); + await DB.instance + .put(boxName: walletId, key: chainArray, value: [address]); + } else { + // Make a deep copy of the existing list + final List newArray = []; + addressArray + .forEach((dynamic _address) => newArray.add(_address as String)); + newArray.add(address); // Add the address passed into the method + await DB.instance + .put(boxName: walletId, key: chainArray, value: newArray); + } + } + + Future _getFees() async { + // TODO: not use random hard coded values here + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, + fast: MoneroTransactionPriority.fast.raw!, + medium: MoneroTransactionPriority.regular.raw!, + slow: MoneroTransactionPriority.slow.raw!, + ); + } + + Future _fetchTransactionData() async { + await walletBase!.updateTransactions(); + final transactions = walletBase?.transactionHistory!.transactions; + + // final cachedTransactions = + // DB.instance.get(boxName: walletId, key: 'latest_tx_model') + // as TransactionData?; + // int latestTxnBlockHeight = + // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + // as int? ?? + // 0; + + // final txidsList = DB.instance + // .get(boxName: walletId, key: "cachedTxids") as List? ?? + // []; + // + // final Set cachedTxids = Set.from(txidsList); + + // sort thing stuff + // change to get Monero price + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final List> midSortedArray = []; + + if (transactions != null) { + for (var tx in transactions.entries) { + // cachedTxids.add(tx.value.id); + Logging.instance.log( + "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " + "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " + "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" + " ${tx.value.keyIndex}", + level: LogLevel.Info); + final worthNow = (currentPrice * + Format.satoshisToAmount( + tx.value.amount!, + coin: coin, + )) + .toStringAsFixed(2); + Map midSortedTx = {}; + // // create final tx map + midSortedTx["txid"] = tx.value.id; + midSortedTx["confirmed_status"] = !tx.value.isPending && + tx.value.confirmations! >= MINIMUM_CONFIRMATIONS; + midSortedTx["confirmations"] = tx.value.confirmations ?? 0; + midSortedTx["timestamp"] = + (tx.value.date.millisecondsSinceEpoch ~/ 1000); + midSortedTx["txType"] = + tx.value.direction == TransactionDirection.incoming + ? "Received" + : "Sent"; + midSortedTx["amount"] = tx.value.amount; + midSortedTx["worthNow"] = worthNow; + midSortedTx["worthAtBlockTimestamp"] = worthNow; + midSortedTx["fees"] = tx.value.fee; + if (tx.value.direction == TransactionDirection.incoming) { + final addressInfo = tx.value.additionalInfo; + + midSortedTx["address"] = walletBase?.getTransactionAddress( + addressInfo!['accountIndex'] as int, + addressInfo['addressIndex'] as int, + ); + } else { + midSortedTx["address"] = ""; + } + + final int txHeight = tx.value.height ?? 0; + midSortedTx["height"] = txHeight; + // if (txHeight >= latestTxnBlockHeight) { + // latestTxnBlockHeight = txHeight; + // } + + midSortedTx["aliens"] = []; + midSortedTx["inputSize"] = 0; + midSortedTx["outputSize"] = 0; + midSortedTx["inputs"] = []; + midSortedTx["outputs"] = []; + midSortedArray.add(midSortedTx); + } + } + + // sort by date ---- + midSortedArray + .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); + Logging.instance.log(midSortedArray, level: LogLevel.Info); + + // buildDateTimeChunks + final Map result = {"dateTimeChunks": []}; + final dateArray = []; + + for (int i = 0; i < midSortedArray.length; i++) { + final txObject = midSortedArray[i]; + final date = extractDateFromTimestamp(txObject["timestamp"] as int); + final txTimeArray = [txObject["timestamp"], date]; + + if (dateArray.contains(txTimeArray[1])) { + result["dateTimeChunks"].forEach((dynamic chunk) { + if (extractDateFromTimestamp(chunk["timestamp"] as int) == + txTimeArray[1]) { + if (chunk["transactions"] == null) { + chunk["transactions"] = >[]; + } + chunk["transactions"].add(txObject); + } + }); + } else { + dateArray.add(txTimeArray[1]); + final chunk = { + "timestamp": txTimeArray[0], + "transactions": [txObject], + }; + result["dateTimeChunks"].add(chunk); + } + } + + // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; + final Map transactionsMap = {}; + transactionsMap + .addAll(TransactionData.fromJson(result).getAllTransactions()); + + final txModel = TransactionData.fromMap(transactionsMap); + + // await DB.instance.put( + // boxName: walletId, + // key: 'storedTxnDataHeight', + // value: latestTxnBlockHeight); + // await DB.instance.put( + // boxName: walletId, key: 'latest_tx_model', value: txModel); + // await DB.instance.put( + // boxName: walletId, + // key: 'cachedTxids', + // value: cachedTxids.toList(growable: false)); + + return txModel; + } + + Future _pathForWalletDir({ + required String name, + required WalletType type, + }) async { + Directory root = await StackFileSystem.applicationRootDirectory(); + + final prefix = walletTypeToString(type).toLowerCase(); + final walletsDir = Directory('${root.path}/wallets'); + final walletDire = Directory('${walletsDir.path}/$prefix/$name'); + + if (!walletDire.existsSync()) { + walletDire.createSync(recursive: true); + } + + return walletDire.path; + } + + Future _pathForWallet({ + required String name, + required WalletType type, + }) async => + await _pathForWalletDir(name: name, type: type) + .then((path) => '$path/$name'); + + Future _getCurrentNode() async { + return NodeService(secureStorageInterface: _secureStorage) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + } + + void onNewBlock() { + // + print("============================="); + print("New Block!"); + print("============================="); + } + + void onNewTransaction() { + // + print("============================="); + print("New Transaction!"); + print("============================="); + + // call this here? + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId, + ), + ); + } + + void syncStatusChanged() async { + final syncStatus = walletBase?.syncStatus; + if (syncStatus != null) { + if (syncStatus.progress() == 1) { + refreshMutex = false; + } + + WalletSyncStatus? status; + _isConnected = true; + + if (syncStatus is SyncingSyncStatus) { + final int blocksLeft = syncStatus.blocksLeft; + + // ensure at least 1 to prevent math errors + final int height = max(1, syncStatus.height); + + final nodeHeight = height + blocksLeft; + + final percent = height / nodeHeight; + + final highest = max(highestPercentCached, percent); + + // update cached + if (highestPercentCached < percent) { + highestPercentCached = percent; + } + + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highest, + walletId, + ), + ); + GlobalEventBus.instance.fire( + BlocksRemainingEvent( + blocksLeft, + walletId, + ), + ); + } else if (syncStatus is SyncedSyncStatus) { + status = WalletSyncStatus.synced; + } else if (syncStatus is NotConnectedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is StartingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is FailedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is ConnectingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is ConnectedSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is LostConnectionSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } + + if (status != null) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + status, + walletId, + coin, + ), + ); + } + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + await _checkReceivingAddressForTransactions(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkReceivingAddressForTransactions() async { + try { + int highestIndex = -1; + for (var element + in walletBase!.transactionHistory!.transactions!.entries) { + if (element.value.direction == TransactionDirection.incoming) { + int curAddressIndex = + element.value.additionalInfo!['addressIndex'] as int; + if (curAddressIndex > highestIndex) { + highestIndex = curAddressIndex; + } + } + } + + // Check the new receiving index + String indexKey = "receivingIndex"; + final curIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + if (highestIndex >= curIndex) { + // First increment the receiving index + await _incrementAddressIndexForChain(0); + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new receiving address + final newReceivingAddress = + await _generateAddressForChain(0, newReceivingIndex); + + // Add that new receiving address to the array of receiving addresses + await _addToAddressesArrayForChain(newReceivingAddress, 0); + + _currentReceivingAddress = Future(() => newReceivingAddress); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + double get highestPercentCached => + DB.instance.get(boxName: walletId, key: "highestPercentCached") + as double? ?? + 0; + + set highestPercentCached(double value) => DB.instance.put( + boxName: walletId, + key: "highestPercentCached", + value: value, + ); } diff --git a/lib/services/notes_service.dart b/lib/services/notes_service.dart index cffe770e5..013600625 100644 --- a/lib/services/notes_service.dart +++ b/lib/services/notes_service.dart @@ -51,6 +51,7 @@ class NotesService extends ChangeNotifier { _notes[txid] = note; await DB.instance .put(boxName: walletId, key: 'notes', value: _notes); + //todo: check if this is needed Logging.instance.log("editOrAddNote: tx note saved", level: LogLevel.Info); await _refreshNotes(); } diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index f4eb41b7c..e45e33aec 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -158,7 +158,7 @@ abstract class DefaultNodes { isDown: false); static NodeModel get bitcoinTestnet => NodeModel( - host: "electrumx-testnet.cypherstack.com", + host: "bitcoin-testnet.cypherstack.com", port: 51002, name: defaultName, id: _nodeId(Coin.bitcoinTestNet), diff --git a/lib/widgets/textfields/exchange_textfield.dart b/lib/widgets/textfields/exchange_textfield.dart index 399d077c4..6681e9e8e 100644 --- a/lib/widgets/textfields/exchange_textfield.dart +++ b/lib/widgets/textfields/exchange_textfield.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; class ExchangeTextField extends StatefulWidget { @@ -62,6 +63,8 @@ class _ExchangeTextFieldState extends State { late final void Function(String)? onChanged; late final void Function(String)? onSubmitted; + final isDesktop = Util.isDesktop; + @override void initState() { borderRadius = widget.borderRadius; @@ -100,10 +103,12 @@ class _ExchangeTextFieldState extends State { enableSuggestions: false, autocorrect: false, readOnly: widget.readOnly, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), decoration: InputDecoration( contentPadding: const EdgeInsets.only( top: 12, diff --git a/pubspec.lock b/pubspec.lock index e8f875d35..ecc0bdf8d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.11" + version: "3.3.0" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" barcode_scan2: dependency: "direct main" description: @@ -190,14 +190,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" checked_yaml: dependency: transitive description: @@ -218,7 +211,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -288,7 +281,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.5.0" cross_file: dependency: transitive description: @@ -442,7 +435,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: "direct main" description: @@ -871,21 +864,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -997,7 +990,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1373,7 +1366,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -1417,7 +1410,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" string_validator: dependency: "direct main" description: @@ -1431,35 +1424,35 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" time: dependency: transitive description: @@ -1508,7 +1501,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: @@ -1592,7 +1585,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "8.2.2" + version: "9.0.0" wakelock: dependency: "direct main" description: diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index b9ab666a9..77d65b253 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # Create template lib/external_api_keys.dart file if it doesn't already exist KEYS=../lib/external_api_keys.dart if ! test -f "$KEYS"; then @@ -6,13 +8,14 @@ if ! test -f "$KEYS"; then fi # Create template wallet test parameter files if they don't already exist -declare -a coins=("bitcoin" "bitcoincash" "dogecoin" "namecoin" "firo" "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed +declare -a coins +coins=("bitcoin" "bitcoincash" "dogecoin" "namecoin" "firo" "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed for coin in "${coins[@]}" do WALLETTESTPARAMFILE="../test/services/coins/${coin}/${coin}_wallet_test_parameters.dart" if ! test -f "$WALLETTESTPARAMFILE"; then echo "prebuild.sh: creating template test/services/coins/${coin}/${coin}_wallet_test_parameters.dart file" - printf 'const TEST_MNEMONIC = "";\nconst ROOT_WIF = "";\nconst NODE_WIF_84 = "";\n' > $WALLETTESTPARAMFILE + printf 'const TEST_MNEMONIC = "";\nconst ROOT_WIF = "";\nconst NODE_WIF_84 = "";\n' > "$WALLETTESTPARAMFILE" fi done