From 76aca78dbbba11fd12f26d65aa356df7fabcf73d Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 10 Jan 2024 10:15:26 -0600 Subject: [PATCH] namecoin tweaks --- lib/services/coins/coin_service.dart | 12 +- .../coins/namecoin/namecoin_wallet.dart | 6960 ++++++++--------- .../crypto_currency/coins/namecoin.dart | 15 +- lib/wallets/wallet/impl/namecoin_wallet.dart | 17 +- .../coins/namecoin/namecoin_wallet_test.dart | 3234 ++++---- 5 files changed, 5113 insertions(+), 5125 deletions(-) diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 6968b3794..92c4e4c04 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -14,8 +14,6 @@ import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; -import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -142,15 +140,7 @@ abstract class CoinServiceAPI { throw UnimplementedError("moved"); case Coin.namecoin: - return NamecoinWallet( - walletId: walletId, - walletName: walletName, - coin: coin, - secureStore: secureStorageInterface, - tracker: tracker, - cachedClient: cachedClient, - client: client, - ); + throw UnimplementedError("moved"); case Coin.nano: throw UnimplementedError("moved"); diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 44a16c802..0a1f32f1d 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1,3480 +1,3480 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:bech32/bech32.dart'; -import 'package:bip32/bip32.dart' as bip32; -import 'package:bip39/bip39.dart' as bip39; -import 'package:bitcoindart/bitcoindart.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:crypto/crypto.dart'; -import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; -import 'package:isar/isar.dart'; -import 'package:stackwallet/db/isar/main_db.dart'; -import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart'; -import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; -import 'package:stackwallet/models/balance.dart'; -import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; -import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/signing_data.dart'; -import 'package:stackwallet/services/coins/coin_service.dart'; -import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; -import 'package:stackwallet/services/mixins/wallet_cache.dart'; -import 'package:stackwallet/services/mixins/wallet_db.dart'; -import 'package:stackwallet/services/mixins/xpubable.dart'; -import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/bip32_utils.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/default_nodes.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; -import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/widgets/crypto_notifications.dart'; -import 'package:tuple/tuple.dart'; -import 'package:uuid/uuid.dart'; - -const int MINIMUM_CONFIRMATIONS = 2; -// Find real dust limit -final Amount DUST_LIMIT = Amount( - rawValue: BigInt.from(546), - fractionDigits: Coin.particl.decimals, -); - -const String GENESIS_HASH_MAINNET = - "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"; -const String GENESIS_HASH_TESTNET = - "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"; - -String constructDerivePath({ - required DerivePathType derivePathType, - required int networkWIF, - int account = 0, - required int chain, - required int index, -}) { - String coinType; - switch (networkWIF) { - case 0xb4: // nmc mainnet wif - coinType = "7"; // nmc mainnet - break; - default: - throw Exception("Invalid Namecoin network wif used!"); - } - - int purpose; - switch (derivePathType) { - case DerivePathType.bip44: - purpose = 44; - break; - case DerivePathType.bip49: - purpose = 49; - break; - case DerivePathType.bip84: - purpose = 84; - break; - default: - throw Exception("DerivePathType $derivePathType not supported"); - } - - return "m/$purpose'/$coinType'/$account'/$chain/$index"; -} - -class NamecoinWallet extends CoinServiceAPI - with - WalletCache, - WalletDB, - ElectrumXParsing - // , CoinControlInterface - implements - XPubAble { - NamecoinWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumXClient client, - required CachedElectrumXClient cachedClient, - required TransactionNotificationTracker tracker, - required SecureStorageInterface secureStore, - MainDB? mockableOverride, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - _secureStore = secureStore; - initCache(walletId, coin); - initWalletDB(mockableOverride: mockableOverride); - // initCoinControlInterface( - // walletId: walletId, - // walletName: walletName, - // coin: coin, - // db: db, - // getChainHeight: () => chainHeight, - // refreshedBalanceCallback: (balance) async { - // _balance = balance; - // await updateCachedBalance(_balance!); - // }, - // ); - } - - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - - final _prefs = Prefs.instance; - - Timer? timer; - late final Coin _coin; - - late final TransactionNotificationTracker txTracker; - - NetworkType get _network { - switch (coin) { - case Coin.namecoin: - return namecoin; - default: - throw Exception("Invalid network type!"); - } - } - - @override - set isFavorite(bool markFavorite) { - _isFavorite = markFavorite; - updateCachedIsFavorite(markFavorite); - } - - @override - bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); - - bool? _isFavorite; - - @override - Coin get coin => _coin; - - @override - Future> get utxos => db.getUTXOs(walletId).findAll(); - - @override - Future> get transactions => - db.getTransactions(walletId).sortByTimestampDesc().findAll(); - - @override - Future get currentReceivingAddress async => - (await _currentReceivingAddress).value; - - Future get _currentReceivingAddress async => - (await db - .getAddresses(walletId) - .filter() - .typeEqualTo(isar_models.AddressType.p2wpkh) - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .sortByDerivationIndexDesc() - .findFirst()) ?? - await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - - Future get currentChangeAddress async => - (await _currentChangeAddress).value; - - Future get _currentChangeAddress async => - (await db - .getAddresses(walletId) - .filter() - .typeEqualTo(isar_models.AddressType.p2wpkh) - .subTypeEqualTo(isar_models.AddressSubType.change) - .sortByDerivationIndexDesc() - .findFirst()) ?? - await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); - - @override - Future exit() async { - _hasCalledExit = true; - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } - - bool _hasCalledExit = false; - - @override - bool get hasCalledExit => _hasCalledExit; - - @override - Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; - - @override - Future get maxFee async { - final fee = (await fees).fast as String; - final satsFee = Decimal.parse(fee) * - Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); - return satsFee.floor().toBigInt().toInt(); - } - - @override - Future> get mnemonic => _getMnemonicList(); - - @override - Future get mnemonicString => - _secureStore.read(key: '${_walletId}_mnemonic'); - - @override - Future get mnemonicPassphrase => _secureStore.read( - key: '${_walletId}_mnemonicPassphrase', - ); - - Future get chainHeight async { - try { - final result = await _electrumXClient.getBlockHeadTip(); - final height = result["height"] as int; - await updateCachedChainHeight(height); - if (height > storedChainHeight) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Updated current chain height in $walletId $walletName!", - walletId, - ), - ); - } - return height; - } catch (e, s) { - Logging.instance.log("Exception caught in chainHeight: $e\n$s", - level: LogLevel.Error); - return storedChainHeight; - } - } - - @override - int get storedChainHeight => getCachedChainHeight(); - - DerivePathType addressType({required String address}) { - Uint8List? decodeBase58; - Segwit? decodeBech32; - try { - decodeBase58 = bs58check.decode(address); - } catch (err) { - // Base58check decode fail - } - if (decodeBase58 != null) { - if (decodeBase58[0] == _network.pubKeyHash) { - // P2PKH - return DerivePathType.bip44; - } - if (decodeBase58[0] == _network.scriptHash) { - // P2SH - return DerivePathType.bip49; - } - throw ArgumentError('Invalid version or Network mismatch'); - } else { - try { - decodeBech32 = segwit.decode(address, namecoin.bech32!); - } catch (err) { - // Bech32 decode fail - } - if (_network.bech32 != decodeBech32!.hrp) { - throw ArgumentError('Invalid prefix or Network mismatch'); - } - if (decodeBech32.version != 0) { - throw ArgumentError('Invalid address version'); - } - // P2WPKH - return DerivePathType.bip84; - } - } - - bool longMutex = false; - - @override - Future recoverFromMnemonic({ - required String mnemonic, - String? mnemonicPassphrase, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - longMutex = true; - final start = DateTime.now(); - try { - Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag", - level: LogLevel.Info); - if (!integrationTestFlag) { - try { - final features = await electrumXClient - .getServerFeatures() - .timeout(const Duration(seconds: 3)); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.namecoin: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); - } - } catch (e, s) { - Logging.instance.log("$e/n$s", level: LogLevel.Info); - } - } - // check to make sure we aren't overwriting a mnemonic - // this should never fail - if ((await mnemonicString) != null || - (await this.mnemonicPassphrase) != null) { - longMutex = false; - throw Exception("Attempted to overwrite mnemonic on restore!"); - } - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); - await _secureStore.write( - key: '${_walletId}_mnemonicPassphrase', - value: mnemonicPassphrase ?? "", - ); - - await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic.trim(), - mnemonicPassphrase: mnemonicPassphrase ?? "", - maxUnusedAddressGap: maxUnusedAddressGap, - maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, - ); - } 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); - } - - Future> _checkGaps( - int maxNumberOfIndexesToCheck, - int maxUnusedAddressGap, - int txCountBatchSize, - bip32.BIP32 root, - DerivePathType type, - int chain) async { - List addressArray = []; - int returningIndex = -1; - Map> derivations = {}; - int gapCounter = 0; - for (int index = 0; - index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; - index += txCountBatchSize) { - List iterationsAddressArray = []; - Logging.instance.log( - "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", - level: LogLevel.Info); - - final _id = "k_$index"; - Map txCountCallArgs = {}; - final Map receivingNodes = {}; - - for (int j = 0; j < txCountBatchSize; j++) { - final derivePath = constructDerivePath( - derivePathType: type, - networkWIF: root.network.wif, - chain: chain, - index: index + j, - ); - final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); - - String addressString; - isar_models.AddressType addrType; - switch (type) { - case DerivePathType.bip44: - addressString = P2PKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data - .address!; - addrType = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - addressString = P2SH( - data: PaymentData( - redeem: P2WPKH( - data: PaymentData(pubkey: node.publicKey), - network: _network, - overridePrefix: namecoin.bech32!) - .data), - network: _network) - .data - .address!; - addrType = isar_models.AddressType.p2sh; - break; - case DerivePathType.bip84: - addressString = P2WPKH( - network: _network, - data: PaymentData(pubkey: node.publicKey), - overridePrefix: namecoin.bech32!) - .data - .address!; - addrType = isar_models.AddressType.p2wpkh; - break; - default: - throw Exception("DerivePathType $type not supported"); - } - - final address = isar_models.Address( - walletId: walletId, - subType: chain == 0 - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.change, - type: addrType, - publicKey: node.publicKey, - value: addressString, - derivationIndex: index + j, - derivationPath: isar_models.DerivationPath()..value = derivePath, - ); - - receivingNodes.addAll({ - "${_id}_$j": { - "node": node, - "address": address, - } - }); - txCountCallArgs.addAll({ - "${_id}_$j": addressString, - }); - } - - // get address tx counts - final counts = await _getBatchTxCount(addresses: txCountCallArgs); - - // check and add appropriate addresses - for (int k = 0; k < txCountBatchSize; k++) { - int count = counts["${_id}_$k"]!; - if (count > 0) { - final node = receivingNodes["${_id}_$k"]; - final address = node["address"] as isar_models.Address; - // add address to array - addressArray.add(address); - iterationsAddressArray.add(address.value); - // set current index - returningIndex = index + k; - // reset counter - gapCounter = 0; - // add info to derivations - derivations[address.value] = { - "pubKey": Format.uint8listToString( - (node["node"] as bip32.BIP32).publicKey), - "wif": (node["node"] as bip32.BIP32).toWIF(), - }; - } - - // increase counter when no tx history found - if (count == 0) { - gapCounter++; - } - } - // cache all the transactions while waiting for the current function to finish. - unawaited(getTransactionCacheEarly(iterationsAddressArray)); - } - return { - "addressArray": addressArray, - "index": returningIndex, - "derivations": derivations - }; - } - - Future getTransactionCacheEarly(List allAddresses) async { - try { - final List> allTxHashes = - await _fetchHistory(allAddresses); - for (final txHash in allTxHashes) { - try { - unawaited(cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - )); - } catch (e) { - continue; - } - } - } catch (e) { - // - } - } - - Future _recoverWalletFromBIP32SeedPhrase({ - required String mnemonic, - required String mnemonicPassphrase, - int maxUnusedAddressGap = 20, - int maxNumberOfIndexesToCheck = 1000, - bool isRescan = false, - }) async { - longMutex = true; - - Map> p2pkhReceiveDerivations = {}; - Map> p2shReceiveDerivations = {}; - Map> p2wpkhReceiveDerivations = {}; - Map> p2pkhChangeDerivations = {}; - Map> p2shChangeDerivations = {}; - Map> p2wpkhChangeDerivations = {}; - - final root = await Bip32Utils.getBip32Root( - mnemonic, - mnemonicPassphrase, - _network, - ); - - List p2pkhReceiveAddressArray = []; - List p2shReceiveAddressArray = []; - List p2wpkhReceiveAddressArray = []; - int p2pkhReceiveIndex = -1; - int p2shReceiveIndex = -1; - int p2wpkhReceiveIndex = -1; - - List p2pkhChangeAddressArray = []; - List p2shChangeAddressArray = []; - List p2wpkhChangeAddressArray = []; - int p2pkhChangeIndex = -1; - int p2shChangeIndex = -1; - int p2wpkhChangeIndex = -1; - - // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 - const txCountBatchSize = 12; - - try { - // receiving addresses - Logging.instance - .log("checking receiving addresses...", level: LogLevel.Info); - final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); - - final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); - - final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0); - - Logging.instance - .log("checking change addresses...", level: LogLevel.Info); - // change addresses - final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); - - final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); - - final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1); - - await Future.wait([ - resultReceive44, - resultReceive49, - resultReceive84, - resultChange44, - resultChange49, - resultChange84 - ]); - - p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; - p2pkhReceiveIndex = (await resultReceive44)['index'] as int; - p2pkhReceiveDerivations = (await resultReceive44)['derivations'] - as Map>; - - p2shReceiveAddressArray = - (await resultReceive49)['addressArray'] as List; - p2shReceiveIndex = (await resultReceive49)['index'] as int; - p2shReceiveDerivations = (await resultReceive49)['derivations'] - as Map>; - - p2wpkhReceiveAddressArray = - (await resultReceive84)['addressArray'] as List; - p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; - p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] - as Map>; - - p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; - p2pkhChangeIndex = (await resultChange44)['index'] as int; - p2pkhChangeDerivations = (await resultChange44)['derivations'] - as Map>; - - p2shChangeAddressArray = - (await resultChange49)['addressArray'] as List; - p2shChangeIndex = (await resultChange49)['index'] as int; - p2shChangeDerivations = (await resultChange49)['derivations'] - as Map>; - - p2wpkhChangeAddressArray = - (await resultChange84)['addressArray'] as List; - p2wpkhChangeIndex = (await resultChange84)['index'] as int; - p2wpkhChangeDerivations = (await resultChange84)['derivations'] - as Map>; - - // save the derivations (if any) - if (p2pkhReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhReceiveDerivations); - } - if (p2shReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shReceiveDerivations); - } - if (p2wpkhReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - derivationsToAdd: p2wpkhReceiveDerivations); - } - if (p2pkhChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhChangeDerivations); - } - if (p2shChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shChangeDerivations); - } - if (p2wpkhChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - derivationsToAdd: p2wpkhChangeDerivations); - } - - // If restoring a wallet that never received any funds, then set receivingArray manually - // If we didn't do this, it'd store an empty array - if (p2pkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - p2pkhReceiveAddressArray.add(address); - } - if (p2shReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip49); - p2shReceiveAddressArray.add(address); - } - if (p2wpkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip84); - p2wpkhReceiveAddressArray.add(address); - } - - // If restoring a wallet that never sent any funds with change, then set changeArray - // manually. If we didn't do this, it'd store an empty array. - if (p2pkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip44); - p2pkhChangeAddressArray.add(address); - } - if (p2shChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip49); - p2shChangeAddressArray.add(address); - } - if (p2wpkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip84); - p2wpkhChangeAddressArray.add(address); - } - - if (isRescan) { - await db.updateOrPutAddresses([ - ...p2wpkhReceiveAddressArray, - ...p2wpkhChangeAddressArray, - ...p2pkhReceiveAddressArray, - ...p2pkhChangeAddressArray, - ...p2shReceiveAddressArray, - ...p2shChangeAddressArray, - ]); - } else { - await db.putAddresses([ - ...p2wpkhReceiveAddressArray, - ...p2wpkhChangeAddressArray, - ...p2pkhReceiveAddressArray, - ...p2pkhChangeAddressArray, - ...p2shReceiveAddressArray, - ...p2shChangeAddressArray, - ]); - } - - await _updateUTXOs(); - - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false), - ]); - - longMutex = false; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Error); - - longMutex = false; - rethrow; - } - } - - Future refreshIfThereIsNewData() async { - if (longMutex) return false; - if (_hasCalledExit) return false; - Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info); - - try { - bool needsRefresh = false; - Set txnsToCheck = {}; - - for (final String txid in txTracker.pendings) { - if (!txTracker.wasNotifiedConfirmed(txid)) { - txnsToCheck.add(txid); - } - } - - for (String txid in txnsToCheck) { - final txn = await electrumXClient.getTransaction(txHash: txid); - int confirmations = txn["confirmations"] as int? ?? 0; - bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; - if (!isUnconfirmed) { - // unconfirmedTxs = {}; - needsRefresh = true; - break; - } - } - if (!needsRefresh) { - var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = await _fetchHistory( - allOwnAddresses.map((e) => e.value).toList(growable: false)); - for (Map transaction in allTxs) { - final txid = transaction['tx_hash'] as String; - if ((await db - .getTransactions(walletId) - .filter() - .txidMatches(txid) - .findFirst()) == - null) { - Logging.instance.log( - " txid not found in address history already ${transaction['tx_hash']}", - level: LogLevel.Info); - needsRefresh = true; - break; - } - } - } - return needsRefresh; - } catch (e, s) { - Logging.instance.log( - "Exception caught in refreshIfThereIsNewData: $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future getAllTxsToWatch() async { - if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; - - final currentChainHeight = await chainHeight; - - final txCount = await db.getTransactions(walletId).count(); - - const paginateLimit = 50; - - for (int i = 0; i < txCount; i += paginateLimit) { - final transactions = await db - .getTransactions(walletId) - .offset(i) - .limit(paginateLimit) - .findAll(); - for (final tx in transactions) { - if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - // get all transactions that were notified as pending but not as confirmed - if (txTracker.wasNotifiedPending(tx.txid) && - !txTracker.wasNotifiedConfirmed(tx.txid)) { - unconfirmedTxnsToNotifyConfirmed.add(tx); - } - } else { - // get all transactions that were not notified as pending yet - if (!txTracker.wasNotifiedPending(tx.txid)) { - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } - } - - // notify on unconfirmed transactions - for (final tx in unconfirmedTxnsToNotifyPending) { - final confirmations = tx.getConfirmations(currentChainHeight); - - if (tx.type == isar_models.TransactionType.incoming) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Incoming transaction", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedPending(tx.txid); - } else if (tx.type == isar_models.TransactionType.outgoing) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Sending transaction", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedPending(tx.txid); - } - } - - // notify on confirmed - for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.type == isar_models.TransactionType.incoming) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Incoming transaction confirmed", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - txid: tx.txid, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.type == isar_models.TransactionType.outgoing) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Outgoing transaction confirmed", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - txid: tx.txid, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedConfirmed(tx.txid); - } - } - } - - bool _shouldAutoSync = false; - - @override - bool get shouldAutoSync => _shouldAutoSync; - - @override - set shouldAutoSync(bool shouldAutoSync) { - if (_shouldAutoSync != shouldAutoSync) { - _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - refresh(); - } - } - } - - @override - bool get isRefreshing => refreshMutex; - - bool refreshMutex = false; - - //TODO Show percentages properly/more consistently - /// 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; - } - - try { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); - - final currentHeight = await chainHeight; - const storedHeight = 1; //await storedChainHeight; - - Logging.instance - .log("chain height: $currentHeight", level: LogLevel.Info); - Logging.instance - .log("cached height: $storedHeight", level: LogLevel.Info); - - if (currentHeight != storedHeight) { - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - await _checkChangeAddressForTransactions(); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - await _checkCurrentReceivingAddressesForTransactions(); - - final fetchFuture = _refreshTransactions(); - final utxosRefreshFuture = _updateUTXOs(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.50, walletId)); - - final feeObj = _getFees(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.60, walletId)); - - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.70, walletId)); - _feeObject = Future(() => feeObj); - - await utxosRefreshFuture; - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.80, walletId)); - - await fetchFuture; - await getAllTxsToWatch(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.90, walletId)); - } - - refreshMutex = false; - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - - if (shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { - Logging.instance.log( - "Periodic refresh check for $walletId $walletName in object instance: $hashCode", - level: LogLevel.Info); - // chain height check currently broken - // if ((await chainHeight) != (await storedChainHeight)) { - if (await refreshIfThereIsNewData()) { - await refresh(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - // } - }); - } - } catch (error, strace) { - refreshMutex = false; - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - coin, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error); - } - } - - @override - Future> prepareSend({ - required String address, - required Amount amount, - Map? args, - }) async { - try { - final feeRateType = args?["feeRate"]; - final customSatsPerVByte = args?["satsPerVByte"] as int?; - final feeRateAmount = args?["feeRateAmount"]; - final utxos = args?["UTXOs"] as Set?; - - if (customSatsPerVByte != null) { - // check for send all - bool isSendAll = false; - if (amount == balance.spendable) { - isSendAll = true; - } - - final bool coinControl = utxos != null; - - final result = await coinSelection( - satoshiAmountToSend: amount.raw.toInt(), - selectedTxFeeRate: -1, - satsPerVByte: customSatsPerVByte, - recipientAddress: address, - isSendAll: isSendAll, - utxos: utxos?.toList(), - coinControl: coinControl, - ); - - Logging.instance - .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); - if (result is int) { - switch (result) { - case 1: - throw Exception("Insufficient balance!"); - case 2: - throw Exception("Insufficient funds to pay for transaction fee!"); - default: - throw Exception("Transaction failed with error code $result"); - } - } else { - final hex = result["hex"]; - if (hex is String) { - final fee = result["fee"] as int; - final vSize = result["vSize"] as int; - - Logging.instance.log("txHex: $hex", level: LogLevel.Info); - Logging.instance.log("fee: $fee", level: LogLevel.Info); - Logging.instance.log("vsize: $vSize", level: LogLevel.Info); - // fee should never be less than vSize sanity check - if (fee < vSize) { - throw Exception( - "Error in fee calculation: Transaction fee cannot be less than vSize"); - } - return result as Map; - } else { - throw Exception("sent hex is not a String!!!"); - } - } - } else if (feeRateType is FeeRateType || feeRateAmount is int) { - late final int rate; - if (feeRateType is FeeRateType) { - int fee = 0; - final feeObject = await fees; - switch (feeRateType) { - case FeeRateType.fast: - fee = feeObject.fast; - break; - case FeeRateType.average: - fee = feeObject.medium; - break; - case FeeRateType.slow: - fee = feeObject.slow; - break; - default: - throw ArgumentError("Invalid use of custom fee"); - } - rate = fee; - } else { - rate = feeRateAmount as int; - } - - // check for send all - bool isSendAll = false; - if (amount == balance.spendable) { - isSendAll = true; - } - - final bool coinControl = utxos != null; - - final txData = await coinSelection( - satoshiAmountToSend: amount.raw.toInt(), - selectedTxFeeRate: rate, - recipientAddress: address, - isSendAll: isSendAll, - utxos: utxos?.toList(), - coinControl: coinControl, - ); - - Logging.instance.log("prepare send: $txData", level: LogLevel.Info); - try { - if (txData is int) { - switch (txData) { - case 1: - throw Exception("Insufficient balance!"); - case 2: - throw Exception( - "Insufficient funds to pay for transaction fee!"); - default: - throw Exception("Transaction failed with error code $txData"); - } - } else { - final hex = txData["hex"]; - - if (hex is String) { - final fee = txData["fee"] as int; - final vSize = txData["vSize"] as int; - - Logging.instance - .log("prepared txHex: $hex", level: LogLevel.Info); - Logging.instance.log("prepared fee: $fee", level: LogLevel.Info); - Logging.instance - .log("prepared vSize: $vSize", level: LogLevel.Info); - - // fee should never be less than vSize sanity check - if (fee < vSize) { - throw Exception( - "Error in fee calculation: Transaction fee cannot be less than vSize"); - } - - return txData as Map; - } else { - throw Exception("prepared hex is not a String!!!"); - } - } - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } else { - throw ArgumentError("Invalid fee rate argument provided!"); - } - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - @override - Future confirmSend({required Map txData}) async { - try { - Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); - - final hex = txData["hex"] as String; - - final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); - Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); - - final utxos = txData["usedUTXOs"] as List; - - // mark utxos as used - await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); - - return txHash; - } catch (e, s) { - Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - @override - Future testNetworkConnection() async { - try { - final result = await _electrumXClient.ping(); - return result; - } catch (_) { - return false; - } - } - - Timer? _networkAliveTimer; - - void startNetworkAlivePinging() { - // call once on start right away - _periodicPingCheck(); - - // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); - } - - void _periodicPingCheck() async { - bool hasNetwork = await testNetworkConnection(); - - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - - _isConnected = hasNetwork; - if (hasNetwork) { - unawaited(refresh()); - } - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - bool _isConnected = false; - - @override - bool get isConnected => _isConnected; - - @override - Future initializeNew( - ({String mnemonicPassphrase, int wordCount})? data, - ) async { - Logging.instance - .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - - if (getCachedId() != null) { - throw Exception( - "Attempted to initialize a new wallet using an existing wallet ID!"); - } - - await _prefs.init(); - try { - await _generateNewWallet(data); - } catch (e, s) { - Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", - level: LogLevel.Fatal); - rethrow; - } - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false), - ]); - } - - @override - Future initializeExisting() async { - Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", - level: LogLevel.Info); - - if (getCachedId() == null) { - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - await _prefs.init(); - // await _checkCurrentChangeAddressesForTransactions(); - // await _checkCurrentReceivingAddressesForTransactions(); - } - - // hack to add tx to txData before refresh completes - // required based on current app architecture where we don't properly store - // transactions locally in a good way - @override - Future updateSentCachedTxData(Map txData) async { - final transaction = isar_models.Transaction( - walletId: walletId, - txid: txData["txid"] as String, - timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - type: isar_models.TransactionType.outgoing, - subType: isar_models.TransactionSubType.none, - // precision may be lost here hence the following amountString - amount: (txData["recipientAmt"] as Amount).raw.toInt(), - amountString: (txData["recipientAmt"] as Amount).toJsonString(), - fee: txData["fee"] as int, - height: null, - isCancelled: false, - isLelantus: false, - otherData: null, - slateId: null, - nonce: null, - inputs: [], - outputs: [], - numberOfMessages: null, - ); - - final address = txData["address"] is String - ? await db.getAddress(walletId, txData["address"] as String) - : null; - - await db.addNewTransactionData( - [ - Tuple2(transaction, address), - ], - walletId, - ); - } - - @override - bool validateAddress(String address) { - return Address.validateAddress(address, _network, namecoin.bech32!); - } - - @override - String get walletId => _walletId; - late final String _walletId; - - @override - String get walletName => _walletName; - late String _walletName; - - // setter for updating on rename - @override - set walletName(String newName) => _walletName = newName; - - late ElectrumXClient _electrumXClient; - - ElectrumXClient get electrumXClient => _electrumXClient; - - late CachedElectrumXClient _cachedElectrumXClient; - - CachedElectrumXClient get cachedElectrumXClient => _cachedElectrumXClient; - - late SecureStorageInterface _secureStore; - - @override - Future updateNode(bool shouldRefresh) async { - final failovers = NodeService(secureStorageInterface: _secureStore) - .failoverNodesFor(coin: coin) - .map((e) => ElectrumXNode( - address: e.host, - port: e.port, - name: e.name, - id: e.id, - useSSL: e.useSSL, - )) - .toList(); - final newNode = await getCurrentNode(); - _electrumXClient = ElectrumXClient.from( - node: newNode, - prefs: _prefs, - failovers: failovers, - ); - _cachedElectrumXClient = CachedElectrumXClient.from( - electrumXClient: _electrumXClient, - ); - - if (shouldRefresh) { - unawaited(refresh()); - } - } - - Future> _getMnemonicList() async { - final _mnemonicString = await mnemonicString; - if (_mnemonicString == null) { - return []; - } - final List data = _mnemonicString.split(' '); - return data; - } - - Future getCurrentNode() async { - final node = NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - - return ElectrumXNode( - address: node.host, - port: node.port, - name: node.name, - useSSL: node.useSSL, - id: node.id, - ); - } - - Future> _fetchAllOwnAddresses() async { - final allAddresses = await db - .getAddresses(walletId) - .filter() - .not() - .typeEqualTo(isar_models.AddressType.nonWallet) - .and() - .group((q) => q - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .or() - .subTypeEqualTo(isar_models.AddressSubType.change)) - .findAll(); - // final List allAddresses = []; - // final receivingAddresses = DB.instance.get( - // boxName: walletId, key: 'receivingAddressesP2WPKH') as List; - // final changeAddresses = DB.instance.get( - // boxName: walletId, key: 'changeAddressesP2WPKH') as List; - // final receivingAddressesP2PKH = DB.instance.get( - // boxName: walletId, key: 'receivingAddressesP2PKH') as List; - // final changeAddressesP2PKH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - // as List; - // final receivingAddressesP2SH = DB.instance.get( - // boxName: walletId, key: 'receivingAddressesP2SH') as List; - // final changeAddressesP2SH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - // as List; - // - // for (var i = 0; i < receivingAddresses.length; i++) { - // if (!allAddresses.contains(receivingAddresses[i])) { - // allAddresses.add(receivingAddresses[i] as String); - // } - // } - // for (var i = 0; i < changeAddresses.length; i++) { - // if (!allAddresses.contains(changeAddresses[i])) { - // allAddresses.add(changeAddresses[i] as String); - // } - // } - // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - // allAddresses.add(receivingAddressesP2PKH[i] as String); - // } - // } - // for (var i = 0; i < changeAddressesP2PKH.length; i++) { - // if (!allAddresses.contains(changeAddressesP2PKH[i])) { - // allAddresses.add(changeAddressesP2PKH[i] as String); - // } - // } - // for (var i = 0; i < receivingAddressesP2SH.length; i++) { - // if (!allAddresses.contains(receivingAddressesP2SH[i])) { - // allAddresses.add(receivingAddressesP2SH[i] as String); - // } - // } - // for (var i = 0; i < changeAddressesP2SH.length; i++) { - // if (!allAddresses.contains(changeAddressesP2SH[i])) { - // allAddresses.add(changeAddressesP2SH[i] as String); - // } - // } - return allAddresses; - } - - Future _getFees() async { - try { - //TODO adjust numbers for different speeds? - const int f = 1, m = 5, s = 20; - - final fast = await electrumXClient.estimateFee(blocks: f); - final medium = await electrumXClient.estimateFee(blocks: m); - final slow = await electrumXClient.estimateFee(blocks: s); - - final feeObject = FeeObject( - numberOfBlocksFast: f, - numberOfBlocksAverage: m, - numberOfBlocksSlow: s, - fast: Amount.fromDecimal( - fast, - fractionDigits: coin.decimals, - ).raw.toInt(), - medium: Amount.fromDecimal( - medium, - fractionDigits: coin.decimals, - ).raw.toInt(), - slow: Amount.fromDecimal( - slow, - fractionDigits: coin.decimals, - ).raw.toInt(), - ); - - Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); - return feeObject; - } catch (e) { - Logging.instance - .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); - rethrow; - } - } - - Future _generateNewWallet( - ({String mnemonicPassphrase, int wordCount})? data, - ) async { - Logging.instance - .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - if (!integrationTestFlag) { - try { - final features = await electrumXClient.getServerFeatures(); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.namecoin: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); - } - } catch (e, s) { - Logging.instance.log("$e/n$s", level: LogLevel.Info); - } - } - - // this should never fail - if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); - } - final int strength; - if (data == null || data.wordCount == 12) { - strength = 128; - } else if (data.wordCount == 24) { - strength = 256; - } else { - throw Exception("Invalid word count"); - } - await _secureStore.write( - key: '${_walletId}_mnemonic', - value: bip39.generateMnemonic(strength: strength)); - await _secureStore.write( - key: '${_walletId}_mnemonicPassphrase', - value: data?.mnemonicPassphrase ?? "", - ); - - // Generate and add addresses to relevant arrays - final initialAddresses = await Future.wait([ - // P2WPKH - _generateAddressForChain(0, 0, DerivePathType.bip84), - _generateAddressForChain(1, 0, DerivePathType.bip84), - - // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44), - _generateAddressForChain(1, 0, DerivePathType.bip44), - - // P2SH - _generateAddressForChain(0, 0, DerivePathType.bip49), - _generateAddressForChain(1, 0, DerivePathType.bip49), - ]); - - await db.putAddresses(initialAddresses); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); - } - - /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( - int chain, - int index, - DerivePathType derivePathType, - ) async { - final _mnemonic = await mnemonicString; - final _mnemonicPassphrase = await mnemonicPassphrase; - if (_mnemonicPassphrase == null) { - Logging.instance.log( - "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", - level: LogLevel.Error); - } - - final derivePath = constructDerivePath( - derivePathType: derivePathType, - networkWIF: _network.wif, - chain: chain, - index: index, - ); - final node = await Bip32Utils.getBip32Node( - _mnemonic!, - _mnemonicPassphrase!, - _network, - derivePath, - ); - - final data = PaymentData(pubkey: node.publicKey); - String address; - isar_models.AddressType addrType; - - switch (derivePathType) { - case DerivePathType.bip44: - address = P2PKH(data: data, network: _network).data.address!; - addrType = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - address = P2SH( - data: PaymentData( - redeem: P2WPKH( - data: data, - network: _network, - overridePrefix: namecoin.bech32!) - .data), - network: _network) - .data - .address!; - addrType = isar_models.AddressType.p2sh; - break; - case DerivePathType.bip84: - address = P2WPKH( - network: _network, data: data, overridePrefix: namecoin.bech32!) - .data - .address!; - addrType = isar_models.AddressType.p2wpkh; - break; - default: - throw Exception("DerivePathType must not be null."); - } - - // add generated address & info to derivations - await addDerivation( - chain: chain, - address: address, - pubKey: Format.uint8listToString(node.publicKey), - wif: node.toWIF(), - derivePathType: derivePathType, - ); - - return isar_models.Address( - walletId: walletId, - derivationIndex: index, - derivationPath: isar_models.DerivationPath()..value = derivePath, - value: address, - publicKey: node.publicKey, - type: addrType, - subType: chain == 0 - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.change, - ); - } - - /// 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, - DerivePathType derivePathType, - ) async { - final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 - ? isar_models.AddressSubType.receiving - : isar_models.AddressSubType.change; - - isar_models.AddressType type; - isar_models.Address? address; - switch (derivePathType) { - case DerivePathType.bip44: - type = isar_models.AddressType.p2pkh; - break; - case DerivePathType.bip49: - type = isar_models.AddressType.p2sh; - break; - case DerivePathType.bip84: - type = isar_models.AddressType.p2wpkh; - break; - default: - throw Exception( - "DerivePathType null or unsupported (${DerivePathType.bip44})"); - } - address = await db - .getAddresses(walletId) - .filter() - .typeEqualTo(type) - .subTypeEqualTo(subType) - .sortByDerivationIndexDesc() - .findFirst(); - return address!.value; - } - - String _buildDerivationStorageKey({ - required int chain, - required DerivePathType derivePathType, - }) { - String key; - String chainId = chain == 0 ? "receive" : "change"; - switch (derivePathType) { - case DerivePathType.bip44: - key = "${walletId}_${chainId}DerivationsP2PKH"; - break; - case DerivePathType.bip49: - key = "${walletId}_${chainId}DerivationsP2SH"; - break; - case DerivePathType.bip84: - key = "${walletId}_${chainId}DerivationsP2WPKH"; - break; - default: - throw Exception("DerivePathType $derivePathType not supported"); - } - return key; - } - - Future> _fetchDerivations({ - required int chain, - required DerivePathType derivePathType, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - return Map.from( - jsonDecode(derivationsString ?? "{}") as Map); - } - - /// Add a single derivation to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite a previous entry where the address of the new derivation - /// matches a derivation currently stored. - Future addDerivation({ - required int chain, - required String address, - required String pubKey, - required String wif, - required DerivePathType derivePathType, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); - - // add derivation - derivations[address] = { - "pubKey": pubKey, - "wif": wif, - }; - - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); - } - - /// Add multiple derivations to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite any previous entries where the address of the new derivation - /// matches a derivation currently stored. - /// The [derivationsToAdd] must be in the format of: - /// { - /// addressA : { - /// "pubKey": , - /// "wif": , - /// }, - /// addressB : { - /// "pubKey": , - /// "wif": , - /// }, - /// } - Future addDerivations({ - required int chain, - required DerivePathType derivePathType, - required Map derivationsToAdd, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); - - // add derivation - derivations.addAll(derivationsToAdd); - - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); - } - - Future _updateUTXOs() async { - final allAddresses = await _fetchAllOwnAddresses(); - - try { - final fetchedUtxoList = >>[]; - - final Map>> batches = {}; - const batchSizeMax = 100; - int batchNumber = 0; - for (int i = 0; i < allAddresses.length; i++) { - if (batches[batchNumber] == null) { - batches[batchNumber] = {}; - } - final scripthash = - _convertToScriptHash(allAddresses[i].value, _network); - - // print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); - batches[batchNumber]!.addAll({ - scripthash: [scripthash] - }); - if (i % batchSizeMax == batchSizeMax - 1) { - batchNumber++; - } - } - - for (int i = 0; i < batches.length; i++) { - final response = - await _electrumXClient.getBatchUTXOs(args: batches[i]!); - for (final entry in response.entries) { - if (entry.value.isNotEmpty) { - fetchedUtxoList.add(entry.value); - } - } - } - - final List outputArray = []; - - for (int i = 0; i < fetchedUtxoList.length; i++) { - for (int j = 0; j < fetchedUtxoList[i].length; j++) { - final jsonUTXO = fetchedUtxoList[i][j]; - - final txn = await cachedElectrumXClient.getTransaction( - txHash: jsonUTXO["tx_hash"] as String, - verbose: true, - coin: coin, - ); - - final vout = jsonUTXO["tx_pos"] as int; - - final outputs = txn["vout"] as List; - - String? utxoOwnerAddress; - // get UTXO owner address - for (final output in outputs) { - if (output["n"] == vout) { - utxoOwnerAddress = - output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; - } - } - - final utxo = isar_models.UTXO( - walletId: walletId, - txid: txn["txid"] as String, - vout: vout, - value: jsonUTXO["value"] as int, - name: "", - isBlocked: false, - blockedReason: null, - isCoinbase: txn["is_coinbase"] as bool? ?? false, - blockHash: txn["blockhash"] as String?, - blockHeight: jsonUTXO["height"] as int?, - blockTime: txn["blocktime"] as int?, - address: utxoOwnerAddress, - ); - - outputArray.add(utxo); - } - } - - Logging.instance - .log('Outputs fetched: $outputArray', level: LogLevel.Info); - - await db.updateUTXOs(walletId, outputArray); - - // finally update balance - await _updateBalance(); - } catch (e, s) { - Logging.instance - .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - } - } - - Future _updateBalance() async { - // await refreshBalance(); - } - - @override - Balance get balance => _balance ??= getCachedBalance(); - Balance? _balance; - - // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - // /// Now also checks for output labeling. - // Future _sortOutputs(List utxos) async { - // final blockedHashArray = - // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - // as List?; - // final List lst = []; - // if (blockedHashArray != null) { - // for (var hash in blockedHashArray) { - // lst.add(hash as String); - // } - // } - // final labels = - // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - // {}; - // - // outputsList = []; - // - // for (var i = 0; i < utxos.length; i++) { - // if (labels[utxos[i].txid] != null) { - // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - // } else { - // utxos[i].txName = 'Output #$i'; - // } - // - // if (utxos[i].status.confirmed == false) { - // outputsList.add(utxos[i]); - // } else { - // if (lst.contains(utxos[i].txid)) { - // utxos[i].blocked = true; - // outputsList.add(utxos[i]); - // } else if (!lst.contains(utxos[i].txid)) { - // outputsList.add(utxos[i]); - // } - // } - // } - // } - - Future getTxCount({required String address}) async { - String? scripthash; - try { - scripthash = _convertToScriptHash(address, _network); - final transactions = - await electrumXClient.getHistory(scripthash: scripthash); - return transactions.length; - } catch (e) { - Logging.instance.log( - "Exception rethrown in _getTxCount(address: $address, scripthash: $scripthash): $e", - level: LogLevel.Error); - rethrow; - } - } - - Future> _getBatchTxCount({ - required Map addresses, - }) async { - try { - final Map> args = {}; - // print("Address $addresses"); - for (final entry in addresses.entries) { - args[entry.key] = [_convertToScriptHash(entry.value, _network)]; - } - // print("Args ${jsonEncode(args)}"); - final response = await electrumXClient.getBatchHistory(args: args); - // print("Response ${jsonEncode(response)}"); - final Map result = {}; - for (final entry in response.entries) { - result[entry.key] = entry.value.length; - } - - return result; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkReceivingAddressForTransactions() async { - try { - final currentReceiving = await _currentReceivingAddress; - - final int txCount = await getTxCount(address: currentReceiving.value); - Logging.instance.log( - 'Number of txs for current receiving address $currentReceiving: $txCount', - level: LogLevel.Info); - - if (txCount >= 1 || currentReceiving.derivationIndex < 0) { - // First increment the receiving index - final newReceivingIndex = currentReceiving.derivationIndex + 1; - - // Use new index to derive a new receiving address - final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - - final existing = await db - .getAddresses(walletId) - .filter() - .valueEqualTo(newReceivingAddress.value) - .findFirst(); - if (existing == null) { - // Add that new change address - await db.putAddress(newReceivingAddress); - } else { - // we need to update the address - await db.updateAddress(existing, newReceivingAddress); - } - // keep checking until address with no tx history is set as current - await _checkReceivingAddressForTransactions(); - } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkChangeAddressForTransactions() async { - try { - final currentChange = await _currentChangeAddress; - final int txCount = await getTxCount(address: currentChange.value); - Logging.instance.log( - 'Number of txs for current change address $currentChange: $txCount', - level: LogLevel.Info); - - if (txCount >= 1 || currentChange.derivationIndex < 0) { - // First increment the change index - final newChangeIndex = currentChange.derivationIndex + 1; - - // Use new index to derive a new change address - final newChangeAddress = await _generateAddressForChain( - 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - - final existing = await db - .getAddresses(walletId) - .filter() - .valueEqualTo(newChangeAddress.value) - .findFirst(); - if (existing == null) { - // Add that new change address - await db.putAddress(newChangeAddress); - } else { - // we need to update the address - await db.updateAddress(existing, newChangeAddress); - } - - // keep checking until address with no tx history is set as current - await _checkChangeAddressForTransactions(); - } - } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", - level: LogLevel.Error); - return; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkCurrentReceivingAddressesForTransactions() async { - try { - // for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(); - // } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - /// public wrapper because dart can't test private... - Future checkCurrentReceivingAddressesForTransactions() async { - if (Platform.environment["FLUTTER_TEST"] == "true") { - try { - return _checkCurrentReceivingAddressesForTransactions(); - } catch (_) { - rethrow; - } - } - } - - Future _checkCurrentChangeAddressesForTransactions() async { - try { - // for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(); - // } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - /// public wrapper because dart can't test private... - Future checkCurrentChangeAddressesForTransactions() async { - if (Platform.environment["FLUTTER_TEST"] == "true") { - try { - return _checkCurrentChangeAddressesForTransactions(); - } catch (_) { - rethrow; - } - } - } - - /// attempts to convert a string to a valid scripthash - /// - /// Returns the scripthash or throws an exception on invalid namecoin address - String _convertToScriptHash(String namecoinAddress, NetworkType network) { - try { - final output = Address.addressToOutputScript( - namecoinAddress, network, namecoin.bech32!); - final hash = sha256.convert(output.toList(growable: false)).toString(); - - final chars = hash.split(""); - final reversedPairs = []; - var i = chars.length - 1; - while (i > 0) { - reversedPairs.add(chars[i - 1]); - reversedPairs.add(chars[i]); - i -= 2; - } - return reversedPairs.join(""); - } catch (e) { - rethrow; - } - } - - Future>> _fetchHistory( - List allAddresses) async { - try { - List> allTxHashes = []; - - final Map>> batches = {}; - final Map requestIdToAddressMap = {}; - const batchSizeMax = 100; - int batchNumber = 0; - for (int i = 0; i < allAddresses.length; i++) { - if (batches[batchNumber] == null) { - batches[batchNumber] = {}; - } - final scripthash = _convertToScriptHash(allAddresses[i], _network); - final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); - requestIdToAddressMap[id] = allAddresses[i]; - batches[batchNumber]!.addAll({ - id: [scripthash] - }); - if (i % batchSizeMax == batchSizeMax - 1) { - batchNumber++; - } - } - - for (int i = 0; i < batches.length; i++) { - final response = - await _electrumXClient.getBatchHistory(args: batches[i]!); - for (final entry in response.entries) { - for (int j = 0; j < entry.value.length; j++) { - entry.value[j]["address"] = requestIdToAddressMap[entry.key]; - if (!allTxHashes.contains(entry.value[j])) { - allTxHashes.add(entry.value[j]); - } - } - } - } - - return allTxHashes; - } catch (e, s) { - Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error); - rethrow; - } - } - - bool _duplicateTxCheck( - List> allTransactions, String txid) { - for (int i = 0; i < allTransactions.length; i++) { - if (allTransactions[i]["txid"] == txid) { - return true; - } - } - return false; - } - - Future>> fastFetch(List allTxHashes) async { - List> allTransactions = []; - - const futureLimit = 30; - List>> transactionFutures = []; - int currentFutureCount = 0; - for (final txHash in allTxHashes) { - Future> transactionFuture = - cachedElectrumXClient.getTransaction( - txHash: txHash, - verbose: true, - coin: coin, - ); - transactionFutures.add(transactionFuture); - currentFutureCount++; - if (currentFutureCount > futureLimit) { - currentFutureCount = 0; - await Future.wait(transactionFutures); - for (final fTx in transactionFutures) { - final tx = await fTx; - - allTransactions.add(tx); - } - } - } - if (currentFutureCount != 0) { - currentFutureCount = 0; - await Future.wait(transactionFutures); - for (final fTx in transactionFutures) { - final tx = await fTx; - - allTransactions.add(tx); - } - } - return allTransactions; - } - - Future _refreshTransactions() async { - final List allAddresses = - await _fetchAllOwnAddresses(); - - final List> allTxHashes = - await _fetchHistory(allAddresses.map((e) => e.value).toList()); - - Set hashes = {}; - for (var element in allTxHashes) { - hashes.add(element['tx_hash'] as String); - } - await fastFetch(hashes.toList()); - List> allTransactions = []; - final currentHeight = await chainHeight; - - for (final txHash in allTxHashes) { - final storedTx = await db - .getTransactions(walletId) - .filter() - .txidEqualTo(txHash["tx_hash"] as String) - .findFirst(); - - if (storedTx == null || - !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); - - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = await db - .getAddresses(walletId) - .filter() - .valueEqualTo(txHash["address"] as String) - .findFirst(); - tx["height"] = txHash["height"]; - allTransactions.add(tx); - } - } - } - - // Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - // Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); - // - // Logging.instance.log("allTransactions length: ${allTransactions.length}", - // level: LogLevel.Info); - - Set vHashes = {}; - for (final txObject in allTransactions) { - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - vHashes.add(prevTxid); - } - } - await fastFetch(vHashes.toList()); - - final List> txnsData = - []; - - for (final txObject in allTransactions) { - final data = await parseTransaction( - txObject, - cachedElectrumXClient, - allAddresses, - coin, - MINIMUM_CONFIRMATIONS, - walletId, - ); - - txnsData.add(data); - } - await db.addNewTransactionData(txnsData, walletId); - - // quick hack to notify manager to call notifyListeners if - // transactions changed - if (txnsData.isNotEmpty) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Transactions updated/added for: $walletId $walletName ", - walletId, - ), - ); - } - } - - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); - } - - /// The coinselection algorithm decides whether or not the user is eligible to make the transaction - /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return - /// a map containing the tx hex along with other important information. If not, then it will return - /// an integer (1 or 2) - dynamic coinSelection({ - required int satoshiAmountToSend, - required int selectedTxFeeRate, - required String recipientAddress, - required bool coinControl, - required bool isSendAll, - int? satsPerVByte, - int additionalOutputs = 0, - List? utxos, - }) async { - Logging.instance - .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? await this.utxos; - final currentChainHeight = await chainHeight; - final List spendableOutputs = []; - int spendableSatoshiValue = 0; - - // Build list of spendable outputs and totaling their satoshi amount - for (final utxo in availableOutputs) { - if (utxo.isBlocked == false && - utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && - utxo.used != true) { - spendableOutputs.add(utxo); - spendableSatoshiValue += utxo.value; - } - } - - if (coinControl) { - if (spendableOutputs.length < availableOutputs.length) { - throw ArgumentError("Attempted to use an unavailable utxo"); - } - } - - // don't care about sorting if using all utxos - if (!coinControl) { - // sort spendable by age (oldest first) - spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); - } - - Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", - level: LogLevel.Info); - Logging.instance - .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); - Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", - level: LogLevel.Info); - Logging.instance - .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); - // If the amount the user is trying to send is smaller than the amount that they have spendable, - // then return 1, which indicates that they have an insufficient balance. - if (spendableSatoshiValue < satoshiAmountToSend) { - return 1; - // If the amount the user wants to send is exactly equal to the amount they can spend, then return - // 2, which indicates that they are not leaving enough over to pay the transaction fee - } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { - return 2; - } - // If neither of these statements pass, we assume that the user has a spendable balance greater - // than the amount they're attempting to send. Note that this value still does not account for - // the added transaction fee, which may require an extra input and will need to be checked for - // later on. - - // Possible situation right here - int satoshisBeingUsed = 0; - int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; - - if (!coinControl) { - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && - i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && - inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; - } - } else { - satoshisBeingUsed = spendableSatoshiValue; - utxoObjectsToUse = spendableOutputs; - inputsBeingConsumed = spendableOutputs.length; - } - - Logging.instance - .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); - Logging.instance - .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); - Logging.instance - .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); - - // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [recipientAddress]; - List recipientsAmtArray = [satoshiAmountToSend]; - - // gather required signing data - final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); - - if (isSendAll) { - Logging.instance - .log("Attempting to send all $coin", level: LogLevel.Info); - - final int vSizeForOneOutput = (await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: [recipientAddress], - satoshiAmounts: [satoshisBeingUsed - 1], - ))["vSize"] as int; - int feeForOneOutput = satsPerVByte != null - ? (satsPerVByte * vSizeForOneOutput) - : estimateTxFee( - vSize: vSizeForOneOutput, - feeRatePerKB: selectedTxFeeRate, - ); - - if (satsPerVByte == null) { - final int roughEstimate = roughFeeEstimate( - spendableOutputs.length, - 1, - selectedTxFeeRate, - ).raw.toInt(); - if (feeForOneOutput < roughEstimate) { - feeForOneOutput = roughEstimate; - } - } - - final int amount = satoshiAmountToSend - feeForOneOutput; - dynamic txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: [amount], - ); - Map transactionObject = { - "hex": txn["hex"], - "recipient": recipientsArray[0], - "recipientAmt": Amount( - rawValue: BigInt.from(amount), - fractionDigits: coin.decimals, - ), - "fee": feeForOneOutput, - "vSize": txn["vSize"], - "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), - }; - return transactionObject; - } - - final int vSizeForOneOutput = (await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: [recipientAddress], - satoshiAmounts: [satoshisBeingUsed - 1], - ))["vSize"] as int; - final int vSizeForTwoOutPuts = (await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: [ - recipientAddress, - await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), - ], - satoshiAmounts: [ - satoshiAmountToSend, - satoshisBeingUsed - satoshiAmountToSend - 1 - ], // dust limit is the minimum amount a change output should be - ))["vSize"] as int; - - // Assume 1 output, only for recipient and no change - final feeForOneOutput = satsPerVByte != null - ? (satsPerVByte * vSizeForOneOutput) - : estimateTxFee( - vSize: vSizeForOneOutput, - feeRatePerKB: selectedTxFeeRate, - ); - // Assume 2 outputs, one for recipient and one for change - final feeForTwoOutputs = satsPerVByte != null - ? (satsPerVByte * vSizeForTwoOutPuts) - : estimateTxFee( - vSize: vSizeForTwoOutPuts, - feeRatePerKB: selectedTxFeeRate, - ); - - Logging.instance - .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); - Logging.instance - .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); - - if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { - if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT.raw.toInt()) { - // Here, we know that theoretically, we may be able to include another output(change) but we first need to - // factor in the value of this output in satoshis. - int changeOutputSize = - satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs; - // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and - // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new - // change address. - if (changeOutputSize > DUST_LIMIT.raw.toInt() && - satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == - feeForTwoOutputs) { - // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(); - final String newChangeAddress = await _getCurrentAddressForChain( - 1, DerivePathTypeExt.primaryFor(coin)); - - int feeBeingPaid = - satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; - - recipientsArray.add(newChangeAddress); - recipientsAmtArray.add(changeOutputSize); - // At this point, we have the outputs we're going to use, the amounts to send along with which addresses - // we intend to send these amounts to. We have enough to send instructions to build the transaction. - Logging.instance.log('2 outputs in tx', level: LogLevel.Info); - Logging.instance - .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); - Logging.instance.log('Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info); - Logging.instance.log('Change Output Size: $changeOutputSize', - level: LogLevel.Info); - Logging.instance.log( - 'Difference (fee being paid): $feeBeingPaid sats', - level: LogLevel.Info); - Logging.instance - .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); - dynamic txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: recipientsAmtArray, - ); - - // make sure minimum fee is accurate if that is being used - if (txn["vSize"] - feeBeingPaid == 1) { - int changeOutputSize = - satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int); - feeBeingPaid = - satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; - recipientsAmtArray.removeLast(); - recipientsAmtArray.add(changeOutputSize); - Logging.instance.log('Adjusted Input size: $satoshisBeingUsed', - level: LogLevel.Info); - Logging.instance.log( - 'Adjusted Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info); - Logging.instance.log( - 'Adjusted Change Output Size: $changeOutputSize', - level: LogLevel.Info); - Logging.instance.log( - 'Adjusted Difference (fee being paid): $feeBeingPaid sats', - level: LogLevel.Info); - Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', - level: LogLevel.Info); - txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: recipientsAmtArray, - ); - } - - Map transactionObject = { - "hex": txn["hex"], - "recipient": recipientsArray[0], - "recipientAmt": Amount( - rawValue: BigInt.from(recipientsAmtArray[0]), - fractionDigits: coin.decimals, - ), - "fee": feeBeingPaid, - "vSize": txn["vSize"], - "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), - }; - return transactionObject; - } else { - // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize - // is smaller than or equal to DUST_LIMIT. Revert to single output transaction. - Logging.instance.log('1 output in tx', level: LogLevel.Info); - Logging.instance - .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); - Logging.instance.log('Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info); - Logging.instance.log( - 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', - level: LogLevel.Info); - Logging.instance - .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); - dynamic txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: recipientsAmtArray, - ); - Map transactionObject = { - "hex": txn["hex"], - "recipient": recipientsArray[0], - "recipientAmt": Amount( - rawValue: BigInt.from(recipientsAmtArray[0]), - fractionDigits: coin.decimals, - ), - "fee": satoshisBeingUsed - satoshiAmountToSend, - "vSize": txn["vSize"], - "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), - }; - return transactionObject; - } - } else { - // No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats - // which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct - // the wallet to begin crafting the transaction that the user requested. - Logging.instance.log('1 output in tx', level: LogLevel.Info); - Logging.instance - .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); - Logging.instance.log('Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info); - Logging.instance.log( - 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', - level: LogLevel.Info); - Logging.instance - .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); - dynamic txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: recipientsAmtArray, - ); - Map transactionObject = { - "hex": txn["hex"], - "recipient": recipientsArray[0], - "recipientAmt": Amount( - rawValue: BigInt.from(recipientsAmtArray[0]), - fractionDigits: coin.decimals, - ), - "fee": satoshisBeingUsed - satoshiAmountToSend, - "vSize": txn["vSize"], - "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), - }; - return transactionObject; - } - } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) { - // In this scenario, no additional change output is needed since inputs - outputs equal exactly - // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin - // crafting the transaction that the user requested. - Logging.instance.log('1 output in tx', level: LogLevel.Info); - Logging.instance - .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); - Logging.instance.log('Recipient output size: $satoshiAmountToSend', - level: LogLevel.Info); - Logging.instance.log( - 'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats', - level: LogLevel.Info); - Logging.instance - .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); - dynamic txn = await buildTransaction( - utxoSigningData: utxoSigningData, - recipients: recipientsArray, - satoshiAmounts: recipientsAmtArray, - ); - Map transactionObject = { - "hex": txn["hex"], - "recipient": recipientsArray[0], - "recipientAmt": Amount( - rawValue: BigInt.from(recipientsAmtArray[0]), - fractionDigits: coin.decimals, - ), - "fee": feeForOneOutput, - "vSize": txn["vSize"], - "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), - }; - return transactionObject; - } else { - // Remember that returning 2 indicates that the user does not have a sufficient balance to - // pay for the transaction fee. Ideally, at this stage, we should check if the user has any - // additional outputs they're able to spend and then recalculate fees. - Logging.instance.log( - 'Cannot pay tx fee - checking for more outputs and trying again', - level: LogLevel.Warning); - // try adding more outputs - if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection( - satoshiAmountToSend: satoshiAmountToSend, - selectedTxFeeRate: selectedTxFeeRate, - satsPerVByte: satsPerVByte, - recipientAddress: recipientAddress, - isSendAll: isSendAll, - additionalOutputs: additionalOutputs + 1, - utxos: utxos, - coinControl: coinControl, - ); - } - return 2; - } - } - - Future> fetchBuildTxData( - List utxosToUse, - ) async { - // return data - List signingData = []; - - try { - // Populating the addresses to check - for (var i = 0; i < utxosToUse.length; i++) { - if (utxosToUse[i].address == null) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - utxosToUse[i] = utxosToUse[i].copyWith( - address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]["address"] as String, - ); - } - } - } - - final derivePathType = addressType(address: utxosToUse[i].address!); - - signingData.add( - SigningData( - derivePathType: derivePathType, - utxo: utxosToUse[i], - ), - ); - } - - Map> receiveDerivations = {}; - Map> changeDerivations = {}; - - for (final sd in signingData) { - String? pubKey; - String? wif; - - // fetch receiving derivations if null - receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( - chain: 0, - derivePathType: sd.derivePathType, - ); - final receiveDerivation = - receiveDerivations[sd.derivePathType]![sd.utxo.address!]; - - if (receiveDerivation != null) { - pubKey = receiveDerivation["pubKey"] as String; - wif = receiveDerivation["wif"] as String; - } else { - // fetch change derivations if null - changeDerivations[sd.derivePathType] ??= await _fetchDerivations( - chain: 1, - derivePathType: sd.derivePathType, - ); - final changeDerivation = - changeDerivations[sd.derivePathType]![sd.utxo.address!]; - if (changeDerivation != null) { - pubKey = changeDerivation["pubKey"] as String; - wif = changeDerivation["wif"] as String; - } - } - - if (wif == null || pubKey == null) { - final address = await db.getAddress(walletId, sd.utxo.address!); - if (address?.derivationPath != null) { - final node = await Bip32Utils.getBip32Node( - (await mnemonicString)!, - (await mnemonicPassphrase)!, - _network, - address!.derivationPath!.value, - ); - - wif = node.toWIF(); - pubKey = Format.uint8listToString(node.publicKey); - } - } - - if (wif != null && pubKey != null) { - final PaymentData data; - final Uint8List? redeemScript; - - switch (sd.derivePathType) { - case DerivePathType.bip44: - data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List(pubKey), - ), - network: _network, - ).data; - redeemScript = null; - break; - - case DerivePathType.bip49: - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List(pubKey), - ), - network: _network, - overridePrefix: _network.bech32!, - ).data; - redeemScript = p2wpkh.output; - data = P2SH(data: PaymentData(redeem: p2wpkh), network: _network) - .data; - break; - - case DerivePathType.bip84: - data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List(pubKey), - ), - network: _network, - overridePrefix: _network.bech32!, - ).data; - redeemScript = null; - break; - - default: - throw Exception("DerivePathType unsupported"); - } - - final keyPair = ECPair.fromWIF( - wif, - network: _network, - ); - - sd.redeemScript = redeemScript; - sd.output = data.output; - sd.keyPair = keyPair; - } - } - - return signingData; - } catch (e, s) { - Logging.instance - .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); - rethrow; - } - } - - /// Builds and signs a transaction - Future> buildTransaction({ - required List utxoSigningData, - required List recipients, - required List satoshiAmounts, - }) async { - Logging.instance - .log("Starting buildTransaction ----------", level: LogLevel.Info); - - final txb = TransactionBuilder(network: _network); - txb.setVersion(2); - - // Add transaction inputs - for (var i = 0; i < utxoSigningData.length; i++) { - final txid = utxoSigningData[i].utxo.txid; - txb.addInput( - txid, - utxoSigningData[i].utxo.vout, - null, - utxoSigningData[i].output!, - _network.bech32!, - ); - } - - // Add transaction output - for (var i = 0; i < recipients.length; i++) { - txb.addOutput(recipients[i], satoshiAmounts[i], _network.bech32!); - } - - try { - // Sign the transaction accordingly - for (var i = 0; i < utxoSigningData.length; i++) { - txb.sign( - vin: i, - keyPair: utxoSigningData[i].keyPair!, - witnessValue: utxoSigningData[i].utxo.value, - redeemScript: utxoSigningData[i].redeemScript, - overridePrefix: _network.bech32!, - ); - } - } catch (e, s) { - Logging.instance.log("Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error); - rethrow; - } - - final builtTx = txb.build(_network.bech32!); - final vSize = builtTx.virtualSize(); - - return {"hex": builtTx.toHex(), "vSize": vSize}; - } - - @override - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - Logging.instance.log("Starting full rescan!", level: LogLevel.Info); - longMutex = true; - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - // clear cache - await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); - - // back up data - // await _rescanBackup(); - - // clear blockchain info - await db.deleteWalletBlockchainData(walletId); - await _deleteDerivations(); - - try { - final _mnemonic = await mnemonicString; - final _mnemonicPassphrase = await mnemonicPassphrase; - if (_mnemonicPassphrase == null) { - Logging.instance.log( - "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", - level: LogLevel.Error); - } - - await _recoverWalletFromBIP32SeedPhrase( - mnemonic: _mnemonic!, - mnemonicPassphrase: _mnemonicPassphrase!, - maxUnusedAddressGap: maxUnusedAddressGap, - maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, - isRescan: true, - ); - - longMutex = false; - await refresh(); - Logging.instance.log("Full rescan complete!", level: LogLevel.Info); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - } catch (e, s) { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - - // restore from backup - // await _rescanRestore(); - - longMutex = false; - Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _deleteDerivations() async { - // P2PKH derivations - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); - - // P2SH derivations - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); - - // P2WPKH derivations - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - } - - // Future _rescanRestore() async { - // Logging.instance.log("starting rescan restore", level: LogLevel.Info); - // - // // restore from backup - // // p2pkh - // final tempReceivingAddressesP2PKH = DB.instance - // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - // final tempChangeAddressesP2PKH = DB.instance - // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - // final tempReceivingIndexP2PKH = DB.instance - // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - // final tempChangeIndexP2PKH = DB.instance - // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2PKH', - // value: tempReceivingAddressesP2PKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2PKH', - // value: tempChangeAddressesP2PKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2PKH', - // value: tempReceivingIndexP2PKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeIndexP2PKH', - // value: tempChangeIndexP2PKH); - // await DB.instance.delete( - // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - // - // // p2Sh - // final tempReceivingAddressesP2SH = DB.instance - // .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); - // final tempChangeAddressesP2SH = DB.instance - // .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); - // final tempReceivingIndexP2SH = DB.instance - // .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); - // final tempChangeIndexP2SH = DB.instance - // .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2SH', - // value: tempReceivingAddressesP2SH); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2SH', - // value: tempChangeAddressesP2SH); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2SH', - // value: tempReceivingIndexP2SH); - // await DB.instance.put( - // boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); - // await DB.instance.delete( - // key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); - // - // // p2wpkh - // final tempReceivingAddressesP2WPKH = DB.instance.get( - // boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); - // final tempChangeAddressesP2WPKH = DB.instance - // .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); - // final tempReceivingIndexP2WPKH = DB.instance - // .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); - // final tempChangeIndexP2WPKH = DB.instance - // .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2WPKH', - // value: tempReceivingAddressesP2WPKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2WPKH', - // value: tempChangeAddressesP2WPKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2WPKH', - // value: tempReceivingIndexP2WPKH); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeIndexP2WPKH', - // value: tempChangeIndexP2WPKH); - // await DB.instance.delete( - // key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); - // await DB.instance.delete( - // key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); - // await DB.instance - // .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); - // - // // P2PKH derivations - // final p2pkhReceiveDerivationsString = await _secureStore.read( - // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - // final p2pkhChangeDerivationsString = await _secureStore.read( - // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2PKH", - // value: p2pkhReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2PKH", - // value: p2pkhChangeDerivationsString); - // - // await _secureStore.delete( - // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - // - // // P2SH derivations - // final p2shReceiveDerivationsString = await _secureStore.read( - // key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - // final p2shChangeDerivationsString = await _secureStore.read( - // key: "${walletId}_changeDerivationsP2SH_BACKUP"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2SH", - // value: p2shReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2SH", - // value: p2shChangeDerivationsString); - // - // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); - // - // // P2WPKH derivations - // final p2wpkhReceiveDerivationsString = await _secureStore.read( - // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - // final p2wpkhChangeDerivationsString = await _secureStore.read( - // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2WPKH", - // value: p2wpkhReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2WPKH", - // value: p2wpkhChangeDerivationsString); - // - // await _secureStore.delete( - // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - // await _secureStore.delete( - // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - // - // // UTXOs - // final utxoData = DB.instance - // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - // await DB.instance.put( - // boxName: walletId, key: 'latest_utxo_model', value: utxoData); - // await DB.instance - // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - // - // Logging.instance.log("rescan restore complete", level: LogLevel.Info); - // } - // - // Future _rescanBackup() async { - // Logging.instance.log("starting rescan backup", level: LogLevel.Info); - // - // // backup current and clear data - // // p2pkh - // final tempReceivingAddressesP2PKH = DB.instance - // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2PKH_BACKUP', - // value: tempReceivingAddressesP2PKH); - // await DB.instance - // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - // - // final tempChangeAddressesP2PKH = DB.instance - // .get(boxName: walletId, key: 'changeAddressesP2PKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2PKH_BACKUP', - // value: tempChangeAddressesP2PKH); - // await DB.instance - // .delete(key: 'changeAddressesP2PKH', boxName: walletId); - // - // final tempReceivingIndexP2PKH = - // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2PKH_BACKUP', - // value: tempReceivingIndexP2PKH); - // await DB.instance - // .delete(key: 'receivingIndexP2PKH', boxName: walletId); - // - // final tempChangeIndexP2PKH = - // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeIndexP2PKH_BACKUP', - // value: tempChangeIndexP2PKH); - // await DB.instance - // .delete(key: 'changeIndexP2PKH', boxName: walletId); - // - // // p2sh - // final tempReceivingAddressesP2SH = DB.instance - // .get(boxName: walletId, key: 'receivingAddressesP2SH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2SH_BACKUP', - // value: tempReceivingAddressesP2SH); - // await DB.instance - // .delete(key: 'receivingAddressesP2SH', boxName: walletId); - // - // final tempChangeAddressesP2SH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2SH_BACKUP', - // value: tempChangeAddressesP2SH); - // await DB.instance - // .delete(key: 'changeAddressesP2SH', boxName: walletId); - // - // final tempReceivingIndexP2SH = - // DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2SH_BACKUP', - // value: tempReceivingIndexP2SH); - // await DB.instance - // .delete(key: 'receivingIndexP2SH', boxName: walletId); - // - // final tempChangeIndexP2SH = - // DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeIndexP2SH_BACKUP', - // value: tempChangeIndexP2SH); - // await DB.instance - // .delete(key: 'changeIndexP2SH', boxName: walletId); - // - // // p2wpkh - // final tempReceivingAddressesP2WPKH = DB.instance - // .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingAddressesP2WPKH_BACKUP', - // value: tempReceivingAddressesP2WPKH); - // await DB.instance - // .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); - // - // final tempChangeAddressesP2WPKH = DB.instance - // .get(boxName: walletId, key: 'changeAddressesP2WPKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeAddressesP2WPKH_BACKUP', - // value: tempChangeAddressesP2WPKH); - // await DB.instance - // .delete(key: 'changeAddressesP2WPKH', boxName: walletId); - // - // final tempReceivingIndexP2WPKH = DB.instance - // .get(boxName: walletId, key: 'receivingIndexP2WPKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'receivingIndexP2WPKH_BACKUP', - // value: tempReceivingIndexP2WPKH); - // await DB.instance - // .delete(key: 'receivingIndexP2WPKH', boxName: walletId); - // - // final tempChangeIndexP2WPKH = - // DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); - // await DB.instance.put( - // boxName: walletId, - // key: 'changeIndexP2WPKH_BACKUP', - // value: tempChangeIndexP2WPKH); - // await DB.instance - // .delete(key: 'changeIndexP2WPKH', boxName: walletId); - // - // // P2PKH derivations - // final p2pkhReceiveDerivationsString = - // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - // final p2pkhChangeDerivationsString = - // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - // value: p2pkhReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2PKH_BACKUP", - // value: p2pkhChangeDerivationsString); - // - // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); - // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); - // - // // P2SH derivations - // final p2shReceiveDerivationsString = - // await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); - // final p2shChangeDerivationsString = - // await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2SH_BACKUP", - // value: p2shReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2SH_BACKUP", - // value: p2shChangeDerivationsString); - // - // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); - // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); - // - // // P2WPKH derivations - // final p2wpkhReceiveDerivationsString = - // await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); - // final p2wpkhChangeDerivationsString = - // await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); - // - // await _secureStore.write( - // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", - // value: p2wpkhReceiveDerivationsString); - // await _secureStore.write( - // key: "${walletId}_changeDerivationsP2WPKH_BACKUP", - // value: p2wpkhChangeDerivationsString); - // - // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); - // await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - // - // // UTXOs - // final utxoData = - // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - // await DB.instance.put( - // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - // await DB.instance - // .delete(key: 'latest_utxo_model', boxName: walletId); - // - // Logging.instance.log("rescan backup complete", level: LogLevel.Info); - // } - - bool isActive = false; - - @override - void Function(bool)? get onIsActiveWalletChanged => - (isActive) => this.isActive = isActive; - - @override - Future estimateFeeFor(Amount amount, int feeRate) async { - final available = balance.spendable; - - if (available == amount) { - return amount - (await sweepAllEstimate(feeRate)); - } else if (amount <= Amount.zero || amount > available) { - return roughFeeEstimate(1, 2, feeRate); - } - - Amount runningBalance = Amount( - rawValue: BigInt.zero, - fractionDigits: coin.decimals, - ); - int inputCount = 0; - for (final output in (await utxos)) { - if (!output.isBlocked) { - runningBalance = runningBalance + - Amount( - rawValue: BigInt.from(output.value), - fractionDigits: coin.decimals, - ); - inputCount++; - if (runningBalance > amount) { - break; - } - } - } - - final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); - final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - - if (runningBalance - amount > oneOutPutFee) { - if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - amount - twoOutPutFee; - if (change > DUST_LIMIT && - runningBalance - amount - change == twoOutPutFee) { - return runningBalance - amount - change; - } else { - return runningBalance - amount; - } - } else { - return runningBalance - amount; - } - } else if (runningBalance - amount == oneOutPutFee) { - return oneOutPutFee; - } else { - return twoOutPutFee; - } - } - - // TODO: Check if this is the correct formula for namecoin - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return Amount( - rawValue: BigInt.from( - ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil()), - fractionDigits: coin.decimals, - ); - } - - Future sweepAllEstimate(int feeRate) async { - int available = 0; - int inputCount = 0; - for (final output in (await utxos)) { - if (!output.isBlocked && - output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { - available += output.value; - inputCount++; - } - } - - // transaction will only have 1 output minus the fee - final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - - return Amount( - rawValue: BigInt.from(available), - fractionDigits: coin.decimals, - ) - - estimatedFee; - } - - @override - Future generateNewAddress() async { - try { - final currentReceiving = await _currentReceivingAddress; - - final newReceivingIndex = currentReceiving.derivationIndex + 1; - - // Use new index to derive a new receiving address - final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - - // Add that new receiving address - await db.putAddress(newReceivingAddress); - - return true; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from generateNewAddress(): $e\n$s", - level: LogLevel.Error); - return false; - } - } - - @override - Future get xpub async { - final node = await Bip32Utils.getBip32Root( - (await mnemonic).join(" "), - await mnemonicPassphrase ?? "", - _network, - ); - - return node.neutered().toBase58(); - } -} - -// Namecoin Network -final namecoin = NetworkType( - messagePrefix: '\x18Namecoin Signed Message:\n', - bech32: 'nc', - bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), - pubKeyHash: 0x34, //From 52 - scriptHash: 0x0d, //13 - wif: 0xb4); //from 180 +// /* +// * This file is part of Stack Wallet. +// * +// * Copyright (c) 2023 Cypher Stack +// * All Rights Reserved. +// * The code is distributed under GPLv3 license, see LICENSE file for details. +// * Generated by Cypher Stack on 2023-05-26 +// * +// */ +// +// import 'dart:async'; +// import 'dart:convert'; +// import 'dart:io'; +// +// import 'package:bech32/bech32.dart'; +// import 'package:bip32/bip32.dart' as bip32; +// import 'package:bip39/bip39.dart' as bip39; +// import 'package:bitcoindart/bitcoindart.dart'; +// import 'package:bs58check/bs58check.dart' as bs58check; +// import 'package:crypto/crypto.dart'; +// import 'package:decimal/decimal.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:isar/isar.dart'; +// import 'package:stackwallet/db/isar/main_db.dart'; +// import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart'; +// import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; +// import 'package:stackwallet/models/balance.dart'; +// import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; +// import 'package:stackwallet/models/paymint/fee_object_model.dart'; +// import 'package:stackwallet/models/signing_data.dart'; +// import 'package:stackwallet/services/coins/coin_service.dart'; +// import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +// import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +// import 'package:stackwallet/services/mixins/wallet_cache.dart'; +// import 'package:stackwallet/services/mixins/wallet_db.dart'; +// import 'package:stackwallet/services/mixins/xpubable.dart'; +// import 'package:stackwallet/services/node_service.dart'; +// import 'package:stackwallet/services/transaction_notification_tracker.dart'; +// import 'package:stackwallet/utilities/amount/amount.dart'; +// import 'package:stackwallet/utilities/bip32_utils.dart'; +// import 'package:stackwallet/utilities/constants.dart'; +// import 'package:stackwallet/utilities/default_nodes.dart'; +// import 'package:stackwallet/utilities/enums/coin_enum.dart'; +// import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; +// import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +// import 'package:stackwallet/utilities/format.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// import 'package:stackwallet/utilities/prefs.dart'; +// import 'package:stackwallet/widgets/crypto_notifications.dart'; +// import 'package:tuple/tuple.dart'; +// import 'package:uuid/uuid.dart'; +// +// const int MINIMUM_CONFIRMATIONS = 2; +// // Find real dust limit +// final Amount DUST_LIMIT = Amount( +// rawValue: BigInt.from(546), +// fractionDigits: Coin.particl.decimals, +// ); +// +// const String GENESIS_HASH_MAINNET = +// "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"; +// const String GENESIS_HASH_TESTNET = +// "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"; +// +// String constructDerivePath({ +// required DerivePathType derivePathType, +// required int networkWIF, +// int account = 0, +// required int chain, +// required int index, +// }) { +// String coinType; +// switch (networkWIF) { +// case 0xb4: // nmc mainnet wif +// coinType = "7"; // nmc mainnet +// break; +// default: +// throw Exception("Invalid Namecoin network wif used!"); +// } +// +// int purpose; +// switch (derivePathType) { +// case DerivePathType.bip44: +// purpose = 44; +// break; +// case DerivePathType.bip49: +// purpose = 49; +// break; +// case DerivePathType.bip84: +// purpose = 84; +// break; +// default: +// throw Exception("DerivePathType $derivePathType not supported"); +// } +// +// return "m/$purpose'/$coinType'/$account'/$chain/$index"; +// } +// +// class NamecoinWallet extends CoinServiceAPI +// with +// WalletCache, +// WalletDB, +// ElectrumXParsing +// // , CoinControlInterface +// implements +// XPubAble { +// NamecoinWallet({ +// required String walletId, +// required String walletName, +// required Coin coin, +// required ElectrumXClient client, +// required CachedElectrumXClient cachedClient, +// required TransactionNotificationTracker tracker, +// required SecureStorageInterface secureStore, +// MainDB? mockableOverride, +// }) { +// txTracker = tracker; +// _walletId = walletId; +// _walletName = walletName; +// _coin = coin; +// _electrumXClient = client; +// _cachedElectrumXClient = cachedClient; +// _secureStore = secureStore; +// initCache(walletId, coin); +// initWalletDB(mockableOverride: mockableOverride); +// // initCoinControlInterface( +// // walletId: walletId, +// // walletName: walletName, +// // coin: coin, +// // db: db, +// // getChainHeight: () => chainHeight, +// // refreshedBalanceCallback: (balance) async { +// // _balance = balance; +// // await updateCachedBalance(_balance!); +// // }, +// // ); +// } +// +// static const integrationTestFlag = +// bool.fromEnvironment("IS_INTEGRATION_TEST"); +// +// final _prefs = Prefs.instance; +// +// Timer? timer; +// late final Coin _coin; +// +// late final TransactionNotificationTracker txTracker; +// +// NetworkType get _network { +// switch (coin) { +// case Coin.namecoin: +// return namecoin; +// default: +// throw Exception("Invalid network type!"); +// } +// } +// +// @override +// set isFavorite(bool markFavorite) { +// _isFavorite = markFavorite; +// updateCachedIsFavorite(markFavorite); +// } +// +// @override +// bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); +// +// bool? _isFavorite; +// +// @override +// Coin get coin => _coin; +// +// @override +// Future> get utxos => db.getUTXOs(walletId).findAll(); +// +// @override +// Future> get transactions => +// db.getTransactions(walletId).sortByTimestampDesc().findAll(); +// +// @override +// Future get currentReceivingAddress async => +// (await _currentReceivingAddress).value; +// +// Future get _currentReceivingAddress async => +// (await db +// .getAddresses(walletId) +// .filter() +// .typeEqualTo(isar_models.AddressType.p2wpkh) +// .subTypeEqualTo(isar_models.AddressSubType.receiving) +// .sortByDerivationIndexDesc() +// .findFirst()) ?? +// await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); +// +// Future get currentChangeAddress async => +// (await _currentChangeAddress).value; +// +// Future get _currentChangeAddress async => +// (await db +// .getAddresses(walletId) +// .filter() +// .typeEqualTo(isar_models.AddressType.p2wpkh) +// .subTypeEqualTo(isar_models.AddressSubType.change) +// .sortByDerivationIndexDesc() +// .findFirst()) ?? +// await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); +// +// @override +// Future exit() async { +// _hasCalledExit = true; +// timer?.cancel(); +// timer = null; +// stopNetworkAlivePinging(); +// } +// +// bool _hasCalledExit = false; +// +// @override +// bool get hasCalledExit => _hasCalledExit; +// +// @override +// Future get fees => _feeObject ??= _getFees(); +// Future? _feeObject; +// +// @override +// Future get maxFee async { +// final fee = (await fees).fast as String; +// final satsFee = Decimal.parse(fee) * +// Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); +// return satsFee.floor().toBigInt().toInt(); +// } +// +// @override +// Future> get mnemonic => _getMnemonicList(); +// +// @override +// Future get mnemonicString => +// _secureStore.read(key: '${_walletId}_mnemonic'); +// +// @override +// Future get mnemonicPassphrase => _secureStore.read( +// key: '${_walletId}_mnemonicPassphrase', +// ); +// +// Future get chainHeight async { +// try { +// final result = await _electrumXClient.getBlockHeadTip(); +// final height = result["height"] as int; +// await updateCachedChainHeight(height); +// if (height > storedChainHeight) { +// GlobalEventBus.instance.fire( +// UpdatedInBackgroundEvent( +// "Updated current chain height in $walletId $walletName!", +// walletId, +// ), +// ); +// } +// return height; +// } catch (e, s) { +// Logging.instance.log("Exception caught in chainHeight: $e\n$s", +// level: LogLevel.Error); +// return storedChainHeight; +// } +// } +// +// @override +// int get storedChainHeight => getCachedChainHeight(); +// +// DerivePathType addressType({required String address}) { +// Uint8List? decodeBase58; +// Segwit? decodeBech32; +// try { +// decodeBase58 = bs58check.decode(address); +// } catch (err) { +// // Base58check decode fail +// } +// if (decodeBase58 != null) { +// if (decodeBase58[0] == _network.pubKeyHash) { +// // P2PKH +// return DerivePathType.bip44; +// } +// if (decodeBase58[0] == _network.scriptHash) { +// // P2SH +// return DerivePathType.bip49; +// } +// throw ArgumentError('Invalid version or Network mismatch'); +// } else { +// try { +// decodeBech32 = segwit.decode(address, namecoin.bech32!); +// } catch (err) { +// // Bech32 decode fail +// } +// if (_network.bech32 != decodeBech32!.hrp) { +// throw ArgumentError('Invalid prefix or Network mismatch'); +// } +// if (decodeBech32.version != 0) { +// throw ArgumentError('Invalid address version'); +// } +// // P2WPKH +// return DerivePathType.bip84; +// } +// } +// +// bool longMutex = false; +// +// @override +// Future recoverFromMnemonic({ +// required String mnemonic, +// String? mnemonicPassphrase, +// required int maxUnusedAddressGap, +// required int maxNumberOfIndexesToCheck, +// required int height, +// }) async { +// longMutex = true; +// final start = DateTime.now(); +// try { +// Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag", +// level: LogLevel.Info); +// if (!integrationTestFlag) { +// try { +// final features = await electrumXClient +// .getServerFeatures() +// .timeout(const Duration(seconds: 3)); +// Logging.instance.log("features: $features", level: LogLevel.Info); +// switch (coin) { +// case Coin.namecoin: +// if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { +// throw Exception("genesis hash does not match main net!"); +// } +// break; +// default: +// throw Exception( +// "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); +// } +// } catch (e, s) { +// Logging.instance.log("$e/n$s", level: LogLevel.Info); +// } +// } +// // check to make sure we aren't overwriting a mnemonic +// // this should never fail +// if ((await mnemonicString) != null || +// (await this.mnemonicPassphrase) != null) { +// longMutex = false; +// throw Exception("Attempted to overwrite mnemonic on restore!"); +// } +// await _secureStore.write( +// key: '${_walletId}_mnemonic', value: mnemonic.trim()); +// await _secureStore.write( +// key: '${_walletId}_mnemonicPassphrase', +// value: mnemonicPassphrase ?? "", +// ); +// +// await _recoverWalletFromBIP32SeedPhrase( +// mnemonic: mnemonic.trim(), +// mnemonicPassphrase: mnemonicPassphrase ?? "", +// maxUnusedAddressGap: maxUnusedAddressGap, +// maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, +// ); +// } 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); +// } +// +// Future> _checkGaps( +// int maxNumberOfIndexesToCheck, +// int maxUnusedAddressGap, +// int txCountBatchSize, +// bip32.BIP32 root, +// DerivePathType type, +// int chain) async { +// List addressArray = []; +// int returningIndex = -1; +// Map> derivations = {}; +// int gapCounter = 0; +// for (int index = 0; +// index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; +// index += txCountBatchSize) { +// List iterationsAddressArray = []; +// Logging.instance.log( +// "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", +// level: LogLevel.Info); +// +// final _id = "k_$index"; +// Map txCountCallArgs = {}; +// final Map receivingNodes = {}; +// +// for (int j = 0; j < txCountBatchSize; j++) { +// final derivePath = constructDerivePath( +// derivePathType: type, +// networkWIF: root.network.wif, +// chain: chain, +// index: index + j, +// ); +// final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); +// +// String addressString; +// isar_models.AddressType addrType; +// switch (type) { +// case DerivePathType.bip44: +// addressString = P2PKH( +// data: PaymentData(pubkey: node.publicKey), +// network: _network) +// .data +// .address!; +// addrType = isar_models.AddressType.p2pkh; +// break; +// case DerivePathType.bip49: +// addressString = P2SH( +// data: PaymentData( +// redeem: P2WPKH( +// data: PaymentData(pubkey: node.publicKey), +// network: _network, +// overridePrefix: namecoin.bech32!) +// .data), +// network: _network) +// .data +// .address!; +// addrType = isar_models.AddressType.p2sh; +// break; +// case DerivePathType.bip84: +// addressString = P2WPKH( +// network: _network, +// data: PaymentData(pubkey: node.publicKey), +// overridePrefix: namecoin.bech32!) +// .data +// .address!; +// addrType = isar_models.AddressType.p2wpkh; +// break; +// default: +// throw Exception("DerivePathType $type not supported"); +// } +// +// final address = isar_models.Address( +// walletId: walletId, +// subType: chain == 0 +// ? isar_models.AddressSubType.receiving +// : isar_models.AddressSubType.change, +// type: addrType, +// publicKey: node.publicKey, +// value: addressString, +// derivationIndex: index + j, +// derivationPath: isar_models.DerivationPath()..value = derivePath, +// ); +// +// receivingNodes.addAll({ +// "${_id}_$j": { +// "node": node, +// "address": address, +// } +// }); +// txCountCallArgs.addAll({ +// "${_id}_$j": addressString, +// }); +// } +// +// // get address tx counts +// final counts = await _getBatchTxCount(addresses: txCountCallArgs); +// +// // check and add appropriate addresses +// for (int k = 0; k < txCountBatchSize; k++) { +// int count = counts["${_id}_$k"]!; +// if (count > 0) { +// final node = receivingNodes["${_id}_$k"]; +// final address = node["address"] as isar_models.Address; +// // add address to array +// addressArray.add(address); +// iterationsAddressArray.add(address.value); +// // set current index +// returningIndex = index + k; +// // reset counter +// gapCounter = 0; +// // add info to derivations +// derivations[address.value] = { +// "pubKey": Format.uint8listToString( +// (node["node"] as bip32.BIP32).publicKey), +// "wif": (node["node"] as bip32.BIP32).toWIF(), +// }; +// } +// +// // increase counter when no tx history found +// if (count == 0) { +// gapCounter++; +// } +// } +// // cache all the transactions while waiting for the current function to finish. +// unawaited(getTransactionCacheEarly(iterationsAddressArray)); +// } +// return { +// "addressArray": addressArray, +// "index": returningIndex, +// "derivations": derivations +// }; +// } +// +// Future getTransactionCacheEarly(List allAddresses) async { +// try { +// final List> allTxHashes = +// await _fetchHistory(allAddresses); +// for (final txHash in allTxHashes) { +// try { +// unawaited(cachedElectrumXClient.getTransaction( +// txHash: txHash["tx_hash"] as String, +// verbose: true, +// coin: coin, +// )); +// } catch (e) { +// continue; +// } +// } +// } catch (e) { +// // +// } +// } +// +// Future _recoverWalletFromBIP32SeedPhrase({ +// required String mnemonic, +// required String mnemonicPassphrase, +// int maxUnusedAddressGap = 20, +// int maxNumberOfIndexesToCheck = 1000, +// bool isRescan = false, +// }) async { +// longMutex = true; +// +// Map> p2pkhReceiveDerivations = {}; +// Map> p2shReceiveDerivations = {}; +// Map> p2wpkhReceiveDerivations = {}; +// Map> p2pkhChangeDerivations = {}; +// Map> p2shChangeDerivations = {}; +// Map> p2wpkhChangeDerivations = {}; +// +// final root = await Bip32Utils.getBip32Root( +// mnemonic, +// mnemonicPassphrase, +// _network, +// ); +// +// List p2pkhReceiveAddressArray = []; +// List p2shReceiveAddressArray = []; +// List p2wpkhReceiveAddressArray = []; +// int p2pkhReceiveIndex = -1; +// int p2shReceiveIndex = -1; +// int p2wpkhReceiveIndex = -1; +// +// List p2pkhChangeAddressArray = []; +// List p2shChangeAddressArray = []; +// List p2wpkhChangeAddressArray = []; +// int p2pkhChangeIndex = -1; +// int p2shChangeIndex = -1; +// int p2wpkhChangeIndex = -1; +// +// // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 +// const txCountBatchSize = 12; +// +// try { +// // receiving addresses +// Logging.instance +// .log("checking receiving addresses...", level: LogLevel.Info); +// final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); +// +// final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); +// +// final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0); +// +// Logging.instance +// .log("checking change addresses...", level: LogLevel.Info); +// // change addresses +// final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); +// +// final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); +// +// final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck, +// maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1); +// +// await Future.wait([ +// resultReceive44, +// resultReceive49, +// resultReceive84, +// resultChange44, +// resultChange49, +// resultChange84 +// ]); +// +// p2pkhReceiveAddressArray = +// (await resultReceive44)['addressArray'] as List; +// p2pkhReceiveIndex = (await resultReceive44)['index'] as int; +// p2pkhReceiveDerivations = (await resultReceive44)['derivations'] +// as Map>; +// +// p2shReceiveAddressArray = +// (await resultReceive49)['addressArray'] as List; +// p2shReceiveIndex = (await resultReceive49)['index'] as int; +// p2shReceiveDerivations = (await resultReceive49)['derivations'] +// as Map>; +// +// p2wpkhReceiveAddressArray = +// (await resultReceive84)['addressArray'] as List; +// p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; +// p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] +// as Map>; +// +// p2pkhChangeAddressArray = +// (await resultChange44)['addressArray'] as List; +// p2pkhChangeIndex = (await resultChange44)['index'] as int; +// p2pkhChangeDerivations = (await resultChange44)['derivations'] +// as Map>; +// +// p2shChangeAddressArray = +// (await resultChange49)['addressArray'] as List; +// p2shChangeIndex = (await resultChange49)['index'] as int; +// p2shChangeDerivations = (await resultChange49)['derivations'] +// as Map>; +// +// p2wpkhChangeAddressArray = +// (await resultChange84)['addressArray'] as List; +// p2wpkhChangeIndex = (await resultChange84)['index'] as int; +// p2wpkhChangeDerivations = (await resultChange84)['derivations'] +// as Map>; +// +// // save the derivations (if any) +// if (p2pkhReceiveDerivations.isNotEmpty) { +// await addDerivations( +// chain: 0, +// derivePathType: DerivePathType.bip44, +// derivationsToAdd: p2pkhReceiveDerivations); +// } +// if (p2shReceiveDerivations.isNotEmpty) { +// await addDerivations( +// chain: 0, +// derivePathType: DerivePathType.bip49, +// derivationsToAdd: p2shReceiveDerivations); +// } +// if (p2wpkhReceiveDerivations.isNotEmpty) { +// await addDerivations( +// chain: 0, +// derivePathType: DerivePathType.bip84, +// derivationsToAdd: p2wpkhReceiveDerivations); +// } +// if (p2pkhChangeDerivations.isNotEmpty) { +// await addDerivations( +// chain: 1, +// derivePathType: DerivePathType.bip44, +// derivationsToAdd: p2pkhChangeDerivations); +// } +// if (p2shChangeDerivations.isNotEmpty) { +// await addDerivations( +// chain: 1, +// derivePathType: DerivePathType.bip49, +// derivationsToAdd: p2shChangeDerivations); +// } +// if (p2wpkhChangeDerivations.isNotEmpty) { +// await addDerivations( +// chain: 1, +// derivePathType: DerivePathType.bip84, +// derivationsToAdd: p2wpkhChangeDerivations); +// } +// +// // If restoring a wallet that never received any funds, then set receivingArray manually +// // If we didn't do this, it'd store an empty array +// if (p2pkhReceiveIndex == -1) { +// final address = +// await _generateAddressForChain(0, 0, DerivePathType.bip44); +// p2pkhReceiveAddressArray.add(address); +// } +// if (p2shReceiveIndex == -1) { +// final address = +// await _generateAddressForChain(0, 0, DerivePathType.bip49); +// p2shReceiveAddressArray.add(address); +// } +// if (p2wpkhReceiveIndex == -1) { +// final address = +// await _generateAddressForChain(0, 0, DerivePathType.bip84); +// p2wpkhReceiveAddressArray.add(address); +// } +// +// // If restoring a wallet that never sent any funds with change, then set changeArray +// // manually. If we didn't do this, it'd store an empty array. +// if (p2pkhChangeIndex == -1) { +// final address = +// await _generateAddressForChain(1, 0, DerivePathType.bip44); +// p2pkhChangeAddressArray.add(address); +// } +// if (p2shChangeIndex == -1) { +// final address = +// await _generateAddressForChain(1, 0, DerivePathType.bip49); +// p2shChangeAddressArray.add(address); +// } +// if (p2wpkhChangeIndex == -1) { +// final address = +// await _generateAddressForChain(1, 0, DerivePathType.bip84); +// p2wpkhChangeAddressArray.add(address); +// } +// +// if (isRescan) { +// await db.updateOrPutAddresses([ +// ...p2wpkhReceiveAddressArray, +// ...p2wpkhChangeAddressArray, +// ...p2pkhReceiveAddressArray, +// ...p2pkhChangeAddressArray, +// ...p2shReceiveAddressArray, +// ...p2shChangeAddressArray, +// ]); +// } else { +// await db.putAddresses([ +// ...p2wpkhReceiveAddressArray, +// ...p2wpkhChangeAddressArray, +// ...p2pkhReceiveAddressArray, +// ...p2pkhChangeAddressArray, +// ...p2shReceiveAddressArray, +// ...p2shChangeAddressArray, +// ]); +// } +// +// await _updateUTXOs(); +// +// await Future.wait([ +// updateCachedId(walletId), +// updateCachedIsFavorite(false), +// ]); +// +// longMutex = false; +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", +// level: LogLevel.Error); +// +// longMutex = false; +// rethrow; +// } +// } +// +// Future refreshIfThereIsNewData() async { +// if (longMutex) return false; +// if (_hasCalledExit) return false; +// Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info); +// +// try { +// bool needsRefresh = false; +// Set txnsToCheck = {}; +// +// for (final String txid in txTracker.pendings) { +// if (!txTracker.wasNotifiedConfirmed(txid)) { +// txnsToCheck.add(txid); +// } +// } +// +// for (String txid in txnsToCheck) { +// final txn = await electrumXClient.getTransaction(txHash: txid); +// int confirmations = txn["confirmations"] as int? ?? 0; +// bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; +// if (!isUnconfirmed) { +// // unconfirmedTxs = {}; +// needsRefresh = true; +// break; +// } +// } +// if (!needsRefresh) { +// var allOwnAddresses = await _fetchAllOwnAddresses(); +// List> allTxs = await _fetchHistory( +// allOwnAddresses.map((e) => e.value).toList(growable: false)); +// for (Map transaction in allTxs) { +// final txid = transaction['tx_hash'] as String; +// if ((await db +// .getTransactions(walletId) +// .filter() +// .txidMatches(txid) +// .findFirst()) == +// null) { +// Logging.instance.log( +// " txid not found in address history already ${transaction['tx_hash']}", +// level: LogLevel.Info); +// needsRefresh = true; +// break; +// } +// } +// } +// return needsRefresh; +// } catch (e, s) { +// Logging.instance.log( +// "Exception caught in refreshIfThereIsNewData: $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future getAllTxsToWatch() async { +// if (_hasCalledExit) return; +// List unconfirmedTxnsToNotifyPending = []; +// List unconfirmedTxnsToNotifyConfirmed = []; +// +// final currentChainHeight = await chainHeight; +// +// final txCount = await db.getTransactions(walletId).count(); +// +// const paginateLimit = 50; +// +// for (int i = 0; i < txCount; i += paginateLimit) { +// final transactions = await db +// .getTransactions(walletId) +// .offset(i) +// .limit(paginateLimit) +// .findAll(); +// for (final tx in transactions) { +// if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { +// // get all transactions that were notified as pending but not as confirmed +// if (txTracker.wasNotifiedPending(tx.txid) && +// !txTracker.wasNotifiedConfirmed(tx.txid)) { +// unconfirmedTxnsToNotifyConfirmed.add(tx); +// } +// } else { +// // get all transactions that were not notified as pending yet +// if (!txTracker.wasNotifiedPending(tx.txid)) { +// unconfirmedTxnsToNotifyPending.add(tx); +// } +// } +// } +// } +// +// // notify on unconfirmed transactions +// for (final tx in unconfirmedTxnsToNotifyPending) { +// final confirmations = tx.getConfirmations(currentChainHeight); +// +// if (tx.type == isar_models.TransactionType.incoming) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Incoming transaction", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, +// txid: tx.txid, +// confirmations: confirmations, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedPending(tx.txid); +// } else if (tx.type == isar_models.TransactionType.outgoing) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Sending transaction", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, +// txid: tx.txid, +// confirmations: confirmations, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedPending(tx.txid); +// } +// } +// +// // notify on confirmed +// for (final tx in unconfirmedTxnsToNotifyConfirmed) { +// if (tx.type == isar_models.TransactionType.incoming) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Incoming transaction confirmed", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: false, +// txid: tx.txid, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedConfirmed(tx.txid); +// } else if (tx.type == isar_models.TransactionType.outgoing) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Outgoing transaction confirmed", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: false, +// txid: tx.txid, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedConfirmed(tx.txid); +// } +// } +// } +// +// bool _shouldAutoSync = false; +// +// @override +// bool get shouldAutoSync => _shouldAutoSync; +// +// @override +// set shouldAutoSync(bool shouldAutoSync) { +// if (_shouldAutoSync != shouldAutoSync) { +// _shouldAutoSync = shouldAutoSync; +// if (!shouldAutoSync) { +// timer?.cancel(); +// timer = null; +// stopNetworkAlivePinging(); +// } else { +// startNetworkAlivePinging(); +// refresh(); +// } +// } +// } +// +// @override +// bool get isRefreshing => refreshMutex; +// +// bool refreshMutex = false; +// +// //TODO Show percentages properly/more consistently +// /// 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; +// } +// +// try { +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.syncing, +// walletId, +// coin, +// ), +// ); +// +// GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); +// +// GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); +// +// final currentHeight = await chainHeight; +// const storedHeight = 1; //await storedChainHeight; +// +// Logging.instance +// .log("chain height: $currentHeight", level: LogLevel.Info); +// Logging.instance +// .log("cached height: $storedHeight", level: LogLevel.Info); +// +// if (currentHeight != storedHeight) { +// GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); +// await _checkChangeAddressForTransactions(); +// +// GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); +// await _checkCurrentReceivingAddressesForTransactions(); +// +// final fetchFuture = _refreshTransactions(); +// final utxosRefreshFuture = _updateUTXOs(); +// GlobalEventBus.instance +// .fire(RefreshPercentChangedEvent(0.50, walletId)); +// +// final feeObj = _getFees(); +// GlobalEventBus.instance +// .fire(RefreshPercentChangedEvent(0.60, walletId)); +// +// GlobalEventBus.instance +// .fire(RefreshPercentChangedEvent(0.70, walletId)); +// _feeObject = Future(() => feeObj); +// +// await utxosRefreshFuture; +// GlobalEventBus.instance +// .fire(RefreshPercentChangedEvent(0.80, walletId)); +// +// await fetchFuture; +// await getAllTxsToWatch(); +// GlobalEventBus.instance +// .fire(RefreshPercentChangedEvent(0.90, walletId)); +// } +// +// refreshMutex = false; +// GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.synced, +// walletId, +// coin, +// ), +// ); +// +// if (shouldAutoSync) { +// timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { +// Logging.instance.log( +// "Periodic refresh check for $walletId $walletName in object instance: $hashCode", +// level: LogLevel.Info); +// // chain height check currently broken +// // if ((await chainHeight) != (await storedChainHeight)) { +// if (await refreshIfThereIsNewData()) { +// await refresh(); +// GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( +// "New data found in $walletId $walletName in background!", +// walletId)); +// } +// // } +// }); +// } +// } catch (error, strace) { +// refreshMutex = false; +// GlobalEventBus.instance.fire( +// NodeConnectionStatusChangedEvent( +// NodeConnectionStatus.disconnected, +// walletId, +// coin, +// ), +// ); +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.unableToSync, +// walletId, +// coin, +// ), +// ); +// Logging.instance.log( +// "Caught exception in refreshWalletData(): $error\n$strace", +// level: LogLevel.Error); +// } +// } +// +// @override +// Future> prepareSend({ +// required String address, +// required Amount amount, +// Map? args, +// }) async { +// try { +// final feeRateType = args?["feeRate"]; +// final customSatsPerVByte = args?["satsPerVByte"] as int?; +// final feeRateAmount = args?["feeRateAmount"]; +// final utxos = args?["UTXOs"] as Set?; +// +// if (customSatsPerVByte != null) { +// // check for send all +// bool isSendAll = false; +// if (amount == balance.spendable) { +// isSendAll = true; +// } +// +// final bool coinControl = utxos != null; +// +// final result = await coinSelection( +// satoshiAmountToSend: amount.raw.toInt(), +// selectedTxFeeRate: -1, +// satsPerVByte: customSatsPerVByte, +// recipientAddress: address, +// isSendAll: isSendAll, +// utxos: utxos?.toList(), +// coinControl: coinControl, +// ); +// +// Logging.instance +// .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); +// if (result is int) { +// switch (result) { +// case 1: +// throw Exception("Insufficient balance!"); +// case 2: +// throw Exception("Insufficient funds to pay for transaction fee!"); +// default: +// throw Exception("Transaction failed with error code $result"); +// } +// } else { +// final hex = result["hex"]; +// if (hex is String) { +// final fee = result["fee"] as int; +// final vSize = result["vSize"] as int; +// +// Logging.instance.log("txHex: $hex", level: LogLevel.Info); +// Logging.instance.log("fee: $fee", level: LogLevel.Info); +// Logging.instance.log("vsize: $vSize", level: LogLevel.Info); +// // fee should never be less than vSize sanity check +// if (fee < vSize) { +// throw Exception( +// "Error in fee calculation: Transaction fee cannot be less than vSize"); +// } +// return result as Map; +// } else { +// throw Exception("sent hex is not a String!!!"); +// } +// } +// } else if (feeRateType is FeeRateType || feeRateAmount is int) { +// late final int rate; +// if (feeRateType is FeeRateType) { +// int fee = 0; +// final feeObject = await fees; +// switch (feeRateType) { +// case FeeRateType.fast: +// fee = feeObject.fast; +// break; +// case FeeRateType.average: +// fee = feeObject.medium; +// break; +// case FeeRateType.slow: +// fee = feeObject.slow; +// break; +// default: +// throw ArgumentError("Invalid use of custom fee"); +// } +// rate = fee; +// } else { +// rate = feeRateAmount as int; +// } +// +// // check for send all +// bool isSendAll = false; +// if (amount == balance.spendable) { +// isSendAll = true; +// } +// +// final bool coinControl = utxos != null; +// +// final txData = await coinSelection( +// satoshiAmountToSend: amount.raw.toInt(), +// selectedTxFeeRate: rate, +// recipientAddress: address, +// isSendAll: isSendAll, +// utxos: utxos?.toList(), +// coinControl: coinControl, +// ); +// +// Logging.instance.log("prepare send: $txData", level: LogLevel.Info); +// try { +// if (txData is int) { +// switch (txData) { +// case 1: +// throw Exception("Insufficient balance!"); +// case 2: +// throw Exception( +// "Insufficient funds to pay for transaction fee!"); +// default: +// throw Exception("Transaction failed with error code $txData"); +// } +// } else { +// final hex = txData["hex"]; +// +// if (hex is String) { +// final fee = txData["fee"] as int; +// final vSize = txData["vSize"] as int; +// +// Logging.instance +// .log("prepared txHex: $hex", level: LogLevel.Info); +// Logging.instance.log("prepared fee: $fee", level: LogLevel.Info); +// Logging.instance +// .log("prepared vSize: $vSize", level: LogLevel.Info); +// +// // fee should never be less than vSize sanity check +// if (fee < vSize) { +// throw Exception( +// "Error in fee calculation: Transaction fee cannot be less than vSize"); +// } +// +// return txData as Map; +// } else { +// throw Exception("prepared hex is not a String!!!"); +// } +// } +// } catch (e, s) { +// Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } else { +// throw ArgumentError("Invalid fee rate argument provided!"); +// } +// } catch (e, s) { +// Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// @override +// Future confirmSend({required Map txData}) async { +// try { +// Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); +// +// final hex = txData["hex"] as String; +// +// final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); +// Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); +// +// final utxos = txData["usedUTXOs"] as List; +// +// // mark utxos as used +// await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); +// +// return txHash; +// } catch (e, s) { +// Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// @override +// Future testNetworkConnection() async { +// try { +// final result = await _electrumXClient.ping(); +// return result; +// } catch (_) { +// return false; +// } +// } +// +// Timer? _networkAliveTimer; +// +// void startNetworkAlivePinging() { +// // call once on start right away +// _periodicPingCheck(); +// +// // then periodically check +// _networkAliveTimer = Timer.periodic( +// Constants.networkAliveTimerDuration, +// (_) async { +// _periodicPingCheck(); +// }, +// ); +// } +// +// void _periodicPingCheck() async { +// bool hasNetwork = await testNetworkConnection(); +// +// if (_isConnected != hasNetwork) { +// NodeConnectionStatus status = hasNetwork +// ? NodeConnectionStatus.connected +// : NodeConnectionStatus.disconnected; +// GlobalEventBus.instance +// .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); +// +// _isConnected = hasNetwork; +// if (hasNetwork) { +// unawaited(refresh()); +// } +// } +// } +// +// void stopNetworkAlivePinging() { +// _networkAliveTimer?.cancel(); +// _networkAliveTimer = null; +// } +// +// bool _isConnected = false; +// +// @override +// bool get isConnected => _isConnected; +// +// @override +// Future initializeNew( +// ({String mnemonicPassphrase, int wordCount})? data, +// ) async { +// Logging.instance +// .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); +// +// if (getCachedId() != null) { +// throw Exception( +// "Attempted to initialize a new wallet using an existing wallet ID!"); +// } +// +// await _prefs.init(); +// try { +// await _generateNewWallet(data); +// } catch (e, s) { +// Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", +// level: LogLevel.Fatal); +// rethrow; +// } +// await Future.wait([ +// updateCachedId(walletId), +// updateCachedIsFavorite(false), +// ]); +// } +// +// @override +// Future initializeExisting() async { +// Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", +// level: LogLevel.Info); +// +// if (getCachedId() == null) { +// throw Exception( +// "Attempted to initialize an existing wallet using an unknown wallet ID!"); +// } +// await _prefs.init(); +// // await _checkCurrentChangeAddressesForTransactions(); +// // await _checkCurrentReceivingAddressesForTransactions(); +// } +// +// // hack to add tx to txData before refresh completes +// // required based on current app architecture where we don't properly store +// // transactions locally in a good way +// @override +// Future updateSentCachedTxData(Map txData) async { +// final transaction = isar_models.Transaction( +// walletId: walletId, +// txid: txData["txid"] as String, +// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, +// type: isar_models.TransactionType.outgoing, +// subType: isar_models.TransactionSubType.none, +// // precision may be lost here hence the following amountString +// amount: (txData["recipientAmt"] as Amount).raw.toInt(), +// amountString: (txData["recipientAmt"] as Amount).toJsonString(), +// fee: txData["fee"] as int, +// height: null, +// isCancelled: false, +// isLelantus: false, +// otherData: null, +// slateId: null, +// nonce: null, +// inputs: [], +// outputs: [], +// numberOfMessages: null, +// ); +// +// final address = txData["address"] is String +// ? await db.getAddress(walletId, txData["address"] as String) +// : null; +// +// await db.addNewTransactionData( +// [ +// Tuple2(transaction, address), +// ], +// walletId, +// ); +// } +// +// @override +// bool validateAddress(String address) { +// return Address.validateAddress(address, _network, namecoin.bech32!); +// } +// +// @override +// String get walletId => _walletId; +// late final String _walletId; +// +// @override +// String get walletName => _walletName; +// late String _walletName; +// +// // setter for updating on rename +// @override +// set walletName(String newName) => _walletName = newName; +// +// late ElectrumXClient _electrumXClient; +// +// ElectrumXClient get electrumXClient => _electrumXClient; +// +// late CachedElectrumXClient _cachedElectrumXClient; +// +// CachedElectrumXClient get cachedElectrumXClient => _cachedElectrumXClient; +// +// late SecureStorageInterface _secureStore; +// +// @override +// Future updateNode(bool shouldRefresh) async { +// final failovers = NodeService(secureStorageInterface: _secureStore) +// .failoverNodesFor(coin: coin) +// .map((e) => ElectrumXNode( +// address: e.host, +// port: e.port, +// name: e.name, +// id: e.id, +// useSSL: e.useSSL, +// )) +// .toList(); +// final newNode = await getCurrentNode(); +// _electrumXClient = ElectrumXClient.from( +// node: newNode, +// prefs: _prefs, +// failovers: failovers, +// ); +// _cachedElectrumXClient = CachedElectrumXClient.from( +// electrumXClient: _electrumXClient, +// ); +// +// if (shouldRefresh) { +// unawaited(refresh()); +// } +// } +// +// Future> _getMnemonicList() async { +// final _mnemonicString = await mnemonicString; +// if (_mnemonicString == null) { +// return []; +// } +// final List data = _mnemonicString.split(' '); +// return data; +// } +// +// Future getCurrentNode() async { +// final node = NodeService(secureStorageInterface: _secureStore) +// .getPrimaryNodeFor(coin: coin) ?? +// DefaultNodes.getNodeFor(coin); +// +// return ElectrumXNode( +// address: node.host, +// port: node.port, +// name: node.name, +// useSSL: node.useSSL, +// id: node.id, +// ); +// } +// +// Future> _fetchAllOwnAddresses() async { +// final allAddresses = await db +// .getAddresses(walletId) +// .filter() +// .not() +// .typeEqualTo(isar_models.AddressType.nonWallet) +// .and() +// .group((q) => q +// .subTypeEqualTo(isar_models.AddressSubType.receiving) +// .or() +// .subTypeEqualTo(isar_models.AddressSubType.change)) +// .findAll(); +// // final List allAddresses = []; +// // final receivingAddresses = DB.instance.get( +// // boxName: walletId, key: 'receivingAddressesP2WPKH') as List; +// // final changeAddresses = DB.instance.get( +// // boxName: walletId, key: 'changeAddressesP2WPKH') as List; +// // final receivingAddressesP2PKH = DB.instance.get( +// // boxName: walletId, key: 'receivingAddressesP2PKH') as List; +// // final changeAddressesP2PKH = +// // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') +// // as List; +// // final receivingAddressesP2SH = DB.instance.get( +// // boxName: walletId, key: 'receivingAddressesP2SH') as List; +// // final changeAddressesP2SH = +// // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') +// // as List; +// // +// // for (var i = 0; i < receivingAddresses.length; i++) { +// // if (!allAddresses.contains(receivingAddresses[i])) { +// // allAddresses.add(receivingAddresses[i] as String); +// // } +// // } +// // for (var i = 0; i < changeAddresses.length; i++) { +// // if (!allAddresses.contains(changeAddresses[i])) { +// // allAddresses.add(changeAddresses[i] as String); +// // } +// // } +// // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { +// // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { +// // allAddresses.add(receivingAddressesP2PKH[i] as String); +// // } +// // } +// // for (var i = 0; i < changeAddressesP2PKH.length; i++) { +// // if (!allAddresses.contains(changeAddressesP2PKH[i])) { +// // allAddresses.add(changeAddressesP2PKH[i] as String); +// // } +// // } +// // for (var i = 0; i < receivingAddressesP2SH.length; i++) { +// // if (!allAddresses.contains(receivingAddressesP2SH[i])) { +// // allAddresses.add(receivingAddressesP2SH[i] as String); +// // } +// // } +// // for (var i = 0; i < changeAddressesP2SH.length; i++) { +// // if (!allAddresses.contains(changeAddressesP2SH[i])) { +// // allAddresses.add(changeAddressesP2SH[i] as String); +// // } +// // } +// return allAddresses; +// } +// +// Future _getFees() async { +// try { +// //TODO adjust numbers for different speeds? +// const int f = 1, m = 5, s = 20; +// +// final fast = await electrumXClient.estimateFee(blocks: f); +// final medium = await electrumXClient.estimateFee(blocks: m); +// final slow = await electrumXClient.estimateFee(blocks: s); +// +// final feeObject = FeeObject( +// numberOfBlocksFast: f, +// numberOfBlocksAverage: m, +// numberOfBlocksSlow: s, +// fast: Amount.fromDecimal( +// fast, +// fractionDigits: coin.decimals, +// ).raw.toInt(), +// medium: Amount.fromDecimal( +// medium, +// fractionDigits: coin.decimals, +// ).raw.toInt(), +// slow: Amount.fromDecimal( +// slow, +// fractionDigits: coin.decimals, +// ).raw.toInt(), +// ); +// +// Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); +// return feeObject; +// } catch (e) { +// Logging.instance +// .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future _generateNewWallet( +// ({String mnemonicPassphrase, int wordCount})? data, +// ) async { +// Logging.instance +// .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); +// if (!integrationTestFlag) { +// try { +// final features = await electrumXClient.getServerFeatures(); +// Logging.instance.log("features: $features", level: LogLevel.Info); +// switch (coin) { +// case Coin.namecoin: +// if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { +// throw Exception("genesis hash does not match main net!"); +// } +// break; +// default: +// throw Exception( +// "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); +// } +// } catch (e, s) { +// Logging.instance.log("$e/n$s", level: LogLevel.Info); +// } +// } +// +// // this should never fail +// if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { +// throw Exception( +// "Attempted to overwrite mnemonic on generate new wallet!"); +// } +// final int strength; +// if (data == null || data.wordCount == 12) { +// strength = 128; +// } else if (data.wordCount == 24) { +// strength = 256; +// } else { +// throw Exception("Invalid word count"); +// } +// await _secureStore.write( +// key: '${_walletId}_mnemonic', +// value: bip39.generateMnemonic(strength: strength)); +// await _secureStore.write( +// key: '${_walletId}_mnemonicPassphrase', +// value: data?.mnemonicPassphrase ?? "", +// ); +// +// // Generate and add addresses to relevant arrays +// final initialAddresses = await Future.wait([ +// // P2WPKH +// _generateAddressForChain(0, 0, DerivePathType.bip84), +// _generateAddressForChain(1, 0, DerivePathType.bip84), +// +// // P2PKH +// _generateAddressForChain(0, 0, DerivePathType.bip44), +// _generateAddressForChain(1, 0, DerivePathType.bip44), +// +// // P2SH +// _generateAddressForChain(0, 0, DerivePathType.bip49), +// _generateAddressForChain(1, 0, DerivePathType.bip49), +// ]); +// +// await db.putAddresses(initialAddresses); +// +// Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); +// } +// +// /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. +// /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! +// /// [index] - This can be any integer >= 0 +// Future _generateAddressForChain( +// int chain, +// int index, +// DerivePathType derivePathType, +// ) async { +// final _mnemonic = await mnemonicString; +// final _mnemonicPassphrase = await mnemonicPassphrase; +// if (_mnemonicPassphrase == null) { +// Logging.instance.log( +// "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", +// level: LogLevel.Error); +// } +// +// final derivePath = constructDerivePath( +// derivePathType: derivePathType, +// networkWIF: _network.wif, +// chain: chain, +// index: index, +// ); +// final node = await Bip32Utils.getBip32Node( +// _mnemonic!, +// _mnemonicPassphrase!, +// _network, +// derivePath, +// ); +// +// final data = PaymentData(pubkey: node.publicKey); +// String address; +// isar_models.AddressType addrType; +// +// switch (derivePathType) { +// case DerivePathType.bip44: +// address = P2PKH(data: data, network: _network).data.address!; +// addrType = isar_models.AddressType.p2pkh; +// break; +// case DerivePathType.bip49: +// address = P2SH( +// data: PaymentData( +// redeem: P2WPKH( +// data: data, +// network: _network, +// overridePrefix: namecoin.bech32!) +// .data), +// network: _network) +// .data +// .address!; +// addrType = isar_models.AddressType.p2sh; +// break; +// case DerivePathType.bip84: +// address = P2WPKH( +// network: _network, data: data, overridePrefix: namecoin.bech32!) +// .data +// .address!; +// addrType = isar_models.AddressType.p2wpkh; +// break; +// default: +// throw Exception("DerivePathType must not be null."); +// } +// +// // add generated address & info to derivations +// await addDerivation( +// chain: chain, +// address: address, +// pubKey: Format.uint8listToString(node.publicKey), +// wif: node.toWIF(), +// derivePathType: derivePathType, +// ); +// +// return isar_models.Address( +// walletId: walletId, +// derivationIndex: index, +// derivationPath: isar_models.DerivationPath()..value = derivePath, +// value: address, +// publicKey: node.publicKey, +// type: addrType, +// subType: chain == 0 +// ? isar_models.AddressSubType.receiving +// : isar_models.AddressSubType.change, +// ); +// } +// +// /// 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, +// DerivePathType derivePathType, +// ) async { +// final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 +// ? isar_models.AddressSubType.receiving +// : isar_models.AddressSubType.change; +// +// isar_models.AddressType type; +// isar_models.Address? address; +// switch (derivePathType) { +// case DerivePathType.bip44: +// type = isar_models.AddressType.p2pkh; +// break; +// case DerivePathType.bip49: +// type = isar_models.AddressType.p2sh; +// break; +// case DerivePathType.bip84: +// type = isar_models.AddressType.p2wpkh; +// break; +// default: +// throw Exception( +// "DerivePathType null or unsupported (${DerivePathType.bip44})"); +// } +// address = await db +// .getAddresses(walletId) +// .filter() +// .typeEqualTo(type) +// .subTypeEqualTo(subType) +// .sortByDerivationIndexDesc() +// .findFirst(); +// return address!.value; +// } +// +// String _buildDerivationStorageKey({ +// required int chain, +// required DerivePathType derivePathType, +// }) { +// String key; +// String chainId = chain == 0 ? "receive" : "change"; +// switch (derivePathType) { +// case DerivePathType.bip44: +// key = "${walletId}_${chainId}DerivationsP2PKH"; +// break; +// case DerivePathType.bip49: +// key = "${walletId}_${chainId}DerivationsP2SH"; +// break; +// case DerivePathType.bip84: +// key = "${walletId}_${chainId}DerivationsP2WPKH"; +// break; +// default: +// throw Exception("DerivePathType $derivePathType not supported"); +// } +// return key; +// } +// +// Future> _fetchDerivations({ +// required int chain, +// required DerivePathType derivePathType, +// }) async { +// // build lookup key +// final key = _buildDerivationStorageKey( +// chain: chain, derivePathType: derivePathType); +// +// // fetch current derivations +// final derivationsString = await _secureStore.read(key: key); +// return Map.from( +// jsonDecode(derivationsString ?? "{}") as Map); +// } +// +// /// Add a single derivation to the local secure storage for [chain] and +// /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. +// /// This will overwrite a previous entry where the address of the new derivation +// /// matches a derivation currently stored. +// Future addDerivation({ +// required int chain, +// required String address, +// required String pubKey, +// required String wif, +// required DerivePathType derivePathType, +// }) async { +// // build lookup key +// final key = _buildDerivationStorageKey( +// chain: chain, derivePathType: derivePathType); +// +// // fetch current derivations +// final derivationsString = await _secureStore.read(key: key); +// final derivations = +// Map.from(jsonDecode(derivationsString ?? "{}") as Map); +// +// // add derivation +// derivations[address] = { +// "pubKey": pubKey, +// "wif": wif, +// }; +// +// // save derivations +// final newReceiveDerivationsString = jsonEncode(derivations); +// await _secureStore.write(key: key, value: newReceiveDerivationsString); +// } +// +// /// Add multiple derivations to the local secure storage for [chain] and +// /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. +// /// This will overwrite any previous entries where the address of the new derivation +// /// matches a derivation currently stored. +// /// The [derivationsToAdd] must be in the format of: +// /// { +// /// addressA : { +// /// "pubKey": , +// /// "wif": , +// /// }, +// /// addressB : { +// /// "pubKey": , +// /// "wif": , +// /// }, +// /// } +// Future addDerivations({ +// required int chain, +// required DerivePathType derivePathType, +// required Map derivationsToAdd, +// }) async { +// // build lookup key +// final key = _buildDerivationStorageKey( +// chain: chain, derivePathType: derivePathType); +// +// // fetch current derivations +// final derivationsString = await _secureStore.read(key: key); +// final derivations = +// Map.from(jsonDecode(derivationsString ?? "{}") as Map); +// +// // add derivation +// derivations.addAll(derivationsToAdd); +// +// // save derivations +// final newReceiveDerivationsString = jsonEncode(derivations); +// await _secureStore.write(key: key, value: newReceiveDerivationsString); +// } +// +// Future _updateUTXOs() async { +// final allAddresses = await _fetchAllOwnAddresses(); +// +// try { +// final fetchedUtxoList = >>[]; +// +// final Map>> batches = {}; +// const batchSizeMax = 100; +// int batchNumber = 0; +// for (int i = 0; i < allAddresses.length; i++) { +// if (batches[batchNumber] == null) { +// batches[batchNumber] = {}; +// } +// final scripthash = +// _convertToScriptHash(allAddresses[i].value, _network); +// +// // print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); +// batches[batchNumber]!.addAll({ +// scripthash: [scripthash] +// }); +// if (i % batchSizeMax == batchSizeMax - 1) { +// batchNumber++; +// } +// } +// +// for (int i = 0; i < batches.length; i++) { +// final response = +// await _electrumXClient.getBatchUTXOs(args: batches[i]!); +// for (final entry in response.entries) { +// if (entry.value.isNotEmpty) { +// fetchedUtxoList.add(entry.value); +// } +// } +// } +// +// final List outputArray = []; +// +// for (int i = 0; i < fetchedUtxoList.length; i++) { +// for (int j = 0; j < fetchedUtxoList[i].length; j++) { +// final jsonUTXO = fetchedUtxoList[i][j]; +// +// final txn = await cachedElectrumXClient.getTransaction( +// txHash: jsonUTXO["tx_hash"] as String, +// verbose: true, +// coin: coin, +// ); +// +// final vout = jsonUTXO["tx_pos"] as int; +// +// final outputs = txn["vout"] as List; +// +// String? utxoOwnerAddress; +// // get UTXO owner address +// for (final output in outputs) { +// if (output["n"] == vout) { +// utxoOwnerAddress = +// output["scriptPubKey"]?["addresses"]?[0] as String? ?? +// output["scriptPubKey"]?["address"] as String?; +// } +// } +// +// final utxo = isar_models.UTXO( +// walletId: walletId, +// txid: txn["txid"] as String, +// vout: vout, +// value: jsonUTXO["value"] as int, +// name: "", +// isBlocked: false, +// blockedReason: null, +// isCoinbase: txn["is_coinbase"] as bool? ?? false, +// blockHash: txn["blockhash"] as String?, +// blockHeight: jsonUTXO["height"] as int?, +// blockTime: txn["blocktime"] as int?, +// address: utxoOwnerAddress, +// ); +// +// outputArray.add(utxo); +// } +// } +// +// Logging.instance +// .log('Outputs fetched: $outputArray', level: LogLevel.Info); +// +// await db.updateUTXOs(walletId, outputArray); +// +// // finally update balance +// await _updateBalance(); +// } catch (e, s) { +// Logging.instance +// .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); +// } +// } +// +// Future _updateBalance() async { +// // await refreshBalance(); +// } +// +// @override +// Balance get balance => _balance ??= getCachedBalance(); +// Balance? _balance; +// +// // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) +// // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. +// // /// Now also checks for output labeling. +// // Future _sortOutputs(List utxos) async { +// // final blockedHashArray = +// // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') +// // as List?; +// // final List lst = []; +// // if (blockedHashArray != null) { +// // for (var hash in blockedHashArray) { +// // lst.add(hash as String); +// // } +// // } +// // final labels = +// // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? +// // {}; +// // +// // outputsList = []; +// // +// // for (var i = 0; i < utxos.length; i++) { +// // if (labels[utxos[i].txid] != null) { +// // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; +// // } else { +// // utxos[i].txName = 'Output #$i'; +// // } +// // +// // if (utxos[i].status.confirmed == false) { +// // outputsList.add(utxos[i]); +// // } else { +// // if (lst.contains(utxos[i].txid)) { +// // utxos[i].blocked = true; +// // outputsList.add(utxos[i]); +// // } else if (!lst.contains(utxos[i].txid)) { +// // outputsList.add(utxos[i]); +// // } +// // } +// // } +// // } +// +// Future getTxCount({required String address}) async { +// String? scripthash; +// try { +// scripthash = _convertToScriptHash(address, _network); +// final transactions = +// await electrumXClient.getHistory(scripthash: scripthash); +// return transactions.length; +// } catch (e) { +// Logging.instance.log( +// "Exception rethrown in _getTxCount(address: $address, scripthash: $scripthash): $e", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future> _getBatchTxCount({ +// required Map addresses, +// }) async { +// try { +// final Map> args = {}; +// // print("Address $addresses"); +// for (final entry in addresses.entries) { +// args[entry.key] = [_convertToScriptHash(entry.value, _network)]; +// } +// // print("Args ${jsonEncode(args)}"); +// final response = await electrumXClient.getBatchHistory(args: args); +// // print("Response ${jsonEncode(response)}"); +// final Map result = {}; +// for (final entry in response.entries) { +// result[entry.key] = entry.value.length; +// } +// +// return result; +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future _checkReceivingAddressForTransactions() async { +// try { +// final currentReceiving = await _currentReceivingAddress; +// +// final int txCount = await getTxCount(address: currentReceiving.value); +// Logging.instance.log( +// 'Number of txs for current receiving address $currentReceiving: $txCount', +// level: LogLevel.Info); +// +// if (txCount >= 1 || currentReceiving.derivationIndex < 0) { +// // First increment the receiving index +// final newReceivingIndex = currentReceiving.derivationIndex + 1; +// +// // Use new index to derive a new receiving address +// final newReceivingAddress = await _generateAddressForChain( +// 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); +// +// final existing = await db +// .getAddresses(walletId) +// .filter() +// .valueEqualTo(newReceivingAddress.value) +// .findFirst(); +// if (existing == null) { +// // Add that new change address +// await db.putAddress(newReceivingAddress); +// } else { +// // we need to update the address +// await db.updateAddress(existing, newReceivingAddress); +// } +// // keep checking until address with no tx history is set as current +// await _checkReceivingAddressForTransactions(); +// } +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future _checkChangeAddressForTransactions() async { +// try { +// final currentChange = await _currentChangeAddress; +// final int txCount = await getTxCount(address: currentChange.value); +// Logging.instance.log( +// 'Number of txs for current change address $currentChange: $txCount', +// level: LogLevel.Info); +// +// if (txCount >= 1 || currentChange.derivationIndex < 0) { +// // First increment the change index +// final newChangeIndex = currentChange.derivationIndex + 1; +// +// // Use new index to derive a new change address +// final newChangeAddress = await _generateAddressForChain( +// 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); +// +// final existing = await db +// .getAddresses(walletId) +// .filter() +// .valueEqualTo(newChangeAddress.value) +// .findFirst(); +// if (existing == null) { +// // Add that new change address +// await db.putAddress(newChangeAddress); +// } else { +// // we need to update the address +// await db.updateAddress(existing, newChangeAddress); +// } +// +// // keep checking until address with no tx history is set as current +// await _checkChangeAddressForTransactions(); +// } +// } on SocketException catch (se, s) { +// Logging.instance.log( +// "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", +// level: LogLevel.Error); +// return; +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future _checkCurrentReceivingAddressesForTransactions() async { +// try { +// // for (final type in DerivePathType.values) { +// await _checkReceivingAddressForTransactions(); +// // } +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// /// public wrapper because dart can't test private... +// Future checkCurrentReceivingAddressesForTransactions() async { +// if (Platform.environment["FLUTTER_TEST"] == "true") { +// try { +// return _checkCurrentReceivingAddressesForTransactions(); +// } catch (_) { +// rethrow; +// } +// } +// } +// +// Future _checkCurrentChangeAddressesForTransactions() async { +// try { +// // for (final type in DerivePathType.values) { +// await _checkChangeAddressForTransactions(); +// // } +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// /// public wrapper because dart can't test private... +// Future checkCurrentChangeAddressesForTransactions() async { +// if (Platform.environment["FLUTTER_TEST"] == "true") { +// try { +// return _checkCurrentChangeAddressesForTransactions(); +// } catch (_) { +// rethrow; +// } +// } +// } +// +// /// attempts to convert a string to a valid scripthash +// /// +// /// Returns the scripthash or throws an exception on invalid namecoin address +// String _convertToScriptHash(String namecoinAddress, NetworkType network) { +// try { +// final output = Address.addressToOutputScript( +// namecoinAddress, network, namecoin.bech32!); +// final hash = sha256.convert(output.toList(growable: false)).toString(); +// +// final chars = hash.split(""); +// final reversedPairs = []; +// var i = chars.length - 1; +// while (i > 0) { +// reversedPairs.add(chars[i - 1]); +// reversedPairs.add(chars[i]); +// i -= 2; +// } +// return reversedPairs.join(""); +// } catch (e) { +// rethrow; +// } +// } +// +// Future>> _fetchHistory( +// List allAddresses) async { +// try { +// List> allTxHashes = []; +// +// final Map>> batches = {}; +// final Map requestIdToAddressMap = {}; +// const batchSizeMax = 100; +// int batchNumber = 0; +// for (int i = 0; i < allAddresses.length; i++) { +// if (batches[batchNumber] == null) { +// batches[batchNumber] = {}; +// } +// final scripthash = _convertToScriptHash(allAddresses[i], _network); +// final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); +// requestIdToAddressMap[id] = allAddresses[i]; +// batches[batchNumber]!.addAll({ +// id: [scripthash] +// }); +// if (i % batchSizeMax == batchSizeMax - 1) { +// batchNumber++; +// } +// } +// +// for (int i = 0; i < batches.length; i++) { +// final response = +// await _electrumXClient.getBatchHistory(args: batches[i]!); +// for (final entry in response.entries) { +// for (int j = 0; j < entry.value.length; j++) { +// entry.value[j]["address"] = requestIdToAddressMap[entry.key]; +// if (!allTxHashes.contains(entry.value[j])) { +// allTxHashes.add(entry.value[j]); +// } +// } +// } +// } +// +// return allTxHashes; +// } catch (e, s) { +// Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error); +// rethrow; +// } +// } +// +// bool _duplicateTxCheck( +// List> allTransactions, String txid) { +// for (int i = 0; i < allTransactions.length; i++) { +// if (allTransactions[i]["txid"] == txid) { +// return true; +// } +// } +// return false; +// } +// +// Future>> fastFetch(List allTxHashes) async { +// List> allTransactions = []; +// +// const futureLimit = 30; +// List>> transactionFutures = []; +// int currentFutureCount = 0; +// for (final txHash in allTxHashes) { +// Future> transactionFuture = +// cachedElectrumXClient.getTransaction( +// txHash: txHash, +// verbose: true, +// coin: coin, +// ); +// transactionFutures.add(transactionFuture); +// currentFutureCount++; +// if (currentFutureCount > futureLimit) { +// currentFutureCount = 0; +// await Future.wait(transactionFutures); +// for (final fTx in transactionFutures) { +// final tx = await fTx; +// +// allTransactions.add(tx); +// } +// } +// } +// if (currentFutureCount != 0) { +// currentFutureCount = 0; +// await Future.wait(transactionFutures); +// for (final fTx in transactionFutures) { +// final tx = await fTx; +// +// allTransactions.add(tx); +// } +// } +// return allTransactions; +// } +// +// Future _refreshTransactions() async { +// final List allAddresses = +// await _fetchAllOwnAddresses(); +// +// final List> allTxHashes = +// await _fetchHistory(allAddresses.map((e) => e.value).toList()); +// +// Set hashes = {}; +// for (var element in allTxHashes) { +// hashes.add(element['tx_hash'] as String); +// } +// await fastFetch(hashes.toList()); +// List> allTransactions = []; +// final currentHeight = await chainHeight; +// +// for (final txHash in allTxHashes) { +// final storedTx = await db +// .getTransactions(walletId) +// .filter() +// .txidEqualTo(txHash["tx_hash"] as String) +// .findFirst(); +// +// if (storedTx == null || +// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { +// final tx = await cachedElectrumXClient.getTransaction( +// txHash: txHash["tx_hash"] as String, +// verbose: true, +// coin: coin, +// ); +// +// // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); +// if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { +// tx["address"] = await db +// .getAddresses(walletId) +// .filter() +// .valueEqualTo(txHash["address"] as String) +// .findFirst(); +// tx["height"] = txHash["height"]; +// allTransactions.add(tx); +// } +// } +// } +// +// // Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); +// // Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); +// // +// // Logging.instance.log("allTransactions length: ${allTransactions.length}", +// // level: LogLevel.Info); +// +// Set vHashes = {}; +// for (final txObject in allTransactions) { +// for (int i = 0; i < (txObject["vin"] as List).length; i++) { +// final input = txObject["vin"]![i] as Map; +// final prevTxid = input["txid"] as String; +// vHashes.add(prevTxid); +// } +// } +// await fastFetch(vHashes.toList()); +// +// final List> txnsData = +// []; +// +// for (final txObject in allTransactions) { +// final data = await parseTransaction( +// txObject, +// cachedElectrumXClient, +// allAddresses, +// coin, +// MINIMUM_CONFIRMATIONS, +// walletId, +// ); +// +// txnsData.add(data); +// } +// await db.addNewTransactionData(txnsData, walletId); +// +// // quick hack to notify manager to call notifyListeners if +// // transactions changed +// if (txnsData.isNotEmpty) { +// GlobalEventBus.instance.fire( +// UpdatedInBackgroundEvent( +// "Transactions updated/added for: $walletId $walletName ", +// walletId, +// ), +// ); +// } +// } +// +// int estimateTxFee({required int vSize, required int feeRatePerKB}) { +// return vSize * (feeRatePerKB / 1000).ceil(); +// } +// +// /// The coinselection algorithm decides whether or not the user is eligible to make the transaction +// /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return +// /// a map containing the tx hex along with other important information. If not, then it will return +// /// an integer (1 or 2) +// dynamic coinSelection({ +// required int satoshiAmountToSend, +// required int selectedTxFeeRate, +// required String recipientAddress, +// required bool coinControl, +// required bool isSendAll, +// int? satsPerVByte, +// int additionalOutputs = 0, +// List? utxos, +// }) async { +// Logging.instance +// .log("Starting coinSelection ----------", level: LogLevel.Info); +// final List availableOutputs = utxos ?? await this.utxos; +// final currentChainHeight = await chainHeight; +// final List spendableOutputs = []; +// int spendableSatoshiValue = 0; +// +// // Build list of spendable outputs and totaling their satoshi amount +// for (final utxo in availableOutputs) { +// if (utxo.isBlocked == false && +// utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && +// utxo.used != true) { +// spendableOutputs.add(utxo); +// spendableSatoshiValue += utxo.value; +// } +// } +// +// if (coinControl) { +// if (spendableOutputs.length < availableOutputs.length) { +// throw ArgumentError("Attempted to use an unavailable utxo"); +// } +// } +// +// // don't care about sorting if using all utxos +// if (!coinControl) { +// // sort spendable by age (oldest first) +// spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); +// } +// +// Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", +// level: LogLevel.Info); +// Logging.instance +// .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); +// Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", +// level: LogLevel.Info); +// Logging.instance +// .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); +// // If the amount the user is trying to send is smaller than the amount that they have spendable, +// // then return 1, which indicates that they have an insufficient balance. +// if (spendableSatoshiValue < satoshiAmountToSend) { +// return 1; +// // If the amount the user wants to send is exactly equal to the amount they can spend, then return +// // 2, which indicates that they are not leaving enough over to pay the transaction fee +// } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { +// return 2; +// } +// // If neither of these statements pass, we assume that the user has a spendable balance greater +// // than the amount they're attempting to send. Note that this value still does not account for +// // the added transaction fee, which may require an extra input and will need to be checked for +// // later on. +// +// // Possible situation right here +// int satoshisBeingUsed = 0; +// int inputsBeingConsumed = 0; +// List utxoObjectsToUse = []; +// +// if (!coinControl) { +// for (var i = 0; +// satoshisBeingUsed < satoshiAmountToSend && +// i < spendableOutputs.length; +// i++) { +// utxoObjectsToUse.add(spendableOutputs[i]); +// satoshisBeingUsed += spendableOutputs[i].value; +// inputsBeingConsumed += 1; +// } +// for (int i = 0; +// i < additionalOutputs && +// inputsBeingConsumed < spendableOutputs.length; +// i++) { +// utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); +// satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; +// inputsBeingConsumed += 1; +// } +// } else { +// satoshisBeingUsed = spendableSatoshiValue; +// utxoObjectsToUse = spendableOutputs; +// inputsBeingConsumed = spendableOutputs.length; +// } +// +// Logging.instance +// .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); +// Logging.instance +// .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); +// Logging.instance +// .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); +// +// // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray +// List recipientsArray = [recipientAddress]; +// List recipientsAmtArray = [satoshiAmountToSend]; +// +// // gather required signing data +// final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); +// +// if (isSendAll) { +// Logging.instance +// .log("Attempting to send all $coin", level: LogLevel.Info); +// +// final int vSizeForOneOutput = (await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: [recipientAddress], +// satoshiAmounts: [satoshisBeingUsed - 1], +// ))["vSize"] as int; +// int feeForOneOutput = satsPerVByte != null +// ? (satsPerVByte * vSizeForOneOutput) +// : estimateTxFee( +// vSize: vSizeForOneOutput, +// feeRatePerKB: selectedTxFeeRate, +// ); +// +// if (satsPerVByte == null) { +// final int roughEstimate = roughFeeEstimate( +// spendableOutputs.length, +// 1, +// selectedTxFeeRate, +// ).raw.toInt(); +// if (feeForOneOutput < roughEstimate) { +// feeForOneOutput = roughEstimate; +// } +// } +// +// final int amount = satoshiAmountToSend - feeForOneOutput; +// dynamic txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: [amount], +// ); +// Map transactionObject = { +// "hex": txn["hex"], +// "recipient": recipientsArray[0], +// "recipientAmt": Amount( +// rawValue: BigInt.from(amount), +// fractionDigits: coin.decimals, +// ), +// "fee": feeForOneOutput, +// "vSize": txn["vSize"], +// "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), +// }; +// return transactionObject; +// } +// +// final int vSizeForOneOutput = (await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: [recipientAddress], +// satoshiAmounts: [satoshisBeingUsed - 1], +// ))["vSize"] as int; +// final int vSizeForTwoOutPuts = (await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: [ +// recipientAddress, +// await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), +// ], +// satoshiAmounts: [ +// satoshiAmountToSend, +// satoshisBeingUsed - satoshiAmountToSend - 1 +// ], // dust limit is the minimum amount a change output should be +// ))["vSize"] as int; +// +// // Assume 1 output, only for recipient and no change +// final feeForOneOutput = satsPerVByte != null +// ? (satsPerVByte * vSizeForOneOutput) +// : estimateTxFee( +// vSize: vSizeForOneOutput, +// feeRatePerKB: selectedTxFeeRate, +// ); +// // Assume 2 outputs, one for recipient and one for change +// final feeForTwoOutputs = satsPerVByte != null +// ? (satsPerVByte * vSizeForTwoOutPuts) +// : estimateTxFee( +// vSize: vSizeForTwoOutPuts, +// feeRatePerKB: selectedTxFeeRate, +// ); +// +// Logging.instance +// .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); +// Logging.instance +// .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); +// +// if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { +// if (satoshisBeingUsed - satoshiAmountToSend > +// feeForOneOutput + DUST_LIMIT.raw.toInt()) { +// // Here, we know that theoretically, we may be able to include another output(change) but we first need to +// // factor in the value of this output in satoshis. +// int changeOutputSize = +// satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs; +// // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and +// // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new +// // change address. +// if (changeOutputSize > DUST_LIMIT.raw.toInt() && +// satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == +// feeForTwoOutputs) { +// // generate new change address if current change address has been used +// await _checkChangeAddressForTransactions(); +// final String newChangeAddress = await _getCurrentAddressForChain( +// 1, DerivePathTypeExt.primaryFor(coin)); +// +// int feeBeingPaid = +// satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; +// +// recipientsArray.add(newChangeAddress); +// recipientsAmtArray.add(changeOutputSize); +// // At this point, we have the outputs we're going to use, the amounts to send along with which addresses +// // we intend to send these amounts to. We have enough to send instructions to build the transaction. +// Logging.instance.log('2 outputs in tx', level: LogLevel.Info); +// Logging.instance +// .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); +// Logging.instance.log('Recipient output size: $satoshiAmountToSend', +// level: LogLevel.Info); +// Logging.instance.log('Change Output Size: $changeOutputSize', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Difference (fee being paid): $feeBeingPaid sats', +// level: LogLevel.Info); +// Logging.instance +// .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); +// dynamic txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: recipientsAmtArray, +// ); +// +// // make sure minimum fee is accurate if that is being used +// if (txn["vSize"] - feeBeingPaid == 1) { +// int changeOutputSize = +// satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int); +// feeBeingPaid = +// satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; +// recipientsAmtArray.removeLast(); +// recipientsAmtArray.add(changeOutputSize); +// Logging.instance.log('Adjusted Input size: $satoshisBeingUsed', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Adjusted Recipient output size: $satoshiAmountToSend', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Adjusted Change Output Size: $changeOutputSize', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Adjusted Difference (fee being paid): $feeBeingPaid sats', +// level: LogLevel.Info); +// Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', +// level: LogLevel.Info); +// txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: recipientsAmtArray, +// ); +// } +// +// Map transactionObject = { +// "hex": txn["hex"], +// "recipient": recipientsArray[0], +// "recipientAmt": Amount( +// rawValue: BigInt.from(recipientsAmtArray[0]), +// fractionDigits: coin.decimals, +// ), +// "fee": feeBeingPaid, +// "vSize": txn["vSize"], +// "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), +// }; +// return transactionObject; +// } else { +// // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize +// // is smaller than or equal to DUST_LIMIT. Revert to single output transaction. +// Logging.instance.log('1 output in tx', level: LogLevel.Info); +// Logging.instance +// .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); +// Logging.instance.log('Recipient output size: $satoshiAmountToSend', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', +// level: LogLevel.Info); +// Logging.instance +// .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); +// dynamic txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: recipientsAmtArray, +// ); +// Map transactionObject = { +// "hex": txn["hex"], +// "recipient": recipientsArray[0], +// "recipientAmt": Amount( +// rawValue: BigInt.from(recipientsAmtArray[0]), +// fractionDigits: coin.decimals, +// ), +// "fee": satoshisBeingUsed - satoshiAmountToSend, +// "vSize": txn["vSize"], +// "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), +// }; +// return transactionObject; +// } +// } else { +// // No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats +// // which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct +// // the wallet to begin crafting the transaction that the user requested. +// Logging.instance.log('1 output in tx', level: LogLevel.Info); +// Logging.instance +// .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); +// Logging.instance.log('Recipient output size: $satoshiAmountToSend', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', +// level: LogLevel.Info); +// Logging.instance +// .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); +// dynamic txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: recipientsAmtArray, +// ); +// Map transactionObject = { +// "hex": txn["hex"], +// "recipient": recipientsArray[0], +// "recipientAmt": Amount( +// rawValue: BigInt.from(recipientsAmtArray[0]), +// fractionDigits: coin.decimals, +// ), +// "fee": satoshisBeingUsed - satoshiAmountToSend, +// "vSize": txn["vSize"], +// "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), +// }; +// return transactionObject; +// } +// } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) { +// // In this scenario, no additional change output is needed since inputs - outputs equal exactly +// // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin +// // crafting the transaction that the user requested. +// Logging.instance.log('1 output in tx', level: LogLevel.Info); +// Logging.instance +// .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); +// Logging.instance.log('Recipient output size: $satoshiAmountToSend', +// level: LogLevel.Info); +// Logging.instance.log( +// 'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats', +// level: LogLevel.Info); +// Logging.instance +// .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); +// dynamic txn = await buildTransaction( +// utxoSigningData: utxoSigningData, +// recipients: recipientsArray, +// satoshiAmounts: recipientsAmtArray, +// ); +// Map transactionObject = { +// "hex": txn["hex"], +// "recipient": recipientsArray[0], +// "recipientAmt": Amount( +// rawValue: BigInt.from(recipientsAmtArray[0]), +// fractionDigits: coin.decimals, +// ), +// "fee": feeForOneOutput, +// "vSize": txn["vSize"], +// "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), +// }; +// return transactionObject; +// } else { +// // Remember that returning 2 indicates that the user does not have a sufficient balance to +// // pay for the transaction fee. Ideally, at this stage, we should check if the user has any +// // additional outputs they're able to spend and then recalculate fees. +// Logging.instance.log( +// 'Cannot pay tx fee - checking for more outputs and trying again', +// level: LogLevel.Warning); +// // try adding more outputs +// if (spendableOutputs.length > inputsBeingConsumed) { +// return coinSelection( +// satoshiAmountToSend: satoshiAmountToSend, +// selectedTxFeeRate: selectedTxFeeRate, +// satsPerVByte: satsPerVByte, +// recipientAddress: recipientAddress, +// isSendAll: isSendAll, +// additionalOutputs: additionalOutputs + 1, +// utxos: utxos, +// coinControl: coinControl, +// ); +// } +// return 2; +// } +// } +// +// Future> fetchBuildTxData( +// List utxosToUse, +// ) async { +// // return data +// List signingData = []; +// +// try { +// // Populating the addresses to check +// for (var i = 0; i < utxosToUse.length; i++) { +// if (utxosToUse[i].address == null) { +// final txid = utxosToUse[i].txid; +// final tx = await _cachedElectrumXClient.getTransaction( +// txHash: txid, +// coin: coin, +// ); +// for (final output in tx["vout"] as List) { +// final n = output["n"]; +// if (n != null && n == utxosToUse[i].vout) { +// utxosToUse[i] = utxosToUse[i].copyWith( +// address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? +// output["scriptPubKey"]["address"] as String, +// ); +// } +// } +// } +// +// final derivePathType = addressType(address: utxosToUse[i].address!); +// +// signingData.add( +// SigningData( +// derivePathType: derivePathType, +// utxo: utxosToUse[i], +// ), +// ); +// } +// +// Map> receiveDerivations = {}; +// Map> changeDerivations = {}; +// +// for (final sd in signingData) { +// String? pubKey; +// String? wif; +// +// // fetch receiving derivations if null +// receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( +// chain: 0, +// derivePathType: sd.derivePathType, +// ); +// final receiveDerivation = +// receiveDerivations[sd.derivePathType]![sd.utxo.address!]; +// +// if (receiveDerivation != null) { +// pubKey = receiveDerivation["pubKey"] as String; +// wif = receiveDerivation["wif"] as String; +// } else { +// // fetch change derivations if null +// changeDerivations[sd.derivePathType] ??= await _fetchDerivations( +// chain: 1, +// derivePathType: sd.derivePathType, +// ); +// final changeDerivation = +// changeDerivations[sd.derivePathType]![sd.utxo.address!]; +// if (changeDerivation != null) { +// pubKey = changeDerivation["pubKey"] as String; +// wif = changeDerivation["wif"] as String; +// } +// } +// +// if (wif == null || pubKey == null) { +// final address = await db.getAddress(walletId, sd.utxo.address!); +// if (address?.derivationPath != null) { +// final node = await Bip32Utils.getBip32Node( +// (await mnemonicString)!, +// (await mnemonicPassphrase)!, +// _network, +// address!.derivationPath!.value, +// ); +// +// wif = node.toWIF(); +// pubKey = Format.uint8listToString(node.publicKey); +// } +// } +// +// if (wif != null && pubKey != null) { +// final PaymentData data; +// final Uint8List? redeemScript; +// +// switch (sd.derivePathType) { +// case DerivePathType.bip44: +// data = P2PKH( +// data: PaymentData( +// pubkey: Format.stringToUint8List(pubKey), +// ), +// network: _network, +// ).data; +// redeemScript = null; +// break; +// +// case DerivePathType.bip49: +// final p2wpkh = P2WPKH( +// data: PaymentData( +// pubkey: Format.stringToUint8List(pubKey), +// ), +// network: _network, +// overridePrefix: _network.bech32!, +// ).data; +// redeemScript = p2wpkh.output; +// data = P2SH(data: PaymentData(redeem: p2wpkh), network: _network) +// .data; +// break; +// +// case DerivePathType.bip84: +// data = P2WPKH( +// data: PaymentData( +// pubkey: Format.stringToUint8List(pubKey), +// ), +// network: _network, +// overridePrefix: _network.bech32!, +// ).data; +// redeemScript = null; +// break; +// +// default: +// throw Exception("DerivePathType unsupported"); +// } +// +// final keyPair = ECPair.fromWIF( +// wif, +// network: _network, +// ); +// +// sd.redeemScript = redeemScript; +// sd.output = data.output; +// sd.keyPair = keyPair; +// } +// } +// +// return signingData; +// } catch (e, s) { +// Logging.instance +// .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); +// rethrow; +// } +// } +// +// /// Builds and signs a transaction +// Future> buildTransaction({ +// required List utxoSigningData, +// required List recipients, +// required List satoshiAmounts, +// }) async { +// Logging.instance +// .log("Starting buildTransaction ----------", level: LogLevel.Info); +// +// final txb = TransactionBuilder(network: _network); +// txb.setVersion(2); +// +// // Add transaction inputs +// for (var i = 0; i < utxoSigningData.length; i++) { +// final txid = utxoSigningData[i].utxo.txid; +// txb.addInput( +// txid, +// utxoSigningData[i].utxo.vout, +// null, +// utxoSigningData[i].output!, +// _network.bech32!, +// ); +// } +// +// // Add transaction output +// for (var i = 0; i < recipients.length; i++) { +// txb.addOutput(recipients[i], satoshiAmounts[i], _network.bech32!); +// } +// +// try { +// // Sign the transaction accordingly +// for (var i = 0; i < utxoSigningData.length; i++) { +// txb.sign( +// vin: i, +// keyPair: utxoSigningData[i].keyPair!, +// witnessValue: utxoSigningData[i].utxo.value, +// redeemScript: utxoSigningData[i].redeemScript, +// overridePrefix: _network.bech32!, +// ); +// } +// } catch (e, s) { +// Logging.instance.log("Caught exception while signing transaction: $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// +// final builtTx = txb.build(_network.bech32!); +// final vSize = builtTx.virtualSize(); +// +// return {"hex": builtTx.toHex(), "vSize": vSize}; +// } +// +// @override +// Future fullRescan( +// int maxUnusedAddressGap, +// int maxNumberOfIndexesToCheck, +// ) async { +// Logging.instance.log("Starting full rescan!", level: LogLevel.Info); +// longMutex = true; +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.syncing, +// walletId, +// coin, +// ), +// ); +// +// // clear cache +// await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); +// +// // back up data +// // await _rescanBackup(); +// +// // clear blockchain info +// await db.deleteWalletBlockchainData(walletId); +// await _deleteDerivations(); +// +// try { +// final _mnemonic = await mnemonicString; +// final _mnemonicPassphrase = await mnemonicPassphrase; +// if (_mnemonicPassphrase == null) { +// Logging.instance.log( +// "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", +// level: LogLevel.Error); +// } +// +// await _recoverWalletFromBIP32SeedPhrase( +// mnemonic: _mnemonic!, +// mnemonicPassphrase: _mnemonicPassphrase!, +// maxUnusedAddressGap: maxUnusedAddressGap, +// maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, +// isRescan: true, +// ); +// +// longMutex = false; +// await refresh(); +// Logging.instance.log("Full rescan complete!", level: LogLevel.Info); +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.synced, +// walletId, +// coin, +// ), +// ); +// } catch (e, s) { +// GlobalEventBus.instance.fire( +// WalletSyncStatusChangedEvent( +// WalletSyncStatus.unableToSync, +// walletId, +// coin, +// ), +// ); +// +// // restore from backup +// // await _rescanRestore(); +// +// longMutex = false; +// Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future _deleteDerivations() async { +// // P2PKH derivations +// await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); +// await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); +// +// // P2SH derivations +// await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); +// await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); +// +// // P2WPKH derivations +// await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); +// await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); +// } +// +// // Future _rescanRestore() async { +// // Logging.instance.log("starting rescan restore", level: LogLevel.Info); +// // +// // // restore from backup +// // // p2pkh +// // final tempReceivingAddressesP2PKH = DB.instance +// // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); +// // final tempChangeAddressesP2PKH = DB.instance +// // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); +// // final tempReceivingIndexP2PKH = DB.instance +// // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); +// // final tempChangeIndexP2PKH = DB.instance +// // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2PKH', +// // value: tempReceivingAddressesP2PKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2PKH', +// // value: tempChangeAddressesP2PKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2PKH', +// // value: tempReceivingIndexP2PKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeIndexP2PKH', +// // value: tempChangeIndexP2PKH); +// // await DB.instance.delete( +// // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); +// // +// // // p2Sh +// // final tempReceivingAddressesP2SH = DB.instance +// // .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); +// // final tempChangeAddressesP2SH = DB.instance +// // .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); +// // final tempReceivingIndexP2SH = DB.instance +// // .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); +// // final tempChangeIndexP2SH = DB.instance +// // .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2SH', +// // value: tempReceivingAddressesP2SH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2SH', +// // value: tempChangeAddressesP2SH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2SH', +// // value: tempReceivingIndexP2SH); +// // await DB.instance.put( +// // boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); +// // await DB.instance.delete( +// // key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); +// // +// // // p2wpkh +// // final tempReceivingAddressesP2WPKH = DB.instance.get( +// // boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); +// // final tempChangeAddressesP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); +// // final tempReceivingIndexP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); +// // final tempChangeIndexP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2WPKH', +// // value: tempReceivingAddressesP2WPKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2WPKH', +// // value: tempChangeAddressesP2WPKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2WPKH', +// // value: tempReceivingIndexP2WPKH); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeIndexP2WPKH', +// // value: tempChangeIndexP2WPKH); +// // await DB.instance.delete( +// // key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); +// // await DB.instance.delete( +// // key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); +// // await DB.instance +// // .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); +// // +// // // P2PKH derivations +// // final p2pkhReceiveDerivationsString = await _secureStore.read( +// // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); +// // final p2pkhChangeDerivationsString = await _secureStore.read( +// // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2PKH", +// // value: p2pkhReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2PKH", +// // value: p2pkhChangeDerivationsString); +// // +// // await _secureStore.delete( +// // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); +// // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); +// // +// // // P2SH derivations +// // final p2shReceiveDerivationsString = await _secureStore.read( +// // key: "${walletId}_receiveDerivationsP2SH_BACKUP"); +// // final p2shChangeDerivationsString = await _secureStore.read( +// // key: "${walletId}_changeDerivationsP2SH_BACKUP"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2SH", +// // value: p2shReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2SH", +// // value: p2shChangeDerivationsString); +// // +// // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); +// // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); +// // +// // // P2WPKH derivations +// // final p2wpkhReceiveDerivationsString = await _secureStore.read( +// // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); +// // final p2wpkhChangeDerivationsString = await _secureStore.read( +// // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2WPKH", +// // value: p2wpkhReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2WPKH", +// // value: p2wpkhChangeDerivationsString); +// // +// // await _secureStore.delete( +// // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); +// // await _secureStore.delete( +// // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); +// // +// // // UTXOs +// // final utxoData = DB.instance +// // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); +// // await DB.instance.put( +// // boxName: walletId, key: 'latest_utxo_model', value: utxoData); +// // await DB.instance +// // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); +// // +// // Logging.instance.log("rescan restore complete", level: LogLevel.Info); +// // } +// // +// // Future _rescanBackup() async { +// // Logging.instance.log("starting rescan backup", level: LogLevel.Info); +// // +// // // backup current and clear data +// // // p2pkh +// // final tempReceivingAddressesP2PKH = DB.instance +// // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2PKH_BACKUP', +// // value: tempReceivingAddressesP2PKH); +// // await DB.instance +// // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); +// // +// // final tempChangeAddressesP2PKH = DB.instance +// // .get(boxName: walletId, key: 'changeAddressesP2PKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2PKH_BACKUP', +// // value: tempChangeAddressesP2PKH); +// // await DB.instance +// // .delete(key: 'changeAddressesP2PKH', boxName: walletId); +// // +// // final tempReceivingIndexP2PKH = +// // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2PKH_BACKUP', +// // value: tempReceivingIndexP2PKH); +// // await DB.instance +// // .delete(key: 'receivingIndexP2PKH', boxName: walletId); +// // +// // final tempChangeIndexP2PKH = +// // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeIndexP2PKH_BACKUP', +// // value: tempChangeIndexP2PKH); +// // await DB.instance +// // .delete(key: 'changeIndexP2PKH', boxName: walletId); +// // +// // // p2sh +// // final tempReceivingAddressesP2SH = DB.instance +// // .get(boxName: walletId, key: 'receivingAddressesP2SH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2SH_BACKUP', +// // value: tempReceivingAddressesP2SH); +// // await DB.instance +// // .delete(key: 'receivingAddressesP2SH', boxName: walletId); +// // +// // final tempChangeAddressesP2SH = +// // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2SH_BACKUP', +// // value: tempChangeAddressesP2SH); +// // await DB.instance +// // .delete(key: 'changeAddressesP2SH', boxName: walletId); +// // +// // final tempReceivingIndexP2SH = +// // DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2SH_BACKUP', +// // value: tempReceivingIndexP2SH); +// // await DB.instance +// // .delete(key: 'receivingIndexP2SH', boxName: walletId); +// // +// // final tempChangeIndexP2SH = +// // DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeIndexP2SH_BACKUP', +// // value: tempChangeIndexP2SH); +// // await DB.instance +// // .delete(key: 'changeIndexP2SH', boxName: walletId); +// // +// // // p2wpkh +// // final tempReceivingAddressesP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingAddressesP2WPKH_BACKUP', +// // value: tempReceivingAddressesP2WPKH); +// // await DB.instance +// // .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); +// // +// // final tempChangeAddressesP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'changeAddressesP2WPKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeAddressesP2WPKH_BACKUP', +// // value: tempChangeAddressesP2WPKH); +// // await DB.instance +// // .delete(key: 'changeAddressesP2WPKH', boxName: walletId); +// // +// // final tempReceivingIndexP2WPKH = DB.instance +// // .get(boxName: walletId, key: 'receivingIndexP2WPKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'receivingIndexP2WPKH_BACKUP', +// // value: tempReceivingIndexP2WPKH); +// // await DB.instance +// // .delete(key: 'receivingIndexP2WPKH', boxName: walletId); +// // +// // final tempChangeIndexP2WPKH = +// // DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); +// // await DB.instance.put( +// // boxName: walletId, +// // key: 'changeIndexP2WPKH_BACKUP', +// // value: tempChangeIndexP2WPKH); +// // await DB.instance +// // .delete(key: 'changeIndexP2WPKH', boxName: walletId); +// // +// // // P2PKH derivations +// // final p2pkhReceiveDerivationsString = +// // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); +// // final p2pkhChangeDerivationsString = +// // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", +// // value: p2pkhReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2PKH_BACKUP", +// // value: p2pkhChangeDerivationsString); +// // +// // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); +// // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); +// // +// // // P2SH derivations +// // final p2shReceiveDerivationsString = +// // await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); +// // final p2shChangeDerivationsString = +// // await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2SH_BACKUP", +// // value: p2shReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2SH_BACKUP", +// // value: p2shChangeDerivationsString); +// // +// // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); +// // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); +// // +// // // P2WPKH derivations +// // final p2wpkhReceiveDerivationsString = +// // await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); +// // final p2wpkhChangeDerivationsString = +// // await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); +// // +// // await _secureStore.write( +// // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", +// // value: p2wpkhReceiveDerivationsString); +// // await _secureStore.write( +// // key: "${walletId}_changeDerivationsP2WPKH_BACKUP", +// // value: p2wpkhChangeDerivationsString); +// // +// // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); +// // await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); +// // +// // // UTXOs +// // final utxoData = +// // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); +// // await DB.instance.put( +// // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); +// // await DB.instance +// // .delete(key: 'latest_utxo_model', boxName: walletId); +// // +// // Logging.instance.log("rescan backup complete", level: LogLevel.Info); +// // } +// +// bool isActive = false; +// +// @override +// void Function(bool)? get onIsActiveWalletChanged => +// (isActive) => this.isActive = isActive; +// +// @override +// Future estimateFeeFor(Amount amount, int feeRate) async { +// final available = balance.spendable; +// +// if (available == amount) { +// return amount - (await sweepAllEstimate(feeRate)); +// } else if (amount <= Amount.zero || amount > available) { +// return roughFeeEstimate(1, 2, feeRate); +// } +// +// Amount runningBalance = Amount( +// rawValue: BigInt.zero, +// fractionDigits: coin.decimals, +// ); +// int inputCount = 0; +// for (final output in (await utxos)) { +// if (!output.isBlocked) { +// runningBalance = runningBalance + +// Amount( +// rawValue: BigInt.from(output.value), +// fractionDigits: coin.decimals, +// ); +// inputCount++; +// if (runningBalance > amount) { +// break; +// } +// } +// } +// +// final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); +// final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); +// +// if (runningBalance - amount > oneOutPutFee) { +// if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { +// final change = runningBalance - amount - twoOutPutFee; +// if (change > DUST_LIMIT && +// runningBalance - amount - change == twoOutPutFee) { +// return runningBalance - amount - change; +// } else { +// return runningBalance - amount; +// } +// } else { +// return runningBalance - amount; +// } +// } else if (runningBalance - amount == oneOutPutFee) { +// return oneOutPutFee; +// } else { +// return twoOutPutFee; +// } +// } +// +// // TODO: Check if this is the correct formula for namecoin +// Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { +// return Amount( +// rawValue: BigInt.from( +// ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * +// (feeRatePerKB / 1000).ceil()), +// fractionDigits: coin.decimals, +// ); +// } +// +// Future sweepAllEstimate(int feeRate) async { +// int available = 0; +// int inputCount = 0; +// for (final output in (await utxos)) { +// if (!output.isBlocked && +// output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { +// available += output.value; +// inputCount++; +// } +// } +// +// // transaction will only have 1 output minus the fee +// final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); +// +// return Amount( +// rawValue: BigInt.from(available), +// fractionDigits: coin.decimals, +// ) - +// estimatedFee; +// } +// +// @override +// Future generateNewAddress() async { +// try { +// final currentReceiving = await _currentReceivingAddress; +// +// final newReceivingIndex = currentReceiving.derivationIndex + 1; +// +// // Use new index to derive a new receiving address +// final newReceivingAddress = await _generateAddressForChain( +// 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); +// +// // Add that new receiving address +// await db.putAddress(newReceivingAddress); +// +// return true; +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from generateNewAddress(): $e\n$s", +// level: LogLevel.Error); +// return false; +// } +// } +// +// @override +// Future get xpub async { +// final node = await Bip32Utils.getBip32Root( +// (await mnemonic).join(" "), +// await mnemonicPassphrase ?? "", +// _network, +// ); +// +// return node.neutered().toBase58(); +// } +// } +// +// // Namecoin Network +// final namecoin = NetworkType( +// messagePrefix: '\x18Namecoin Signed Message:\n', +// bech32: 'nc', +// bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), +// pubKeyHash: 0x34, //From 52 +// scriptHash: 0x0d, //13 +// wif: 0xb4); //from 180 diff --git a/lib/wallets/crypto_currency/coins/namecoin.dart b/lib/wallets/crypto_currency/coins/namecoin.dart index cfe36d46b..02fc3b1c0 100644 --- a/lib/wallets/crypto_currency/coins/namecoin.dart +++ b/lib/wallets/crypto_currency/coins/namecoin.dart @@ -45,9 +45,12 @@ class Namecoin extends Bip39HDCurrency { case DerivePathType.bip44: purpose = 44; break; - case DerivePathType.bip49: - purpose = 49; - break; + + // TODO: [prio=low] Add P2SH support. Disable for now as our p2sh was actually p2sh-p2wpkh (wrapped segwit) + // case DerivePathType.bip49: + // purpose = 49; + // break; + case DerivePathType.bip84: purpose = 84; break; @@ -103,7 +106,7 @@ class Namecoin extends Bip39HDCurrency { {required coinlib.ECPublicKey publicKey, required DerivePathType derivePathType}) { switch (derivePathType) { - // case DerivePathType.bip16: // TODO: [prio=low] Add P2SH support. + // case DerivePathType.bip16: case DerivePathType.bip44: final addr = coinlib.P2PKHAddress.fromPublicKey( @@ -113,6 +116,7 @@ class Namecoin extends Bip39HDCurrency { return (address: addr, addressType: AddressType.p2pkh); + // TODO: [prio=low] Add P2SH support. Disable for now as our p2sh was actually p2sh-p2wpkh (wrapped segwit) // case DerivePathType.bip49: case DerivePathType.bip84: @@ -151,8 +155,9 @@ class Namecoin extends Bip39HDCurrency { @override List get supportedDerivationPathTypes => [ - // DerivePathType.bip16, // TODO: [prio=low] Add P2SH support. + // DerivePathType.bip16, DerivePathType.bip44, + // TODO: [prio=low] Add P2SH support. Disable for now as our p2sh was actually p2sh-p2wpkh (wrapped segwit) // DerivePathType.bip49, DerivePathType.bip84, ]; diff --git a/lib/wallets/wallet/impl/namecoin_wallet.dart b/lib/wallets/wallet/impl/namecoin_wallet.dart index f1358f15b..055523db8 100644 --- a/lib/wallets/wallet/impl/namecoin_wallet.dart +++ b/lib/wallets/wallet/impl/namecoin_wallet.dart @@ -15,7 +15,6 @@ class NamecoinWallet extends Bip39HDWallet NamecoinWallet(CryptoCurrencyNetwork network) : super(Namecoin(network)); - // TODO: double check these filter operations are correct and do not require additional parameters @override FilterOperation? get changeAddressFilterOperation => FilterGroup.and(standardChangeAddressFilters); @@ -45,9 +44,17 @@ class NamecoinWallet extends Bip39HDWallet // =========================================================================== @override - Future<({bool blocked, String? blockedReason, String? utxoLabel})> - checkBlockUTXO(Map jsonUTXO, String? scriptPubKeyHex, - Map jsonTX, String? utxoOwnerAddress) async { + Future< + ({ + bool blocked, + String? blockedReason, + String? utxoLabel, + })> checkBlockUTXO( + Map jsonUTXO, + String? scriptPubKeyHex, + Map jsonTX, + String? utxoOwnerAddress, + ) async { // Namecoin doesn't have special outputs like tokens, ordinals, etc. return (blocked: false, blockedReason: null, utxoLabel: null); } @@ -72,7 +79,7 @@ class NamecoinWallet extends Bip39HDWallet Future updateTransactions() async { final currentChainHeight = await fetchChainHeight(); - // TODO: [prio=low] switch to V2 transactions. + // TODO: [prio=high] switch to V2 transactions. final data = await fetchTransactionsV1( addresses: await fetchAddressesForElectrumXScan(), currentChainHeight: currentChainHeight, diff --git a/test/services/coins/namecoin/namecoin_wallet_test.dart b/test/services/coins/namecoin/namecoin_wallet_test.dart index 451f54885..4d641a696 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.dart @@ -1,21 +1,7 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart'; import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; -import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - -import 'namecoin_wallet_test.mocks.dart'; -import 'namecoin_wallet_test_parameters.dart'; @GenerateMocks([ ElectrumXClient, @@ -23,1614 +9,1614 @@ import 'namecoin_wallet_test_parameters.dart'; TransactionNotificationTracker, ]) void main() { - group("namecoin constants", () { - test("namecoin minimum confirmations", () async { - expect(MINIMUM_CONFIRMATIONS, 2); - }); - test("namecoin dust limit", () async { - expect( - DUST_LIMIT, - Amount( - rawValue: BigInt.from(546), - fractionDigits: 8, - ), - ); - }); - test("namecoin mainnet genesis block hash", () async { - expect(GENESIS_HASH_MAINNET, - "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"); - }); - test("namecoin testnet genesis block hash", () async { - expect(GENESIS_HASH_TESTNET, - "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); - }); - }); - - group("validate mainnet namecoin addresses", () { - MockElectrumXClient? client; - MockCachedElectrumXClient? cachedClient; - late FakeSecureStorage secureStore; - MockTransactionNotificationTracker? tracker; - - NamecoinWallet? mainnetWallet; - - setUp(() { - client = MockElectrumXClient(); - cachedClient = MockCachedElectrumXClient(); - secureStore = FakeSecureStorage(); - tracker = MockTransactionNotificationTracker(); - - mainnetWallet = NamecoinWallet( - walletId: "validateAddressMainNet", - walletName: "validateAddressMainNet", - coin: Coin.namecoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - secureStore: secureStore, - ); - }); - - test("valid mainnet legacy/p2pkh address type", () { - expect( - mainnetWallet?.addressType( - address: "N673DDbjPcrNgJmrhJ1xQXF9LLizQzvjEs"), - DerivePathType.bip44); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - }); - - test("valid mainnet bech32 p2wpkh address type", () { - expect( - mainnetWallet?.addressType( - address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v"), - DerivePathType.bip84); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - }); - - test("invalid bech32 address type", () { - expect( - () => mainnetWallet?.addressType( - address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), - throwsArgumentError); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - }); - - test("address has no matching script", () { - expect( - () => mainnetWallet?.addressType( - address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), - throwsArgumentError); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - }); - }); - - group("testNetworkConnection", () { - MockElectrumXClient? client; - MockCachedElectrumXClient? cachedClient; - late FakeSecureStorage secureStore; - MockTransactionNotificationTracker? tracker; - - NamecoinWallet? nmc; - - setUp(() { - client = MockElectrumXClient(); - cachedClient = MockCachedElectrumXClient(); - secureStore = FakeSecureStorage(); - tracker = MockTransactionNotificationTracker(); - - nmc = NamecoinWallet( - walletId: "testNetworkConnection", - walletName: "testNetworkConnection", - coin: Coin.namecoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - secureStore: secureStore, - ); - }); - - test("attempted connection fails due to server error", () async { - when(client?.ping()).thenAnswer((_) async => false); - final bool? result = await nmc?.testNetworkConnection(); - expect(result, false); - expect(secureStore.interactions, 0); - verify(client?.ping()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("attempted connection fails due to exception", () async { - when(client?.ping()).thenThrow(Exception); - final bool? result = await nmc?.testNetworkConnection(); - expect(result, false); - expect(secureStore.interactions, 0); - verify(client?.ping()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("attempted connection test success", () async { - when(client?.ping()).thenAnswer((_) async => true); - final bool? result = await nmc?.testNetworkConnection(); - expect(result, true); - expect(secureStore.interactions, 0); - verify(client?.ping()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - }); - - group("basic getters, setters, and functions", () { - const testWalletId = "NMCtestWalletID"; - const testWalletName = "NMCWallet"; - - MockElectrumXClient? client; - MockCachedElectrumXClient? cachedClient; - - late FakeSecureStorage secureStore; - MockTransactionNotificationTracker? tracker; - - NamecoinWallet? nmc; - - setUp(() async { - client = MockElectrumXClient(); - cachedClient = MockCachedElectrumXClient(); - - secureStore = FakeSecureStorage(); - tracker = MockTransactionNotificationTracker(); - - nmc = NamecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: Coin.namecoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - secureStore: secureStore, - ); - }); - - test("get networkType main", () async { - expect(Coin.namecoin, Coin.namecoin); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get networkType test", () async { - nmc = NamecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: Coin.namecoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - secureStore: secureStore, - ); - expect(Coin.namecoin, Coin.namecoin); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get cryptoCurrency", () async { - expect(Coin.namecoin, Coin.namecoin); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get coinName", () async { - expect(Coin.namecoin, Coin.namecoin); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get coinTicker", () async { - expect(Coin.namecoin, Coin.namecoin); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get and set walletName", () async { - expect(Coin.namecoin, Coin.namecoin); - nmc?.walletName = "new name"; - expect(nmc?.walletName, "new name"); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("estimateTxFee", () async { - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1), 356); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 900), 356); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 999), 356); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1000), 356); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1001), 712); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); - expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get fees succeeds", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.estimateFee(blocks: 1)) - .thenAnswer((realInvocation) async => Decimal.zero); - when(client?.estimateFee(blocks: 5)) - .thenAnswer((realInvocation) async => Decimal.one); - when(client?.estimateFee(blocks: 20)) - .thenAnswer((realInvocation) async => Decimal.ten); - - final fees = await nmc?.fees; - expect(fees, isA()); - expect(fees?.slow, 1000000000); - expect(fees?.medium, 100000000); - expect(fees?.fast, 0); - - verify(client?.estimateFee(blocks: 1)).called(1); - verify(client?.estimateFee(blocks: 5)).called(1); - verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("get fees fails", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.estimateFee(blocks: 1)) - .thenAnswer((realInvocation) async => Decimal.zero); - when(client?.estimateFee(blocks: 5)) - .thenAnswer((realInvocation) async => Decimal.one); - when(client?.estimateFee(blocks: 20)) - .thenThrow(Exception("some exception")); - - bool didThrow = false; - try { - await nmc?.fees; - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.estimateFee(blocks: 1)).called(1); - verify(client?.estimateFee(blocks: 5)).called(1); - verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - // test("get maxFee", () async { - // when(client?.ping()).thenAnswer((_) async => true); - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_TESTNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.estimateFee(blocks: 20)) - // .thenAnswer((realInvocation) async => Decimal.zero); - // when(client?.estimateFee(blocks: 5)) - // .thenAnswer((realInvocation) async => Decimal.one); - // when(client?.estimateFee(blocks: 1)) - // .thenAnswer((realInvocation) async => Decimal.ten); - // - // final maxFee = await nmc?.maxFee; - // expect(maxFee, 1000000000); - // - // verify(client?.estimateFee(blocks: 1)).called(1); - // verify(client?.estimateFee(blocks: 5)).called(1); - // verify(client?.estimateFee(blocks: 20)).called(1); - // expect(secureStore.interactions, 0); - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(tracker); - // - // }); - }); - - group("Namecoin service class functions that depend on shared storage", () { - const testWalletId = "NMCtestWalletID"; - const testWalletName = "NMCWallet"; - - bool hiveAdaptersRegistered = false; - - MockElectrumXClient? client; - MockCachedElectrumXClient? cachedClient; - - late FakeSecureStorage secureStore; - MockTransactionNotificationTracker? tracker; - - NamecoinWallet? nmc; - - setUp(() async { - await setUpTestHive(); - if (!hiveAdaptersRegistered) { - hiveAdaptersRegistered = true; - - final wallets = await Hive.openBox('wallets'); - await wallets.put('currentWalletName', testWalletName); - } - - client = MockElectrumXClient(); - cachedClient = MockCachedElectrumXClient(); - - secureStore = FakeSecureStorage(); - tracker = MockTransactionNotificationTracker(); - - nmc = NamecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: Coin.namecoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - secureStore: secureStore, - ); - }); - - // test("initializeWallet no network", () async { - // when(client?.ping()).thenAnswer((_) async => false); - // expect(await nmc?.initializeWallet(), false); - // expect(secureStore.interactions, 0); - // verify(client?.ping()).called(1); - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // - // }); - - // test("initializeWallet no network exception", () async { - // when(client?.ping()).thenThrow(Exception("Network connection failed")); - // final wallets = await Hive.openBox(testWalletId); - // expect(await nmc?.initializeExisting(), false); - // expect(secureStore.interactions, 0); - // verify(client?.ping()).called(1); - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // - // }); - - test("initializeWallet mainnet throws bad network", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - // await nmc?.initializeNew(); - await Hive.openBox(testWalletId); - - await expectLater( - () => nmc?.initializeExisting(), throwsA(isA())) - .then((_) { - expect(secureStore.interactions, 0); - // verify(client?.ping()).called(1); - // verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - }); - - test("initializeWallet throws mnemonic overwrite exception", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - await secureStore.write( - key: "${testWalletId}_mnemonic", value: "some mnemonic"); - - await Hive.openBox(testWalletId); - await expectLater( - () => nmc?.initializeExisting(), throwsA(isA())) - .then((_) { - expect(secureStore.interactions, 1); - // verify(client?.ping()).called(1); - // verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - }); - - // test( - // "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", - // () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_TESTNET, - // "hash_function": "sha256", - // "services": [] - // }); - // - // bool hasThrown = false; - // try { - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // } catch (_) { - // hasThrown = true; - // } - // expect(hasThrown, false); - // - // verify(client?.getServerFeatures()).called(1); - // - // expect(secureStore.interactions, 0); - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // - // }); - - test( - "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - await secureStore.write( - key: "${testWalletId}_mnemonic", value: "some mnemonic words"); - - bool hasThrown = false; - try { - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - verify(client?.getServerFeatures()).called(1); - - expect(secureStore.interactions, 2); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - // test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // await DB.instance.init(); - // await Hive.openBox(testWalletId); - // bool hasThrown = false; - // try { - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // } catch (_) { - // hasThrown = true; - // } - // expect(hasThrown, false); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - // - // expect(secureStore.interactions, 20); - // expect(secureStore.writes, 7); - // expect(secureStore.reads, 13); - // expect(secureStore.deletes, 0); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - // - // test("get mnemonic list", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // - // await Hive.openBox(testWalletId); - // - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // expect(await nmc?.mnemonic, TEST_MNEMONIC.split(" ")); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - - // test("recoverFromMnemonic using non empty seed on mainnet succeeds", - // () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => historyBatchResponse); - // - // List dynamicArgValues = []; - // - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((realInvocation) async { - // if (realInvocation.namedArguments.values.first.length == 1) { - // dynamicArgValues.add(realInvocation.namedArguments.values.first); - // } - // - // return historyBatchResponse; - // }); - // - // await Hive.openBox(testWalletId); - // - // bool hasThrown = false; - // try { - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // } catch (_) { - // hasThrown = true; - // } - // expect(hasThrown, false); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - // - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // - // verify(client?.getBatchHistory(args: map)).called(1); - // expect(activeScriptHashes.contains(map.values.first.first as String), - // true); - // } - // - // expect(secureStore.interactions, 14); - // expect(secureStore.writes, 7); - // expect(secureStore.reads, 7); - // expect(secureStore.deletes, 0); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - // - // test("fullRescan succeeds", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => historyBatchResponse); - // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - // .thenAnswer((realInvocation) async {}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // when(client?.getBatchHistory(args: { - // "0": [ - // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // when(client?.getBatchHistory(args: { - // "0": [ - // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // final wallet = await Hive.openBox(testWalletId); - // - // // restore so we have something to rescan - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // // fetch valid wallet data - // final preReceivingAddressesP2PKH = - // await wallet.get('receivingAddressesP2PKH'); - // final preReceivingAddressesP2SH = - // await wallet.get('receivingAddressesP2SH'); - // final preReceivingAddressesP2WPKH = - // await wallet.get('receivingAddressesP2WPKH'); - // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - // final preChangeAddressesP2WPKH = - // await wallet.get('changeAddressesP2WPKH'); - // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - // final preUtxoData = await wallet.get('latest_utxo_model'); - // final preReceiveDerivationsStringP2PKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2PKH"); - // final preChangeDerivationsStringP2PKH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - // final preReceiveDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - // final preChangeDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - // final preReceiveDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2WPKH"); - // final preChangeDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_changeDerivationsP2WPKH"); - // - // // destroy the data that the rescan will fix - // await wallet.put( - // 'receivingAddressesP2PKH', ["some address", "some other address"]); - // await wallet.put( - // 'receivingAddressesP2SH', ["some address", "some other address"]); - // await wallet.put( - // 'receivingAddressesP2WPKH', ["some address", "some other address"]); - // await wallet - // .put('changeAddressesP2PKH', ["some address", "some other address"]); - // await wallet - // .put('changeAddressesP2SH', ["some address", "some other address"]); - // await wallet - // .put('changeAddressesP2WPKH', ["some address", "some other address"]); - // await wallet.put('receivingIndexP2PKH', 123); - // await wallet.put('receivingIndexP2SH', 123); - // await wallet.put('receivingIndexP2WPKH', 123); - // await wallet.put('changeIndexP2PKH', 123); - // await wallet.put('changeIndexP2SH', 123); - // await wallet.put('changeIndexP2WPKH', 123); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - // await secureStore.write( - // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); - // await secureStore.write( - // key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); - // await secureStore.write( - // key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); - // - // bool hasThrown = false; - // try { - // await nmc?.fullRescan(2, 1000); - // } catch (_) { - // hasThrown = true; - // } - // expect(hasThrown, false); - // - // // fetch wallet data again - // final receivingAddressesP2PKH = - // await wallet.get('receivingAddressesP2PKH'); - // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - // final receivingAddressesP2WPKH = - // await wallet.get('receivingAddressesP2WPKH'); - // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - // final utxoData = await wallet.get('latest_utxo_model'); - // final receiveDerivationsStringP2PKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2PKH"); - // final changeDerivationsStringP2PKH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - // final receiveDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - // final changeDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - // final receiveDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2WPKH"); - // final changeDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_changeDerivationsP2WPKH"); - // - // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - // expect(preChangeAddressesP2SH, changeAddressesP2SH); - // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - // expect(preReceivingIndexP2SH, receivingIndexP2SH); - // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - // expect(preChangeIndexP2PKH, changeIndexP2PKH); - // expect(preChangeIndexP2SH, changeIndexP2SH); - // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - // expect(preUtxoData, utxoData); - // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - // ] - // })).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - // ] - // })).called(2); - // - // verify(client?.getBatchHistory(args: { - // "0": [ - // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - // ] - // })).called(2); - // - // verify(client?.getBatchHistory(args: { - // "0": [ - // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - // ] - // })).called(2); - // - // verify(client?.getBatchHistory(args: { - // "0": [ - // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - // ] - // })).called(2); - // - // verify(client?.getBatchHistory(args: { - // "0": [ - // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - // ] - // })).called(2); - // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - // .called(1); - // - // // for (final arg in dynamicArgValues) { - // // final map = Map>.from(arg as Map); - // // Map argCount = {}; - // // - // // // verify(client?.getBatchHistory(args: map)).called(1); - // // // expect(activeScriptHashes.contains(map.values.first.first as String), - // // // true); - // // } - // - // // Map argCount = {}; - // // - // // for (final arg in dynamicArgValues) { - // // final map = Map>.from(arg as Map); - // // - // // final str = jsonEncode(map); - // // - // // if (argCount[str] == null) { - // // argCount[str] = 1; - // // } else { - // // argCount[str] = argCount[str]! + 1; - // // } - // // } - // // - // // argCount.forEach((key, value) => print("arg: $key\ncount: $value")); - // - // expect(secureStore.writes, 25); - // expect(secureStore.reads, 32); - // expect(secureStore.deletes, 6); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - // - // test("fullRescan fails", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => historyBatchResponse); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(client?.getBatchHistory(args: { - // "0": [ - // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - // ] - // })).thenAnswer((realInvocation) async => {"0": []}); - // - // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - // .thenAnswer((realInvocation) async {}); - // - // final wallet = await Hive.openBox(testWalletId); - // - // // restore so we have something to rescan - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // // fetch wallet data - // final preReceivingAddressesP2PKH = - // await wallet.get('receivingAddressesP2PKH'); - // final preReceivingAddressesP2SH = - // await wallet.get('receivingAddressesP2SH'); - // final preReceivingAddressesP2WPKH = - // await wallet.get('receivingAddressesP2WPKH'); - // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - // final preChangeAddressesP2WPKH = - // await wallet.get('changeAddressesP2WPKH'); - // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - // final preUtxoData = await wallet.get('latest_utxo_model'); - // final preReceiveDerivationsStringP2PKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2PKH"); - // final preChangeDerivationsStringP2PKH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - // final preReceiveDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - // final preChangeDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - // final preReceiveDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2WPKH"); - // final preChangeDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_changeDerivationsP2WPKH"); - // - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenThrow(Exception("fake exception")); - // - // bool hasThrown = false; - // try { - // await nmc?.fullRescan(2, 1000); - // } catch (_) { - // hasThrown = true; - // } - // expect(hasThrown, true); - // - // // fetch wallet data again - // final receivingAddressesP2PKH = - // await wallet.get('receivingAddressesP2PKH'); - // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - // final receivingAddressesP2WPKH = - // await wallet.get('receivingAddressesP2WPKH'); - // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - // final utxoData = await wallet.get('latest_utxo_model'); - // final receiveDerivationsStringP2PKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2PKH"); - // final changeDerivationsStringP2PKH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - // final receiveDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - // final changeDerivationsStringP2SH = - // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - // final receiveDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2WPKH"); - // final changeDerivationsStringP2WPKH = await secureStore.read( - // key: "${testWalletId}_changeDerivationsP2WPKH"); - // - // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - // expect(preChangeAddressesP2SH, changeAddressesP2SH); - // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - // expect(preReceivingIndexP2SH, receivingIndexP2SH); - // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - // expect(preChangeIndexP2PKH, changeIndexP2PKH); - // expect(preChangeIndexP2SH, changeIndexP2SH); - // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - // expect(preUtxoData, utxoData); - // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - // - // verify(client?.getBatchHistory(args: { - // "0": [ - // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - // ] - // })).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - // ] - // })).called(1); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - // ] - // })).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - // ] - // })).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - // ] - // })).called(2); - // verify(client?.getBatchHistory(args: { - // "0": [ - // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - // ] - // })).called(2); - // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - // .called(1); - // - // expect(secureStore.writes, 19); - // expect(secureStore.reads, 32); - // expect(secureStore.deletes, 12); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - // - // test("prepareSend fails", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => historyBatchResponse); - // - // List dynamicArgValues = []; - // - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((realInvocation) async { - // if (realInvocation.namedArguments.values.first.length == 1) { - // dynamicArgValues.add(realInvocation.namedArguments.values.first); - // } - // - // return historyBatchResponse; - // }); - // - // await Hive.openBox(testWalletId); - // - // when(cachedClient?.getTransaction( - // txHash: - // "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", - // coin: Coin.namecoin)) - // .thenAnswer((_) async => tx2Raw); - // when(cachedClient?.getTransaction( - // txHash: - // "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", - // coin: Coin.namecoin)) - // .thenAnswer((_) async => tx3Raw); - // when(cachedClient?.getTransaction( - // txHash: - // "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", - // coin: Coin.namecoin, - // )).thenAnswer((_) async => tx4Raw); - // - // // recover to fill data - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // // modify addresses to properly mock data to build a tx - // final rcv44 = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2PKH"); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2PKH", - // value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", - // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); - // final rcv49 = - // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2SH", - // value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", - // "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); - // final rcv84 = await secureStore.read( - // key: "${testWalletId}_receiveDerivationsP2WPKH"); - // await secureStore.write( - // key: "${testWalletId}_receiveDerivationsP2WPKH", - // value: rcv84?.replaceFirst( - // "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", - // "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); - // - // // nmc?.outputsList = utxoList; - // - // bool didThrow = false; - // try { - // await nmc?.prepareSend( - // address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v", - // satoshiAmount: 15000); - // } catch (_) { - // didThrow = true; - // } - // - // expect(didThrow, true); - // - // verify(client?.getServerFeatures()).called(1); - // - // /// verify transaction no matching calls - // - // // verify(cachedClient?.getTransaction( - // // txHash: - // // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", - // // coin: Coin.namecoin, - // // callOutSideMainIsolate: false)) - // // .called(1); - // // verify(cachedClient?.getTransaction( - // // txHash: - // // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", - // // coin: Coin.namecoin, - // // callOutSideMainIsolate: false)) - // // .called(1); - // // verify(cachedClient?.getTransaction( - // // txHash: - // // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", - // // coin: Coin.namecoin, - // // callOutSideMainIsolate: false)) - // // .called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - // - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // - // verify(client?.getBatchHistory(args: map)).called(1); - // expect(activeScriptHashes.contains(map.values.first.first as String), - // true); - // } - // - // expect(secureStore.interactions, 20); - // expect(secureStore.writes, 10); - // expect(secureStore.reads, 10); - // expect(secureStore.deletes, 0); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - - test("confirmSend no hex", () async { - bool didThrow = false; - try { - await nmc?.confirmSend(txData: {"some": "strange map"}); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("confirmSend hex is not string", () async { - bool didThrow = false; - try { - await nmc?.confirmSend(txData: {"hex": true}); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("confirmSend hex is string but missing other data", () async { - bool didThrow = false; - try { - await nmc?.confirmSend(txData: {"hex": "a string"}); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.broadcastTransaction( - rawTx: "a string", requestID: anyNamed("requestID"))) - .called(1); - - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("confirmSend fails due to vSize being greater than fee", () async { - bool didThrow = false; - try { - await nmc - ?.confirmSend(txData: {"hex": "a string", "fee": 1, "vSize": 10}); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.broadcastTransaction( - rawTx: "a string", requestID: anyNamed("requestID"))) - .called(1); - - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - }); - - test("confirmSend fails when broadcast transactions throws", () async { - when(client?.broadcastTransaction( - rawTx: "a string", requestID: anyNamed("requestID"))) - .thenThrow(Exception("some exception")); - - bool didThrow = false; - try { - await nmc - ?.confirmSend(txData: {"hex": "a string", "fee": 10, "vSize": 10}); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.broadcastTransaction( - rawTx: "a string", requestID: anyNamed("requestID"))) - .called(1); - - expect(secureStore.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - }); - // - // // this test will create a non mocked electrumx client that will try to connect - // // to the provided ipAddress below. This will throw a bunch of errors - // // which what we want here as actually calling electrumx calls here is unwanted. - // // test("listen to NodesChangedEvent", () async { - // // nmc = NamecoinWallet( - // // walletId: testWalletId, - // // walletName: testWalletName, - // // networkType: BasicNetworkType.test, - // // client: client, - // // cachedClient: cachedClient, - // // - // // secureStore: secureStore, - // - // // ); - // // - // // // set node - // // final wallet = await Hive.openBox(testWalletId); - // // await wallet.put("nodes", { - // // "default": { - // // "id": "some nodeID", - // // "ipAddress": "some address", - // // "port": "9000", - // // "useSSL": true, - // // } - // // }); - // // await wallet.put("activeNodeID_Bitcoin", "default"); - // // - // // final a = nmc.cachedElectrumXClient; - // // - // // // return when refresh is called on node changed trigger - // // nmc.longMutex = true; - // // - // // GlobalEventBus.instance - // // .fire(NodesChangedEvent(NodesChangedEventType.updatedCurrentNode)); - // // - // // // make sure event has processed before continuing - // // await Future.delayed(Duration(seconds: 5)); - // // - // // final b = nmc.cachedElectrumXClient; - // // - // // expect(identical(a, b), false); - // // - // // await nmc.exit(); - // // - // // expect(secureStore.interactions, 0); - // // verifyNoMoreInteractions(client); - // // verifyNoMoreInteractions(cachedClient); - // // - // // }); - - // test("refresh wallet mutex locked", () async { - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getBatchHistory(args: historyBatchArgs0)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs2)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs3)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs4)) - // .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs5)) - // .thenAnswer((_) async => historyBatchResponse); - // - // List dynamicArgValues = []; - // - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((realInvocation) async { - // if (realInvocation.namedArguments.values.first.length == 1) { - // dynamicArgValues.add(realInvocation.namedArguments.values.first); - // } - // - // return historyBatchResponse; - // }); - // - // await Hive.openBox(testWalletId); - // - // // recover to fill data - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // nmc?.refreshMutex = true; - // - // await nmc?.refresh(); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - // - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // - // verify(client?.getBatchHistory(args: map)).called(1); - // expect(activeScriptHashes.contains(map.values.first.first as String), - // true); - // } - // - // expect(secureStore.interactions, 14); - // expect(secureStore.writes, 7); - // expect(secureStore.reads, 7); - // expect(secureStore.deletes, 0); - // - // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(tracker); - // }); - // - // test("refresh wallet normally", () async { - // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - // {"height": 520481, "hex": "some block hex"}); - // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, - // "pruning": null, - // "server_version": "Unit tests", - // "protocol_min": "1.4", - // "protocol_max": "1.4.2", - // "genesis_hash": GENESIS_HASH_MAINNET, - // "hash_function": "sha256", - // "services": [] - // }); - // when(client?.getHistory(scripthash: anyNamed("scripthash"))) - // .thenAnswer((_) async => []); - // when(client?.estimateFee(blocks: anyNamed("blocks"))) - // .thenAnswer((_) async => Decimal.one); - // - // final List dynamicArgValues = []; - // - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((realInvocation) async { - // dynamicArgValues.add(realInvocation.namedArguments.values.first); - // return historyBatchResponse; - // }); - // - // await Hive.openBox(testWalletId); - // - // // recover to fill data - // await nmc?.recoverFromMnemonic( - // mnemonic: TEST_MNEMONIC, - // maxUnusedAddressGap: 2, - // maxNumberOfIndexesToCheck: 1000, - // height: 4000); - // - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((_) async => {}); - // when(client?.getBatchUTXOs(args: anyNamed("args"))) - // .thenAnswer((_) async => emptyHistoryBatchResponse); - // - // await nmc?.refresh(); - // - // verify(client?.getServerFeatures()).called(1); - // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); - // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); - // verify(client?.getBlockHeadTip()).called(1); - // - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // - // verify(client?.getBatchHistory(args: map)).called(1); - // } - // - // expect(secureStore.interactions, 14); - // expect(secureStore.writes, 7); - // expect(secureStore.reads, 7); - // expect(secureStore.deletes, 0); - // - // // verifyNoMoreInteractions(client); - // verifyNoMoreInteractions(cachedClient); - // }); - - tearDown(() async { - await tearDownTestHive(); - }); - }); + // group("namecoin constants", () { + // test("namecoin minimum confirmations", () async { + // expect(MINIMUM_CONFIRMATIONS, 2); + // }); + // test("namecoin dust limit", () async { + // expect( + // DUST_LIMIT, + // Amount( + // rawValue: BigInt.from(546), + // fractionDigits: 8, + // ), + // ); + // }); + // test("namecoin mainnet genesis block hash", () async { + // expect(GENESIS_HASH_MAINNET, + // "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"); + // }); + // test("namecoin testnet genesis block hash", () async { + // expect(GENESIS_HASH_TESTNET, + // "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); + // }); + // }); + // + // group("validate mainnet namecoin addresses", () { + // MockElectrumXClient? client; + // MockCachedElectrumXClient? cachedClient; + // late FakeSecureStorage secureStore; + // MockTransactionNotificationTracker? tracker; + // + // NamecoinWallet? mainnetWallet; + // + // setUp(() { + // client = MockElectrumXClient(); + // cachedClient = MockCachedElectrumXClient(); + // secureStore = FakeSecureStorage(); + // tracker = MockTransactionNotificationTracker(); + // + // mainnetWallet = NamecoinWallet( + // walletId: "validateAddressMainNet", + // walletName: "validateAddressMainNet", + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // }); + // + // test("valid mainnet legacy/p2pkh address type", () { + // expect( + // mainnetWallet?.addressType( + // address: "N673DDbjPcrNgJmrhJ1xQXF9LLizQzvjEs"), + // DerivePathType.bip44); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("valid mainnet bech32 p2wpkh address type", () { + // expect( + // mainnetWallet?.addressType( + // address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v"), + // DerivePathType.bip84); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("invalid bech32 address type", () { + // expect( + // () => mainnetWallet?.addressType( + // address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), + // throwsArgumentError); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("address has no matching script", () { + // expect( + // () => mainnetWallet?.addressType( + // address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), + // throwsArgumentError); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // }); + // + // group("testNetworkConnection", () { + // MockElectrumXClient? client; + // MockCachedElectrumXClient? cachedClient; + // late FakeSecureStorage secureStore; + // MockTransactionNotificationTracker? tracker; + // + // NamecoinWallet? nmc; + // + // setUp(() { + // client = MockElectrumXClient(); + // cachedClient = MockCachedElectrumXClient(); + // secureStore = FakeSecureStorage(); + // tracker = MockTransactionNotificationTracker(); + // + // nmc = NamecoinWallet( + // walletId: "testNetworkConnection", + // walletName: "testNetworkConnection", + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // }); + // + // test("attempted connection fails due to server error", () async { + // when(client?.ping()).thenAnswer((_) async => false); + // final bool? result = await nmc?.testNetworkConnection(); + // expect(result, false); + // expect(secureStore.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("attempted connection fails due to exception", () async { + // when(client?.ping()).thenThrow(Exception); + // final bool? result = await nmc?.testNetworkConnection(); + // expect(result, false); + // expect(secureStore.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("attempted connection test success", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // final bool? result = await nmc?.testNetworkConnection(); + // expect(result, true); + // expect(secureStore.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // }); + // + // group("basic getters, setters, and functions", () { + // const testWalletId = "NMCtestWalletID"; + // const testWalletName = "NMCWallet"; + // + // MockElectrumXClient? client; + // MockCachedElectrumXClient? cachedClient; + // + // late FakeSecureStorage secureStore; + // MockTransactionNotificationTracker? tracker; + // + // NamecoinWallet? nmc; + // + // setUp(() async { + // client = MockElectrumXClient(); + // cachedClient = MockCachedElectrumXClient(); + // + // secureStore = FakeSecureStorage(); + // tracker = MockTransactionNotificationTracker(); + // + // nmc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // }); + // + // test("get networkType main", () async { + // expect(Coin.namecoin, Coin.namecoin); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get networkType test", () async { + // nmc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // expect(Coin.namecoin, Coin.namecoin); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get cryptoCurrency", () async { + // expect(Coin.namecoin, Coin.namecoin); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get coinName", () async { + // expect(Coin.namecoin, Coin.namecoin); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get coinTicker", () async { + // expect(Coin.namecoin, Coin.namecoin); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get and set walletName", () async { + // expect(Coin.namecoin, Coin.namecoin); + // nmc?.walletName = "new name"; + // expect(nmc?.walletName, "new name"); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("estimateTxFee", () async { + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1), 356); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 900), 356); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 999), 356); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1000), 356); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1001), 712); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); + // expect(nmc?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get fees succeeds", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.estimateFee(blocks: 1)) + // .thenAnswer((realInvocation) async => Decimal.zero); + // when(client?.estimateFee(blocks: 5)) + // .thenAnswer((realInvocation) async => Decimal.one); + // when(client?.estimateFee(blocks: 20)) + // .thenAnswer((realInvocation) async => Decimal.ten); + // + // final fees = await nmc?.fees; + // expect(fees, isA()); + // expect(fees?.slow, 1000000000); + // expect(fees?.medium, 100000000); + // expect(fees?.fast, 0); + // + // verify(client?.estimateFee(blocks: 1)).called(1); + // verify(client?.estimateFee(blocks: 5)).called(1); + // verify(client?.estimateFee(blocks: 20)).called(1); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get fees fails", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.estimateFee(blocks: 1)) + // .thenAnswer((realInvocation) async => Decimal.zero); + // when(client?.estimateFee(blocks: 5)) + // .thenAnswer((realInvocation) async => Decimal.one); + // when(client?.estimateFee(blocks: 20)) + // .thenThrow(Exception("some exception")); + // + // bool didThrow = false; + // try { + // await nmc?.fees; + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.estimateFee(blocks: 1)).called(1); + // verify(client?.estimateFee(blocks: 5)).called(1); + // verify(client?.estimateFee(blocks: 20)).called(1); + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // // test("get maxFee", () async { + // // when(client?.ping()).thenAnswer((_) async => true); + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_TESTNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.estimateFee(blocks: 20)) + // // .thenAnswer((realInvocation) async => Decimal.zero); + // // when(client?.estimateFee(blocks: 5)) + // // .thenAnswer((realInvocation) async => Decimal.one); + // // when(client?.estimateFee(blocks: 1)) + // // .thenAnswer((realInvocation) async => Decimal.ten); + // // + // // final maxFee = await nmc?.maxFee; + // // expect(maxFee, 1000000000); + // // + // // verify(client?.estimateFee(blocks: 1)).called(1); + // // verify(client?.estimateFee(blocks: 5)).called(1); + // // verify(client?.estimateFee(blocks: 20)).called(1); + // // expect(secureStore.interactions, 0); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(tracker); + // // + // // }); + // }); + // + // group("Namecoin service class functions that depend on shared storage", () { + // const testWalletId = "NMCtestWalletID"; + // const testWalletName = "NMCWallet"; + // + // bool hiveAdaptersRegistered = false; + // + // MockElectrumXClient? client; + // MockCachedElectrumXClient? cachedClient; + // + // late FakeSecureStorage secureStore; + // MockTransactionNotificationTracker? tracker; + // + // NamecoinWallet? nmc; + // + // setUp(() async { + // await setUpTestHive(); + // if (!hiveAdaptersRegistered) { + // hiveAdaptersRegistered = true; + // + // final wallets = await Hive.openBox('wallets'); + // await wallets.put('currentWalletName', testWalletName); + // } + // + // client = MockElectrumXClient(); + // cachedClient = MockCachedElectrumXClient(); + // + // secureStore = FakeSecureStorage(); + // tracker = MockTransactionNotificationTracker(); + // + // nmc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // }); + // + // // test("initializeWallet no network", () async { + // // when(client?.ping()).thenAnswer((_) async => false); + // // expect(await nmc?.initializeWallet(), false); + // // expect(secureStore.interactions, 0); + // // verify(client?.ping()).called(1); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // + // // }); + // + // // test("initializeWallet no network exception", () async { + // // when(client?.ping()).thenThrow(Exception("Network connection failed")); + // // final wallets = await Hive.openBox(testWalletId); + // // expect(await nmc?.initializeExisting(), false); + // // expect(secureStore.interactions, 0); + // // verify(client?.ping()).called(1); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // + // // }); + // + // test("initializeWallet mainnet throws bad network", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // // await nmc?.initializeNew(); + // await Hive.openBox(testWalletId); + // + // await expectLater( + // () => nmc?.initializeExisting(), throwsA(isA())) + // .then((_) { + // expect(secureStore.interactions, 0); + // // verify(client?.ping()).called(1); + // // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // }); + // + // test("initializeWallet throws mnemonic overwrite exception", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // await secureStore.write( + // key: "${testWalletId}_mnemonic", value: "some mnemonic"); + // + // await Hive.openBox(testWalletId); + // await expectLater( + // () => nmc?.initializeExisting(), throwsA(isA())) + // .then((_) { + // expect(secureStore.interactions, 1); + // // verify(client?.ping()).called(1); + // // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // }); + // + // // test( + // // "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", + // // () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_TESTNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // + // // bool hasThrown = false; + // // try { + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // } catch (_) { + // // hasThrown = true; + // // } + // // expect(hasThrown, false); + // // + // // verify(client?.getServerFeatures()).called(1); + // // + // // expect(secureStore.interactions, 0); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // + // // }); + // + // test( + // "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await secureStore.write( + // key: "${testWalletId}_mnemonic", value: "some mnemonic words"); + // + // bool hasThrown = false; + // try { + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // verify(client?.getServerFeatures()).called(1); + // + // expect(secureStore.interactions, 2); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // // test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // await DB.instance.init(); + // // await Hive.openBox(testWalletId); + // // bool hasThrown = false; + // // try { + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // } catch (_) { + // // hasThrown = true; + // // } + // // expect(hasThrown, false); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // // + // // expect(secureStore.interactions, 20); + // // expect(secureStore.writes, 7); + // // expect(secureStore.reads, 13); + // // expect(secureStore.deletes, 0); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // // + // // test("get mnemonic list", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // + // // await Hive.openBox(testWalletId); + // // + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // expect(await nmc?.mnemonic, TEST_MNEMONIC.split(" ")); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // + // // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // // () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => historyBatchResponse); + // // + // // List dynamicArgValues = []; + // // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((realInvocation) async { + // // if (realInvocation.namedArguments.values.first.length == 1) { + // // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // // } + // // + // // return historyBatchResponse; + // // }); + // // + // // await Hive.openBox(testWalletId); + // // + // // bool hasThrown = false; + // // try { + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // } catch (_) { + // // hasThrown = true; + // // } + // // expect(hasThrown, false); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // + // // verify(client?.getBatchHistory(args: map)).called(1); + // // expect(activeScriptHashes.contains(map.values.first.first as String), + // // true); + // // } + // // + // // expect(secureStore.interactions, 14); + // // expect(secureStore.writes, 7); + // // expect(secureStore.reads, 7); + // // expect(secureStore.deletes, 0); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // // + // // test("fullRescan succeeds", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // // .thenAnswer((realInvocation) async {}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // final wallet = await Hive.openBox(testWalletId); + // // + // // // restore so we have something to rescan + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // // fetch valid wallet data + // // final preReceivingAddressesP2PKH = + // // await wallet.get('receivingAddressesP2PKH'); + // // final preReceivingAddressesP2SH = + // // await wallet.get('receivingAddressesP2SH'); + // // final preReceivingAddressesP2WPKH = + // // await wallet.get('receivingAddressesP2WPKH'); + // // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // // final preChangeAddressesP2WPKH = + // // await wallet.get('changeAddressesP2WPKH'); + // // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // // final preUtxoData = await wallet.get('latest_utxo_model'); + // // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH"); + // // final preChangeDerivationsStringP2PKH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // // final preReceiveDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // // final preChangeDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_changeDerivationsP2WPKH"); + // // + // // // destroy the data that the rescan will fix + // // await wallet.put( + // // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // // await wallet.put( + // // 'receivingAddressesP2SH', ["some address", "some other address"]); + // // await wallet.put( + // // 'receivingAddressesP2WPKH', ["some address", "some other address"]); + // // await wallet + // // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // // await wallet + // // .put('changeAddressesP2SH', ["some address", "some other address"]); + // // await wallet + // // .put('changeAddressesP2WPKH', ["some address", "some other address"]); + // // await wallet.put('receivingIndexP2PKH', 123); + // // await wallet.put('receivingIndexP2SH', 123); + // // await wallet.put('receivingIndexP2WPKH', 123); + // // await wallet.put('changeIndexP2PKH', 123); + // // await wallet.put('changeIndexP2SH', 123); + // // await wallet.put('changeIndexP2WPKH', 123); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // // await secureStore.write( + // // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); + // // await secureStore.write( + // // key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); + // // await secureStore.write( + // // key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); + // // + // // bool hasThrown = false; + // // try { + // // await nmc?.fullRescan(2, 1000); + // // } catch (_) { + // // hasThrown = true; + // // } + // // expect(hasThrown, false); + // // + // // // fetch wallet data again + // // final receivingAddressesP2PKH = + // // await wallet.get('receivingAddressesP2PKH'); + // // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // // final receivingAddressesP2WPKH = + // // await wallet.get('receivingAddressesP2WPKH'); + // // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // // final utxoData = await wallet.get('latest_utxo_model'); + // // final receiveDerivationsStringP2PKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH"); + // // final changeDerivationsStringP2PKH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // // final receiveDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // // final changeDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // // final receiveDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // // final changeDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_changeDerivationsP2WPKH"); + // // + // // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // // expect(preChangeIndexP2SH, changeIndexP2SH); + // // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // // expect(preUtxoData, utxoData); + // // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // // ] + // // })).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // // ] + // // })).called(2); + // // + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // // ] + // // })).called(2); + // // + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // // ] + // // })).called(2); + // // + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // // ] + // // })).called(2); + // // + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // // ] + // // })).called(2); + // // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // // .called(1); + // // + // // // for (final arg in dynamicArgValues) { + // // // final map = Map>.from(arg as Map); + // // // Map argCount = {}; + // // // + // // // // verify(client?.getBatchHistory(args: map)).called(1); + // // // // expect(activeScriptHashes.contains(map.values.first.first as String), + // // // // true); + // // // } + // // + // // // Map argCount = {}; + // // // + // // // for (final arg in dynamicArgValues) { + // // // final map = Map>.from(arg as Map); + // // // + // // // final str = jsonEncode(map); + // // // + // // // if (argCount[str] == null) { + // // // argCount[str] = 1; + // // // } else { + // // // argCount[str] = argCount[str]! + 1; + // // // } + // // // } + // // // + // // // argCount.forEach((key, value) => print("arg: $key\ncount: $value")); + // // + // // expect(secureStore.writes, 25); + // // expect(secureStore.reads, 32); + // // expect(secureStore.deletes, 6); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // // + // // test("fullRescan fails", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => historyBatchResponse); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(client?.getBatchHistory(args: { + // // "0": [ + // // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // // ] + // // })).thenAnswer((realInvocation) async => {"0": []}); + // // + // // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // // .thenAnswer((realInvocation) async {}); + // // + // // final wallet = await Hive.openBox(testWalletId); + // // + // // // restore so we have something to rescan + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // // fetch wallet data + // // final preReceivingAddressesP2PKH = + // // await wallet.get('receivingAddressesP2PKH'); + // // final preReceivingAddressesP2SH = + // // await wallet.get('receivingAddressesP2SH'); + // // final preReceivingAddressesP2WPKH = + // // await wallet.get('receivingAddressesP2WPKH'); + // // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // // final preChangeAddressesP2WPKH = + // // await wallet.get('changeAddressesP2WPKH'); + // // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // // final preUtxoData = await wallet.get('latest_utxo_model'); + // // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH"); + // // final preChangeDerivationsStringP2PKH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // // final preReceiveDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // // final preChangeDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_changeDerivationsP2WPKH"); + // // + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenThrow(Exception("fake exception")); + // // + // // bool hasThrown = false; + // // try { + // // await nmc?.fullRescan(2, 1000); + // // } catch (_) { + // // hasThrown = true; + // // } + // // expect(hasThrown, true); + // // + // // // fetch wallet data again + // // final receivingAddressesP2PKH = + // // await wallet.get('receivingAddressesP2PKH'); + // // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // // final receivingAddressesP2WPKH = + // // await wallet.get('receivingAddressesP2WPKH'); + // // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // // final utxoData = await wallet.get('latest_utxo_model'); + // // final receiveDerivationsStringP2PKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH"); + // // final changeDerivationsStringP2PKH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // // final receiveDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // // final changeDerivationsStringP2SH = + // // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // // final receiveDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // // final changeDerivationsStringP2WPKH = await secureStore.read( + // // key: "${testWalletId}_changeDerivationsP2WPKH"); + // // + // // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // // expect(preChangeIndexP2SH, changeIndexP2SH); + // // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // // expect(preUtxoData, utxoData); + // // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // // + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // // ] + // // })).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // // ] + // // })).called(1); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // // ] + // // })).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // // ] + // // })).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // // ] + // // })).called(2); + // // verify(client?.getBatchHistory(args: { + // // "0": [ + // // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // // ] + // // })).called(2); + // // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // // .called(1); + // // + // // expect(secureStore.writes, 19); + // // expect(secureStore.reads, 32); + // // expect(secureStore.deletes, 12); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // // + // // test("prepareSend fails", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => historyBatchResponse); + // // + // // List dynamicArgValues = []; + // // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((realInvocation) async { + // // if (realInvocation.namedArguments.values.first.length == 1) { + // // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // // } + // // + // // return historyBatchResponse; + // // }); + // // + // // await Hive.openBox(testWalletId); + // // + // // when(cachedClient?.getTransaction( + // // txHash: + // // "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + // // coin: Coin.namecoin)) + // // .thenAnswer((_) async => tx2Raw); + // // when(cachedClient?.getTransaction( + // // txHash: + // // "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + // // coin: Coin.namecoin)) + // // .thenAnswer((_) async => tx3Raw); + // // when(cachedClient?.getTransaction( + // // txHash: + // // "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", + // // coin: Coin.namecoin, + // // )).thenAnswer((_) async => tx4Raw); + // // + // // // recover to fill data + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // // modify addresses to properly mock data to build a tx + // // final rcv44 = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH"); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2PKH", + // // value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", + // // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); + // // final rcv49 = + // // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2SH", + // // value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", + // // "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); + // // final rcv84 = await secureStore.read( + // // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // // await secureStore.write( + // // key: "${testWalletId}_receiveDerivationsP2WPKH", + // // value: rcv84?.replaceFirst( + // // "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", + // // "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); + // // + // // // nmc?.outputsList = utxoList; + // // + // // bool didThrow = false; + // // try { + // // await nmc?.prepareSend( + // // address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v", + // // satoshiAmount: 15000); + // // } catch (_) { + // // didThrow = true; + // // } + // // + // // expect(didThrow, true); + // // + // // verify(client?.getServerFeatures()).called(1); + // // + // // /// verify transaction no matching calls + // // + // // // verify(cachedClient?.getTransaction( + // // // txHash: + // // // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // // // coin: Coin.namecoin, + // // // callOutSideMainIsolate: false)) + // // // .called(1); + // // // verify(cachedClient?.getTransaction( + // // // txHash: + // // // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // // // coin: Coin.namecoin, + // // // callOutSideMainIsolate: false)) + // // // .called(1); + // // // verify(cachedClient?.getTransaction( + // // // txHash: + // // // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // // // coin: Coin.namecoin, + // // // callOutSideMainIsolate: false)) + // // // .called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // + // // verify(client?.getBatchHistory(args: map)).called(1); + // // expect(activeScriptHashes.contains(map.values.first.first as String), + // // true); + // // } + // // + // // expect(secureStore.interactions, 20); + // // expect(secureStore.writes, 10); + // // expect(secureStore.reads, 10); + // // expect(secureStore.deletes, 0); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // + // test("confirmSend no hex", () async { + // bool didThrow = false; + // try { + // await nmc?.confirmSend(txData: {"some": "strange map"}); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("confirmSend hex is not string", () async { + // bool didThrow = false; + // try { + // await nmc?.confirmSend(txData: {"hex": true}); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("confirmSend hex is string but missing other data", () async { + // bool didThrow = false; + // try { + // await nmc?.confirmSend(txData: {"hex": "a string"}); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.broadcastTransaction( + // rawTx: "a string", requestID: anyNamed("requestID"))) + // .called(1); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("confirmSend fails due to vSize being greater than fee", () async { + // bool didThrow = false; + // try { + // await nmc + // ?.confirmSend(txData: {"hex": "a string", "fee": 1, "vSize": 10}); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.broadcastTransaction( + // rawTx: "a string", requestID: anyNamed("requestID"))) + // .called(1); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("confirmSend fails when broadcast transactions throws", () async { + // when(client?.broadcastTransaction( + // rawTx: "a string", requestID: anyNamed("requestID"))) + // .thenThrow(Exception("some exception")); + // + // bool didThrow = false; + // try { + // await nmc + // ?.confirmSend(txData: {"hex": "a string", "fee": 10, "vSize": 10}); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.broadcastTransaction( + // rawTx: "a string", requestID: anyNamed("requestID"))) + // .called(1); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // // + // // // this test will create a non mocked electrumx client that will try to connect + // // // to the provided ipAddress below. This will throw a bunch of errors + // // // which what we want here as actually calling electrumx calls here is unwanted. + // // // test("listen to NodesChangedEvent", () async { + // // // nmc = NamecoinWallet( + // // // walletId: testWalletId, + // // // walletName: testWalletName, + // // // networkType: BasicNetworkType.test, + // // // client: client, + // // // cachedClient: cachedClient, + // // // + // // // secureStore: secureStore, + // // + // // // ); + // // // + // // // // set node + // // // final wallet = await Hive.openBox(testWalletId); + // // // await wallet.put("nodes", { + // // // "default": { + // // // "id": "some nodeID", + // // // "ipAddress": "some address", + // // // "port": "9000", + // // // "useSSL": true, + // // // } + // // // }); + // // // await wallet.put("activeNodeID_Bitcoin", "default"); + // // // + // // // final a = nmc.cachedElectrumXClient; + // // // + // // // // return when refresh is called on node changed trigger + // // // nmc.longMutex = true; + // // // + // // // GlobalEventBus.instance + // // // .fire(NodesChangedEvent(NodesChangedEventType.updatedCurrentNode)); + // // // + // // // // make sure event has processed before continuing + // // // await Future.delayed(Duration(seconds: 5)); + // // // + // // // final b = nmc.cachedElectrumXClient; + // // // + // // // expect(identical(a, b), false); + // // // + // // // await nmc.exit(); + // // // + // // // expect(secureStore.interactions, 0); + // // // verifyNoMoreInteractions(client); + // // // verifyNoMoreInteractions(cachedClient); + // // // + // // // }); + // + // // test("refresh wallet mutex locked", () async { + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getBatchHistory(args: historyBatchArgs0)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs1)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs2)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs3)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs4)) + // // .thenAnswer((_) async => historyBatchResponse); + // // when(client?.getBatchHistory(args: historyBatchArgs5)) + // // .thenAnswer((_) async => historyBatchResponse); + // // + // // List dynamicArgValues = []; + // // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((realInvocation) async { + // // if (realInvocation.namedArguments.values.first.length == 1) { + // // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // // } + // // + // // return historyBatchResponse; + // // }); + // // + // // await Hive.openBox(testWalletId); + // // + // // // recover to fill data + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // nmc?.refreshMutex = true; + // // + // // await nmc?.refresh(); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // + // // verify(client?.getBatchHistory(args: map)).called(1); + // // expect(activeScriptHashes.contains(map.values.first.first as String), + // // true); + // // } + // // + // // expect(secureStore.interactions, 14); + // // expect(secureStore.writes, 7); + // // expect(secureStore.reads, 7); + // // expect(secureStore.deletes, 0); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(tracker); + // // }); + // // + // // test("refresh wallet normally", () async { + // // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // // {"height": 520481, "hex": "some block hex"}); + // // when(client?.getServerFeatures()).thenAnswer((_) async => { + // // "hosts": {}, + // // "pruning": null, + // // "server_version": "Unit tests", + // // "protocol_min": "1.4", + // // "protocol_max": "1.4.2", + // // "genesis_hash": GENESIS_HASH_MAINNET, + // // "hash_function": "sha256", + // // "services": [] + // // }); + // // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // // .thenAnswer((_) async => []); + // // when(client?.estimateFee(blocks: anyNamed("blocks"))) + // // .thenAnswer((_) async => Decimal.one); + // // + // // final List dynamicArgValues = []; + // // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((realInvocation) async { + // // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // // return historyBatchResponse; + // // }); + // // + // // await Hive.openBox(testWalletId); + // // + // // // recover to fill data + // // await nmc?.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((_) async => {}); + // // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // + // // await nmc?.refresh(); + // // + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); + // // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); + // // verify(client?.getBlockHeadTip()).called(1); + // // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // + // // verify(client?.getBatchHistory(args: map)).called(1); + // // } + // // + // // expect(secureStore.interactions, 14); + // // expect(secureStore.writes, 7); + // // expect(secureStore.reads, 7); + // // expect(secureStore.deletes, 0); + // // + // // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // }); + // + // tearDown(() async { + // await tearDownTestHive(); + // }); + // }); }