From a60c4fef1443efc9ea91596a0a577b71edd838f1 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 28 Dec 2022 10:25:55 -0600 Subject: [PATCH 01/26] libmonero xmr sync listener callbacks --- crypto_plugins/flutter_libmonero | 2 +- lib/services/coins/coin_service.dart | 2 +- lib/services/coins/monero/monero_wallet.dart | 2255 ++++++++---------- pubspec.lock | 45 +- 4 files changed, 986 insertions(+), 1318 deletions(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index de29931da..e21cdbf09 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit de29931dacc9aefaf42a9ca139a8754a42adc40d +Subproject commit e21cdbf0940c75c9cd78972e101d4a7097fdbefe 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..82d8610a7 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 => throw UnimplementedError(); - 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) { + // not used for xmr + throw UnimplementedError(); + } + + @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/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: From ae7a33689d972c326124041c5c6ee22ba93ace28 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 28 Dec 2022 17:02:09 -0600 Subject: [PATCH 02/26] ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index e21cdbf09..d44160950 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit e21cdbf0940c75c9cd78972e101d4a7097fdbefe +Subproject commit d44160950c0879dacd754c2dcf7b8cb44c723dfb From 74bd454795eed9c79f986f635a35452fd5dba811 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 29 Dec 2022 10:25:39 -0600 Subject: [PATCH 03/26] ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index d44160950..9504de7be 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit d44160950c0879dacd754c2dcf7b8cb44c723dfb +Subproject commit 9504de7bef7f3b0731774ddd29cdecaad8125370 From 999fd71f8f8e3c54f5719aafa4c2f51949a94ec0 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 29 Dec 2022 10:43:19 -0600 Subject: [PATCH 04/26] ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 9504de7be..48243ebc4 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 9504de7bef7f3b0731774ddd29cdecaad8125370 +Subproject commit 48243ebc4deadb4bb131eae8172d3fe28ceeef6b From a1a11355b07c157189fdf96857385030db7f7e5c Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 29 Dec 2022 10:44:16 -0600 Subject: [PATCH 05/26] ignore windows dlls --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index a36824135..e2b5c5a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,8 @@ test/services/coins/particl/particl_wallet_test_parameters.dart coverage scripts/**/build /lib/external_api_keys.dart + +libcw_monero.dll +libcw_wownero.dll +libepic_cash_wallet.dll +libmobileliblelantus.dll From 5accda7355a42c4a2b4b119d1cb7ba398580a3b7 Mon Sep 17 00:00:00 2001 From: shillo Date: Thu, 22 Dec 2022 17:24:12 -0600 Subject: [PATCH 06/26] disable unsupported device locale on windows --- lib/services/coins/bitcoin/bitcoin_wallet.dart | 2 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 2 +- lib/services/coins/dogecoin/dogecoin_wallet.dart | 2 +- lib/services/coins/firo/firo_wallet.dart | 12 ++++++------ lib/services/coins/litecoin/litecoin_wallet.dart | 2 +- lib/services/coins/namecoin/namecoin_wallet.dart | 2 +- lib/services/coins/particl/particl_wallet.dart | 2 +- lib/services/locale_service.dart | 3 ++- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 059b73b7d..1ed87681b 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1297,7 +1297,7 @@ class BitcoinWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index c9499b054..b18a97186 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1174,7 +1174,7 @@ class BitcoinCashWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index e477dfc78..5b0da3fb3 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1064,7 +1064,7 @@ class DogecoinWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index bfddf2560..5d29bd0a9 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -883,7 +883,7 @@ class FiroWallet extends CoinServiceAPI { @override Future updateSentCachedTxData(Map txData) async { final currentPrice = await firoPrice; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / @@ -2756,7 +2756,7 @@ class FiroWallet extends CoinServiceAPI { var price = await firoPrice; var builtHex = txb.build(); // return builtHex; - final locale = await Devicelocale.currentLocale; + final locale =Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; return { "transaction": builtHex, "txid": txId, @@ -2810,7 +2810,7 @@ class FiroWallet extends CoinServiceAPI { final currentPrice = await firoPrice; // Grab the most recent information on all the joinsplits - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final updatedJSplit = await getJMintTransactions(cachedElectrumXClient, joinsplits, _prefs.currency, coin, currentPrice, locale!); @@ -3249,7 +3249,7 @@ class FiroWallet extends CoinServiceAPI { final currentPrice = await firoPrice; final List> midSortedArray = []; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; Logging.instance.log("refresh the txs", level: LogLevel.Info); for (final txObject in allTransactions) { @@ -4324,7 +4324,7 @@ class FiroWallet extends CoinServiceAPI { currency, coin, currentPrice, - (await Devicelocale.currentLocale)!); + (Platform.isWindows ? "en_US" : await Devicelocale.currentLocale)!); Logging.instance.log(spendTxs, level: LogLevel.Info); for (var element in spendTxs) { transactionMap[element.txid] = element; @@ -4375,7 +4375,7 @@ class FiroWallet extends CoinServiceAPI { final lelantusEntry = await _getLelantusEntry(); final anonymitySets = await fetchAnonymitySets(); final locktime = await getBlockHead(electrumXClient); - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; ReceivePort receivePort = await getIsolate({ "function": "createJoinSplit", diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 7f3be9ea1..269f59610 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1299,7 +1299,7 @@ class LitecoinWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 72ba2e450..85fea61ad 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1287,7 +1287,7 @@ class NamecoinWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 19d3ceba7..fe535dbf0 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1200,7 +1200,7 @@ class ParticlWallet extends CoinServiceAPI { final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = await Devicelocale.currentLocale; + final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; final String worthNow = Format.localizedStringAsFixed( value: ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / diff --git a/lib/services/locale_service.dart b/lib/services/locale_service.dart index 9ed504cf9..e91f5a1c6 100644 --- a/lib/services/locale_service.dart +++ b/lib/services/locale_service.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/material.dart'; @@ -7,7 +8,7 @@ class LocaleService extends ChangeNotifier { String get locale => _locale; Future loadLocale({bool notify = true}) async { - _locale = await Devicelocale.currentLocale ?? "en_US"; + _locale =Platform.isWindows ? "en_US" : await Devicelocale.currentLocale ?? "en_US"; if (notify) { notifyListeners(); } From e89b213798e6306d5c8fe6063186b8eb42a64526 Mon Sep 17 00:00:00 2001 From: shillo Date: Thu, 29 Dec 2022 10:45:20 -0600 Subject: [PATCH 07/26] temp disable git versions --- .../global_settings_view/about_view.dart | 12 ++--- .../advanced_views/debug_view.dart | 13 +++--- .../settings_menu/desktop_about_view.dart | 12 ++--- pubspec.lock | 45 +++++++++++-------- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index a1e78ba7d..0e08a29a2 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; -import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -98,9 +98,9 @@ class AboutView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - String firoCommit = FIRO_VERSIONS.getPluginVersion(); - String epicCashCommit = EPIC_VERSIONS.getPluginVersion(); - String moneroCommit = MONERO_VERSIONS.getPluginVersion(); + String firoCommit ="fixme";//FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit ="fixme";// EPIC_VERSIONS.getPluginVersion(); + String moneroCommit ="fixme";// MONERO_VERSIONS.getPluginVersion(); List futureFiroList = [ doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit), isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index 33f21759e..c3402d121 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -7,11 +7,11 @@ import 'package:event_bus/event_bus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; @@ -20,7 +20,6 @@ import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -295,11 +294,11 @@ class _DebugViewState extends ConsumerState { packageInfo.buildSignature; final appName = packageInfo.appName; String firoCommit = - FIRO_VERSIONS.getPluginVersion(); + "fixme";// FIRO_VERSIONS.getPluginVersion(); String epicCashCommit = - EPIC_VERSIONS.getPluginVersion(); + "fixme";// EPIC_VERSIONS.getPluginVersion(); String moneroCommit = - MONERO_VERSIONS.getPluginVersion(); + "fixme";// MONERO_VERSIONS.getPluginVersion(); DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfo = diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 18988cb68..92660a532 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; -import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -98,9 +98,9 @@ class DesktopAboutView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - String firoCommit = FIRO_VERSIONS.getPluginVersion(); - String epicCashCommit = EPIC_VERSIONS.getPluginVersion(); - String moneroCommit = MONERO_VERSIONS.getPluginVersion(); + String firoCommit = "fixme";//FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit = "fixme";//EPIC_VERSIONS.getPluginVersion(); + String moneroCommit = "fixme";//MONERO_VERSIONS.getPluginVersion(); List futureFiroList = [ doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit), isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit), diff --git a/pubspec.lock b/pubspec.lock index ecc0bdf8d..e8f875d35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.3.0" + version: "3.1.11" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.9.0" + version: "2.8.2" barcode_scan2: dependency: "direct main" description: @@ -190,7 +190,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" checked_yaml: dependency: transitive description: @@ -211,7 +218,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" code_builder: dependency: transitive description: @@ -281,7 +288,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.2.0" cross_file: dependency: transitive description: @@ -435,7 +442,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.3.0" ffi: dependency: "direct main" description: @@ -864,21 +871,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.12" + version: "0.12.11" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.1.4" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.7.0" mime: dependency: transitive description: @@ -990,7 +997,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -1366,7 +1373,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -1410,7 +1417,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" string_validator: dependency: "direct main" description: @@ -1424,35 +1431,35 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.1" + version: "0.3.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.4" + version: "1.21.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.12" + version: "0.4.9" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.16" + version: "0.4.13" time: dependency: transitive description: @@ -1501,7 +1508,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.3.0" universal_io: dependency: transitive description: @@ -1585,7 +1592,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "9.0.0" + version: "8.2.2" wakelock: dependency: "direct main" description: From cd86085d16003dfd926293e2033c17c73b9bca92 Mon Sep 17 00:00:00 2001 From: shillo Date: Thu, 29 Dec 2022 10:45:29 -0600 Subject: [PATCH 08/26] add windows path --- lib/utilities/stack_file_system.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/utilities/stack_file_system.dart b/lib/utilities/stack_file_system.dart index 5177f1973..22078bfee 100644 --- a/lib/utilities/stack_file_system.dart +++ b/lib/utilities/stack_file_system.dart @@ -15,8 +15,7 @@ abstract class StackFileSystem { } else if (Platform.isLinux) { appDirectory = Directory("${Platform.environment['HOME']}/.stackwallet"); } else if (Platform.isWindows) { - // TODO: windows root .stackwallet dir location - throw Exception("Unsupported platform"); + appDirectory = await getApplicationSupportDirectory(); } else if (Platform.isMacOS) { // currently run in ipad mode?? throw Exception("Unsupported platform"); From b09ea7f781db3d2ef56a37ca0f10747b23152ee9 Mon Sep 17 00:00:00 2001 From: shillo Date: Thu, 22 Dec 2022 16:58:55 -0600 Subject: [PATCH 09/26] app name changes --- macos/Runner.xcodeproj/project.pbxproj | 6 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 8 ++++---- macos/Runner/Configs/AppInfo.xcconfig | 4 ++-- windows/runner/Runner.rc | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 6cc16a184..6506a5075 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -55,7 +55,7 @@ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* firo_wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = firo_wallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* Stack Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stack Wallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -112,7 +112,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* firo_wallet.app */, + 33CC10ED2044A3C60003C045 /* Stack Wallet.app */, ); name = Products; sourceTree = ""; @@ -193,7 +193,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* firo_wallet.app */; + productReference = 33CC10ED2044A3C60003C045 /* Stack Wallet.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9c847111c..c9216c9be 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index b68859075..6889aa0a7 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = firo_wallet +PRODUCT_NAME = Stack Wallet // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.firoWallet +PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stack_wallet // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 com.cypherstack. All rights reserved. diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 871652d68..5fa3067e5 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.cypherstack" "\0" - VALUE "FileDescription", "firo_wallet" "\0" + VALUE "FileDescription", "Stack Wallet" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "firo_wallet" "\0" + VALUE "InternalName", "Stack Wallet" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 com.cypherstack. All rights reserved." "\0" - VALUE "OriginalFilename", "firo_wallet.exe" "\0" - VALUE "ProductName", "firo_wallet" "\0" + VALUE "OriginalFilename", "Stack Wallet.exe" "\0" + VALUE "ProductName", "Stack Wallet" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END From 61d3afbe6ee43c7eac8ddb0d5f7cb2297b77ea58 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 29 Dec 2022 10:21:34 -0600 Subject: [PATCH 10/26] add windows build all script and point epic and lelantus plugins to new ref --- crypto_plugins/flutter_libepiccash | 2 +- crypto_plugins/flutter_liblelantus | 2 +- scripts/windows/build_all.sh | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100755 scripts/windows/build_all.sh diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 9a150d8cd..0309140a9 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 9a150d8cd2c3625424b0059e6b7306f3659fdbe0 +Subproject commit 0309140a95a51388df0effcc39ff0a25b2752b29 diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index db928e6f1..6864d7c0d 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit db928e6f11844138ce87de2b8c1abe3305ec589e +Subproject commit 6864d7c0d4fa68c371e3f0c067afd50b0d59cc9b diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh new file mode 100755 index 000000000..d890ed70a --- /dev/null +++ b/scripts/windows/build_all.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +mkdir -p build +(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) & +(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) & +(cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./build_all.sh) & + +wait +echo "Done building" From 7013e99081b123a16dca2e228cdf4e2d2fd952ed Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 29 Dec 2022 13:38:39 -0600 Subject: [PATCH 11/26] enable wow on desktop --- .../sub_widgets/searchable_coin_list.dart | 6 +++--- lib/utilities/enums/coin_enum.dart | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart index 38181b9e1..935b5f231 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart @@ -33,9 +33,9 @@ class SearchableCoinList extends ConsumerWidget { _coins.remove(Coin.firoTestNet); // Kidgloves for Wownero on desktop - if(isDesktop) { - _coins.remove(Coin.wownero); - } + // if(isDesktop) { + // _coins.remove(Coin.wownero); + // } return _coins; } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index e90509305..df586ebf5 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -11,12 +11,9 @@ import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart' import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' as nmc; +import 'package:stackwallet/services/coins/particl/particl_wallet.dart' + as particl; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; -import 'package:stackwallet/services/coins/particl/particl_wallet.dart' - as particl; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/services/coins/particl/particl_wallet.dart' - as particl; enum Coin { bitcoin, @@ -42,7 +39,7 @@ enum Coin { firoTestNet, } -final int kTestNetCoinCount = Util.isDesktop ? 5 : 4; +final int kTestNetCoinCount = 4; // Util.isDesktop ? 5 : 4; extension CoinExt on Coin { String get prettyName { From 1789a1f05dfb4b3809fcd716dae177ae319fe286 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 29 Dec 2022 13:42:32 -0600 Subject: [PATCH 12/26] update libmonero ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 48243ebc4..d4cbce367 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 48243ebc4deadb4bb131eae8172d3fe28ceeef6b +Subproject commit d4cbce3677ce4a565972a658479e1349140e8c86 From 624e4e35d858a8161d529cdd71225fdb448ffef7 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 29 Dec 2022 13:49:00 -0600 Subject: [PATCH 13/26] debugPrint word answer for developer's mental health --- .../verify_recovery_phrase_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index 0ce1fac26..e8a680678 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -161,7 +161,7 @@ class _VerifyRecoveryPhraseViewState result.insert(random.nextInt(wordsToShow), chosenWord); //todo: this prints sensitive info - // debugPrint("Mnemonic game correct word: $chosenWord"); + debugPrint("Mnemonic game correct word: $chosenWord"); return Tuple2(result, chosenWord); } From 2c1a02ac92756cec75684eb326fdf04116028837 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 29 Dec 2022 18:10:31 -0600 Subject: [PATCH 14/26] ref update: delay save (store) if android ref update --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index d4cbce367..fcf2f5706 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit d4cbce3677ce4a565972a658479e1349140e8c86 +Subproject commit fcf2f5706bd0a00712eeff54b7aa8b8d7eafd104 From e84051b1408796f1a8506c1b6e9b21b9ade936c1 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 08:57:17 -0600 Subject: [PATCH 15/26] xmr send fix --- lib/services/coins/monero/monero_wallet.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 82d8610a7..2f72e0dc2 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -141,7 +141,7 @@ class MoneroWallet extends CoinServiceAPI { @override // not used for monero - Future> get allOwnAddresses => throw UnimplementedError(); + Future> get allOwnAddresses async => []; @override Future get availableBalance async { @@ -841,9 +841,9 @@ class MoneroWallet extends CoinServiceAPI { } @override - Future updateSentCachedTxData(Map txData) { + Future updateSentCachedTxData(Map txData) async { // not used for xmr - throw UnimplementedError(); + return; } @override From e462ddeac1770cea60ad319469cb4504e516929e Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 09:14:55 -0600 Subject: [PATCH 16/26] small screen recovery phrase warning view fix --- ...w_wallet_recovery_phrase_warning_view.dart | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index 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, From b50c69b8c2f76885485fcd847495e515dc853d96 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 29 Dec 2022 20:41:34 -0600 Subject: [PATCH 17/26] ref update: don't use windows-specific api class --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index fcf2f5706..d636f16aa 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit fcf2f5706bd0a00712eeff54b7aa8b8d7eafd104 +Subproject commit d636f16aa0079c7ef47f0c8547fba1635e5eb33b From c16db8b963dc4cd0310c968ef553087b0b444ac5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 30 Dec 2022 07:29:51 -0800 Subject: [PATCH 18/26] Building (#277) * Update ca-certificates package in docker build image. * Add shebang to prebuild script. * Double quote WALLETTESTPARAMFILE bash variable to prevent glob expansion, word splitting, and breaking on spaces. * declare and assign array seprarately to fix Syntax error: "(" unexpected --- dockerfile.linux | 8 ++++---- scripts/prebuild.sh | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) 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/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 From 8f40275a140eb4901f9d8360a1a20b4faa8d2f5d Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 10:14:20 -0600 Subject: [PATCH 19/26] mnemonic field crashes fix --- .../restore_wallet_view/restore_wallet_view.dart | 6 ++++++ 1 file changed, 6 insertions(+) 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"), From c42ea26a3b2c510ec83b3b4ad3914801b1ec90b9 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 11:12:27 -0600 Subject: [PATCH 20/26] desktop restore date fix --- .../restore_options_view.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 7c5890cc7..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 @@ -322,12 +322,17 @@ class _RestoreOptionsViewState extends ConsumerState { onTap: chooseDate, controller: _dateController, ), - if (isDesktop) - // TODO desktop date picker - RestoreFromDatePicker( - onTap: chooseDesktopDate, - 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 && From 4b58f3ec6059a3f40204a0b8e0c62cbd12a0d855 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 11:36:00 -0600 Subject: [PATCH 21/26] mac desktop keyboard type crash fix --- .../generate_receiving_uri_qr_code_view.dart | 5 +++-- lib/pages/send_view/send_view.dart | 21 +++++++++++-------- .../wallet_settings_view.dart | 3 ++- .../transaction_search_filter_view.dart | 10 +++++---- .../wallet_view/sub_widgets/desktop_send.dart | 20 +++++++++++------- .../textfields/exchange_textfield.dart | 13 ++++++++---- 6 files changed, 44 insertions(+), 28 deletions(-) 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/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index f1b967471..14f097bde 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 @@ -1002,10 +1002,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 +1058,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/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, From ae8daa4902c7da32e4effd3a69e52fc5788b7b22 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 12:11:05 -0600 Subject: [PATCH 22/26] firo (and possibly other) send fix --- .../wallet_view/sub_widgets/desktop_send.dart | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 14f097bde..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, + ), + ], ), ], ), From 2253629dd6b481b3f0538924827b0fd04fad3a0d Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 12:18:46 -0600 Subject: [PATCH 23/26] key fix for duplicate wallets edge case --- .../sub_widgets/contact_list_item.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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( From 9765393f61f3d642fed051932b94b8de38b0a60c Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 12:50:55 -0600 Subject: [PATCH 24/26] re enable git versions --- .../global_settings_view/about_view.dart | 12 ++++++------ .../advanced_views/debug_view.dart | 12 ++++++------ .../settings/settings_menu/desktop_about_view.dart | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index 0e08a29a2..a1e78ba7d 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; -// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -98,9 +98,9 @@ class AboutView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - String firoCommit ="fixme";//FIRO_VERSIONS.getPluginVersion(); - String epicCashCommit ="fixme";// EPIC_VERSIONS.getPluginVersion(); - String moneroCommit ="fixme";// MONERO_VERSIONS.getPluginVersion(); + String firoCommit = FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit = EPIC_VERSIONS.getPluginVersion(); + String moneroCommit = MONERO_VERSIONS.getPluginVersion(); List futureFiroList = [ doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit), isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index c3402d121..442ad5d2a 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -7,11 +7,11 @@ import 'package:event_bus/event_bus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; @@ -294,11 +294,11 @@ class _DebugViewState extends ConsumerState { packageInfo.buildSignature; final appName = packageInfo.appName; String firoCommit = - "fixme";// FIRO_VERSIONS.getPluginVersion(); + FIRO_VERSIONS.getPluginVersion(); String epicCashCommit = - "fixme";// EPIC_VERSIONS.getPluginVersion(); + EPIC_VERSIONS.getPluginVersion(); String moneroCommit = - "fixme";// MONERO_VERSIONS.getPluginVersion(); + MONERO_VERSIONS.getPluginVersion(); DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfo = diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 92660a532..18988cb68 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -// import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; -// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; +import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS; +import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; -// import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; +import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -98,9 +98,9 @@ class DesktopAboutView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - String firoCommit = "fixme";//FIRO_VERSIONS.getPluginVersion(); - String epicCashCommit = "fixme";//EPIC_VERSIONS.getPluginVersion(); - String moneroCommit = "fixme";//MONERO_VERSIONS.getPluginVersion(); + String firoCommit = FIRO_VERSIONS.getPluginVersion(); + String epicCashCommit = EPIC_VERSIONS.getPluginVersion(); + String moneroCommit = MONERO_VERSIONS.getPluginVersion(); List futureFiroList = [ doesCommitExist("cypherstack", "flutter_liblelantus", firoCommit), isHeadCommit("cypherstack", "flutter_liblelantus", "main", firoCommit), From 9aa593146cb0e52052ecaf4d848d7e2aa218a65d Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 16:15:03 -0600 Subject: [PATCH 25/26] wownero sync refactor --- lib/services/coins/coin_service.dart | 2 +- lib/services/coins/monero/monero_wallet.dart | 4 +- .../coins/wownero/wownero_wallet.dart | 2555 ++++++++--------- pubspec.lock | 45 +- 4 files changed, 1149 insertions(+), 1457 deletions(-) diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index ceb9e7663..a690157ee 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -197,7 +197,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 2f72e0dc2..caf41c34d 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1096,14 +1096,14 @@ class MoneroWallet extends CoinServiceAPI { void onNewBlock() { // print("============================="); - print("New Block!"); + print("New Block! :: $walletName"); print("============================="); } void onNewTransaction() { // print("============================="); - print("New Transaction!"); + print("New Transaction! :: $walletName"); print("============================="); // call this here? diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 9e152d9e6..470df05e3 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; @@ -16,7 +17,6 @@ import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/pending_wownero_transaction.dart'; import 'package:cw_wownero/wownero_amount_format.dart'; import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:dart_numerics/dart_numerics.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_libmonero/core/key_service.dart'; @@ -33,7 +33,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'; @@ -52,1347 +51,46 @@ import 'package:stackwallet/utilities/stack_file_system.dart'; const int MINIMUM_CONFIRMATIONS = 10; -//https://github.com/wownero-project/wownero/blob/8361d60aef6e17908658128284899e3a11d808d4/src/cryptonote_config.h#L162 -const String GENESIS_HASH_MAINNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; -const String GENESIS_HASH_TESTNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; - class WowneroWallet extends CoinServiceAPI { - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final _prefs = Prefs.instance; - - Timer? timer; - Timer? wowneroAutosaveTimer; - late Coin _coin; - - late SecureStorageInterface _secureStore; - - late PriceAPI _priceAPI; - - Future getCurrentNode() async { - return NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - } - - WowneroWallet( - {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; - - @override - bool get shouldAutoSync => _shouldAutoSync; - - @override - set shouldAutoSync(bool shouldAutoSync) { - if (_shouldAutoSync != shouldAutoSync) { - _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - wowneroAutosaveTimer?.cancel(); - timer = null; - wowneroAutosaveTimer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - // Walletbase needs to be open for this to work - refresh(); - } - } - } - - @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.wownero)); - - // TODO: is this sync call needed? Do we need to notify ui here? - await walletBase?.startSync(); - - if (shouldRefresh) { - await refresh(); - } - } - - Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { - return []; - } - final List data = mnemonicString.split(' '); - return data; - } - - @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 wownero 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; - } - } - - @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; - - Logging.instance.log( - "currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached", - 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 wownero 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(); - } 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 wownero 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", - level: LogLevel.Error); - } - 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 wownero 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)); - // } - // } - }); - wowneroAutosaveTimer ??= - Timer.periodic(const Duration(seconds: 93), (timer) async { - //todo: check if print needed - // debugPrint("run wownero 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); - } - } - - @override - // TODO: implement allOwnAddresses - Future> get allOwnAddresses { - return Future(() => []); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - Format.satoshisToAmount(await maxFee, coin: Coin.wownero); - - @override - Future get currentReceivingAddress => - _currentReceivingAddress ??= _getCurrentAddressForChain(0); - - @override - Future exit() async { - await stopSyncPercentTimer(); - _hasCalledExit = true; - isActive = false; - await walletBase?.save(prioritySave: true); - walletBase?.close(); - wowneroAutosaveTimer?.cancel(); - wowneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } - + bool _isConnected = false; 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!, - ); - } - - @override - Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; - - @override - // TODO: implement fullRescan - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - var restoreHeight = walletBase?.walletInfo.restoreHeight; - 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); - - 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); - } - } - - /// 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; - } - - //TODO: take in the default language when creating wallet. - Future _generateNewWallet({int seedWordsLength = 14}) async { - Logging.instance - .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - // TODO: ping wownero 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!"); - // } - // } - // } - - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); - } - - // TODO: Wallet Service may need to be switched to Wownero - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - try { - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.wownero); - final path = await pathForWallet(name: name, type: WalletType.wownero); - credentials = wownero.createWowneroNewWalletCredentials( - name: name, language: "English", seedWordsLength: seedWordsLength); - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.wownero), - name: name, - type: WalletType.wownero, - 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?.create(credentials); - - final bufferedCreateHeight = (seedWordsLength == 14) - ? getSeedHeightSync(wallet?.seed.trim() as String) - : wownero.getHeightByDate( - date: DateTime.now().subtract(const Duration( - days: - 2))); // subtract a couple days to ensure we have a buffer for SWB - - await DB.instance.put( - boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); - walletInfo.restoreHeight = bufferedCreateHeight; - - await _secureStore.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 WowneroWalletBase; - } 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.wownero)); - await walletBase?.startSync(); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - 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); - - // Generate and add addresses to relevant arrays - final initialReceivingAddress = await _generateAddressForChain(0, 0); - // final initialChangeAddress = await _generateAddressForChain(1, 0); - - await _addToAddressesArrayForChain(initialReceivingAddress, 0); - // await _addToAddressesArrayForChain(initialChangeAddress, 1); - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [initialReceivingAddress]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - - _currentReceivingAddress = Future(() => initialReceivingAddress); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); - } - - @override - // TODO: implement initializeWallet - Future initializeNew({int seedWordsLength = 14}) async { - await _prefs.init(); - // TODO: ping actual wownero 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 = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _generateNewWallet(seedWordsLength: seedWordsLength); - // 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 WowneroWalletBase; - // 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.wownero)); - // 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 needed - // debugPrint("Exception was thrown"); - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - - walletService = - wownero.createWowneroWalletService(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 WowneroWalletBase; - 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( - "wownero address in init existing: $newReceivingAddress", - level: LogLevel.Info); - _currentReceivingAddress = Future(() => newReceivingAddress); - } - - @override - Future get maxFee async { - var bal = await availableBalance; - var fee = walletBase!.calculateEstimatedFee( - wownero.getDefaultTransactionPriority(), - Format.decimalAmountToSatoshis(bal, coin), - ); - - return fee; - } - - @override - // TODO: implement pendingBalance - Future get pendingBalance => throw UnimplementedError(); - + bool refreshMutex = false; bool longMutex = false; - // TODO: are these needed? - WalletService? walletService; KeyService? keysStorage; WowneroWalletBase? walletBase; WalletCreationService? _walletCreationService; + Timer? _autoSaveTimer; - 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 { - final int seedLength = mnemonic.trim().split(" ").length; - if (!(seedLength == 14 || seedLength == 25)) { - throw Exception("Invalid wownero mnemonic length found: $seedLength"); - } - - 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()); - - // extract seed height from 14 word seed - if (seedLength == 14) { - height = getSeedHeightSync(mnemonic.trim()); - } else { - // 25 word seed. TODO validate - if (height == 0) { - height = wownero.getHeightByDate( - date: DateTime.now().subtract(const Duration( - days: - 2))); // subtract a couple days to ensure we have a buffer for SWB\ - } - } - - await DB.instance - .put(boxName: walletId, key: "restoreHeight", value: height); - - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.wownero); - final path = await pathForWallet(name: name, type: WalletType.wownero); - credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, - height: height, - mnemonic: mnemonic.trim(), - ); - try { - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.wownero), - name: name, - type: WalletType.wownero, - 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 WowneroWalletBase; - 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) { - //todo: come back to this - 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.wownero)); - 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!; - } - } - - // 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 = wowneroAmountToString(amount: bal); - - return Decimal.parse(am); - } else { - String am = wowneroAmountToString(amount: transactionBalance); - - return Decimal.parse(am); - } - } - - @override - // TODO: implement onIsActiveWalletChanged - void Function(bool)? get onIsActiveWalletChanged => (isActive) async { - await walletBase?.save(); - walletBase?.close(); - wowneroAutosaveTimer?.cancel(); - wowneroAutosaveTimer = 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 fou" - "*nd $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as WowneroWalletBase?; - 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.wownero)); - await walletBase?.startSync(); - } - await refresh(); - } - this.isActive = isActive; - }; - - bool isActive = false; - - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); + Future? _currentReceivingAddress; + Future? _feeObject; Future? _transactionData; - // not used in wownero - TransactionData? cachedTxData; + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); - @override - Future updateSentCachedTxData(Map txData) async { - // not used in wownero - } - - 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 Wownero 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 = wowneroAmountToString(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 != null && - 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 wownero 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); - } + WowneroWallet({ + 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 { @@ -1408,17 +106,59 @@ class WowneroWallet extends CoinServiceAPI { } @override - // TODO: implement availableBalance - Future get availableBalance async { - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.unlockedBalance; - } - String am = wowneroAmountToString(amount: bal); - - return Decimal.parse(am); + set isFavorite(bool markFavorite) { + DB.instance.put( + boxName: walletId, key: "isFavorite", value: markFavorite); } + @override + bool get shouldAutoSync => _shouldAutoSync; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + // wow 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 + String get walletName => _walletName; + + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; + + @override + // not really used for wownero + Future> get allOwnAddresses async => []; + + @override + Future get availableBalance async { + int runningBalance = 0; + for (final entry in walletBase!.balance!.entries) { + runningBalance += entry.value.unlockedBalance; + } + return Format.satoshisToAmount(runningBalance, coin: coin); + } + + @override + // not used + Future get balanceMinusMaxFee => throw UnimplementedError(); + @override Coin get coin => _coin; @@ -1446,97 +186,9 @@ class WowneroWallet extends CoinServiceAPI { } } - // TODO: fix the double free memory crash error. @override - Future> prepareSend( - {required String address, - required int satoshiAmount, - Map? args}) async { - int amount = satoshiAmount; - String toAddress = address; - try { - final feeRate = args?["feeRate"]; - if (feeRate is FeeRateType) { - MoneroTransactionPriority feePriority; - switch (feeRate) { - case FeeRateType.fast: - feePriority = MoneroTransactionPriority.fast; - break; - case FeeRateType.average: - feePriority = MoneroTransactionPriority.regular; - break; - case FeeRateType.slow: - feePriority = MoneroTransactionPriority.slow; - break; - } - - Future? awaitPendingTransaction; - try { - // check for send all - bool isSendAll = false; - final balance = await availableBalance; - final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal()); - if (satInDecimal == balance) { - isSendAll = true; - } - Logging.instance - .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = wowneroAmountToString(amount: amount); - Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); - - wownero_output.Output output = wownero_output.Output(walletBase!); - output.address = toAddress; - output.sendAll = isSendAll; - output.setCryptoAmount(amountToSend); - - List outputs = [output]; - Object tmp = wownero.createWowneroTransactionCreationCredentials( - outputs: outputs, priority: feePriority); - - await prepareSendMutex.protect(() async { - awaitPendingTransaction = walletBase!.createTransaction(tmp); - }); - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", - level: LogLevel.Warning); - } - - PendingWowneroTransaction pendingWowneroTransaction = - await (awaitPendingTransaction!) as PendingWowneroTransaction; - int realfee = Format.decimalAmountToSatoshis( - Decimal.parse(pendingWowneroTransaction.feeFormatted), coin); - //todo: check if print needed - // debugPrint("fee? $realfee"); - Map txData = { - "pendingWowneroTransaction": pendingWowneroTransaction, - "fee": realfee, - "addresss": toAddress, - "recipientAmt": satoshiAmount, - }; - - Logging.instance.log("prepare send: $txData", level: LogLevel.Info); - return txData; - } else { - throw ArgumentError("Invalid fee rate argument provided!"); - } - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepare send(): $e\n$s", - level: LogLevel.Info); - - if (e.toString().contains("Incorrect unlocked balance")) { - throw Exception("Insufficient balance!"); - } else if (e is CreationTransactionException) { - throw Exception("Insufficient funds to pay for transaction fee!"); - } else { - throw Exception("Transaction failed with error code $e"); - } - } - } - - Mutex prepareSendMutex = Mutex(); - Mutex estimateFeeMutex = Mutex(); + Future get currentReceivingAddress => + _currentReceivingAddress ??= _getCurrentAddressForChain(0); @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { @@ -1587,6 +239,32 @@ class WowneroWallet extends CoinServiceAPI { return fee; } + @override + 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(); + + @override + Future fullRescan( + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + ) async { + var restoreHeight = walletBase?.walletInfo.restoreHeight; + highestPercentCached = 0; + await walletBase?.rescan(height: restoreHeight); + } + @override Future generateNewAddress() async { try { @@ -1615,4 +293,1025 @@ class WowneroWallet extends CoinServiceAPI { return false; } } + + @override + bool get hasCalledExit => _hasCalledExit; + + @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 needed + // debugPrint("Exception was thrown"); + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + walletService = + wownero.createWowneroWalletService(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 (e, s) { + throw Exception("Password not found $e, $s"); + } + walletBase = (await walletService?.openWallet(_walletId, password!)) + as WowneroWalletBase; + + 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( + "wownero address in init existing: $newReceivingAddress", + level: LogLevel.Info); + _currentReceivingAddress = Future(() => newReceivingAddress); + } + + @override + Future initializeNew({int seedWordsLength = 14}) async { + await _prefs.init(); + + // this should never fail + if ((await _secureStorage.read(key: '${_walletId}_mnemonic')) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + // TODO: Wallet Service may need to be switched to Wownero + walletService = + wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + try { + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.wownero); + final path = await _pathForWallet(name: name, type: WalletType.wownero); + credentials = wownero.createWowneroNewWalletCredentials( + name: name, + language: "English", + seedWordsLength: seedWordsLength, + ); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.wownero), + name: name, + type: WalletType.wownero, + 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: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService?.changeWalletType(); + // To restore from a seed + final wallet = await _walletCreationService?.create(credentials); + + final bufferedCreateHeight = (seedWordsLength == 14) + ? getSeedHeightSync(wallet?.seed.trim() as String) + : wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB + + await DB.instance.put( + boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); + walletInfo.restoreHeight = bufferedCreateHeight; + + 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 WowneroWalletBase; + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + walletBase?.close(); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node(uri: "$host:${node.port}", type: WalletType.wownero)); + await walletBase?.startSync(); + await DB.instance + .put(boxName: walletId, key: "id", value: _walletId); + + // Set relevant indexes + await DB.instance + .put(boxName: walletId, key: "receivingIndex", value: 0); + 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); + + // Generate and add addresses to relevant arrays + final initialReceivingAddress = await _generateAddressForChain(0, 0); + // final initialChangeAddress = await _generateAddressForChain(1, 0); + + await _addToAddressesArrayForChain(initialReceivingAddress, 0); + // await _addToAddressesArrayForChain(initialChangeAddress, 1); + + await DB.instance.put( + boxName: walletId, + key: 'receivingAddresses', + value: [initialReceivingAddress]); + await DB.instance + .put(boxName: walletId, key: "receivingIndex", value: 0); + walletBase?.close(); + _currentReceivingAddress = Future(() => initialReceivingAddress); + + Logging.instance + .log("initializeNew for $walletName $walletId", level: LogLevel.Info); + } + + @override + bool get isConnected => _isConnected; + + @override + bool get isRefreshing => refreshMutex; + + @override + // not used in wow + Future get maxFee => throw UnimplementedError(); + + @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 + // not used in wow + Future get pendingBalance => throw UnimplementedError(); + + @override + Future> prepareSend({ + required String address, + required int satoshiAmount, + Map? args, + }) async { + try { + final feeRate = args?["feeRate"]; + if (feeRate is FeeRateType) { + MoneroTransactionPriority feePriority; + switch (feeRate) { + case FeeRateType.fast: + feePriority = MoneroTransactionPriority.fast; + break; + case FeeRateType.average: + feePriority = MoneroTransactionPriority.regular; + break; + case FeeRateType.slow: + feePriority = MoneroTransactionPriority.slow; + break; + } + + Future? awaitPendingTransaction; + try { + // check for send all + bool isSendAll = false; + final balance = await availableBalance; + final satInDecimal = + Format.satoshisToAmount(satoshiAmount, coin: coin); + + if (satInDecimal == balance) { + isSendAll = true; + } + Logging.instance + .log("$address $satoshiAmount $args", level: LogLevel.Info); + String amountToSend = satInDecimal + .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + Logging.instance + .log("$satoshiAmount $amountToSend", level: LogLevel.Info); + + wownero_output.Output output = wownero_output.Output(walletBase!); + output.address = address; + output.sendAll = isSendAll; + output.setCryptoAmount(amountToSend); + + List outputs = [output]; + Object tmp = wownero.createWowneroTransactionCreationCredentials( + outputs: outputs, + priority: feePriority, + ); + + await prepareSendMutex.protect(() async { + awaitPendingTransaction = walletBase!.createTransaction(tmp); + }); + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Warning); + } + + PendingWowneroTransaction pendingWowneroTransaction = + await (awaitPendingTransaction!) as PendingWowneroTransaction; + int realfee = Format.decimalAmountToSatoshis( + Decimal.parse(pendingWowneroTransaction.feeFormatted), coin); + //todo: check if print needed + // debugPrint("fee? $realfee"); + Map txData = { + "pendingWowneroTransaction": pendingWowneroTransaction, + "fee": realfee, + "addresss": address, + "recipientAmt": satoshiAmount, + }; + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + return txData; + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepare send(): $e\n$s", + level: LogLevel.Info); + + if (e.toString().contains("Incorrect unlocked balance")) { + throw Exception("Insufficient balance!"); + } else if (e is CreationTransactionException) { + throw Exception("Insufficient funds to pay for transaction fee!"); + } else { + throw Exception("Transaction failed with error code $e"); + } + } + } + + @override + Future recoverFromMnemonic({ + required String mnemonic, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + final int seedLength = mnemonic.trim().split(" ").length; + if (!(seedLength == 14 || seedLength == 25)) { + throw Exception("Invalid wownero mnemonic length found: $seedLength"); + } + + await _prefs.init(); + longMutex = true; + final start = DateTime.now(); + try { + // 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()); + + // extract seed height from 14 word seed + if (seedLength == 14) { + height = getSeedHeightSync(mnemonic.trim()); + } else { + // 25 word seed. TODO validate + if (height == 0) { + height = wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB\ + } + } + + await DB.instance + .put(boxName: walletId, key: "restoreHeight", value: height); + + walletService = + wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.wownero); + final path = await _pathForWallet(name: name, type: WalletType.wownero); + credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, + height: height, + mnemonic: mnemonic.trim(), + ); + try { + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.wownero), + name: name, + type: WalletType.wownero, + 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: _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 WowneroWalletBase; + 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) { + //todo: come back to this + 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.wownero)); + await walletBase?.rescan(height: credentials.height); + walletBase?.close(); + } 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 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 WowneroWalletBase?; + + 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(); + } + }; + + @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); + + // 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 Wownero 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 = wowneroAmountToString(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 != null && + 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 wownero 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() ?? {}; + 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 Wownero Block! :: $walletName"); + print("============================="); + } + + void onNewTransaction() { + // + print("============================="); + print("New Wownero Transaction! :: $walletName"); + 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/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: From 9ab2404970e4344c08d49e569027a0daaeaa5dfe Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 30 Dec 2022 17:08:59 -0600 Subject: [PATCH 26/26] update ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index d636f16aa..66eaa2f3c 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit d636f16aa0079c7ef47f0c8547fba1635e5eb33b +Subproject commit 66eaa2f3c7133f1dbf0b1fc950e7a9e3cc611185