From d5b82f26f73835fa4f47f4efb84e6bc698c4577c Mon Sep 17 00:00:00 2001 From: Likho Date: Tue, 6 Sep 2022 16:17:45 +0200 Subject: [PATCH 01/20] WIP: Add bitcoincash --- .../add_edit_node_view.dart | 2 + .../coins/bitcoincash/bitcoincash_wallet.dart | 3013 +++++++++++++++++ lib/services/coins/coin_service.dart | 11 + lib/utilities/address_utils.dart | 3 + lib/utilities/assets.dart | 6 + lib/utilities/block_explorers.dart | 2 + lib/utilities/cfcolors.dart | 3 + lib/utilities/constants.dart | 3 + lib/utilities/default_nodes.dart | 16 + lib/utilities/enums/coin_enum.dart | 18 + 10 files changed, 3077 insertions(+) create mode 100644 lib/services/coins/bitcoincash/bitcoincash_wallet.dart diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 0d1ce7ad8..5a4d03c57 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -115,6 +115,7 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.bitcoin: case Coin.dogecoin: case Coin.firo: + case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -527,6 +528,7 @@ class _NodeFormState extends ConsumerState { case Coin.bitcoin: case Coin.dogecoin: case Coin.firo: + case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart new file mode 100644 index 000000000..da45bd84e --- /dev/null +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -0,0 +1,3013 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +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:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:http/http.dart'; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/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/node_service.dart'; +import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/services/price.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/assets.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/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:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +const int MINIMUM_CONFIRMATIONS = 3; +const int DUST_LIMIT = 1000000; + +const String GENESIS_HASH_MAINNET = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const String GENESIS_HASH_TESTNET = + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + +enum DerivePathType { bip44 } + +bip32.BIP32 getBip32Node(int chain, int index, String mnemonic, + NetworkType network, DerivePathType derivePathType) { + final root = getBip32Root(mnemonic, network); + + final node = getBip32NodeFromRoot(chain, index, root, derivePathType); + return node; +} + +/// wrapper for compute() +bip32.BIP32 getBip32NodeWrapper( + Tuple5 args, +) { + return getBip32Node( + args.item1, + args.item2, + args.item3, + args.item4, + args.item5, + ); +} + +bip32.BIP32 getBip32NodeFromRoot( + int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { + String coinType; + switch (root.network.wif) { + case 0x9e: // bch mainnet wif + coinType = "145"; // bch mainnet + break; + default: + throw Exception("Invalid Bitcoincash network type used!"); + } + switch (derivePathType) { + case DerivePathType.bip44: + return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + default: + throw Exception("DerivePathType must not be null."); + } +} + +/// wrapper for compute() +bip32.BIP32 getBip32NodeFromRootWrapper( + Tuple4 args, +) { + return getBip32NodeFromRoot( + args.item1, + args.item2, + args.item3, + args.item4, + ); +} + +bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { + final seed = bip39.mnemonicToSeed(mnemonic); + final networkType = bip32.NetworkType( + wif: network.wif, + bip32: bip32.Bip32Type( + public: network.bip32.public, + private: network.bip32.private, + ), + ); + + final root = bip32.BIP32.fromSeed(seed, networkType); + return root; +} + +/// wrapper for compute() +bip32.BIP32 getBip32RootWrapper(Tuple2 args) { + return getBip32Root(args.item1, args.item2); +} + +class BitcoinCashWallet extends CoinServiceAPI { + static const integrationTestFlag = + bool.fromEnvironment("IS_INTEGRATION_TEST"); + final _prefs = Prefs.instance; + + Timer? timer; + late Coin _coin; + + late final TransactionNotificationTracker txTracker; + + NetworkType get _network { + switch (coin) { + case Coin.bitcoincash: + return bitcoincash; + default: + throw Exception("Bitcoincash network type not set!"); + } + } + + List outputsList = []; + + @override + Coin get coin => _coin; + + @override + Future> get allOwnAddresses => + _allOwnAddresses ??= _fetchAllOwnAddresses(); + Future>? _allOwnAddresses; + + Future? _utxoData; + Future get utxoData => _utxoData ??= _fetchUtxoData(); + + @override + Future> get unspentOutputs async => + (await utxoData).unspentOutputArray; + + @override + Future get availableBalance async { + final data = await utxoData; + return Format.satoshisToAmount( + data.satoshiBalance - data.satoshiBalanceUnconfirmed); + } + + @override + Future get pendingBalance async { + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + } + + @override + Future get balanceMinusMaxFee async => + (await availableBalance) - + (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(); + + @override + Future get totalBalance async { + if (!isActive) { + final totalBalance = DB.instance + .get(boxName: walletId, key: 'totalBalance') as int?; + if (totalBalance == null) { + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalance); + } else { + return Format.satoshisToAmount(totalBalance); + } + } + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalance); + } + + @override + Future get currentReceivingAddress => + _currentReceivingAddressP2PKH ??= + _getCurrentAddressForChain(0, DerivePathType.bip44); + + Future? _currentReceivingAddressP2PKH; + + @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; + final satsFee = + Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin); + return satsFee.floor().toBigInt().toInt(); + } + + @override + Future> get mnemonic => _getMnemonicList(); + + Future get chainHeight async { + try { + final result = await _electrumXClient.getBlockHeadTip(); + return result["height"] as int; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return -1; + } + } + + Future get storedChainHeight async { + final storedHeight = DB.instance + .get(boxName: walletId, key: "storedChainHeight") as int?; + return storedHeight ?? 0; + } + + Future updateStoredChainHeight({required int newHeight}) async { + DB.instance.put( + boxName: walletId, key: "storedChainHeight", value: newHeight); + } + + 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; + } + throw ArgumentError('Invalid version or Network mismatch'); + } else { + try { + decodeBech32 = segwit.decode(address); + } 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'); + } + } + throw ArgumentError('$address has no matching Script'); + } + + bool longMutex = false; + + @override + Future recoverFromMnemonic({ + required String mnemonic, + 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) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.dogecoin: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + case Coin.dogecoinTestNet: + if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + throw Exception("genesis hash does not match test net!"); + } + break; + default: + throw Exception( + "Attempted to generate a BitcoinCashWallet using a non dogecoin coin type: ${coin.name}"); + } + } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic.trim(), + 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 _recoverWalletFromBIP32SeedPhrase({ + required String mnemonic, + int maxUnusedAddressGap = 20, + int maxNumberOfIndexesToCheck = 1000, + }) async { + longMutex = true; + + Map> p2pkhReceiveDerivations = {}; + Map> p2pkhChangeDerivations = {}; + + final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + + List p2pkhReceiveAddressArray = []; + int p2pkhReceiveIndex = -1; + + List p2pkhChangeAddressArray = []; + int p2pkhChangeIndex = -1; + + // The gap limit will be capped at [maxUnusedAddressGap] + int receivingGapCounter = 0; + int changeGapCounter = 0; + + // actual size is 12 due to p2pkh so 12x1 + const txCountBatchSize = 12; + + try { + // receiving addresses + Logging.instance + .log("checking receiving addresses...", level: LogLevel.Info); + for (int index = 0; + index < maxNumberOfIndexesToCheck && + receivingGapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + Logging.instance.log( + "index: $index, \t receivingGapCounter: $receivingGapCounter", + level: LogLevel.Info); + + final receivingP2pkhID = "k_$index"; + Map txCountCallArgs = {}; + final Map receivingNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + // bip44 / P2PKH + final node44 = await compute( + getBip32NodeFromRootWrapper, + Tuple4( + 0, + index + j, + root, + DerivePathType.bip44, + ), + ); + final p2pkhReceiveAddress = P2PKH( + data: PaymentData(pubkey: node44.publicKey), + network: _network) + .data + .address!; + receivingNodes.addAll({ + "${receivingP2pkhID}_$j": { + "node": node44, + "address": p2pkhReceiveAddress, + } + }); + txCountCallArgs.addAll({ + "${receivingP2pkhID}_$j": p2pkhReceiveAddress, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: txCountCallArgs); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!; + if (p2pkhTxCount > 0) { + final node = receivingNodes["${receivingP2pkhID}_$k"]; + // add address to array + p2pkhReceiveAddressArray.add(node["address"] as String); + // set current index + p2pkhReceiveIndex = index + k; + // reset counter + receivingGapCounter = 0; + // add info to derivations + p2pkhReceiveDerivations[node["address"] as String] = { + "pubKey": Format.uint8listToString( + (node["node"] as bip32.BIP32).publicKey), + "wif": (node["node"] as bip32.BIP32).toWIF(), + }; + } + + // increase counter when no tx history found + if (p2pkhTxCount == 0) { + receivingGapCounter++; + } + } + } + + Logging.instance + .log("checking change addresses...", level: LogLevel.Info); + // change addresses + for (int index = 0; + index < maxNumberOfIndexesToCheck && + changeGapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + Logging.instance.log( + "index: $index, \t changeGapCounter: $changeGapCounter", + level: LogLevel.Info); + final changeP2pkhID = "k_$index"; + Map args = {}; + final Map changeNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + // bip44 / P2PKH + final node44 = await compute( + getBip32NodeFromRootWrapper, + Tuple4( + 1, + index + j, + root, + DerivePathType.bip44, + ), + ); + final p2pkhChangeAddress = P2PKH( + data: PaymentData(pubkey: node44.publicKey), + network: _network) + .data + .address!; + changeNodes.addAll({ + "${changeP2pkhID}_$j": { + "node": node44, + "address": p2pkhChangeAddress, + } + }); + args.addAll({ + "${changeP2pkhID}_$j": p2pkhChangeAddress, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: args); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!; + if (p2pkhTxCount > 0) { + final node = changeNodes["${changeP2pkhID}_$k"]; + // add address to array + p2pkhChangeAddressArray.add(node["address"] as String); + // set current index + p2pkhChangeIndex = index + k; + // reset counter + changeGapCounter = 0; + // add info to derivations + p2pkhChangeDerivations[node["address"] as String] = { + "pubKey": Format.uint8listToString( + (node["node"] as bip32.BIP32).publicKey), + "wif": (node["node"] as bip32.BIP32).toWIF(), + }; + } + + // increase counter when no tx history found + if (p2pkhTxCount == 0) { + changeGapCounter++; + } + } + } + + // save the derivations (if any) + if (p2pkhReceiveDerivations.isNotEmpty) { + await addDerivations( + chain: 0, + derivePathType: DerivePathType.bip44, + derivationsToAdd: p2pkhReceiveDerivations); + } + if (p2pkhChangeDerivations.isNotEmpty) { + await addDerivations( + chain: 1, + derivePathType: DerivePathType.bip44, + derivationsToAdd: p2pkhChangeDerivations); + } + + // 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); + p2pkhReceiveIndex = 0; + } + + // 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); + p2pkhChangeIndex = 0; + } + + await DB.instance.put( + boxName: walletId, + key: 'receivingAddressesP2PKH', + value: p2pkhReceiveAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'changeAddressesP2PKH', + value: p2pkhChangeAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'receivingIndexP2PKH', + value: p2pkhReceiveIndex); + await DB.instance.put( + boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); + await DB.instance + .put(boxName: walletId, key: "id", value: _walletId); + await DB.instance + .put(boxName: walletId, key: "isFavorite", value: false); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Info); + + 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; + Logging.instance.log( + "notified unconfirmed transactions: ${txTracker.pendings}", + level: LogLevel.Info); + 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); + var confirmations = txn["confirmations"]; + if (confirmations is! int) continue; + bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + // unconfirmedTxs = {}; + needsRefresh = true; + break; + } + } + if (!needsRefresh) { + var allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = + await _fetchHistory(allOwnAddresses); + final txData = await transactionData; + for (Map transaction in allTxs) { + if (txData.findTransaction(transaction['tx_hash'] as String) == + 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.Info); + rethrow; + } + } + + Future getAllTxsToWatch( + TransactionData txData, + ) async { + if (_hasCalledExit) return; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; + + // Get all unconfirmed incoming transactions + for (final chunk in txData.txChunks) { + for (final tx in chunk.transactions) { + if (tx.confirmedStatus) { + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + } else { + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); + } + } + } + } + + // notify on new incoming transaction + for (final tx in unconfirmedTxnsToNotifyPending) { + if (tx.txType == "Received") { + NotificationApi.showNotification( + title: "Incoming transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ); + await txTracker.addNotifiedPending(tx.txid); + } else if (tx.txType == "Sent") { + NotificationApi.showNotification( + title: "Sending transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ); + await txTracker.addNotifiedPending(tx.txid); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.txType == "Received") { + NotificationApi.showNotification( + title: "Incoming transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ); + + await txTracker.addNotifiedConfirmed(tx.txid); + } else if (tx.txType == "Sent") { + NotificationApi.showNotification( + title: "Outgoing transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ); + await txTracker.addNotifiedConfirmed(tx.txid); + } + } + } + + bool refreshMutex = false; + + 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(); + } + } + } + + //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) { + if (currentHeight != -1) { + // -1 failed to fetch current height + updateStoredChainHeight(newHeight: currentHeight); + } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + await _checkChangeAddressForTransactions(DerivePathType.bip44); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + await _checkCurrentReceivingAddressesForTransactions(); + + final newTxData = _fetchTransactionData(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.50, walletId)); + + final newUtxoData = _fetchUtxoData(); + final feeObj = _getFees(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.60, walletId)); + + _transactionData = Future(() => newTxData); + + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.70, walletId)); + _feeObject = Future(() => feeObj); + _utxoData = Future(() => newUtxoData); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.80, walletId)); + + await getAllTxsToWatch(await newTxData); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.90, walletId)); + } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + refreshMutex = false; + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + // 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 int satoshiAmount, + Map? args, + }) async { + try { + final feeRateType = args?["feeRate"]; + final feeRateAmount = args?["feeRateAmount"]; + 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; + } + rate = fee; + } else { + rate = feeRateAmount as int; + } + // check for send all + bool isSendAll = false; + final balance = Format.decimalAmountToSatoshis(await availableBalance); + if (satoshiAmount == balance) { + isSendAll = true; + } + + final result = + await coinSelection(satoshiAmount, rate, address, isSendAll); + Logging.instance.log("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 { + 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({dynamic txData}) async { + try { + Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + final txHash = await _electrumXClient.broadcastTransaction( + rawTx: txData["hex"] as String); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future send({ + required String toAddress, + required int amount, + Map args = const {}, + }) async { + try { + final txData = await prepareSend( + address: toAddress, satoshiAmount: amount, args: args); + final txHash = await confirmSend(txData: txData); + return txHash; + } catch (e, s) { + Logging.instance + .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future testNetworkConnection() async { + 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(); + _isConnected = hasNetwork; + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + GlobalEventBus.instance + .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); + } + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + bool _isConnected = false; + + @override + bool get isConnected => _isConnected; + + @override + Future initializeNew() async { + Logging.instance + .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); + + if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + throw Exception( + "Attempted to initialize a new wallet using an existing wallet ID!"); + } + await _prefs.init(); + try { + await _generateNewWallet(); + } catch (e, s) { + Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", + level: LogLevel.Fatal); + rethrow; + } + await Future.wait([ + DB.instance.put(boxName: walletId, key: "id", value: _walletId), + DB.instance + .put(boxName: walletId, key: "isFavorite", value: false), + ]); + } + + @override + Future initializeExisting() async { + Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + level: LogLevel.Info); + + if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + await _prefs.init(); + final data = + DB.instance.get(boxName: walletId, key: "latest_tx_model") + as TransactionData?; + if (data != null) { + _transactionData = Future(() => data); + } + } + + @override + Future get transactionData => + _transactionData ??= _fetchTransactionData(); + Future? _transactionData; + + @override + bool validateAddress(String address) { + return Address.validateAddress(address, _network); + } + + @override + String get walletId => _walletId; + late String _walletId; + + @override + String get walletName => _walletName; + late String _walletName; + + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; + + late ElectrumX _electrumXClient; + + ElectrumX get electrumXClient => _electrumXClient; + + late CachedElectrumX _cachedElectrumXClient; + + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; + + late FlutterSecureStorageInterface _secureStore; + + late PriceAPI _priceAPI; + + BitcoinCashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + PriceAPI? priceAPI, + FlutterSecureStorageInterface? secureStore, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + + _priceAPI = priceAPI ?? PriceAPI(Client()); + _secureStore = + secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + } + + @override + Future updateNode(bool shouldRefresh) async { + final failovers = NodeService() + .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(); + _cachedElectrumXClient = CachedElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + _electrumXClient = ElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + + if (shouldRefresh) { + refresh(); + } + } + + Future> _getMnemonicList() async { + final mnemonicString = + await _secureStore.read(key: '${_walletId}_mnemonic'); + if (mnemonicString == null) { + return []; + } + final List data = mnemonicString.split(' '); + return data; + } + + Future getCurrentNode() async { + final node = NodeService().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 List allAddresses = []; + + final receivingAddressesP2PKH = DB.instance.get( + boxName: walletId, key: 'receivingAddressesP2PKH') as List; + final changeAddressesP2PKH = + DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + as List; + + // for (var i = 0; i < receivingAddresses.length; i++) { + // if (!allAddresses.contains(receivingAddresses[i])) { + // allAddresses.add(receivingAddresses[i]); + // } + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // if (!allAddresses.contains(changeAddresses[i])) { + // allAddresses.add(changeAddresses[i]); + // } + // } + 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); + } + } + 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: Format.decimalAmountToSatoshis(fast), + medium: Format.decimalAmountToSatoshis(medium), + slow: Format.decimalAmountToSatoshis(slow), + ); + + 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() async { + Logging.instance + .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); + if (!integrationTestFlag) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.bitcoincash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a BitcoinWallet using a non bitcoin coin type: ${coin.name}"); + } + } + + // this should never fail + if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', + value: bip39.generateMnemonic(strength: 256)); + + // Set relevant indexes + await DB.instance + .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); + await DB.instance + .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); + await DB.instance.put( + boxName: walletId, + key: 'blocked_tx_hashes', + value: ["0xdefault"], + ); // A list of transaction hashes to represent frozen utxos in wallet + // initialize address book entries + await DB.instance.put( + boxName: walletId, + key: 'addressBookEntries', + value: {}); + + // Generate and add addresses to relevant arrays + // final initialReceivingAddress = + // await _generateAddressForChain(0, 0, DerivePathType.bip44); + // final initialChangeAddress = + // await _generateAddressForChain(1, 0, DerivePathType.bip44); + final initialReceivingAddressP2PKH = + await _generateAddressForChain(0, 0, DerivePathType.bip44); + final initialChangeAddressP2PKH = + await _generateAddressForChain(1, 0, DerivePathType.bip44); + + // await _addToAddressesArrayForChain( + // initialReceivingAddress, 0, DerivePathType.bip44); + // await _addToAddressesArrayForChain( + // initialChangeAddress, 1, DerivePathType.bip44); + await _addToAddressesArrayForChain( + initialReceivingAddressP2PKH, 0, DerivePathType.bip44); + await _addToAddressesArrayForChain( + initialChangeAddressP2PKH, 1, DerivePathType.bip44); + + // this._currentReceivingAddress = Future(() => initialReceivingAddress); + _currentReceivingAddressP2PKH = Future(() => initialReceivingAddressP2PKH); + + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + } + + /// Generates a new internal or external chain address for the wallet using a BIP44 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 _secureStore.read(key: '${_walletId}_mnemonic'); + final node = await compute( + getBip32NodeWrapper, + Tuple5( + chain, + index, + mnemonic!, + _network, + derivePathType, + ), + ); + final data = PaymentData(pubkey: node.publicKey); + String address; + + switch (derivePathType) { + case DerivePathType.bip44: + address = P2PKH(data: data, network: _network).data.address!; + break; + // default: + // // should never hit this due to all enum cases handled + // return null; + } + + // add generated address & info to derivations + await addDerivation( + chain: chain, + address: address, + pubKey: Format.uint8listToString(node.publicKey), + wif: node.toWIF(), + derivePathType: derivePathType, + ); + + return address; + } + + /// Increases the index for either the internal or external chain, depending on [chain]. + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _incrementAddressIndexForChain( + int chain, DerivePathType derivePathType) async { + // Here we assume chain == 1 if it isn't 0 + String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + + final newIndex = + (DB.instance.get(boxName: walletId, key: indexKey)) + 1; + await DB.instance + .put(boxName: walletId, key: indexKey, value: newIndex); + } + + /// Adds [address] to the relevant chain's address array, which is determined by [chain]. + /// [address] - Expects a standard native segwit address + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _addToAddressesArrayForChain( + String address, int chain, DerivePathType derivePathType) async { + String chainArray = ''; + if (chain == 0) { + chainArray = 'receivingAddresses'; + } else { + chainArray = 'changeAddresses'; + } + switch (derivePathType) { + case DerivePathType.bip44: + chainArray += "P2PKH"; + break; + } + + final addressArray = + DB.instance.get(boxName: walletId, key: chainArray); + if (addressArray == null) { + Logging.instance.log( + 'Attempting to add the following to $chainArray array for chain $chain:${[ + address + ]}', + level: LogLevel.Info); + await DB.instance + .put(boxName: walletId, key: chainArray, value: [address]); + } else { + // Make a deep copy of the existing list + final List newArray = []; + addressArray + .forEach((dynamic _address) => newArray.add(_address as String)); + newArray.add(address); // Add the address passed into the method + await DB.instance + .put(boxName: walletId, key: chainArray, value: newArray); + } + } + + /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] + /// and + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _getCurrentAddressForChain( + int chain, DerivePathType derivePathType) async { + // Here, we assume that chain == 1 if it isn't 0 + String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + switch (derivePathType) { + case DerivePathType.bip44: + arrayKey += "P2PKH"; + break; + } + final internalChainArray = + DB.instance.get(boxName: walletId, key: arrayKey); + return internalChainArray.last as String; + } + + 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; + } + 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 _fetchUtxoData() async { + final List allAddresses = await _fetchAllOwnAddresses(); + + try { + final fetchedUtxoList = >>[]; + + final Map>> batches = {}; + const batchSizeMax = 10; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scripthash = _convertToScriptHash(allAddresses[i], _network); + 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 priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final List> outputArray = []; + int satoshiBalance = 0; + int satoshiBalancePending = 0; + + for (int i = 0; i < fetchedUtxoList.length; i++) { + for (int j = 0; j < fetchedUtxoList[i].length; j++) { + int value = fetchedUtxoList[i][j]["value"] as int; + satoshiBalance += value; + + final txn = await cachedElectrumXClient.getTransaction( + txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + final Map utxo = {}; + final int confirmations = txn["confirmations"] as int? ?? 0; + final bool confirmed = txn["confirmations"] == null + ? false + : txn["confirmations"] as int >= MINIMUM_CONFIRMATIONS; + if (!confirmed) { + satoshiBalancePending += value; + } + + utxo["txid"] = txn["txid"]; + utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; + utxo["value"] = value; + + utxo["status"] = {}; + utxo["status"]["confirmed"] = confirmed; + utxo["status"]["confirmations"] = confirmations; + utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; + utxo["status"]["block_hash"] = txn["blockhash"]; + utxo["status"]["block_time"] = txn["blocktime"]; + + final fiatValue = ((Decimal.fromInt(value) * currentPrice) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2); + utxo["rawWorth"] = fiatValue; + utxo["fiatWorth"] = fiatValue.toString(); + outputArray.add(utxo); + } + } + + Decimal currencyBalanceRaw = + ((Decimal.fromInt(satoshiBalance) * currentPrice) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2); + + final Map result = { + "total_user_currency": currencyBalanceRaw.toString(), + "total_sats": satoshiBalance, + "total_btc": (Decimal.fromInt(satoshiBalance) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + .toString(), + "outputArray": outputArray, + "unconfirmed": satoshiBalancePending, + }; + + final dataModel = UtxoData.fromJson(result); + + final List allOutputs = dataModel.unspentOutputArray; + Logging.instance + .log('Outputs fetched: $allOutputs', level: LogLevel.Info); + await _sortOutputs(allOutputs); + await DB.instance.put( + boxName: walletId, key: 'latest_utxo_model', value: dataModel); + await DB.instance.put( + boxName: walletId, + key: 'totalBalance', + value: dataModel.satoshiBalance); + return dataModel; + } catch (e, s) { + Logging.instance + .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); + final latestTxModel = + DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + + if (latestTxModel == null) { + final emptyModel = { + "total_user_currency": "0.00", + "total_sats": 0, + "total_btc": "0", + "outputArray": [] + }; + return UtxoData.fromJson(emptyModel); + } else { + Logging.instance + .log("Old output model located", level: LogLevel.Warning); + return latestTxModel as models.UtxoData; + } + } + } + + /// 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 = {}; + for (final entry in addresses.entries) { + args[entry.key] = [_convertToScriptHash(entry.value, _network)]; + } + final response = await electrumXClient.getBatchHistory(args: args); + + 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( + DerivePathType derivePathType) async { + try { + final String currentExternalAddr = + await _getCurrentAddressForChain(0, derivePathType); + final int txCount = await getTxCount(address: currentExternalAddr); + Logging.instance.log( + 'Number of txs for current receiving address $currentExternalAddr: $txCount', + level: LogLevel.Info); + + if (txCount >= 1) { + // First increment the receiving index + await _incrementAddressIndexForChain(0, derivePathType); + + // Check the new receiving index + String indexKey = "receivingIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, derivePathType); + + // Add that new receiving address to the array of receiving addresses + await _addToAddressesArrayForChain( + newReceivingAddress, 0, derivePathType); + + // Set the new receiving address that the service + + switch (derivePathType) { + case DerivePathType.bip44: + _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); + break; + } + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkChangeAddressForTransactions( + DerivePathType derivePathType) async { + try { + final String currentExternalAddr = + await _getCurrentAddressForChain(1, derivePathType); + final int txCount = await getTxCount(address: currentExternalAddr); + Logging.instance.log( + 'Number of txs for current change address $currentExternalAddr: $txCount', + level: LogLevel.Info); + + if (txCount >= 1) { + // First increment the change index + await _incrementAddressIndexForChain(1, derivePathType); + + // Check the new change index + String indexKey = "changeIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + final newChangeIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new change address + final newChangeAddress = + await _generateAddressForChain(1, newChangeIndex, derivePathType); + + // Add that new receiving address to the array of change addresses + await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkChangeAddressForTransactions($derivePathType): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(type); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Info); + 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(type); + } + } 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 bch address + String _convertToScriptHash(String bchAddress, NetworkType network) { + try { + final output = Address.addressToOutputScript(bchAddress, network); + 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 = 10; + 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 = 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 _fetchTransactionData() async { + final List allAddresses = await _fetchAllOwnAddresses(); + + final changeAddressesP2PKH = + DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + as List; + + final List> allTxHashes = + await _fetchHistory(allAddresses); + + final cachedTransactions = + DB.instance.get(boxName: walletId, key: 'latest_tx_model') + as TransactionData?; + int latestTxnBlockHeight = + DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + as int? ?? + 0; + + final unconfirmedCachedTransactions = + cachedTransactions?.getAllTransactions() ?? {}; + unconfirmedCachedTransactions + .removeWhere((key, value) => value.confirmedStatus); + + if (cachedTransactions != null) { + for (final tx in allTxHashes.toList(growable: false)) { + final txHeight = tx["height"] as int; + if (txHeight > 0 && + txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { + if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { + allTxHashes.remove(tx); + } + } + } + } + + List> allTransactions = []; + + for (final txHash in allTxHashes) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + // TODO fix this for sent to self transactions? + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = txHash["address"]; + 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); + + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final List> midSortedArray = []; + + for (final txObject in allTransactions) { + List sendersArray = []; + List recipientsArray = []; + + // Usually only has value when txType = 'Send' + int inputAmtSentFromWallet = 0; + // Usually has value regardless of txType due to change addresses + int outputAmtAddressedToWallet = 0; + int fee = 0; + + Map midSortedTx = {}; + + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, coin: coin); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + final address = out["scriptPubKey"]["addresses"][0] as String?; + if (address != null) { + sendersArray.add(address); + } + } + } + } + + Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); + + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0] as String?; + if (address != null) { + recipientsArray.add(address); + } + } + + Logging.instance + .log("recipientsArray: $recipientsArray", level: LogLevel.Info); + + final foundInSenders = + allAddresses.any((element) => sendersArray.contains(element)); + Logging.instance + .log("foundInSenders: $foundInSenders", level: LogLevel.Info); + + // If txType = Sent, then calculate inputAmtSentFromWallet + if (foundInSenders) { + int totalInput = 0; + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + inputAmtSentFromWallet += + (Decimal.parse(out["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + } + } + } + totalInput = inputAmtSentFromWallet; + int totalOutput = 0; + + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0]; + final value = output["value"]; + final _value = (Decimal.parse(value.toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + totalOutput += _value; + if (changeAddressesP2PKH.contains(address)) { + inputAmtSentFromWallet -= _value; + } else { + // change address from 'sent from' to the 'sent to' address + txObject["address"] = address; + } + } + // calculate transaction fee + fee = totalInput - totalOutput; + // subtract fee from sent to calculate correct value of sent tx + inputAmtSentFromWallet -= fee; + } else { + // counters for fee calculation + int totalOut = 0; + int totalIn = 0; + + // add up received tx value + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0]; + if (address != null) { + final value = (Decimal.parse(output["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + totalOut += value; + if (allAddresses.contains(address)) { + outputAmtAddressedToWallet += value; + } + } + } + + // calculate fee for received tx + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + totalIn += (Decimal.parse(out["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + } + } + } + fee = totalIn - totalOut; + } + + // create final tx map + midSortedTx["txid"] = txObject["txid"]; + midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && + (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); + midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; + midSortedTx["timestamp"] = txObject["blocktime"] ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000); + + if (foundInSenders) { + midSortedTx["txType"] = "Sent"; + midSortedTx["amount"] = inputAmtSentFromWallet; + final String worthNow = + ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2) + .toStringAsFixed(2); + midSortedTx["worthNow"] = worthNow; + midSortedTx["worthAtBlockTimestamp"] = worthNow; + } else { + midSortedTx["txType"] = "Received"; + midSortedTx["amount"] = outputAmtAddressedToWallet; + final worthNow = + ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2) + .toStringAsFixed(2); + midSortedTx["worthNow"] = worthNow; + } + midSortedTx["aliens"] = []; + midSortedTx["fees"] = fee; + midSortedTx["address"] = txObject["address"]; + midSortedTx["inputSize"] = txObject["vin"].length; + midSortedTx["outputSize"] = txObject["vout"].length; + midSortedTx["inputs"] = txObject["vin"]; + midSortedTx["outputs"] = txObject["vout"]; + + final int height = txObject["height"] as int; + midSortedTx["height"] = height; + + if (height >= latestTxnBlockHeight) { + latestTxnBlockHeight = height; + } + + midSortedArray.add(midSortedTx); + } + + // sort by date ---- //TODO not sure if needed + // shouldn't be any issues with a null timestamp but I got one at some point? + midSortedArray + .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); + // { + // final aT = a["timestamp"]; + // final bT = b["timestamp"]; + // + // if (aT == null && bT == null) { + // return 0; + // } else if (aT == null) { + // return -1; + // } else if (bT == null) { + // return 1; + // } else { + // return bT - aT; + // } + // }); + + // buildDateTimeChunks + final Map result = {"dateTimeChunks": []}; + final dateArray = []; + + for (int i = 0; i < midSortedArray.length; i++) { + final txObject = midSortedArray[i]; + final date = extractDateFromTimestamp(txObject["timestamp"] as int); + final txTimeArray = [txObject["timestamp"], date]; + + if (dateArray.contains(txTimeArray[1])) { + result["dateTimeChunks"].forEach((dynamic chunk) { + if (extractDateFromTimestamp(chunk["timestamp"] as int) == + txTimeArray[1]) { + if (chunk["transactions"] == null) { + chunk["transactions"] = >[]; + } + chunk["transactions"].add(txObject); + } + }); + } else { + dateArray.add(txTimeArray[1]); + final chunk = { + "timestamp": txTimeArray[0], + "transactions": [txObject], + }; + result["dateTimeChunks"].add(chunk); + } + } + + final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; + transactionsMap + .addAll(TransactionData.fromJson(result).getAllTransactions()); + + final txModel = TransactionData.fromMap(transactionsMap); + + await DB.instance.put( + boxName: walletId, + key: 'storedTxnDataHeight', + value: latestTxnBlockHeight); + await DB.instance.put( + boxName: walletId, key: 'latest_tx_model', value: txModel); + + return txModel; + } + + 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(int satoshiAmountToSend, int selectedTxFeeRate, + String _recipientAddress, bool isSendAll, + {int additionalOutputs = 0, List? utxos}) async { + Logging.instance + .log("Starting coinSelection ----------", level: LogLevel.Info); + final List availableOutputs = utxos ?? outputsList; + final List spendableOutputs = []; + int spendableSatoshiValue = 0; + + // Build list of spendable outputs and totaling their satoshi amount + for (var i = 0; i < availableOutputs.length; i++) { + if (availableOutputs[i].blocked == false && + availableOutputs[i].status.confirmed == true) { + spendableOutputs.add(availableOutputs[i]); + spendableSatoshiValue += availableOutputs[i].value; + } + } + + // sort spendable by age (oldest first) + spendableOutputs.sort( + (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + + 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 = []; + + 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; + } + + Logging.instance + .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); + Logging.instance + .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); + Logging.instance + .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); + Logging.instance + .log('satoshiAmountToSend $satoshiAmountToSend', 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [_recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + int feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { + feeForOneOutput = (vSizeForOneOutput + 1) * 1000; + } + + final int amount = satoshiAmountToSend - feeForOneOutput; + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: [amount], + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": amount, + "fee": feeForOneOutput, + "vSize": txn["vSize"], + }; + return transactionObject; + } + + final int vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [_recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + final int vSizeForTwoOutPuts = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [ + _recipientAddress, + await _getCurrentAddressForChain(1, DerivePathType.bip44), + ], + satoshiAmounts: [ + satoshiAmountToSend, + satoshisBeingUsed - satoshiAmountToSend - 1, + ], // dust limit is the minimum amount a change output should be + ))["vSize"] as int; + debugPrint("vSizeForOneOutput $vSizeForOneOutput"); + debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); + + // Assume 1 output, only for recipient and no change + var feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + // Assume 2 outputs, one for recipient and one for change + var feeForTwoOutputs = estimateTxFee( + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ); + + Logging.instance + .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); + Logging.instance + .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); + if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { + feeForOneOutput = (vSizeForOneOutput + 1) * 1000; + } + if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1) * 1000)) { + feeForTwoOutputs = ((vSizeForTwoOutPuts + 1) * 1000); + } + + 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) { + // 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 > 546 satoshis, we perform the mechanics required to properly generate and use a new + // change address. + if (changeOutputSize > DUST_LIMIT && + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == + feeForTwoOutputs) { + // generate new change address if current change address has been used + await _checkChangeAddressForTransactions(DerivePathType.bip44); + final String newChangeAddress = + await _getCurrentAddressForChain(1, DerivePathType.bip44); + + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + } + + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": feeBeingPaid, + "vSize": txn["vSize"], + }; + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + }; + return transactionObject; + } + } else { + // No additional outputs needed since adding one would mean that it'd be smaller than 546 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + }; + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": feeForOneOutput, + "vSize": txn["vSize"], + }; + 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, selectedTxFeeRate, + _recipientAddress, isSendAll, + additionalOutputs: additionalOutputs + 1, utxos: utxos); + } + return 2; + } + } + + Future> fetchBuildTxData( + List utxosToUse, + ) async { + // return data + Map results = {}; + Map> addressTxid = {}; + + // addresses to check + List addressesP2PKH = []; + + try { + // Populating the addresses to check + for (var i = 0; i < utxosToUse.length; i++) { + 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) { + final address = output["scriptPubKey"]["addresses"][0] as String; + if (!addressTxid.containsKey(address)) { + addressTxid[address] = []; + } + (addressTxid[address] as List).add(txid); + switch (addressType(address: address)) { + case DerivePathType.bip44: + addressesP2PKH.add(address); + break; + } + } + } + } + + // p2pkh / bip44 + final p2pkhLength = addressesP2PKH.length; + if (p2pkhLength > 0) { + final receiveDerivations = await _fetchDerivations( + chain: 0, + derivePathType: DerivePathType.bip44, + ); + final changeDerivations = await _fetchDerivations( + chain: 1, + derivePathType: DerivePathType.bip44, + ); + for (int i = 0; i < p2pkhLength; i++) { + // receives + final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; + // if a match exists it will not be null + if (receiveDerivation != null) { + final data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + receiveDerivation["pubKey"] as String)), + network: _network, + ).data; + + for (String tx in addressTxid[addressesP2PKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + receiveDerivation["wif"] as String, + network: _network, + ), + }; + } + } else { + // if its not a receive, check change + final changeDerivation = changeDerivations[addressesP2PKH[i]]; + // if a match exists it will not be null + if (changeDerivation != null) { + final data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + changeDerivation["pubKey"] as String)), + network: _network, + ).data; + + for (String tx in addressTxid[addressesP2PKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + changeDerivation["wif"] as String, + network: _network, + ), + }; + } + } + } + } + } + + return results; + } catch (e, s) { + Logging.instance + .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); + rethrow; + } + } + + /// Builds and signs a transaction + Future> buildTransaction({ + required List utxosToUse, + required Map utxoSigningData, + required List recipients, + required List satoshiAmounts, + }) async { + Logging.instance + .log("Starting buildTransaction ----------", level: LogLevel.Info); + + final txb = TransactionBuilder(network: _network); + txb.setVersion(1); + + // Add transaction inputs + for (var i = 0; i < utxosToUse.length; i++) { + final txid = utxosToUse[i].txid; + txb.addInput(txid, utxosToUse[i].vout, null, + utxoSigningData[txid]["output"] as Uint8List); + } + + // Add transaction output + for (var i = 0; i < recipients.length; i++) { + txb.addOutput(recipients[i], satoshiAmounts[i]); + } + + try { + // Sign the transaction accordingly + for (var i = 0; i < utxosToUse.length; i++) { + final txid = utxosToUse[i].txid; + txb.sign( + vin: i, + keyPair: utxoSigningData[txid]["keyPair"] as ECPair, + witnessValue: utxosToUse[i].value, + redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + ); + } + } catch (e, s) { + Logging.instance.log("Caught exception while signing transaction: $e\n$s", + level: LogLevel.Error); + rethrow; + } + + final builtTx = txb.build(); + 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 + _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + + // back up data + await _rescanBackup(); + + try { + final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + ); + + longMutex = false; + 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 _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); + + // 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"); + + // 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); + + // 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"); + + // 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); + } + + @override + set isFavorite(bool markFavorite) { + DB.instance.put( + boxName: walletId, key: "isFavorite", value: markFavorite); + } + + @override + bool get isFavorite { + try { + return DB.instance.get(boxName: walletId, key: "isFavorite") + as bool; + } catch (e, s) { + Logging.instance + .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + bool get isRefreshing => refreshMutex; + + bool isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => + (isActive) => this.isActive = isActive; + + @override + Future estimateFeeFor(int satoshiAmount, int feeRate) async { + final available = Format.decimalAmountToSatoshis(await availableBalance); + + if (available == satoshiAmount) { + return satoshiAmount - sweepAllEstimate(feeRate); + } else if (satoshiAmount <= 0 || satoshiAmount > available) { + return roughFeeEstimate(1, 2, feeRate); + } + + int runningBalance = 0; + int inputCount = 0; + for (final output in outputsList) { + runningBalance += output.value; + inputCount++; + if (runningBalance > satoshiAmount) { + break; + } + } + + final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); + final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); + + if (runningBalance - satoshiAmount > oneOutPutFee) { + if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - satoshiAmount - twoOutPutFee; + if (change > DUST_LIMIT && + runningBalance - satoshiAmount - change == twoOutPutFee) { + return runningBalance - satoshiAmount - change; + } else { + return runningBalance - satoshiAmount; + } + } else { + return runningBalance - satoshiAmount; + } + } else if (runningBalance - satoshiAmount == oneOutPutFee) { + return oneOutPutFee; + } else { + return twoOutPutFee; + } + } + + // TODO: correct formula for doge? + int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return ((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil(); + } + + int sweepAllEstimate(int feeRate) { + int available = 0; + int inputCount = 0; + for (final output in outputsList) { + if (output.status.confirmed) { + available += output.value; + inputCount++; + } + } + + // transaction will only have 1 output minus the fee + final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); + + return available - estimatedFee; + } +} + +// Bitcoincash Network +final bitcoincash = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80); diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 3d8b1bd10..4099c34c1 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; +import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -132,6 +133,16 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); + + case Coin.bitcoincash: + return BitcoinCashWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + client: client, + cachedClient: cachedClient, + tracker: tracker, + ); } } diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 805dc64db..f251f7523 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -5,6 +5,7 @@ import 'package:crypto/crypto.dart'; import 'package:flutter_libepiccash/epic_cash.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -49,6 +50,8 @@ class AddressUtils { case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); + case Coin.bitcoincash: + return Address.validateAddress(address, bitcoincash); case Coin.bitcoinTestNet: return Address.validateAddress(address, testnet); case Coin.firoTestNet: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 9adc50e59..80e718883 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -110,6 +110,7 @@ class _SVG { String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; String get firoTestnet => "assets/svg/coin_icons/Firo.svg"; String get dogecoinTestnet => "assets/svg/coin_icons/Dogecoin.svg"; + String get bitcoincash => "assets/svg/coin_icons/Bitcoin.svg"; String iconFor({required Coin coin}) { switch (coin) { @@ -123,6 +124,8 @@ class _SVG { return firo; case Coin.monero: return monero; + case Coin.bitcoincash: + return bitcoincash; case Coin.bitcoinTestNet: return bitcoinTestnet; case Coin.firoTestNet: @@ -144,6 +147,7 @@ class _PNG { String get dogecoin => "assets/images/doge.png"; String get bitcoin => "assets/images/bitcoin.png"; String get epicCash => "assets/images/epic-cash.png"; + String get bitcoincash => "assets/images/bitcoin.png"; String imageFor({required Coin coin}) { switch (coin) { @@ -156,6 +160,8 @@ class _PNG { case Coin.epicCash: return epicCash; case Coin.firo: + case Coin.bitcoincash: + return bitcoincash; case Coin.firoTestNet: return firo; case Coin.monero: diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index a9b3d5db5..9040c956e 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -22,5 +22,7 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://explorer.firo.org/tx/$txid"); case Coin.firoTestNet: return Uri.parse("https://testexplorer.firo.org/tx/$txid"); + case Coin.bitcoincash: + return Uri.parse("https://www.blockchain.com/bch/tx/$txid"); } } diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index ce6097b7d..d50a2c7ea 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -10,6 +10,7 @@ class _CoinThemeColor { Color get dogecoin => const Color(0xFFFFE079); Color get epicCash => const Color(0xFFC1C1FF); Color get monero => const Color(0xFFB1C5FF); + Color get bitcoincash => const Color(0xFFFCC17B); Color forCoin(Coin coin) { switch (coin) { @@ -26,6 +27,8 @@ class _CoinThemeColor { return firo; case Coin.monero: return monero; + case Coin.bitcoincash: + return bitcoincash; } } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index b8e5b5bce..97b3d8896 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -45,6 +45,7 @@ abstract class Constants { case Coin.dogecoinTestNet: case Coin.firoTestNet: case Coin.epicCash: + case Coin.bitcoincash: values.addAll([24, 21, 18, 15, 12]); break; @@ -75,6 +76,8 @@ abstract class Constants { case Coin.monero: return 120; + case Coin.bitcoincash: + return 600; } } diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index d2e042439..09e373df0 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -13,6 +13,7 @@ abstract class DefaultNodes { firo, monero, epicCash, + bitcoincash, bitcoinTestnet, dogecoinTestnet, firoTestnet, @@ -80,6 +81,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get bitcoincash => NodeModel( + host: "https://electrum1.cipig.net:20055", + port: 20055, + name: defaultName, + id: _nodeId(Coin.bitcoincash), + useSSL: true, + enabled: true, + coinName: Coin.bitcoincash.name, + isFailover: true, + isDown: false, + ); + static NodeModel get bitcoinTestnet => NodeModel( host: "electrumx-testnet.cypherstack.com", port: 51002, @@ -133,6 +146,9 @@ abstract class DefaultNodes { case Coin.monero: return monero; + case Coin.bitcoincash: + return bitcoincash; + case Coin.bitcoinTestNet: return bitcoinTestnet; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 9e489dde4..fb7b83f5d 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -5,6 +5,8 @@ import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart' as epic; import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as firo; import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; +import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart' + as bch; enum Coin { bitcoin, @@ -12,6 +14,7 @@ enum Coin { epicCash, firo, monero, + bitcoincash, /// /// @@ -38,6 +41,8 @@ extension CoinExt on Coin { return "Firo"; case Coin.monero: return "Monero"; + case Coin.bitcoincash: + return "Bitcoincash"; case Coin.bitcoinTestNet: return "tBitcoin"; case Coin.firoTestNet: @@ -59,6 +64,8 @@ extension CoinExt on Coin { return "FIRO"; case Coin.monero: return "XMR"; + case Coin.bitcoincash: + return "BCH"; case Coin.bitcoinTestNet: return "tBTC"; case Coin.firoTestNet: @@ -81,6 +88,8 @@ extension CoinExt on Coin { return "firo"; case Coin.monero: return "monero"; + case Coin.bitcoincash: + return "bitcoincash"; case Coin.bitcoinTestNet: return "bitcoin"; case Coin.firoTestNet: @@ -95,6 +104,7 @@ extension CoinExt on Coin { case Coin.bitcoin: case Coin.dogecoin: case Coin.firo: + case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -125,6 +135,9 @@ extension CoinExt on Coin { case Coin.monero: return xmr.MINIMUM_CONFIRMATIONS; + + case Coin.bitcoincash: + return bch.MINIMUM_CONFIRMATIONS; } } } @@ -146,6 +159,9 @@ Coin coinFromPrettyName(String name) { case "Monero": case "monero": return Coin.monero; + case "Bitcoincash": + case "bitcoincash": + return Coin.bitcoincash; case "Bitcoin Testnet": case "tBitcoin": case "bitcoinTestNet": @@ -176,6 +192,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.firo; case "xmr": return Coin.monero; + case "bch": + return Coin.bitcoincash; case "tbtc": return Coin.bitcoinTestNet; case "tfiro": From 5cbaa597d3ee1049d5b247f6978e251b542a7332 Mon Sep 17 00:00:00 2001 From: Likho Date: Wed, 7 Sep 2022 13:11:50 +0200 Subject: [PATCH 02/20] Update bch network --- .../coins/bitcoincash/bitcoincash_wallet.dart | 14 +++++++------- lib/utilities/default_nodes.dart | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index da45bd84e..4174dbc8e 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -77,7 +77,7 @@ bip32.BIP32 getBip32NodeFromRoot( int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { String coinType; switch (root.network.wif) { - case 0x9e: // bch mainnet wif + case 0x80: // bch mainnet wif coinType = "145"; // bch mainnet break; default: @@ -298,16 +298,16 @@ class BitcoinCashWallet extends CoinServiceAPI { final features = await electrumXClient.getServerFeatures(); Logging.instance.log("features: $features", level: LogLevel.Info); switch (coin) { - case Coin.dogecoin: + case Coin.bitcoincash: if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { throw Exception("genesis hash does not match main net!"); } break; - case Coin.dogecoinTestNet: - if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - throw Exception("genesis hash does not match test net!"); - } - break; + // case Coin.dogecoinTestNet: + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // break; default: throw Exception( "Attempted to generate a BitcoinCashWallet using a non dogecoin coin type: ${coin.name}"); diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 09e373df0..e986c9a0b 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -82,7 +82,7 @@ abstract class DefaultNodes { ); static NodeModel get bitcoincash => NodeModel( - host: "https://electrum1.cipig.net:20055", + host: "electrum1.cipig.net", port: 20055, name: defaultName, id: _nodeId(Coin.bitcoincash), From cbb3c3f241078ba41d4991509acdfdaedf87d63f Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 7 Sep 2022 20:54:18 +0800 Subject: [PATCH 03/20] bitcoin cash sending works for legacy and new addresses bip44 --- .../coins/bitcoincash/bitcoincash_wallet.dart | 123 ++++++++++++------ pubspec.yaml | 4 + 2 files changed, 87 insertions(+), 40 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 4174dbc8e..65464b5da 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -41,6 +41,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; +import 'package:bitbox/bitbox.dart' as Bitbox; const int MINIMUM_CONFIRMATIONS = 3; const int DUST_LIMIT = 1000000; @@ -735,6 +736,9 @@ class BitcoinCashWallet extends CoinServiceAPI { /// Refreshes display data for the wallet @override Future refresh() async { + final bchaddr = Bitbox.Address.toCashAddress(await currentReceivingAddress); + print("bchaddr: $bchaddr ${await currentReceivingAddress}"); + if (refreshMutex) { Logging.instance.log("$walletId $walletName refreshMutex denied", level: LogLevel.Info); @@ -1050,7 +1054,14 @@ class BitcoinCashWallet extends CoinServiceAPI { @override bool validateAddress(String address) { - return Address.validateAddress(address, _network); + try { + // 0 for bitcoincash: address scheme, 1 for legacy address + final format = Bitbox.Address.detectFormat(address); + print("format $format"); + return true; + } catch (e, s) { + return false; + } } @override @@ -1947,6 +1958,7 @@ class BitcoinCashWallet extends CoinServiceAPI { List> allTransactions = []; for (final txHash in allTxHashes) { + Logging.instance.log("bch: $txHash", level: LogLevel.Info); final tx = await cachedElectrumXClient.getTransaction( txHash: txHash["tx_hash"] as String, verbose: true, @@ -2318,8 +2330,8 @@ class BitcoinCashWallet extends CoinServiceAPI { vSize: vSizeForOneOutput, feeRatePerKB: selectedTxFeeRate, ); - if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { - feeForOneOutput = (vSizeForOneOutput + 1) * 1000; + if (feeForOneOutput < (vSizeForOneOutput + 1)) { + feeForOneOutput = (vSizeForOneOutput + 1); } final int amount = satoshiAmountToSend - feeForOneOutput; @@ -2375,11 +2387,11 @@ class BitcoinCashWallet extends CoinServiceAPI { .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); Logging.instance .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); - if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { - feeForOneOutput = (vSizeForOneOutput + 1) * 1000; + if (feeForOneOutput < (vSizeForOneOutput + 1)) { + feeForOneOutput = (vSizeForOneOutput + 1); } - if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1) * 1000)) { - feeForTwoOutputs = ((vSizeForTwoOutPuts + 1) * 1000); + if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1))) { + feeForTwoOutputs = ((vSizeForTwoOutPuts + 1)); } Logging.instance @@ -2679,45 +2691,76 @@ class BitcoinCashWallet extends CoinServiceAPI { required List recipients, required List satoshiAmounts, }) async { - Logging.instance - .log("Starting buildTransaction ----------", level: LogLevel.Info); + final builder = Bitbox.Bitbox.transactionBuilder(); - final txb = TransactionBuilder(network: _network); - txb.setVersion(1); + // retrieve address' utxos from the rest api + List _utxos = + []; // await Bitbox.Address.utxo(address) as List; + utxosToUse.forEach((element) { + _utxos.add(Bitbox.Utxo( + element.txid, + element.vout, + Bitbox.BitcoinCash.fromSatoshi(element.value), + element.value, + 0, + MINIMUM_CONFIRMATIONS + 1)); + }); + Logger.print("bch utxos: ${_utxos}"); - // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List); + // placeholder for input signatures + final signatures = []; + + // placeholder for total input balance + int totalBalance = 0; + + // iterate through the list of address _utxos and use them as inputs for the + // withdrawal transaction + _utxos.forEach((Bitbox.Utxo utxo) { + // add the utxo as an input for the transaction + builder.addInput(utxo.txid, utxo.vout); + final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; + + final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF()); + + // add a signature to the list to be used later + signatures.add({ + "vin": signatures.length, + "key_pair": bitboxEC, + "original_amount": utxo.satoshis + }); + + totalBalance += utxo.satoshis; + }); + + // calculate the fee based on number of inputs and one expected output + final fee = + Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); + + // calculate how much balance will be left over to spend after the fee + final sendAmount = totalBalance - fee; + + // add the output based on the address provided in the testing data + for (int i = 0; i < recipients.length; i++) { + String recipient = recipients[i]; + int satoshiAmount = satoshiAmounts[i]; + builder.addOutput(recipient, satoshiAmount); } - // Add transaction output - for (var i = 0; i < recipients.length; i++) { - txb.addOutput(recipients[i], satoshiAmounts[i]); - } + // sign all inputs + signatures.forEach((signature) { + builder.sign( + signature["vin"] as int, + signature["key_pair"] as Bitbox.ECPair, + signature["original_amount"] as int); + }); - try { - // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.sign( - vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, - ); - } - } catch (e, s) { - Logging.instance.log("Caught exception while signing transaction: $e\n$s", - level: LogLevel.Error); - rethrow; - } + // build the transaction + final tx = builder.build(); + final txHex = tx.toHex(); + final vSize = tx.virtualSize(); + Logger.print("bch raw hex: $txHex"); - final builtTx = txb.build(); - final vSize = builtTx.virtualSize(); - - return {"hex": builtTx.toHex(), "vSize": vSize}; + return {"hex": txHex, "vSize": vSize}; } @override diff --git a/pubspec.yaml b/pubspec.yaml index f2faf1497..dafa6189e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,10 @@ dependencies: git: url: https://github.com/cypherstack/stack-bip39.git ref: 3bef5acc21340f3cc78df0ad1dce5868a3ed68a5 + bitbox: + git: + url: https://github.com/Quppy/bitbox-flutter.git + ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 bip32: ^2.0.0 bech32: ^0.2.1 bs58check: ^1.0.2 From e365bb0c16de8680c0ee5135d37b43cb366b39f4 Mon Sep 17 00:00:00 2001 From: Likho Date: Wed, 7 Sep 2022 17:43:39 +0200 Subject: [PATCH 04/20] Update blockexplorer for bch and fix USD amount not showing in send view --- lib/services/coins/bitcoincash/bitcoincash_wallet.dart | 9 ++------- lib/services/price.dart | 2 +- lib/utilities/block_explorers.dart | 2 +- lib/utilities/enums/coin_enum.dart | 3 ++- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 65464b5da..1fd4d9b66 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -304,14 +304,9 @@ class BitcoinCashWallet extends CoinServiceAPI { throw Exception("genesis hash does not match main net!"); } break; - // case Coin.dogecoinTestNet: - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // break; default: throw Exception( - "Attempted to generate a BitcoinCashWallet using a non dogecoin coin type: ${coin.name}"); + "Attempted to generate a BitcoinCashWallet using a non bch coin type: ${coin.name}"); } } // check to make sure we aren't overwriting a mnemonic @@ -3023,7 +3018,7 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - // TODO: correct formula for doge? + // TODO: correct formula for bch? int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { return ((181 * inputCount) + (34 * outputCount) + 10) * (feeRatePerKB / 1000).ceil(); diff --git a/lib/services/price.dart b/lib/services/price.dart index 6a7160576..5fd124e16 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -79,7 +79,7 @@ class PriceAPI { Map> result = {}; try { final uri = Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"); + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash&order=market_cap_desc&per_page=10&page=1&sparkline=false"); // final uri = Uri.parse( // "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero%2Cbitcoin%2Cepic-cash%2Czcoin%2Cdogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"); diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 9040c956e..e1073e018 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -23,6 +23,6 @@ Uri getBlockExplorerTransactionUrlFor({ case Coin.firoTestNet: return Uri.parse("https://testexplorer.firo.org/tx/$txid"); case Coin.bitcoincash: - return Uri.parse("https://www.blockchain.com/bch/tx/$txid"); + return Uri.parse("https://blockchair.com/bitcoin-cash/transaction/$txid"); } } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index fb7b83f5d..647709190 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -42,7 +42,7 @@ extension CoinExt on Coin { case Coin.monero: return "Monero"; case Coin.bitcoincash: - return "Bitcoincash"; + return "Bitcoin Cash"; case Coin.bitcoinTestNet: return "tBitcoin"; case Coin.firoTestNet: @@ -161,6 +161,7 @@ Coin coinFromPrettyName(String name) { return Coin.monero; case "Bitcoincash": case "bitcoincash": + case "Bitcoin Cash": return Coin.bitcoincash; case "Bitcoin Testnet": case "tBitcoin": From 6fab5c9976bc828c1d07176a522743030b64c497 Mon Sep 17 00:00:00 2001 From: Likho Date: Fri, 9 Sep 2022 12:59:40 +0200 Subject: [PATCH 05/20] Add images for bch --- assets/images/bitcoincash.png | Bin 0 -> 363022 bytes assets/svg/coin_icons/Bitcoincash.svg | 1 + .../add_edit_node_view.dart | 2 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 1 + lib/services/coins/coin_service.dart | 20 +++++------ lib/utilities/address_utils.dart | 4 +-- lib/utilities/assets.dart | 13 +++---- lib/utilities/cfcolors.dart | 6 ++-- lib/utilities/constants.dart | 7 ++-- lib/utilities/default_nodes.dart | 30 ++++++++-------- lib/utilities/enums/coin_enum.dart | 34 +++++++++--------- pubspec.yaml | 2 ++ 12 files changed, 63 insertions(+), 57 deletions(-) create mode 100644 assets/images/bitcoincash.png create mode 100644 assets/svg/coin_icons/Bitcoincash.svg diff --git a/assets/images/bitcoincash.png b/assets/images/bitcoincash.png new file mode 100644 index 0000000000000000000000000000000000000000..18552e02e5e1ae9343170f724676c2cfdf81c9a1 GIT binary patch literal 363022 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�um` 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

ok{r^= z!o)Kag5p)MjxZndCUp2^m@b_Cr8x;b(g3AW6mk#1xtyE?LLPCbyT#817lhQvv$R_j z!MzjmgmhBd$%!Y){os@KLs-Q>QPqsV7QNcNW&&QvEpp$c&ofTg;d5F!U0}gwtJ|@F zc|IN9oO15N)4%4Ra%ol zz5wBJz`iqf`q%U;nv&khE?&miAX~EBzL#A?t@=NWQP4)dcQerJJC$fQ)oQr&lC4C^? ziKvf0Pui|#`cH1|;H=5!UI|^TPO(0Y79u0crYq^DIfqFVt0xPo^Hn4DL@ z@QwW0vo7Z#I47rX^372;{~v_)SujgM-)Y5g`C^rXMkyjHs@52{S`fZ$2kBe3af3ti z08*s|c&08hA?qRj_Q&TwNIl%pC_CH*Mcy}D_~`qZ;LVF7*kM{VUvfWCNO=$?tP*YO zOZ#xSUYf}9Z&sfC=jRSMOu1k?7thJ9K4sHuF7Q}Jcz|%QmMH4=kKf(?$kI0%(XO*Y z8Ozr^B+9gkQpdABsWA83F&RP`Jho)nW+h}sqWX8Yml>2m`CD1wN~&fnHkgOfVCaWhhRMcQ^9@HI1$azdxJGhDjvBHZt`io@*v&xH|v48ZwBoh+ozYmsZ zKpH?HCqLI}EG*!*Ot4;lWE)PhcAi$$=)a;*#Y3AmHELMY@wL8t?u|k=UO2xsekU0b z?S2P~xclv=YX=Rcwy$z3ol^L3qx3+{3h?jGMn)NoVE4?6U;De-F65dLz1rYZ#F3Kx zD7ux}5w(0>@i_OM2l0~^!}+o&NsWx$T44-k_u1`ZS+@UzrQvIZYu4e_0kp@8lof;- z!KN8-mqD}JIko0q=pA!U1yE33*oWsi5(xnkoc2Vr^^{Q)yWGzMh0h&!PY)tT*u^J8g}5Jhz3Yv~=h)+HU{KkR3Su`Ty+j zquK}EtPau9e1#1k(Uy zFld(}D*I2*}hb%+U~~2 zfJl+jmc$;Do(o8IB%-FDisl~EDI`1Z**#r6=wcsDJuT3#>6@GkN>O03^fCOA#NB zngi@Zu5)sj>l&~dP&6WzijXmS?3g*#9+rw4rQ2j+udbRLC`vn>rL{AeTTm^b8!*)n zx4|8rZ@AxFrA=n27g$;;tm~XqpS&b!7nkko{1tolnP5h9tEUv>qbJuwjHt+K#g>cx ze-)YIR*E+FB@!&Le<5Zd{$G$&T0M(S=X75gUL}1F%ypNaUw-xIIr@P=E@Qby^wT)M zaXvSPUrz|Xkp}Qn$*uBu>5#mc;l@M*n}*Jqyq>pV?iZVhWS8~A2Yu9WxSXSxapp~b zOmJLbu|WYgpUoQ;ykU6AYnd>9Im-789=m5Om3b%(YTb%|2QC^K8b^dXNl8hg{y}#1 z%4vWmC8g-F zc&S(E&YCe@=~oY}@3MF~Z$(4gryHq$1T8Jps!~v`>O!&_hi@zFPMR|kJXk7^dJi$R zjqg~);FDHGbXhQksjJ)5h_xIh{tLqujyyuyh6f$I%hdBQ*Q$@qv6cK-J=W|&tIW7q!0mv( zCnO18oXM9L2ie@3=062rO%i44c_ui(t3KuQ{j(`g=`wC|e(W)b(RHa>%}JdS_{hfH zv{&&9D|1UcBEQ0(3fUGdVPtwL#KB?RDUz!sB022x8ynGuU>`lK$HT+x9~r@I&W&DD zFoiYkM62o`zu6%f5H#Ap+DJMC+T&b{lv=!!CEjlpdM3dE6RFYq+5H<~ttPX~9ka1s zYQO9XkDQdWjEtis2IqnU0m~5&@EuK&m6esb>UCXxtub!w2a?kPNu4-#j96$ zY^v--maQjbU6JGT6;pTWkoSr?r6E->GxJdV9t&-S-x_p52OlY#92?uGXIVXLs-t%Q)QafI{_`V?Z-2GiZV+#_~vJCZ^Y{c|W*~k#BkC&R&z0*bz$;c_bu!w{UIl-%yZH~ddG``v61tp-8Y5K3*5HE+okH-*}%%H!O0}Oq#^gelS zwO@e$gLs}zP!Yq9wfw+k*I||k>&Tt+soteFctp~(j++x|S8dcMLP^#LBr|oMiKu^* z{MqS9_%pHID25s8u4GB79v;Y?ww3)|@`Yf67Mwu~CJv(68!oQZ?<&TW(Qo zG}}*2#9Kks=*18zn!bO}e|>fWnKgmUrt2w{fMJdo1-%+jhfbmOza0@;<_@q;nGm#v z1P32^p35}bAt%VzRn9~$lR<#{^;oj3Gc5mIm~7a*E=Y-188O&!3i~JaG8ruUE^K7S zd$+9@5{Gl&Mp>x1cF8TTxG%X6JV|;RTQO;?(qlUTJ7!a|!nC!4@w`C5v8_U4}X9Pw6Cu(Hr zND`W&sa{G0v>GcaEso=|&3fhCftI1{wAE7q>p31svE;687Z}oWg8FIm?WVBzE+K^| z&6B}Irl5mQqSsV+s8G~1&vd;<;6pXhEc_O-J$Z=k+{0#Su$K+}7Q{q-6swgWNEXn# zsJ-%K<>QxuTw^N_iSnj~-yH$nDw1y6e=w(OCy_+$vp8te4-YjfSA9Fx)Ii*|3ReNM zR^>fysa9ZvdOiO-u%G1j+h+af0Kc_69#?$nO*uN@RnBwh_ciXl!NI~hHoCXEfC!#+ zDcwLvTQJ29-P8Fo)rMlCHBGH6z;1toO)xR4QM@%2L*-}uyI?~OJV(3BhyG?Gb_O!z|mUs8p#6s-t9YdKZI}XlG z-ho3)qTv*nrz}?LA~CPk`1J|Wk22VAZ!Yegv$%zj;-eTWIHX^-P$%?XJkREwO#y8> z_Dg6A9;VNzKQrg3rtkDow-vu+^K@I=hiL`DM+kb7ZCe-Yy3g-4mQB!K1b^VQ%gi4p z_Rb^sVSe_@eDXIFloXBD32ghetzfedgle74b1q5S=O&SieGA5Tt@F`JM;AwHXmXu zfZnyY)2i_Xur7Jb+2%L#jPR1v?rQj zTC1@AZezpgPHQUiAxTm@-(kWUnPVWXNY%VFi3JTBw_&^zCp5`l`-jBIVaJ*&Os8A1 zoiWhbUH?BZv8s*ErT@NI=BcC@`~Xj4=bIn6aw?!6a2u9tjCT?2o?pOkA!n6656tJ2 z9zl)q7hi222CKNRGj>S2~--8RPM)%2uArbln55UaU>*4fY2ZgQbL#|p9N=+KRZ&@d6aUQ9+vkiLSMF8Q149eSHdTDFFD6&Ap!kQ zDBk6Iojggi&@r<*;?(1(YU%oxSgn(uQ}mx^&TkG?Xt#ubInp}jvn?>NSB?GnJYw`8 zKiNSU0C+}~Q$mh@>~wa1>NESq1PdWFys7nD93avqeerkE=CA%Mj~WU8D(n`|2Hjcq z^-#}Gmeek_J*SS}<6lZKE}kJ~rYqgn$Ynk<9FGRA9fNU$HbE+-uT!`kl{jcU7rNYK zeY*FGczn2hgGc}U`~s^Oy&E67zJm^2QZ=;#G3*`0gt%fF$NMeNMY2}ROJgx5v^7#m ztmHidx@STCcjb@Hk46QS?F*zMff~hY2$DQ}7}T$XQxd|~iPEMaZR>3OlX;};O<9~#Pd zcWO^krlip|O81%i$-Ga|;V01XGDI)G{-oS6HDhObm}HglOPAEGW6=SYW(;M_Zr>k_ zcyv~bA0Du%2TGxMoH?0WpeEx+5LHY zL(Hhzo6R#K_mu;IIhMJ3&gba+?3r!F!mS&J=f8-j347o&uSp17n(lds{7G+8Hd?7O zdekOx{^8%s*v4<$Z|8QOhG+gmvw#T7lr5g39!X`Cqw;E zy#Whf!4G?BHo74LiPBvR{m`+owerR|A!7q|$!I55`SB zhax%$42LucP(ms|p~w_mb)%JdCgRfihZ)DH;R|Ph$xluB#e=5Q8RLv1XPF@F#Sy^=;L$xgJ>AT)g>+{sXiWSBI)WGEg!&*!#8suj znm_TvfUgjHYqdurd#kU%KXr9nGP-su+mR-KclizqBfpZoF(l>^1_fZ?i-xnjW+Vh62%eD(l@;V`fuWF%*R6FO#e34EBo znlO#56N~)hr!OygS*_er@qsb}PC?b8&pGUsD)v}ds7_Zkqd}e0xw2S~{%J7G;$fiD zdjtZpnWJmE3-hV!-iaCf@+Rlok-eM_h_`(w1L382rlzKDZ*08BRI@c+g2yPE^cUoU z>WmtDzU(xRD+n7tUzL?fXsWn)FyA;t%m5wn^!mP5)+09N$f5d}U)%ZPG4qtr-$iOe zc4*RNf_YtBSoM&Z6i4Ujmm|s3+cf+{TClQDw|9nr)nI8%B>+k2`CH_MI2V`)8B(Rb z=0NRaRF^jPjas4i;y9(&U?@fs^JPn@$T&+D=25D6$HC2L^bnh?cM|uLkh%S&Me_gf z945~Uqh1lGlI=PnoL~0uq9Sx92SoG?H(4;n&CQm;__Z#}(h5KUI0D>;A zs>F@DhRZj62fZgcf4OU~d?PyL>S#@k5lZn$boFx(UT2HF!=Y(j*L`-b-24wCriG#@nr7)`I0SoXT3Vj0am(NT-z8HyY^SbPp=?+k z7#B2teEIn&j>x$5jk7Q(Jg98BtYoLAM->AxuQWeB@J|q*QGbo_r=FJodSKh7jpCxj z)WFzJvyF*4CzlA*^*#5MRZs>3*MJ3Ag?sk7J6pGr7hj>@EGn$D6Tw#J#cY&+;Ogr76o@XGZ>}$o^#kuf zJSVcEN9TM-6yBxQ!cM7e{-}qX^*t%;7^lA|-B<4SwUKp`8oF6j3Hm=8r8vUVPj_$@0A!!T!m|v%Bof}?Q z+L()Jf2rjBDZJh!123@cVqg69VXf0Lsh09!PPxCf6qApYnHdWMBclU7f8zbl%wHPZ zkd{mZU1K*uu>1`QCM!a5oafr5nxx)usnbv)aLtxkuL^q|n$X3~#g+NZ1`J@-U8g^j@h_kW-Yl}1ki(MKaKS`XSchZZQY|*Y= zLMqID1tc@m-_eQL|IoSz@S~f4MH-ZZ@6j!P`8zLG+KU|2hH8sF?uJ-tc(XAyJt%dQ za8d0mK3?}dkWbGZ(}@R7i*Fj&ERAOt;~`IBCw#R0Uh#dV;)W`Qq2?VaJx+dfw)x)W zz%pOD-vb{l8l1X8Qj;Ko`*=CZ-k$i8Me%CP-zmSDSTl13Ta;N;mbruy^&=2U&AU3W zBRf#f^3u{dfJ^dU{rd>Lkr0R3)fz=I9}WyCP!dUgfmK9e&qIIpsu;0d>Y9^QQba{b ziypnDNg;!EVB(&uVLCvjTjq-j1n(Tn`+x#R)2dE+R@U)F#YZ4)sISW-5BX?4`vs4| zywusBiA<2+@u`zaDPU0@XNLZf+m&yFJ|t%Izty=_c`UHa#T-C7DjOi1!t(T9zdJ=T z#lR$1<(WPQWULTr)>>BPek*GK^24{tH7csVUp`~L@aa?)e$f6r_NQ_C>~xPnfnRzV zQIyfth`xy3o6mLni7RbyBU+T@vfxkejo&e!jdwBSk!5p%Thck|ZrKd1yA4%;p0WpS zO;&rop=eh0V6=92CstMBS)91w)>5sYIED)|bJhmyHZMlAsOszMV|sXa6up3)WDW=@ zE}@n<>E9IQ=DL+NYiX3Y2__xlE~_x-<%f4gOZ%EYHbF9|b_8&V`=OXEbC)Q_F9^QE z765wt2IIS_#Gpknsw`C|(GM82Yz)6Xo~Kjwdt@|#1^V$;5}tJ&{itdDArI&12BNSZ zPQG7WbsFsM#>@tp-EDhWOtbc6!JApyr*nUzm!dgvq9Uy>4_EAs0Hov8k)ow|21c2{MeAI04digF$ z)`b<5zDIpON$bV~`)6VGh?No0wE)=i+x$J9Cp*m{SlR!3OUH_!4*v9eq=p4Jzp&f3 zx3|eDDTni0wzB}2xYw*dXHCyqgvw2Ro%uFwhe@BS$kt@jGz;b_$~Ysh$2>EuDU-iWVWO=T_y7AT73TY;Sp4#oeb-AuHLZ)nq1>I3zk%%M*65@S zNy6&Wbf&{;Qq%cQnvN{r&bw)9LfB~#>m0vD%7K|9>KA^mFywjbpHdaRPbWP8e+i%w z`^?kDhD%B7>m^_Jz=i;2#;KI_-1^KTKz}X2AgM%UJwd2)r59HcA|x<1An%N*s=KGl zpF!%pfH9r{FoEY^OJ+f`B4V}_*t7vB@7=nTN@=euKRIIKc6+j7amHVVlG&4R>~;q!E(!U7dQI_}r%=VkV!=P6~yrAGm%n*TS%Ui9c<^gXRxt=o|1< zS;-iz=}6RG-?{~FDS^L6+KS5q`>)d@Hn02 zsTg-SjnJ&6_XKe_zodXaflro-g&#Kc}F!j zWQ@cb*a80d@ncQGUH}OsP{XRap@6?i1%kON0b^ZWM+d%no>5&3R+@OLKx|fRbkRMh zWjUHzurpI)@_@=^>7gK?T)W~{`>iIQ{bf8dKLTzNW6`W%!!O@!^lRGi3w{SGp0t-L z$&c9l5Lnk|(kfvqek@lGOiG2o!EH8vp!rVSDJ?(p}Jqlisol# zJx>Uh=B4|kWAsL;aqB6jQN>$9-$9Yhrd?($(-G{o*I=c=LbBf8hG+fZx|aREV_LIz z(8Yqn8`9|X`ajsMum@TJSfj)2?ZbAwXEbd80P^?nyO@2f85!VNXjEEFmfyc4Nf!*w zRNB0_%gA9_?WJ$3DmAq-HSHIv0f6q4mDleMGl_bE#i`YTiST7yk`Q~v<(==Cmcy{} zg#l7rcFb8X4D^}x(;MbzEs8JJLIaM>rJti~uT%BpESYSpt=-;vQ#j#<`{)5JilbNmFiy{%5 zjb;!uWOtvCGH^{Ai4`vv4Af~Uu7-TVT~lX|;B+9VR%WR)a@I%Tje|0Wbe6I*h%mMH zXPC1&$;rQm!))#N$NH>>(Q$#Ti+e8JtU$!9b1UD2{!a|Ez3DU0jiHy*qEj+-VhHr= zoq;S(U3xkeLfbK}z6Y8CVE)p_h47mP<;WEY~9m4oz&d|{pJeDo?0ho zZydFa*zNiD%J-dP4n^!`K}xKZclzE?I_-hP14rfSs-lE+N=Lv zM%ZU6a-L{uK6~K##yMDojzH=bX+jpXMUyZ`j`2JqQ?lT&>Z8zb* zyXJZ5fYj2fF?23rT*n&Lp5C(hmc|XB-fX-x1LkhE%&Q6bS%xP zdH{2$=ye?JEWih_>{&o6b?9~KLlv%vY(=~GsoJb~w29E3W**Vikx~iT0$(sXn9_lu zP9|XWDChEnK#^5&qErVJbKUzqcy=uhWhf21Qjxz~Ip-DJOrUMZ2K{Bqc233)SP%SD z)#4~v+ISrgD0t&7?m730I}&O50}YhZqmKNs^sNl2mr$ARo$~l74n=nUR|eE`)@31Z;SU^WzmzX#Nc* zVc9FMsFUs$K)k>AI=2_$s}vzwb_K$gj}s0Vlii`fIqt$$qUPkzJLbY9T*8GGsKK^c zT2A2JFGtVa^ACGJHYZOvVQ4=w1N5~oBGW?lk3ktp69|`c%sqYFv}yf3$~YB@}NQ*!7{G-l#3@4yC#gIj0|T2G?!8pRH$$?EEb!7%-Un zYRZoU3@=lf_}TLv=W(FgTn{b%m$bp>sXl?qJBFGq;PBj zO&b__RD16fL~S482M@K}w#y1@-~;s@hKAV1YJ`s9$>|oZ5CygnM?ec8?n_JDFB&;p z_L(z4fq)>hVD`iL{xR@%umetCMn8W^n70Ej@=)YKJV%`10Vm?&#Jl>zctK|2j z#+xrcar0F31{40YSiX<27Gu^TPniS(^600`?=J4?BGzl^8krBV08M^o++|KN<|GO+ zdaW0J$qHPt`0fZs)xUHwj{u@?02mp6{`~1nb(5EW2#aQA4OiKM`br+3b+I@6n67a$ zxHD?LUo+)+;D8LcGbP$v;c0lnGwXinl3VfUyB|?6aC82w%IT#;%9g>hV)B|izFBcm zoBU@_{qwjW*tFsE;C(Sa4A-A${{nyk+)D%W&|!0W{7lzgg3dcq=qwaF*}UrOwRH{yFHXMXSF_SUY$ySbbdO{?+S7 zO}CQ$z|uj?o2z`sRed*ccW(Ht#etq!3>#p?-nbtxzwvGSKH8QO%l$LIq06ns%|-2h zeVGo9yF=KXju-bvREFjQF2-4o-UC@TQF119x&XuVw_i2T-?dBgO#9u*ea3*1N5M_Z z$<6@(^_^sjWEPc-J;BArr3O+gh{lYvoYd9hh=M%e$wZaQ2IH!5vIudpz)947Lc+B< z9m_|Mt<&Av!ra+RqngrMGU$Nhp9Y|M(E`rFu02(Lr>ig64l+fxS2D7Dt$0x4Z%HY| z-Q44Zt^BcF7+*XJ+D~EaPYW-2ARFd1|Kb<{CW(K`|Ck(HIpTi!t|^qdO74Giuxg!1 zlOX9(f;I|QoJ=5MZii3$!Jy{-EIsaAn>G*ep6;0n}#qJiat{Q`vxw=YN&d--uQBd^=D!e2Jyeqn1!cTTPR(T%v@rz*O^0Z z7Y%%e&wsfm$Jh+SX!{;&KAjX1GgP&jDkagJP+1I|q;8o}Y%d=B0WCl3akUIF5Z4D= zskboltTHTzQo6h4+WF!G?ZTgnhwNn4Odr)>F&?c1^{+i9`g#BJyVfmf#;?=x${~ym z_dns$b8b@8+_J}1?KuNq-9H~IGuiceawz-iLB3^DR+W+?Bn$-t)}ZE+9StBa*MpM& zKZG=MW2?aIxr{cw=fYnIkkw*?H=mq>VhE#33E>17STtG_s4hZVNblf$rBb?NC5&x> zUR*}E+z)8Ov_RJ|8IW68uK`G2&PW{u_gP8A^|GtDH*B<}V96vZ$HPTXZC~Q^z0)v9 zn((4sL8mmWBmI;Ii~AhRNC(l5A zIDQELtWWPRvUgm0b|#CpD;#vCz-yH}T(!cWS;@}y+UP>09PlX2d!-hvJ~vK0^g`=q z=DqDDG3e(w`EjCw-`7PTXpPJcR7+w_4ws00?el7Cuvk_#Wp<+W>3f_V^^ z%h9UlllofXnN@xxK+WT<9dCo=ZrI4lJpz*QGNl4Zt}3N0$y`^!3n37vVUM0yNK7K? zHoB?6-T-ZsfU_wE`{8JQ0DASsZH!Xq0!-^nG{(vE;06HJEQW~ zf~4t9uL$f0Om7IIyv>1a$hd~hUc^&!WUj~?g7lonQP0x8^D-EWtZY6O(EelX!V&#( z+iw9$_fwj1&gV5=znWISKV@m_r({+qEPAEbeG32Bh9{w{lMzaGlH_c|3!F9DGVC96 z+Ku8vv9|zoBNjes)Z_2B0@QM&`IF1l;13VGEa>tl^I?yjjZt{A(g`9QO;vSbl6tDv z;ot#wey9G({U@eWHk1aHmev^3py~NyMz@BD=vxcL+7+-x{kyoBn9>dQ1Y+Zs#qaD} zdVi{`7Y^uKcHtlS8$AU$ovn9A8)3!ydxr&_e%2on!gEWC8nAG0XMVlye{op2Gjan# z?$7^?Vihy#JYDskG1vB`-NR+nDx=%ms??74 zwqs^7MpW8A&ER&PTV}XgMbbQv7dytRDhM`8QXJ7eab z@GEr(1>EN$6kR6CandIyIk&FadBEc(?ax#HpI7nXQ0cZ}01*lc7HdxHJc{XMR0e(w zI@;P6d`jBp&{)ZMm$>hD(H0_3oW{q^X!mJ7_V>8}<)k^Otj zN~t_4|%D@LZEKG!-5Y&9!JXZ#0zyF=qrr-s1dtm3C| z+r}Z`gwlA>|K8Ln7SKq-Z6`I0!hf_$$XRk+!6h%_T;A~8^*=Fw)lm*!(olL~+%!H8 z4PXcs+$8?rsS{;+Y2o1iW9b|m`fUHVpIf%AWw(}%ty(SHwQAWdF4xksZ7Yb+tE+l~$}6MPT#1kKr?=XR zJ`4l{oNqnShj18;dzsp46ZWvdKqg+ph)O8=KO`U9R@Hn+NEfd3-t5TO_ySzwHIGuS z-Y~JVtCz7K{?BaF^bM3MwI5lQHB6;ZkiIzvEoq7VPf!?Mr;1!leWy;ipk#lQeOnxC z_Hu1nAZt`RZHMX}`L2&@UU1p+$LqJKy5DSg5fWl|iT-1oP3@@EO!a)gV7usEso$YD z*3WGvV5aPMkey9I%;)qD)MHZ=^)QFKaIzPi|BcaT7kHE2f6TiO)OBvRT@wnAj9e_q ze+tCL!^_Rj@4K+Jf6oyr-LGKGGb;QmM^7Ymskx=;?O+O|EwF~Q_EqTBI`@^S`YuM2=I6y9BURO0dS zf2Q0X+;sHxT|@DV*PHJc`>`;NP*FGHJ0WuDJsR?9hwwO*e6M$#wqhg7?DA^r9}cg} zF4rvEIT%-cqP;wux9Goqt8xZzk?Oz_v5#Q_2M|nD`DupssLcd5u6X@c-wM__bLi`x z{}ewF5z5x96MMO}xgThtqre^c-Ah0g)4QLwR{QAKl5p!BC1z*IV-uFzoEoveJyxrZO#S`Ow`hhK-HA z_jfVF4ffUUQCf7=Z7C2hgL`PN1*e)xG) z?0+m$SQy0VqCYXYY|YeY8s#iC-P4 zme>tm+6D*S{|z2-U@=6h*TD!0oWM${#m=%-gY+K}Q%JezZP8pk3c^$rnDaqIk>o!k zI;@m-tg7n^!{n$0&SiCVS@Sezie{B`*-qwECWznMr$U(uV9}8BqT$_smFCJDpQ)hr zN4Alva6jR+%K#mCB_C8+wB?V5cAq>62??*>BO#UTMFpMhxzXMP2Vro66V!xO|8T{> zvThCB&4CPT<<59uH;);oZ&H6xG?a3dUS~T&!8f6EE#-gLTP6KF(`TD0tssX39b>!s z@38t?At(Kwpil26{4Yh&E)+)iLo>xde4c4X$5+^6TSGh7zF zd_w-{XD&2N#FOiXRPWH|#^h&=CEnO%2&~=BB=S0H8li1dJ^!g=BBRN{pKV39owc;y zou!m2?lLA{dt#Ex&fF~4Sz-W?H-0)Zh5O5pYLu%FLSyfev4R#G$UN2%pZ9oD3yfaz6==}bCv_69Fa^xkB!LYPebnTBJG%!;aif`*ssQy#) z&agrq4OE$IsH@v=x!e1z!9UjkQ;2m5{sxK5wL`P3}7W2*MJZX#|p7aRI|Zk{csLV5H^k9r%T z+RA#a%!jl9dzIbx`}c03yT{Kl!>~~F0ogG7$){>Pj^|fyc+FIz)HP+M!a%v?@PthF z@l%rf4|@{2k1J}czeX*6pw~1UX%7U-Msj3hl}`T(x&8A_3)Rqd-;fK*d|}6Q zKIiME#PottjuAHH_h{eWCJxmo&Lgi4B2J;m$umklE>=xOl!X1HVUX^zrTC=&@w(kA zuU}}4+zL)%ydA!?K7dL?$GJ%dpb8}&9XZ9Xn10)=usDwGZk}Wt^Kc3J@n|<-Ei8KF zl4LF0f3XmxbLBBuS1+AgzaJL<>A~F!tq0FIO3w*gOQE2s6P1uKp_LLmTn)kfX^0;= zQvWL}9N3cT*P{e3aGfU+bCB5 zS;`QKEGYP0HmFo|LKCe8+E#dBapTQ(@&?Zt=7jO`3I0MO`|%Ee#etd<=)6)&MJ13Z zwxQl8T-ESY*Su%vp9nT}1`jfHFh0Mxd(UO__MI~ zp;qWn%g+&E0@z3j{I#mdGRK0!6*y9?ETX^_;&ZwbjJ3i3$DUu&#qC=TQ5+Gqi0fIp z%q)Ww_PUlc*-6tu&)vpD!-f>gavpM6e#~7PmICEBhv+Raq6j%(plS_=D|e-Cr3;3R z)N*;E*20H#WKZ2B5tpst@_co0M)%CW$QneZVmIm=48Vp3#7&pNhpasrjKbm4AKUO4k%mQU(chf>fQ|#n+`2D;48-CF z)NHDyr5zp;HkmL`=BvZ*DO_u&LLR#C zDDH30Cppr5!6Na+cskwn`Q@MMh-CES-BICCQC@BuVuPhusXkT&*8jDhnR5A?Zcb%b zuk$rXg*{khLbOAakK#ZWL_VCps$K&ZNAgc_o9u(_OxL}-`s%N5wg|*lGxA9P*sq0- zg&%JHaR)*_x7TATdhZbsNJs(M1rI(A1~W_#SCiOW!CjfO?67Bv$nzSJXq;#e; zSW&v%U|OWh>METHP`R2tv*yBRV&{ctqbiR)2p@Owa}pC0jNas#nCZvMi5bAK@o2Yx(TFOpjFY7`j!<{Iwv-SqMkV2Gst~XubQ4!n5$?KuvWU%h&2Jl7OwQ0 z`CKz9Ax@a|#gET$@^kVwn*lgD2}0v|3!#ZW{`qhK`}R^-R6?%_JW+89#ZANbr{)#8`mp`VAGKgiYDpVBEhHxbpULcqPYC201W zUMdt#2U@0bH!I%N;Z5{&DGcX$#3KL#k)Gl zl74PQhv@B&Rs(5_tke?v4L0dEB=TVji~DakP9%h*X7Ae$o|NcRf{g$k!?f@eg=BVY zBkp-fg|4Nd2-5^jwL;9(X2E&hYdiS-Zs*nrl=~84`>qN?zPTIh<47FqAnu zH&4;f|6xmiKr#u%{RL^aUGc?!pPxB-bKbuLVX6oc{KVgfM4F7bI3h^Ib?3FFI1byt zWEgQZhK=K;LbYY6Gy#L1y~2Q=ihI}@BO`b2s=CqrQ($1Axvj0V0O_0P%iR7`i;G3B zzOpiBA$RrU!~JUX77rFGdEZ$Cou#wXd%$$iz(|Gu$NOY_O;pAC-`sfqrfajkxCPd# zat)!YYK2I2?3RW3`QT%<&+&@CEgCTla|HY-v6u$|+Gh+!8@|=0_@?{aoMkjhEpj+V zt(LcQVSvjUZI{dEGs$5P0Wx$I&&VDt!?qE^Gw;94`GsVI(FQ%}p0QC}CqpR|#&O3% zJL&*0WZvMO>e{{B0FJn_ zUpfX#*6p764uX%K6`UF!ymk0z(VI!BuMZTwc}^gDAyUc>b* z6r%Ig2<`E87OCv1zVr0jf2gHv&r7$f5ubjAnI{mI0%TnYMAo(;6YCl2(?0vrplb<-2e*)AMO1q33GOc#57CFVkMwPdxZr~nA*tu6}g@~eW zU3~%O1uhOwc6m99O#@M(e7YD9@i*C9*?&%a8@G=Wi;E?;OHKHZU~4#UA*g)$-xYoc zovQ%0*7ko0$30G9pJ z(#9@3q2ca(@O&!$$QYbG?xs^3IA~)*pXgSkU`ESZ1Xwi-`{Yd5B=%O6#Q(x+M7S6W zp{0JXk8B`UwC2%Z{gSq0FmIdLPi53PSINRNi6EXnd>6TSdMf}KYO%UnkKx?DQ2IQT z2c^3hF`RY+(jMjHU_aY{))er{?@`Aamx!1l& zpSv%C-UTsp)r}L(p1}jhjhV65Kvh$|^udP$nbwjm6gOrtZMNQRU_6H*QujIW;GbV; z_{ObfX`(SKc<``!3{~zxuxW_Ikc1Bp*als7v4i3I!<7#D!vtk~2FA)*;&OP3& zC!gEEV0uPS52>=s-E7{bRzhvVB~Sc?c-{nK*Oe46Z3`+J_3Zl@nrk{XH}u5nVZ71S z0B~d@ReuZuT)=YgyCY*aqcwS^cg|&`z%qhMsBOLg>T)fe_d+gJLD)e9o8mE?c44Ho)~Xy6w42pE2pGM$qvHZLNYbt; z1_81!5?7~yQcxM`}^7Pkw zr)!Tl8@8jZ?}kt@b6$^6q|XveiR@&J`9ngEthm^GUp=3^iBFHa#q3pv>{VS7-)k8> z(bUkt(8z=p&^??zs=}ywOr$T{Xc$&QrI-CiFR4I|;}@%@HFok+YIPzqh$W2^)JnGcOf$5Hk0RdKCTRG-m*u9%Us&h07(vcn!0 zQ+hy|bFN|9YF_3C~_eS}%E-AHo=->EM4;m3~3 zl61%i0m*4!fQR$H>k8HVl=8XNa(ml6MLdLs8r#O!HhArH06@9egoGkJRWvn$Bers` zw8ueH<*SlXPLBh^Uz)F(QLE;kz;$%i) zyqEkwL@4)Re8p zonZ4205XQcPwd7oUTj(PgT`Fj!r$#?jU^Kwg@J~gYc}fcoLT@CZ?(!H)@JvFy)0@c z+D>CJR`%yI@Q?$2NI;L_Kf-%n8sw;fEjl{7abZVENm$I#0HOFZKv~`9`l8i1R=4R{ zaEeMgX)PMO)#U1dIT6@~iT|4xeZ?ZUI<*zfGsh6i`}AMzf2Km$q&W+RbE-SwEUhr> zm}}OY_}p}OlAZfDF~ZN-4cUqXfiE&wU~E*Is9HMZ@XT8}6>6USNrN;}azaob#8}X5 zT#%zNt9)^6poq0W??_4_Q7jNon+9JyFP_~5Clk^_F;8jd6&|{VmgPlEqZ{{x3qEq@ z%O?`e<&Vj=Y$o-N3g(!PDn%{DIo`9+rja`iwg{-fmo*o>Skw~20^C0<$=p%5s@PUr zji_ehRk}U|qw7MM7I(8SF-_0RiHnOTrKL?5pJXZywvot%Y zO|`5zF!j(Rn8-6jb@e5F_&gOQ)!s`p_C*I@=T4OOWUz|!E>oK9n^_hy_2q==(#J(4 z%9K>*7eaxCZRms>uc}vUp|i7(6L={Q%*@hWA41HlurJVPh=4i-2?TvALIj#Oo{&kEp5;_S+Kr{4^zOaGjNTR z6dB1UQp_w&4(XQs$){}T=pX~5(q=+0dcP}2|MM&7t9wtrGu#V7g#K-iPy-fZHcd~& z0JZfB(iNUL|Hi#&j^bhdH}?&!RqJIf9|10gWVE5ScJJ$pkqOIl#yFcF!e~70kTjsS z%9qU5xvVW`1P^8)lE5|-w$OfW>xm>OXD#U_8cy7=KVr_n)V0_9=j2b9>FIOE=6!i* z4BoMj(W>I2{)={2w6uN=H^kbjaMRh-kCe^S2Y+)jkCm-MAo-YiIKO#Irn@m6Th#@& z{AO?(BfcqYHD$6jAi5>S{QUk;TO~A^o^xRSbLH*a7A+W>$Wp;WJr=w_Uklck>I~mn zVQg66`K9J;=V>$6gZXLry=WcG{{9&cKJqA|L?XFXNtfiq6Q)a{_;-Z-sn-mcm5a7d z)H!2RzKsDJ7hV>1EWVANY3%~Mwm_x4db-STwNNo7=b)Qc=fOodllnXJcDh*kVOJse z9@ylMK3LA`TwUZpbo6aWgC)6F6?zC=$A6qF9oNS_iE69JL*AspSk|kPWgsGlZG4Zu zUrV2cg|Vc6HpX2?dGRMhVxPQ`1n&XS!H@eX+DsECc?l=dhGJuQHNjP96_Q)S-3c1? zy&%O+Xbk3GFyV~%y=LN|is~vBCSMaud`9U&`(}4Tu=|b4rpYM%LZ=F`<0+7hw#n~< zdbm`fy@@BfC9sFD+&;v>@t_1(!ZNerz`Orj3rzoP5O(vMwZ$wD zdZ)_gXsGcV^zJp<-#-o}WEgvA|9mp)YI=HfAlN_C6?3;xC3^>RHAXI+=_$a+SE8_O z7W1W|<|!5PWO$6?@o#^5g>=Mt7sHM_R-GQO6k)w zy};vG!9PR@!`s&dd7b{ueIMLBW@MH%tBAK*SVQ*8;@r{hD!n|;2S^nBLqhb%@>dl< zB@!~;pxzo#r>*Gza0txRAjx#);o<&eZhxl!_pJ%?18c#nAI?e~iI<($$As)~Q27pVJGbD&FB^)}YzTvcj0IcYjLBOM%(=UQA8GUyP&kLfPS7H+jONv|-U(q5?irgePjp9SI9dkxG_h1vy$;-^;@0BO@ab3dYXiig-|b|Or6VVfsz z*QBRJ!UFUdzhzCEJQe)x5hefyvBcqCfaGcT2lczmA3ydP9*{o6?^O>T+p$NSPy!{7 za}h=)jflj3A(cQEgrIa>3u0Av5#eKDP*qaNOh-$8Bd+%J;Vqv&j@l!`_z9z#hP;WW zPFv+;Ph{cQD;Qjbgs>bjt1>d5w&=s|ZACA%#7t7su^N71nxJIwoQ-km>j<~#!j>j; zkK(iF)X_}yx8Y#RYBDcUdUXW@9HU}t)NpOI-jBGdz8tI-P-|0yf_EA2nIEx{wy^r& zTQODidyXKtjG+vUS*HuFef4~g&^%?m92hW%83a(6t&371`VL?_*m-%2ONVG662JI4 z@TP0h`%pM0AF@0SbWptk0A7g8c#WO(rGKah;Zt2zYo|WWK%up?KYOrG{vfvTqq$of z>Nt59i$h(P+oFL*b;DsnX`4BI3xv7zkt>XeCSDFRxhzc9%uS7!2MOPWXpsD_Q2Lwt^nvdQsud^LxEak-BGlS#;jj@wi50_;v-eg^IH(hrt) z>V1ffB%?JxNrQeL&?h{{$?kWeeT)I218kQ=kV3VeTcPO4?3)caKx zl~Rj3hye0!I<18+Sq0BsddqLghNiZgG7OdzrQr{Vox6|)xmn9>1W3kf8Xfr%E2XG} z9d$Vv1j8q>8S5GoA>j7&~NuHle_En_q(*&=7MAVqh6D^0e9={X6&Hg zDvXIuc8yyzWzY9{u!f!IAQ`UnK5gy*$teV9j|*NNp19ok6NwP?5Xy!GF}zgySlz4L zHQ}=GY$&#WKdd2K!Ne|c=UCf-lgGTPTBYCICbDWWnfJ~J0g0Pz5|i&Okl#A*G2^UV z{BZYfgxSITGxp^E;K6AYUg&@B)OXhhb(&l=WVYm*J$NnX{lo*Z*Mtt+9r))wesyhv z((8Ie0)TCaDOR*+9~b~-?<(i9uH<aOyl{>f|p~)8tDX z!DI!Mrel8~abDc_^ULT;{XLIezK{50aMk7p07a>Pd*yk91SThqWsQWwM1sBsl(3XT z!ea_Io4G)q=+IL+Th_nJukUt!Ng0gfeZ89B_J{biNH5S;wOlH`1zn=n4fDV@G7}}U z^Ofi3&r4EBNEtornUR-${PuH;_*O`vMt}u;j>3BXZz>X0KNGT*)sXz4OnT0!Q6BA23n{-*4D<2d}#)s4NaIywO zCGK}Sr`hOSTdP`ez$$?VOj)Ai$JD=yrg`Qs_ z=8*@2lJ034I0{Z=k^?YnbBn%;{{lBsZw0uTwf$rtk_XFbIAdUbL)+~s0DiEv{Yh~wLTF3a=(^6Z6H#Ae*0so!HIB6% zq36D~T`0AfI28}qV%R5#n_HkaTnPL+*{)OXSy>$hG#*eZh>1PLyJRiMmW+=8kI_4Q zxZXxJAlfAA_om#EWi(Bh7n+Fp%l{g?0W`q^yfcrxO>C*AyrE5q-4cA<4SUv9fZcAXk(U;3kyeE|J zr)z2ZVQ6S{m6bHgY<6L-N&Z`_ERHuN`{SlR6n>El-l*2Lf<)8d(sMB28zrC5Wau_J zzDBXf8&RNROn8=Gt7eyvyL-(Heg>ZdDfeOWa1;FTa>h4mBX4q0<^H=|G?~L2DU+Yu z^+%#Y4dcr^2{XP9!v;Jdk1eR$tFPaEn9c123VQHSf|-s%V8<^Lj^YMS+O8jL_`U!B z9c{grL29LQ#O>}ubjoRU=Hu`+@I#sn4PlYwN$z|^eJ1v?2Xt(ViTtMGvW1IwVSyJ! zcfT!i54N{KW!SJ?-u$-(PzoydC$YW2iAcGl62{M}wNyc+@02wW#%bqC-T~_$^QjLF z^p&Kd0T24;UVHPrxycu>@oV;K84($C<$wq3_})pTJDEg0yfbDprjZrp45 zfvkj(ox{LEJ)M@vmPXrJ=+Dk3Z>7iI2-7bKw_aBx7y(!Us0Q#-_7vgOM(7iioG6Ev zpDh%@^&C$3OUN#nPtzZnT>w{mB63tOU79O)41Fhd=P_}fULImbz5>K z&AnqYGACe6d?A87xCQ1N*n1%_Vhg7*LUt&qtc|i=^cy~-7%7hA(uSW0WOY|og zu*PYANTGOGSQ0NeFJH!GCKYDT(c?jW$(N0*JRuc8q@=92VdIXlZs~{;=C4ctuqz=! zaJ4hEO~-hqv3T3}qKse9a}$A^ksCom6q?`%au@1Nt40#{x=ThfAE?-^#Ni0M$-szx zqPOfZrE9J>WUMe0Ab}+}hRV(jSj&0-nmmla@k0_Y^N?yUz z>PEy?I90rx^`?v^JlRinI%U0df zDYo+xWI$kGXkPYYQ{9Clj=~!$lT$vHTs@4ds00;kI$Yahq|6Ki!rHXmBkGVpKCoVC zE{@e0^1itD*n+M&B85F5T4G)b1xhbA^QgL0ZnhikW&s5;lp^C}hrvc0Z}eRkwx=x&Vb zyM$S`3wa%H=>lc5-J!vE(8eEi(z8WlGg>8zBU#vK?ojq# zQC@nB&f3}bSX>flr)HCb`Nx(j^aIA9>qje$^Zn^NBe|rQ5&8$N4jSTpa~2yo|%4A>TB%>1LN&CbCK=Gi4&z8~G9j*#r}l?5CVK^8SE(+UfJJ zTr_X_^f)6(eA2{x+}fT0s6%#o^Nn&B5tB$}wPX}selzTP<$8flA_AIUVmNVhaKePW?{KYc}YmSHU>PE!@U*Wwp2WDO&AG|?itB@J{*gpDE zYjygT6DQ+|O#c9Qm8qDyY(aw|Tozd}XXJ|?4}uAkA?xi5n>e)&MVchFl*v+w>svcU zeDro12S&%y>1v1>?wB1P?R33}gyy|*)Z$gO`SgM1CIgaX!(Iq(k2BvEx>!V?A#zTo zmTv~D5G#-&k(K@|Yf$^*z|}7_330k`ycp^#ctGB`@*$ls2g=#vI>SVgZz9Ls&QXdE zBl`EP!sLhDDFE)B0L-FjuTLH~u9fqCmX=H43Qs)wX7+)bPv+df0i89Uh1vfL*n0!+ zr(w*3+5m)LuF`qY{^y@o05bA7)sQd~@{hqYWdvrJLT^>09zq|jO$t)auJ42rbWPZX z1q(r#+}2A)kDKf;pX{T)s!k8YO3qd6bKmCI;Kkw78v|2|I@D2H3%v-_7l%e>H7PZ9 z^~2fid(w6OLcKI2AP)E2eULR3{#E#joI=Qb(%1i^759Y%}d9)bOFx6 zpH!Q21d4Bpv%(+e|9=%;^?N$DK45dV9)Xw4kqhuCcSf2lV1e7mQHFTp5-+Ii##H1l-aaD7MbVCpm@&4Kf1IW^3 z`HHCul+RcQ?r6NX%Pd9C(+z+b`}}zL0T#BW^S9^*H+Oz}!q;8KI@=8f zYl2NIl+Ilo>!3cP(~bwGaw?FBFkx#n!FgU|p@?Pc=v`z}z;3Y$A^^EI*cH~8yxgy^ zlOG%$th=;CGXreYvf@M#w=KbJk%*U>0mP%2_+BU+#;1OCS(>gCr<{@If;>|22AYFB zB39Q9KcD>-f9f@T^aYvxhIg4d4Jcpww#h+Q1?|jUhvImNcC+jUKGVQK!>t|k{}RAt zFYQMmnI(Jle7K~6N`h8u8#@QWkU~Li1cF>!y;t}c67~;6Hoh{l# zE-PZCP9Q~u=F$mqwW2l}`r6|1kz`n-jdK_CTtq4X{H@!T+%kS59m!Z^H z_@M#dFnOp7rGPQQSO(yOFOsHqyy;0%ATH+3eZ-Q8G(pFYhtNbq3X&_2R$A~(O-(iG ztq8QVwG~xW<8hEs7QMFJAHmw}ND=ZO*SPSHFqadHxcoZ=$)MtQF z(1oeN^pp&C8_&^g)9Q?*(JmND8Fo)f8Rx5#(+gYMICSph7=+-!r$!Geqxp*$b?iAx zvU|kk2ZR2D3qqWvhIQt0B0Eh}|Cfwm`*ZZ=L#A;<-s$?1GB_{|ps``yk^SbC$?FJ5 zLPGLmt@~Mr1G=rUQUOpFEWp-OtwJaC&1xF22k_W!4fIOFL3E zd|;E8i=v`a-T#J(FxpO=l89MIWD0xfg9=vh+^q698)NcVc*C6a*LB1`w*p^>JqUd$@S6U;5Sd`h>FVRyCVbAXMG&`Aq=o zm5rerPx?_aTAoWC*ow2}2X8wi;vP{0g3hA^Th5-dsmLikz}8j5gp31ER|8=p|88XH z9G|nlJ5i3q;7|2f|Jy^4E&2SK=Us|d-Ycn|8n;KUq-My(9v~iQl@(J8_!o+{IpvR^Wx#dMVI~j_<8$DC}$AQqSUbM88 zvZ#iW(qJeq+4j(i=sh>7!aOu`xxa65wAMk!+Ir^qqTy#0tNLF}%#u7V(SI%vR~7;n z|4|hIrN+hfFhCOr0SOIHy1ZAa3kby2JorDo!?b=Zy51<`%<=wktcczyRrzLqvlj&p z|4Q>pt(B`v{r>%4iyyR`cQsyc;Q$*x66ndrx#A6ydP+ml*fYym3@poJwXRmo8v-5# z6eLyfnb`^XD5bAXP38KZ1KjsPR%<~4oUaIk{?jI2)$6(_exOZnjw090hn&1xmqHUMFD)}$5-ifBSOtjFSc4$eWtfedJLqPn)PA_UAuFc>*Axeb&%Ba}-gnK? zeq@LV=yvvweWq=Dfmg?kemv@#K}A^LgI}~8ezu9+822_+z}|Jm*_>glu&{JAx}x`; z(VYB~y|Kj#mixZ`|U;JraoTo`LsDmU8B|VIe*0ae?F>VP_sS<^3iuua+b)H z!R}EVxkQ(8U}lW93KUC`A$4i2vW-eo`hn26&BG+koqTp>E- zoI|Cpea)(m*36b?FRJRD+UsI~VJvG6re$ONYfX-{K_Isb$+g+38iGPMo~5C5L^`UZ zQ)j0@p~ygxlnnwW2i~3E@TT}#_1y^@-+J6P#e-HUC8+Pv#coicpI}&Uidv8m(bW^r zuN=oV?$!0AB*jUF5q0vcg$lvDCCg zkqJiv?a+!$8g9_?tkO0sq`QX)|9f#!WchFk)eYe#-=1y0B`@-52_@@-WF?AbK4V`j zJHGX=8}3TFQal+d9dh(?k#OfgsLAGf93Zm-SqTqYw|)1Jfa3OQ4NTL(uRdII_!|NC zz+JVm)!p(uA?vSp5GRAcob{?WG!H-@o0(uvVB_hbThoa$Eeix+%WrItN2b>)AMELht;Zf!_10Pz%(m02Kaz`xFN4$wC` zWh;+*MPAWIG@$w~-0`>j7?54}o=3IY7*tm=tA{NtFwo!i5UZc=4?wXMQihP7^oA;X z-Q9Z8vfG{Uct0PXb~~%<1j;!OpaR1A^b(5cmuAf8@#&C^cowo-N%5pl66nfNwA^lm zo>w#R4F|Ab_t3OHQ8jf}Cw@z1^j<__L0qf8!*szN9h^?8gf<`qrYkD;h|mW9;FH?4 z6TVf03AnijF`5%DT5E@mBwsQM#o8d4Os``B*8>&oSAoy{Y6g{Lb85rHHaUfy}a=$>hlWKgZ-$ z(y+d>H*QEv3lWe=q&c?32glk>9AGCFD)?7U^ z724p1GvKAgdy|)CFt)%&9}xc}K+b*bWPdU-CzjS<4g9#`!e6iVBOdj*w4~&!>`IOv z5M}Z^+FBPZTkz`Ec_~!;h4Gyu(x86Th`8L z_fG$GDa>CvZSZqk#EH@d?`{St;-f(D&wL`{dH^r1me#@VB^A-hu8&>Z2~AX~aWzj> z!NMip>B2v0{%I_{lXi^A2p*NWTsD&_TA8TC9GCnhYVu+mNVe0NggQ;{DuIc>g&m*M zR=YU)0)5Wr@&%=E4`&0n{Eh_~ zkDW+^PGZv{DC8_wy!ffGbDkc!jRY_s2?64?F=gU?#fA7{X3GnND+b+H8hHZ zNAJdz*ZT?eVJlcHL_7=uIw5Q(K}9&WHdA3QGZMI06jy)wSahJE0>+VI0-=J$S#GxR zT{%|NmM}c30o~|?kiX>*hO-Upxf}80?dd>vPw)g8zIdxvTk9}XozZD2^GZ1zAb|-uz)4Y~=GMu>@8cs; zejxqHpCDl=j#aVjz`J^}({k4Tv0YI;k2P-JgjjbXDIeY$VTn3)l5@8Z!g8JdJ$9Uc7g1cp` zrG0o6?ZZkq?vrvG_2R+$*-3%>Uii zec|oK1aH^pL$P=`xdZ+T|J=8y*l?txF|I1?ri&slXDAl=VoJlZ)QAy*f&yGdGc|vs zNCiv8wcvJd0DL1KkLPH<2}{@CD`e0-4(A%&zdCuN$P~^w(!slt&cg>U6A#q*yjW{@;%pzq?wmS8$Fy3H4LPlO+2(I$$B(u1JMiZ%UTms%_bjk!CPaftBuX4R^!o+3yvv*sHlGI~d@#8xjK;cB8{Y z-Ui8h_ndX6-gfj8uzN$7y_A0iJKljojpLGg&>pW514iG=gixz(-#6LEHJ!x(*6U-@ zWu26PoZ$_LFmS`FH0%NE8goSjNaAqcVL+vsPx$%xZ!p&FybtgE@9R^6&BBRQkyv#- z@;66+cZ#nHucMa@?-G7pVd5L8$v_7mF44QFPTN*v z>pP2*plt5zD5_<_@SYxM9*}aYw^)Z&x~R6EkZLoT3-SH?_wo7pqDr3vT{WGE*>22m zFzyo+^c@%&Gw48^QYeX{ja8H@oVM->BsDRP$S`_xUP8H>NqKYs!o?o6-Da$lzqEVM zf?*64#&*U|4u_z8ct}X~lJ2OMCyhN~Eted=XN?s=9`CmzA`fJi#C{>yqTV@!VG}@T z83#HZOu=Xf)5=D~Q`AT_jn$6TWAV@_|B2qsy2Q94*C<3WPzX0x81hyqb=_P4wfy?; zBCFt!hL5naJRn$a4MZ7x8bRqlCRQ5mc0GS;B(`fcq{1Y{ac@qm2`*-^;~GPzSP=BP zfI?$gt0gE^E1$#}ul<9Fi0CwabnSk9%#5>K?MBgt(6Cg_IFS$K^+t->2eTOUeA%LQ zvoX@6)doDQZ^Y)dFbuoGsNjC;)+lD#>ZH$;zutE_xp=xKWSIU)SZb)2Ku#Q*>%sJM zmCB>)@eI=QG+4Ov;e)09E{W@Uxvf{}%MXrXk(Kw`uzGFfX9Qz#R4bbET}n0aPjDS|~Ci01nH`_GPQ#*Egwpp%%`4C|*M>u~tiGla2%3Ly$~ z`kk(hTx@=_AQJQGNJqQtluntL6B=Vt$jKvb{8Mgo`}W9rcc9+!>#(8)_2a4=RYm*5 zcP8z6(Aw|~ zWpT!MCTZA_6Jr8%zF1HCHTw}q;NwCre>?bbEm=*kc`+0#%+)C#LaCvzXnu3~$EcYfYbl1Fi)_Pfapc$~O@xwL^!yudQL}tD}9d?Qa z#IQ5L1S9Sb3kI5Bs^^r~BD!){c0CNaTa{T&udzye)7D%A)zBRXJf?j^Ca>g*vk@`) z&U~@sRvX=SK*nL>i@eFo@*$=HbG7q_!z$o=(mj_;rt(d$)lPIdQu?e1R5P~5#R7@s zUKM#>9(35{aOk=?St_F*=N`GAAu;Q&>_f;Mu$R#qIgWh4~5A z@l}wPQcDhipS_QtSg*bBw+)0t=zOe{d}`F(o7_@BbEI=jHvrZi<2hmYk=V9wsUSYS zQgL0*^K*I`c$lk=ukYnhakykuO-nr9#ihr5+PKTR9!S=PO-%K~aT79cyKcb=dZ^TZ z%^rSMo!6KNIXqaj`{9hm@+fJSLCrj>)_w>XVPyLz?hEJ(;bBkh{~GZ@XQ+deImPHN(i2swv=MKlYDsU8Td?ww% zx6(+#?)l+RAy+F*e^H=Irbz#2f01_gg_x6>4g#yW^?ltRYVkfs1!?0tq}rW8Opg$W zY&MwHTYRef`#TyPv74%&IUHlrOV-d-p%bz*IMfy&2b?jXL`4d+H%7C7HiaY6*|-;Vva^b06^$y@ITJs+>Q*$IW3-E zh}*=eEk1kguTfkf;9~WnIMyVYgy|vqYt0OlQ%sU5x1%`tTu%Ra&^o2erk0S)J@F;2 zc(hNzC107fWB)<@YAn!6h&DPpI`A@XU9PoIFJNi^Bo{EHYyu-4A^s<@6{TV~kes2) z^yP1>=xC9ARrn{$6g;El`#dY%-U%CQjtJP-dy4{qTn4Eh(pNy!^D_Rh?0?b#2lOoI22zxNv9 zI#hN(bE~Uyes`B>mx;@=h+AS&%T9C< z2X&_c>B;on730s`F)*t{`NL7j^3-1Eh8M$FdWi$ybrsSp>n2WbhW>c%W)Su-#MZ-E z39v7(0(C)milR=d10TdhCxVNAJy3{|OmzL6J+NB^mK2}Gm``Ga2HLL`r0e>hWz>8R zl++3vF3(qzOL>tz6p1|JPdV6IYN7^OAp`%$X~)oKPLik3jfvNVR1TieqF09bOnT#AlcDzZ^@O}z36+4-nWckpmV zrs~sHkLC^*rd&oTKJf(A90Z&WvYyufRoyW6TfSYxC5zTxkqBXv`6i``)4rEKG6lT{j?>q^A}QX+@ql;LZZxZS4H9(B0_C4_{K0 zu>ie!t*0TNH`ksGl3{&X0#+FA9GNo~t=#L&4wU=P*XTfPikuwZe`JY);o)D^ayvs- zqQ{VW8U#4ef z0(TT-TyO?oaU{*|zc_yV0*5Y@rmw5Uo}PwgDlV=tGk`Gr1x0kZ?bsNVhtXoYWy!Hf z!%;lP@Ij{W;Tfn7%9 zKD?T`I%I#zT-uG~vpIm}>^taA+nAcCpq)HezWPXz%_0^n#T*-+A{ALZc^1i0=j{l1 zY$^+eCw4ePrkskv|J8kF&DozIA=s!ctlMN|VU6boWHwFl1)nex0K)cuC+iNMRVJ%9 zhmgkgUqOdAggxEA>cc~mzlq;~g+fh{>)s_tiA3h$pQaBOAAX9Y=k5P9k@)2GpFZy4 zv1U`;WG++cyfeWMt{~_I}0dUW~+2U~neH@$um(fZ<=| z$l5qDv%G_`Godxq1nE;NUhhyV&QJKgi~D!+njr=R6s($w3#D6TIquc80-5D~hXXx| zjJ7W+uRO&n(`+HX2XS{@Vt^pAU3^Q1FVANahOeF($I*`@=jNa3w7E-__us@lJr0S$zZ;m>gG9>;jvK%bC`;QserC2 zikxa@jq?O?lq=ubcMR@2@8c36k>`NQ0TK6+DOKq!$UFAO?4u(GI26XVSY;%RnYXdD zz4jiDd5HeS4@uF9m?wI%Ai7TfbbQOf_ngcIUq{QNSeb|PU*zyffmQmR#~<)WL0t&L z2%&SqRtYKHKL@^ym4aB9&c?en)GyFEV0G&Zvvh(=iGd>g5` z^wJJ`S`1M8qFp$Bl%laO3bTRPAKYI5#EkZLTRCAY(eZ3^-c$&MlYdZ8gV?_7^b(N* z;}=+e^7W;aY`kLi^g5zP9}?phsIz!2#z3pPexs4+9Xh?8*j@b2W3bDG2~Ya-^Pt7g z*2-P*Pz|I!8>Ku4t;~@!)a(wR5EvfU8UFP-EO~Cmc!Ru^m6a7JxZZD{49P4EIa_Xj zr3J0uoc(N_7yU09t&{1zR~Ea+9J`uL+?Gt(*h=@RvgUXdH2FPk*iilGE#}^E#GAB% zJ1~{-sglz(qBuw&R8bA-ejq33qH`V9yDkUZ+tlm5RO8Ga@-$z=U|#?QlvkP1?Q!#T z=2Sasj{Fy;Z7)-5{f{AuW97E-zF!#LG~?>Aq6N%T+{^DMOVdoJb%7PI#lTtO zCskCpHd8~kYD^*lo86TjkYqy1hBzbd0RD+nro<#9ByTu5XUw=;e~V$h1R^?(99R~% z%x@MyK_eLG>1R0~Jm?qWiiemf_kMRy7{w_6;Dv_5n`8GJFclgZVlVrus|OF{9#oTc z^22pd{v_Xu)8U!g@P{8?bJP3Z^X$Xg8Eiq7ePp^u*_Qd02DP{VdrEbLUR6<~Vh(zD|wU8-`Fdy1_I2vp!m0dzm%F6jj=^aGKQottVB z5#s%EjCxy|P-QkT$EKR&C6xlx%NQoNDzq<8Zvf&ctCn0rpU3W_2RZFK<^eHOWlHH)HoPG)Wi;gQyqD?GWpfeti zCCkL++D29`r3)9xd0NuJexz#&S4em0zU{O&;04ZG+j-=K%FXlMhL|(Z&T&=Zr@4G< zMS+?dQQ+U;@1Tcu#{bWY$ztRjl^6taogYX?3`ddWCj#UWnFlBA+i<>4^HfTL`$2>p zDv*f=emn&@KcMrz2p4blJNc%3ehkrHUHLNYmFIw#es1{m)ae zv*E`k`yvett9P9N<`%H8OJ9dMJ)u4;!GNg#vu2<-$8>|Qx3X>l*jO_TtuhYD>oDAw-vA2R0cxz@ET=v!M9_tK=;vrB~@G+xB`=x*ZP@L%I6OnPquNxf7eWee|FJZ9y%g)MD}! z2Fe}1aR&ld0Gk_~5C{ZnbMR=S8=*}-OK$_`ziI~N@Q*SsK&U;~&<2nU0jcDk>LKuE zZBKg8dZT&ph!|I*4nr~*Uje=4?*Pal%{8=&t%1D41t3SIuKv_}L za<(*?JN_p?Yjom?4g?~ziwEbu@&QVl$klM_iaYMhb=ciJO-wlsJu#SbR1)UGCp;-1 zkZ!UjiicDZu}P?{$Qgj_1WZ?x*J38)%sk^n|0=Q1;=6koO2CsM#GEL6{ar3GXXDkX znshc|{1SIXREp9lOD051y}j2)qjXkpN_%*pxJrGvUWqXP1K3P=v&T^%(NPG(pBXy{Dn$NPOWx6t@&Gu@-Zx~57SR0?BYr-?)ta3`5IP=`g6oTj-jQttjzu5q& z4!{(=N_Z~%#PJ7P@c4Kmwu4vvN#qA#C6%q(=3Pd)g<7akRw!H5Hr9E=!MFS}6PeQj6m?r)1pI&j~IFb`REYH zgU;MIi4xiKG{-h7pmuWFP)A3nulZfsC9vA$HsCK$F#7ZQ;y3HW`>R8hG8MR=D=vM0 zSo>!T*dPJ&UfLFG-|$cGRpXA(Va`Rr(6~%h>N{BtRy@9FYXxfS6em@9_*t2mnRB93 zV-=*LWa+XI=oT2jv-mInpt3Y~%MmD(8ceu>!#Bjv_opQ3dLE3!PJaJ(tGt6S*)QO= z9+OgZmUg-5nlV+lQgm#$oF{$6$+GCX`3c;iiu8Wy|F*{ob>brSDHas`%qzQ@ecF`u zWL(L9Gr3J}18s!<&+JGuGHRN{SMQ6tSA|xhRkw(dAP6N|QV6^^D6rRdQ=Ll0bHS zp+Dg}e2lhqKJjkmAV@>E!vAO}A-soMggY|G5W9050xM95woyT8Dd zp#r&h#r?EC{`|t5I~$(rqEGgGq0^OlOyE#XPS{3dXsjgaB}TW{|GM1IBD_4ZpYutU zOKeEt8Sw6CAGT2{Ut7mwx?->{fB`9Fu(6^E!8ie#gow7&p=+at9wd6d~qiB3_)n0oCU^UuhyK>8cdH}&x6`wkFl<30;C95Tq>#WF~% z?EC=NQismvQizNsIlv_NW2ZZ{-e*t+X_(;m4=IpAfO@hYMmD1X5>33VZKy>Y()^?f zb9%(fikJT&_3N>K00x@5if-7-r9jI%fuoeNG9GXF;`S7b4J-`!EIU40n9oz~_nVOw z9pU2VNK(s4Y zmgTK{e@uGgY6U4?GulZXPPf33057!CkQ7t?@aL+^CLqKa!+U36P)C&w3-j3eMTy z+}r-16(8iA#Of@xBqfI;6H?Q=rsc2;Q8$Giw#+@xBl`b*1$++e_B3_H9}QXs0l2GQ zt)M~s8n*U!7RI^FnXDpr0;Ht_fiG*@gZ}4zGL#>`^JJ(Xs*+7AMPKv`b$taWf#!Oh ziXL#fKOp{eXJ+YV(?ADEEpZ@RNcoP+K_N0phAuGm{VQ(tAza@Yctj2YEvYzn?FdEh zx2&?ANI$%IKZOq)pJaXYU!QmAj&Q z1GP2nUGS%0Rh4t;GBuIpig$-YM2cbcjw>ztVF>rKwcp>2f>eR15g6c<%OseO26M1g ziCW)r#pQ<4`%{LA#Imet#suy05<}D!-R6R}Ciu9a`2Pr)Mz2?ETw*kAaNB0RxV?RB z^o1PfJOwmtg4j({Fl*BxUB@O=ETr^ZyG+RLT&~{?8wa?J_8*K9u%ATk=>(+CJG`;& zl5$n5_YCmPp}7l^6{1S%00P^s*nAXOIlhmD*GgVTgzr1(mY7HkVA9minnA_9e1$r&O@y?j?gcSe0w<}uFZt{sj!axKknz*NL83}9mCU#*S zb4Il@2AV7L5ZS53&h6xD0F(I4zAooaJH^C2q$vJ{vgS=}mnT;#(_eJ7b1@*I^BP1_ zT>s}f{dZ~#D@xP&**I3eX)L&g-zO#pk-o}e8R~{-~=;EJFouuUl=So-yJlZjfuMXT&cui91G))?{H4R zY`~3>l*f51Rui1dTv6)J;T7p-<8pFOl0Hu;taDx~VUO)s>ARw^yH6B)HS&&S%&##r~^N5N%%uMR8)_c5oLe>aGDP~Cy~1S>5GTuj*4jel@~phS5Yw$ICZ-M6#WOu zLys58)0ceq(u|2oc#0FtZR-nu*%zm8hfZ{-hB3u8*M-R8MfPU%vOcOXj}Grg()i&( zov^IAusgw{9!dYzLZ^lRb`hFXb$n9SwrfE20vMYgx6U%V=l41G>o5m64H*q+F1(a# zzelijV(1X`zlbL}i*L1OqxIM>9h*CKu_6?$J-qaqAsa$IfL`k)L#hu~HjIt?78lWq zQ7?*5yzbW)EP43&g0UEU)v*?~WfFI$Wgu*$ZyWN)TgVryS3jsOKPJ61?(zU?k0>nr z683Ea=LNARS^{yMaiP{bH~U{D#M%Y0r0b_HO(@wY-J?BRGuyo+<8LrtvBUM15h_W! z{Sk{+oso6~U9qp9gYnIUYQmMFzC^2D$Pac;j!$F^zWadY3EJw3$PTNL7bhZZZoT^g zCh?!Gr@%tXU^5a9b9`siV<&>rYyvw3_md%_d02&MIS!fC`Y2&V%4YNnLzL#K{AMl} zUMYGnK2iR^^Bt_YN}4%Y3G1X}7kz%*P7stCeF~=jOoz{U-Z=B z9epfVm~KB4Z4YK=(RKP_hdzi&)C2LS`<%qmmT%)?o^1)N%(?MVe1MT~HHu(Vg5Xmn$sPe4qq&v9a}^%2TiIagUbT`dW7I zb;sRG+;X$KnUz%$qI!}DUGyZNDHb8zn3*OKvc9EhteXS==sZW!m+Z3dV5OM5D?Pp8 z^$YmKtCAyG5{8-AN=6C6(L8+)M|s+r`MDUOh#0Y3N3U3{0)ZN-Fsk3g~U$4@d-dh z*9WVQ_V;4}*F-}8>l_Leno!|>axw*hq{`D$E-ho%F0w$iyU-8GfCy`ErdlB%?K zFxM(QL`K;-#y@GehQGt%v^zYX7_6u8hIBGkbXKYr{}WHdQ=smAiyb=3);wxOiek_I>02LtQGaR0 zuPYzM)F5KyN&eOFH^n%PZ?|pIPju&{qaF{dEyUAbGcL{Qiv!3x&&OB6H&=Uib&rj0 z^#bB69q`dO*{spDC-Le0^*)8pmWt2q^!ueS^(PJkA-?g5U;Q0lUq&l;Cmr|w*%b0Y z@_&KvtexBttKV7kH1qK{w=}dDD`t3We`|{l$2BMuA($X>Cwzi-pmEFFOA>MSQ92jH z{|+O)9<+Vwqj^A_-l#E@*ry2AK1!D#h2_E=MzkoOO2ZI+6@mAN_jU9?osaq-G4w{) za9y#g|L!;z{aiMU*7@}(fcPsDesR!8kbfv{L#T+)NqwwX5LW&EcbL)B;-B7Ld1I>k zr8DTJE%=UYC--^J!@GG|@p#ftcagAyMpIJv(&MrD6}(*+S(A*wi$x4ZTHU+VSpwa6 zS{4=R>kWjvTKbICfF^j`UPc7hyBy1%$^8o{%bbGSD{r>M$(edU9-03`<+W|wX}UwH zF4&K3_381-nX?9a?-)cWDqwHdo>O-a!M~+R`qhs7G*tv!E%3BJH5@YQt9+_7PDdms z!BMyzF5}w-(VqFZnwv;nvpNybF1|KLwzbBm+!7mOrt(PIMN{V|nd*Wg=b@%+puW>tsbwxRQz*Ef9*dz>N+lLXJfO6@6a?uZmioCm)(coGl-Y4U)4J- zM*^i!zhsybiguAMw|8-+4^iZnDssO~m#gV0_d(fzeI6>e*p6q`yw zqY1v$m{H?z0l_+JB64|*(*z&dl|wRrluTAH7{cYS{NfFp;&HCIB?Y2#`@wTM8$MTu zGu1;;bnaRgjpbd#2KWFYYSIAvma zX6=|DElmGs7TxzJl`}~a+#q>$3=zJIyBrl4A1``uuSOM;r!7CpSjD=d5NK$Th`Az9N-X2#M(}_0`IhNZ(K|LpBJCu&oCLTB)0<-j&rzci ze0%pV^v7u)Vb=H6d$)Ghf~^@@SB)|iwBEtx*XMq5ISaa;qN~Zco==CX56#IY$=w5!V$2}w!EZDm7rwQt8PGM{ zr8KL;FeKGs;KWOhwX!4pwb}kcIv9QJT^H;bF78{#e9j^nn7UC&y=hXct1HNaq|1k#S0NHxl`27wB+hTaa#CTWCQjW1S;a4;Hr1_z7c5km8Eseq9BW5s}TMbC>Knq;-!cqGWo&X(BfR2N)&DWkr& zqr>D47R_5lyBi|iS0;&P>)^YzN4XU_vqwaQluYajXk{~Yoz(B-YK5;Xpc-P7McBGD z@Vk?(t78*MTr-A-hOW6g`fjt;I2}Wc5@zXl#a#>C_Mnu~1V@^NjwkbZzW_xAqY%PAwpxE-I{Hn10 zm|a9TEbW5eL*7Uv&gPPy51=ioq0zS@q$KOd?cYa@Oq=%(?GQ)G9rq3XY^r^#Z@b{U z--kYO`0rTyV~V=(zd=B=i60&d=zYa%>2e3TD+;>YrUo+5T>Fxze6uX7=_j7o^8P#d z6XgCIEMV*hbr;><>-gOC1vE=sjMG8cb0cWLQGH!ezvf4y6F#ryl9>J%g60~g(Xadz zAtEyb{ZwY7AqvK&K&Ds`Zm~~5V}lY^f8vf2IWB_6z23TJq@+x&YrA-`{uV*2!t{QY zNpUQ+mjA=xed#lp`Z}#n&Ts^Vo_2))ODvZv_~luVpj7>ygYEbC#Z}zD)z{4=xTm9@ zFTLitIC3G0x}H$x+ATRTZg$^oM9@fInE}&_WMy_X$_z(N*TlcU1=@ZrR^aksz&P!h z!Fbhk*E>z2=BzFNB5hR1RjDz}LRttUI3Lje54KT@RY&^0?_Wvoc=Gf76BjMos z@)P*JSx+cmU;gL1HZwpE6G*M50-!GW7|S9r>WyX}7?x zIb*Z|uO&T@^dpU9TQS-;pbpgyL(rr|K~!E}sGQi4})CtL$DnX}bmWWaV^F38H&d+OkIk!{z7*KU35e>3ecyyu^1poH7_E$+AI z5?77TbzT2$U8m!<&LEj{e=so-|HX!#?{iCz3lAmuTsG8##58*5T`S{Q9dUiNH+t`# zpY8Ftk@#OL)5AW@Ai~^>_V%Gz-o2l;3pDxYzDUpMIvZW~W^a&7KlH%WefzASoz=5dT(ovhP?)1V#VE3?fbD5Q9|-+A1Kv>Q z+D0r)&hRYZV}1sSDy6mRMTwHfWrPjNV$l1omy=q`*Oze+jE`DRtWO9Ab3kp2e2YAU zYG{Y}JDL;uQ$jlOw|IX{-sH;e@Bxn_C~5;l9>-kN(1#a#qwCL>nJRQMSAp3u-RfATDf$)? z+!nNx&9H#7w_R=8o6js&a~o_$fQ~2fl$gNoxgIkYzc`JXyMVVhMC#;5-WTfzIs%E> z9JH3}hUHz((Exm-`7?I|Fne4zVQ|E=E3m1gQe_F$p7R%ltV=D4%04Fj9Za-oVVwi|R;ns>9K zExa;=J26!N+p45au1p#ZDQoiiigqy2`{H_xPtE);mhL(qdQVqVTUT)_Buu>D)7lA$ zCQ{`l-Tl8w^aIahDb8jwwm&JPX9B^mDhEIQVn?ojGX}-db{jib7Zj>gH0Y@x3(0$Hrqz~*5 zQgP<1b_o4CE)TOvs_8z>mKQ>P$HQlQbut!G@H=SiX{T6ANL)D7HA+yj+EW-;xbfKn zsXB_jnPl#cI%0MTH@B3k!HM9j1C_f7fOP4ZD|<-1=M+f2S)!gkIN6rL=1s()ejt`j zi`PUs9@zR$m|E$ulTN(Ee9Xd$@O_-*4cPMXkgcV0bo?ECr- zXL#Ly3102Y-B+SKTgRRD(Uh027cbk{*Zz=sjFlX(Y5xR)PdUC$xVBP2PaC81Z?zo! z#1_liH?~&kD2dufX+9HUP%qgz11DpIC-sJT7T6PPJX=NyqPi-a9`xzZi^;mF`hj*% zb1rti@QUn>LPumU8}!Q6k?oCCq5JAjEv;}b?FQIP0jfM1M}dpC2HGftfY0<5s>J(Y z(2>XV@9I-JQU~4hw~qH}_F0S{F?RdvKE|8GIVf|g#UN6)`z|HOT$A2V@`pf(I5mX_ zwy{5}FDJ86)@TDtxyJ97fHn~8ilHN0HjEg%?7%UL?Yyy3*iX29HBKqF4uE_v>;G-I zThlI?ex6vL)rR}JU1}wVwuScDf7+yJ?#h8e;LF4hblw52;%?1TM;BghZcWDxXXO_Q z2gPtqX{Z@xwCaa0TicxQ~7%P)I)VP?O{yZV>@DsS&`qM*9KuG)jWYWac zAMfE2mQMPmx%rqmB%@~v)Cohxd}hPf7bSq-?Zs+3gF?>`BkRF`?aCtb+i1S*5({nr zh7~=X+s-X+cYcp`r~cRfu5A-wDz7S%aRoW_rJ4R05tlkEa+6M!vbCxarhU_9gq1Re znqs$5(ys?W*oD$=2b&4<9j%6>CZ9=@H4BFAkXC5Kt1{IP&Ttx+m$Vl?eyo2}|G6#bg06()b0K$P ziM39aQuRdfB>A70_+-<&@P5tFpu*Ks8H>-;_7psf^$qb_fsb8V%qF7#`0GxT4A&i4 zYwnfhbd00YnVFeDQBFrHtb>B2_GiHTN&m?tdnF^yIpf)YL{_bH_rb)g1MslT6K&e; z4E(%>3A5>Rz46I?y%Bdz94TRUT&k5s&(O)as4E@k*c21SYL?mlo582bvU;&@i~X`S z(N@FSwYKXJa80?uPkP($MILu6AA?cfz4{|K9`Vit=o7N!GBq4{?e-r2q9u+Gr0Pr- z-%gy`Q9W$#DI2nqTU=EkvRYJh+X&?Xci$NZ=B~7s4rb?uP@2ac_pZ-z6W?Y%tQ_C*hOgQt_4!T=qthuK8^ik(v_JXnPM^T1{2Rl9)=^_c< zP05Zcd7p_}M=INy@hj|HM4x7T#&}@GEs>+r!z2Csi`MxqRMm8eata}_djO9(I^w$S z*VIL^!N?r^ymkTl(u5NN2MnFG{^yg*@uSjtE8^%ig4>30M|2%X*v75I56cQaWBY1G z$t8{3{haCk)JyE7hvd55e?4jKYPr>-eUAH2_iB)8+!>l{iDSm_%W*hC5D$pD&Kt@L ziCz2j3CFQ-=UCJ8g{0=Xmw~~XkoM%}Z&xMHT35iGn0}-GCi=t?ixZzh$fno7sXhc! zO@3_bux!FpR|P>Y{Xm;FC-n9>;G1P8#rKj?doqE6DHi13lHn>qlSaR8z-=9Ah`AHp z^+Ggbnu2MuV96gj59X(rLS>j&|Dm|Kx-%}_9VXbgjg`<*0jtZKHt8*scI@mRHJ2yT zwyz%L4up#l2NPzESsm$J>U#xs$4!wBHKW(C3nbEHh z{>k^*TtCWCFWS2*shS%B*mJhsETY)~e1*c-@m}j*OCCplfaArda|S`0Q#iHz7YhM! zC|xe89*?KPTr5-6Qab~nEZYlbRE&G40Z+E4CizOs3DRi$R&IBPrnYd|Eehr$SU9;@ zxB^+akJ7YwNAVjmhRiTGf7-7S=o6(7MM8(LY}HcC(Q=||Q$Z|lVUf`G>r26tl+4HT zWiDp?n&yZ3UB})k+?KyJPW_z)ygY>CRO~s?_Dt4r?s9s=0Aa!C$`YwNCARNhy@Yv- zS6=Ho(Of*F&hFVu#TBCPr3e9o5cP!E`Kss*?Z4rZaDvLbOi5)Z&tZ9ttuN9?(x1BQ zwevTVZq#B{$pX~|S8uYW#q`DJYFsNX(pSGi)^U=r2m=%295yRZMd+yi(nK3S#7CJy z!rG)sA%Zuk7_~kyOkZszJC_za9eKuW_p^`#B&|v9#N|sZ+F2` zAHX-h8c0xG83xz2JdNti`9K+Llc{|bK{zsFk6Fpuj@@;nzxpZ8yU@Yh$4<4?vH>+^ z2njcDh1Qd=JA*&x6iagfRyuIc{gSVA(n--;gv@r0mFt?2`iI_+0 z;NU`^TpZBLa(Y$Es#)vb^5 zxgZVOeO_=L#PCVqB(o4QxEo7-13JD@H%1EYxoaqvPPz4u`FFqr{oJT8C-9zy)Lk8Z z$F5kR4p=SM(GE)c9s8hMlD(TJCGhn^-* z1JY&;1e?) zKzr!$;Z8g))2wz|5=n%*gV89>qZ5fn#o%=ZIx-u;Islw^`HR`glHlzOQx6N;rIMhN z=Zvl1G~f>|g~C!?Tx6)4&t4|oJR64r&%hm~+kY?FX+vaC;wS=9;U-#lmu2c<7Qum{ zkgzU8Ty__(^zVLllpEbwvdLsdt#Z;$@3aNa4w@X<`X-;V>Evo9L&gxj(&raLMi&>D zUlbfg#~0D>?*E&gQbt;qWR}Vpo7+0J*?YdNHniER2(n5Yj{S|vVVvGE7zIxl`s@T< z?*_W6S`Lg{n<{{X87fVLj{4?R$)g{#xX{UZ3wMJ~TCe#we$S>abYkSpmHg``l*@da z+U~w-duMU6)oGK~nTh`t6iBLKP0+iXTZMYWYCd{=D0Xjh3H2su<>d*@AOB0Q?^Fmd z>Wj+~WI!Gl)-yCJArG=!QYhA!eY&E^yFC|*p?4$ zg`0~Kwr{`j4_zQRS2@>$u2(vW26fY=0ryQ=&5y}0j{|+WtZF2qsTd)sT1cu=`eSjS z$KvEIj7hP8$sKeO7EmHrFytI}+sx@Gp|0DxsFdF{ zqSy4**zuB@FntP|moh%L!^8?VrEAZV_4?B&3dS&2;*V=^zutH8D&hBzryshI1m+Sl+18kmF zbO1r!`h=d7D=eAhlSP6}TKE3@GF@$VJy^8TVU;_Y&X3zFU76rJoE`C}UQ9T!+H!2e zm`E(VprBdp(ksv68SD$!nzkK16;{1-8uVp{o_Mvk(BsG{rGHjk^$CX2#}J-`$XQp| zQBh*&_7-m_syt^g+r}0@9qPiAIKX62r^QO~Xq-8KP&+F^K>8 zMlcj-kfpvfobDnt;2Eg80mYNxZ_E61poXB^KLWuUuvJrSMtC=a*d9XS2B?d8MR1{=js!Y_CgoD>rCXuR|-O z)LhXM76rf2>R?dh%A$;lXC{aFd7}f%cuq4x0j`JJjAR&GKHG3JlV+3)vUl*!%gJX{ z=YqzMZlGXN$PcM4or}BF^{t;^t;o{ZDRERo9- z7gG!pzXRgd5S>J2H{uM!yz0{z4;gHT&B9#R*Je|RD#K-oCGI?86E{&e!MdE*Z0DIX zO}Epot{}oByp-*a_C38)tXtQ#cGNfOrPJmg)n~*pkggfiXSoRLDLnmCofXLaqkD6t zz47v4D8eR?XPRjJszb9Lqqe<36=#yoIlSV@U#tFCVSk9TDGm8PKE(eL%ky%<(vc=- ziq>^eF2S&Ea^%d>|17P+_l8%~Z}dIGJbrV2wiVQ(CR3jj!WBB>H&p_Q+#5P$%N#c5 zvy#Se`_;f(?xBegq0S%LE> z9dpgy>ARWrNuSQyo|ekvg&m_16BN>c3#Yw%(dVNm%XKN`719{8exvqQZ?i&tUcQ(h z>uEx5{q|L5f>vAlOVr~cM1R(tfi|#0bd-w7lZhIQEgo;D+pWal%FD66;akb9^}EJJ zuD7mvA&RqjtW1@o*>>8Vjw|gn8;{2Cv08<^4)NQrT93@Uy>EY4Yi!?)j6_$+aFO5A z1w6t7V$J*Gy8oLGzHWsj?AZnJN1fFTL5xG)zRJbn6C1OMtqNl27V1dqiillt5Y?(7 zVAsEzVo0c#i~HsmwbJYEEMB_^@t*Z zkMWS-ed;R`Dr6iT*$^$jnNj6jc?seG00e;l9P(~*a`FW^IR(>f z)hd-w{WJTR`0h=q$v{W-kJ*kysW@DVG-pg+YR!a$7`2UaoH>>C-n!tu-^VuX{L?p| z-&DxD?#;_DQ#W%g6;#j)NIIcglbL2VdZIv|j>8(9*1I@4Va2?DUS3|^_Dj2P_1+tb z_G|fQ6B^kg@w=+yaaFxuyJE;tmxQ-;Df87aEq1A`u#+~Winm9!3CxXv4p+mSgOXDtqU>T zsFV0t^$dZT&lavTn$4B8T6b?-XRlkiF`JnTY_E7Yj9%|!GHg_T06IM@ydM>&g2e;f zYm!YK-2Yy(GOyADBpAw(<8pM?uEL0P9G3T(xA!ia#FWpoi$nD)yl9>sQqsJs*+bh7 zbr*`28S(IJa(@V*G2wmt^V-`1_vd@(w9CaGQQqBZK{Nb> z92y_xcy+S<#l5xKi0tbrHo4VQaI|)4<1&BTa)To_D3_rc&g!(kh0{agyuVyc0YnW5 zep$IXFMF`~)#X_t7O<0I>2$KF(@=lGRMXB-S1EMWTx0)@?RvEVUAP+GT#I!i?DDIp z3FW=@_~jVTPd{I=_qOJ&x}W|5 zo)(|u8&zv%;~6`hdcveW#@0Y&z=+j4o8zoH4Bar+DOp7BHkkBPJobqpbI0~$>Pu#Z zz-CNs(dHl{DFq&k5jo*iZ>sXv*!_TNwFGGJTsr=#@Rx1vOwzs3(|-olMwiPBCxZ0_ zb-|a2r_m_;kxo=Hs=>0|MtZMGg6Mj9+8>6#Q=I}s*b79Ff{X!00Zou$n)jF{qK;;* z#Vyb0N~SDDlgGLGd&Hb~L#go;!mjA$u@fUd&#${$!E^O4YeN8{GQYIu@qm@4b6`iu z8W5bhSo-|tyyHF~cK993;%dm;fvJ8MiwR>HGJ*A^os~yT*5lRhM(+|a$MvID9Dt05 zyo-^wB+Qt;LwQc0?Y|3ZS?K=E>Tvo|f_{Rfd;kt93lw^y)KG#M zJ6(&Pc|}K=XQuoz4Ab;f;%<{({5X{;BA^~9qM;yN&g5ckQiIg2Mp1K8ogeK5p*jv7gX|$cUvW4xt1XHCKH`rYGN+)_h0_mX9FqB5Z zS@A^Aym*@S&av%svHw4dx+)O)>1tkJD%W!gZnceMVbi{i2^JCt5rRoUPZjT6cnRWn zWu#$8E?|FClQ_nCL4o+XI36>6=A{2V0{;k>@5DBfKJM?cGK+`qC#i!sAUN*~GzNS? zXGcHU4}0nvPA^DN5lnTeWdV}eBgh_xaa)gRjBEy4;z%Rl=AGZNipKWPJwI^vFh&0y z*QTOg4DEy7A`auOJkA*&u3yj39Mjpcm5f|;`uVU!T0>W8p_<;Nd){bTtwzt~le3o% zpQ>Cn-{b@xU;u9yfoC*Y3-yPno)37mp8OY!gInxPpllGxAVzLCx9RS}(Wbt3L}KDu zb)R`>%uD_u5cz$L7pH+sLaqx-XEU-uuF!s=|L@w=pTT$DZ zd-*4a%e-GN6c#+c)S<&8^HbGyoVoe2%Ffw&L2c6sxZ`O7sn?EKk~;HU={ixgV!_22 zzBClu14KnsDi^dcS5`p~e$D z*%G=Yld6J`*#3R(6pj#{blgjT1_1))2IKn|W*Cz1E1JmzUTJA*rJ1N>`lH3f->G}L zC=vbH=I+-gKf4Gek)MA*b*u1DtK-uF`oNRalJ=h8<6 zIFc09J?_PBVZzJSW7h{;wNI=TkG^0Kj^$b=bJD_x$LCc(G3f&~kcEz}Soy5O!Ujh; z-#Vd(^`pdtuW9|(1%pHemdW163`JQ}R@DF<5=0)1TBQ~b3vxXXzu4$0jIZjqDQ6gK zMXwH;ZX7MKv&v~4v$qS-2VfSZ)8cYE6Ai&o-60orMZk*atQO%a5VgXNU)X|~dWon}0FaQC_YB@;<; zem8ozV#|?j+6NrcS9Hh-pkdKml8Hn^{Q>tPdo|SGGf3Wq1x8ouQngI0Ah$_g_OST; zYdYLKJ`BVk8X9V-m>IF+f6(CP`2Tx59F_WYX)<9dogy zx;8=Vw0N1+J*`R0nxN7%f#|;zP;boN5%R>a4jXIWbvLdWHl3Ee60U45<(vjODVw*} z4?|8`;#=M*UKg6-$0F%vxD$78s|yTOz|C~oA(A@!I+UFJ8By&KocxE1dX~~_iP(it zb2_fibP(QubustvWF?>EEHrrIX0CHHJuL+NRp2f>t0C6TgDhPRuAeC~Wds|?%k?%| z_UuO=2&J9xh0l$X{?V$=5yu38SdJ4;$6%!xI)x+(FJ=0PBj+Cb7ib#il+%1ljz2*slc#5Ikne%d~jnL0&BYF>R@F=4H$g&r9~1 zF5rGVyM6M3l7=`RkprvBp7?DHMW{A_G*VJibhCq0p@w{w4ET0Y4L5Dy)<7P;{nv(g z=3)-E*jyMH%6;mG;SlG4wUXBWx)FP8?nZTg5O?VMO3dmG*dri%;J?2%d$t}b6l+N;~so;e$({|Q*`PVSI&+K51nz82)n zyE50{;NAH2k@)PIzr?qUt(n&FxqF~y$g*o_{wX!_k-)RrqOxwmyh>{rZG)xQLDH~; zB9iI?BQ0<O( z{?m!mQ82$wrxpgvbC*MpKCk35?l607*lx2^w^7=$>pRXq z35P<-n)X;ZGS5u<-o|gy>-N~`=Ow055Yy}L2Oy|Yb}hrXv1pC z-w%ouCAGkSX|zeR2cf9_;r{A{)N`S_T^HT5;CO4pMGL3RK)uK>`w&A5sh?(g;fwhq zd%`@w+a++RRr0MQIC%FpnIV=k4W1>Jr#maUSxpdJv)hH)qY>u-B4Es#@WVH!U(sDM z7f+h3Vg{7oVt-y8%wvxC;kOLQjz%!sq?{Dsj+MJWY_?o?I_KFHROM*oco+)!a9{%q zDeYuJmLbzAF0fhE88X!GBZh=9Iv}ssZmuQu@_g*|PQO zZ>jH|9hPBaizaqz@b&>8$(L8wx&Cjm_wYH>U+LwWZS}~5Ei=TJW|G_Y&Y(0`A_9C+{aim+o zb3e3>_Mn(4ecV5klz6N?U|mR*T52?~J5I*&N7&0rPB8-MK97^~nq+%@zq<5atNss= zkR_qMjnZi`W8wM!1QEWzFCbj?WQ3}x_i_pPF#l&;Tz{jQA(}e?h1*~O%LH(B@|5;p ze&bN^h7fsw8GE8j5zDtm2{{+9L?}!Ru30%SJo0I@djb@IPtK&cy%q6fj)!L5)u)fW z*M6STebYQ%;nQWuqirg>JUu?UO>9*7wQlN!A#BV{6lN3m6Qp5LQsYe&h4hw{{q>f) z8Rlhe@+C*;>0<6kPK#wU11KUfqN4j(+=z5XI@R6`5v2$Cs!5}tPEVoY3&*%oD6cnh z=UlA;e&);$tXgO6Wvb;>{mYR<9gRXgdSzRuV5Dw72kyoBUU0xYnFAO)*+ChjT}A0; z{}%37a{F7N6@VB_B+#wztT>XKSDk2hp#kEOJQKH{MD}F9Ex^OS14vCGhGePS)+1ab ziFBS~u3?f;hYif89%B60>a(ee{X2jy%nYEVP1cGtB~c5-OsIcalJo@l92Odc$Kp*w zgupqpwz!Pq0Gs&SV*Oien5ZWI_AL@spDoURhQHGV`{<4OBrQjg{bET!#dR< z&Gn`{We@ziRW6}%x?sR>QrC_AZv@i>UA^kay?93@!ofzJ;`zbjjKdrX@hD?jpS_v%IP!B9d~e+dWh=TTkE3O z%;{-oI^fy~Z!Nv0!%Ms<8m7eTSDkoXPg_bJ%@B|^sCWyye$T@FTKkv)inzn#*meAi z&+U?8=wT^p>?^(JAkKx9e|lg+M)9T0D4O-#hK; z1YGSJF-(+bD!=6Bk}V^f%o7@YZFS<^Ed@J!J2bJ$5L?k^9+8hr(EPK#$#+x_kJ{2MGHUEQ-fgJ=heg2Ky+*Ls{fNL7~7 zPhaCABk?Z$+jW2lc|_5m!|6L*wjsC-0_0Irfcm#(44O=*G9^;mtfJsvj~Kg%!)4)Y z7xH@?)Nsfv%&T+Yyw`J{qPWiL{KtA)l6&`q?3g_O+R~S7E=}a{^`hq6;v_FVk7f(s zWddRvD$W+?Fi}vdi!S?OO0ISkCQavHw@72fk^NMx81klD;10z>)}yE)2Ui|m%-FvHCpdNQ3m5f7GTM&7opLzl7ntos)_ zHsWr_nLh+Ig0?u!{qZB-rvNLiceJZ0g9Q&p*rfCkrf%y=Lm%j@KPPU5qR*V)IiQjh3GLY%dhH)NN?8014+J* z%%3p=1z=Ot8S>%LH)vY%Zd^laf}17l2y_kT3aa7_n_@^1Bdx};Ajp<))|ZiI>F+is zwuWR>Fbev0Mg;X1kxn`#9C?Lj%X%eUGpfJvEZY4TW3Rno)sxuQC*ZlNtT5y9JM6#w zNf80QnsC=BmCjZhw&9S?I%5=juz|4{sz1(*J{bEi>DwUQe~ zMr`+|s@Y}gOhBWL^OsED;B&iI^vYQ%+?f%a4a5-Ze{Evr6F)j@xbLX1Qq3>2TNZ|nk~%r?EmA~?|pX? z^F)TY4#G{_prUtT$df(oKE4aCyS$knuo)K$1lu~PlU{7Vhn{NhFVKW%8AWE9&Xjy` z7oHPSUz?vHn6-=o0MG)t6KefmOtO-_0Mnr$?@7;ET;?9b0S9mP$KMRQS4vb7G|^38 z$Cyr$H`+89G!c7I*%W`hj&)1(>ChhJS2Ar0qQ_+JpNC-dU^(Na7#0Y*qo9M*UlY z#wFx=!`e!U6gUmxblE^Xb$HC!TehtFEIO=!FzHR`vc1O4-qpf zXr=kjV*E)ar+-(Xe2*u+<|-Tc;WFUs0xRtjvqhVNJ9aGg;?!46#=W5@)vCskkJWY% z_VfiYgZKsY3%TXOJTOXJl?a5m#et`3gc;Y@d=V>FlE)_yke7v!o2ICJF% zkV{y9t1(2w4rHqrRiKs>N&KH#Up`uNeFv@L84xK;ZA(1zzj@Eww+?jnIFo9n;VKM& zx37{dm&CtU8LLo|@mv3_UZIM-9FwZ%$O|P8lRpukV7|g`F>mvy(0QhD2Oi&!ItZCt zDiVM5G@A?UxHR2v%cc`8%!nuCS$5RWZiICHZfh6Z6r>JMZ{Ri^TJe z#WcA;f+KO28yW;Xf&*YA_@hc`OGhht`X4|D8~@tIl|@MJ8wa}OGc4e)X^bkc+>sNt zOcKQTF50uMyGmEIE?2VMg*qr`^6^v)gEKZnf;eqd8{PF9IC>Fd$y>x+B2zVlhp3g} z`%K_vdM%SS#~ymM8>WbkJ2d-g=`(Grn-6)U0xh<@?Acm%MY!?ZHwpoP_b!M-QVSX~Rfp2t(atxB zv5Cbm+uPf~Bd#vH;l>^3v8v-RJ))tTq@#rRFIs zcoA8;m*hm4ego}!=2gR%Fva))Su9=MFCJ^!OKro3eqpgp^O2Wwkp(C}Y9=a|mAx$x z+0z2&-DwN4{SG&4>Y0R5Ec}M9yu-|`QiWNf=hms_rAKu0FJtL5wSA=WS|q9mzX2h4 z3D5r#QTwknFnDwSPUl+MxqQ8!5F(S82RiwY{rbPuiHfCNS!0vc#^l$DM&hZDxYR|G z()t+R<%~Jb@0=CSZ%^y4`o9z_gjJfX&2E_ONd9fa$nM1){`drW_Wl0Ws?JwWi_!G+ zcU$epKx6!k+Wz2AU&CIXy2``cRN?ge-F@0#?~@satx%88^U&+xJV1f)kvcy#q$arq z=A*he;G2=V0a0K5ZjwVsRWX@#$}<6Noetq-cugDW#BNAd8h#5{$2`fNVB8Zd5|O*L z?bYQEa5Jf+6gJ!vbqcE1?C*o;rZD*=FsWeRfvJf3QY??tm>Gvut%AIuioT$M-VIWdht}fswuG8B1lvr@pLipAP2xv_ zmzATCp9LMjc)H)58nQ-qWLlOK++|it>OmibhkvesHl|M>JH|ElG-kcr6AiD3L)u2} zE?-+T-~oYR@9}q{i4ouDbvw^QIHY#J&5Oa+CEB7>$)HS`wzsVRi*rck< zETp^$jwq;5rD1b3Xtu7h1f*kxNTW`JmrVnIyh9Y9`g+&^heOKxpiUJvV13%}5DpC$ z&-mS#7qNqd_395#TvM@0}&2)@O%xL9GI3wu5Ydb^Z?R68^_`(9H%KJ2c&(WZ{ z$4hkqN~s+(3bKUF@-Ilgn9#Tx|4T^cdyAgCL*4&TQVqbqclaCSBPD1$8N+jDb@h~L zjRLR!(r8T(9#t7KZlo4+{>uRiI9I6Y)v+%@f$%6%380N8&ttk>^*LMHv=&xY16Fzg zde#4ZJTHU}aA6$U@0|`-0ZHT56ifM}9#9j43-i6@ZssdTs_TaDt^23;9Lvh4dYjIy z1Vu{{g>n(e>R-xo?aZ{U=>Zq3!E(8Wf*R-WIbC>75xyIQ@N9syy3d-~uD#79OLtm( z-XWVdEcS)PL9-M(ODtZbrmP8Xz?j6%%U~Hny=)*SeWt5^+BB5z>}waKFw>nfk~uzB z4FQ(dC-;VezLzHZwdt>61n5u#7Q*aLRqK+@{?z!U_gZ`6SE4tCoNx<~o$}ers^RP^ zSirq%IArEFb#S$Jo*Kk$B!yd3@e6cB$(}QEXaeGMi*Og8a=o3O0 zb%05OZl#hD0chLwQB89{uC4O?eiJF|mEz9zMR~Gey3bNj;n$Z^I^~oazp~Bo=Ew}n z)-~ImM+ny)QJ&O2Vf5n=DWOJcE+F^!BZlg(&v21_7Q{q+He0HhREf^cc+$n!q_Wkmxy&GPRMGG1? zI3@qpX`zx}$w@x@8n+)HLO6wo3?dMK*q`GqPV1%eiDx1;5x?IqE#Ko4M3_0(%)FLLFrp>2St||mgd^1x5HXQB&vi2r7T22 zeoh9Gsp&E*N5w%;FC#=r9G=Qt*Qp}cdMJH!v1*V zBX5>|w=g2=ScNi2VK(88Q_ANb?BZV0v{QJSXXd?6d#Do;o<}40+!NcVNr^fUEWABu zWU$H+f(~$~PzWC0=$*Gm{RZA^7>51M9b8f#N%=Y_>*W#0A2s(O@u*ET9UVD>OQwrE zs3CCgZijT2Q9tsOvg8U8hY@7NRKngg60f=`zS`-@&hDhSR7azDEM^Rewh zb`Po5)hf2OzK)2sf2KtZqzfN^4`i4GBRT&Q5vE(PHj6-5k9Nm9hS`2HYAZ-tXn=p* z@SNT75pB4`6|5W7gqxYAOGYK+Tt((s1l}L>tUKr4bKiY9z~c9u3Rv?Bq{_e;^3&7e z$=bGZAbDi#;d>l1!}Vb0_I;WYbhc(GvQ$F|05=@JNuP9Vub2*#EvKYhaPjtScGlDM zv$e{7iRvn&sdG~*ydSkuzu&(4E8lEr;_{ynEwt#mtAb2&Y0&-kYxxrns2rQ%lfaSL z+=cd`#o>iwWqo`43M`Kd(K3YV(8lmzYj2ATzzB%yG;%p2PK?Bb*Jd`bxvIs_oL}mFlL) z!PwX*oCCc2D{yags1xuN;62R&Gz|}D<>wc=0=3>-Mqj+)V;?*S*tVahWjg~VZiA@nH6hS)Fig2{IttV2ex-s>QH>sJOxfO?SL zhAdZ`iWppcw52$940W~NB=PhcjBshkYG%ifU6;`LBlhdRdiyjy1!vmfJko+L(%oSl zeQB`@*g&aV_Cnh#VI6jZX-A&XVN3{j8gPrCogL!EI3F+0xOYBQLH6a1j}xr>?p}s% z=MqhZV2XLZiDn(&AU0ne%6zFs{(<1fnRwx^#cn|a}ukzCo zbU8o-!zr*o_ujxKHB;5hLCK`m))K@9^7~ z>l|+VH@sVj>{nHbb$nEf+H{`HLU6@!w*~udtUO5Wk0u1De6~nkFXj-(0tkk_`!q<> zZ?tFLYy3x4pkMiYwUF~KhUh3Qw$7<8Tws%UB@5iw3OFj&Y7X;#0&qYf2&xE2dNixn zr1hOUccHL=4z@GppN318rhu+tlh$<8ZVAJ5zL-0K{f?@IfwW#lqH+XsnwKbhOyVRo zR10P3l3KZ^sOR2p=wU5t2CQ&Zah~@z1{lQ1%SuTyF4?&xboELj&h-MF)z#G(t)b8? zqAoGt*WQov(h(;TJPR~kW%~Su2SBHhqgypX+%PK5cH-l{W~plJ#>?{d*Q+{nbMw5s zAq6+PKjn`cre>)=8%nd-eGXb^&4EZwi=eX#lELUWhW51S%J=wkATV10%}fw zYl5M}m)isqAt6qHC?diuRL_< z9q-%Mp@@j^JRm} zVg1VuohBw3<1A-Bd)J@<5fH|Pa?$0?hU`uw-9E_T+n<(`GdI(vNAB6de0CiImJ=MJ zPOo|v?^=FPtiiO0R6v(Bv`m|C=_h`IG%d=;m$BaJ406~Oxnj=H>Y4wjN+5}T!Bl_J zEy7C>b|%S&SEb5~j?uGvBUua(8lI8)xa7h>G>Q}csI;CH3vud#v-rZo_*^3-H5>~1 zn);LdN5vIv@JHG8_v|n@G(JHxWx<>BC!u7*of5~~RZJ6+kARqwHxVtzj;0%RjWM2V zD*mZcOoM^6&^NCw@%yJSMGWsPOW!QxBmb6&d9O2?USa<96lw;5DvljM)xARogy86( zUZ>5+J<;_cMvwKRr=1rfI#GBf_23sv^Gegposyl@Fiq_^Af&7fu2`iY?tQ%>QWk=A z46E5E0q3L;r?*_o+N^uZ3c^UoN@JafM>*a_APd+rEDxE6f1_%+tOB<$FX+bTf_>p$ zr)5ijD$Zc^aq`h6OwW$}qoPxo*8Gg96p5JL+2A2rQbNVEdmg&Z@$nkeCHu>)5mvH> z-CEdSBWRMtk%hkJ&{+J7q|!arHGth7`fkFH)v8*9g+(NITCzY-I>dr7A0hwYtW|yM zdERq!Tcp98*X3cU7EJo;A)KZa=EupPNei5a1sV6TpcP=Tlmzmu@XR@E8p!lDwm@kp z1q3uc!LXc;wJH3xMKZqifz=Fbj0^a~8ah_A19D7?3X-rS0R2l~^M~ba=#L4jmxN7E zr_VnVTzI^yeXQ-A=_l?13oxYW`mF1jgmP6fp|=t#zLS@MULVp!v6erZ;7r}w6}!8X zE`Q(7y#`1M9`c_PX#>5U&%GpRxZHzPG53eo-`-@7ryg&XbbL0cy5;kN?F%cKbz6xQ z!vc04DFW&)yNAgaO22&Qbs6809%zk{oRX!|RDv*sm*HtwgY z2?{N}t_jMg+vyw^L&sFnnk_3UMm#QgxI}@Z4{3i61pQW1DPdo0*R{GnC6$Jv??w{2 zLyn55twv+JoKED0g#Q|~HQ-=?e`-yOO$Gip)!sS116C}JZYKRGLP8C|(W}`VK`W}# zXIb86=_v}Go3h^C!d`oG4?p^F(b@j7SWc;=E?@((9wKE+aS>_{rj*;fSef{*TlW-h zT4j?;K6E8z?^@B0(X;a)DGd_ZE$0p|Eak0yAwRe6EZgSS*aYNSN)kN`7_X-hOU=M{ zZa=x|MZ9MA@o=SRIOShQ$TU50?4itO%M6~j-aJ&6b(`DsNP!rRbZAF<=ttsl9Sr-9 zK+J7F()jydJUcl2_}jEle$huGEv9IPFCCAtGjXU%TgFwk=B%;9$LBB~Q!D{9X5% zaF~mmiTum6dftTZrCvS3p!I1fCh?4rGp4oe>*^0smEdOSns;dUBw04sr7D&)+ao32 zycX@?m9(#OKR?E-=su~fZAOV^!B#PD4V!&1#dNrAend1T_nfEp9U(Y;{1vl*F36c+ zZ1WS6j3s-&I?bQn>Tw%2h>?O4;ou`XpN-%)`~cp}dQ9U0VD6}cnfM%W7lNx6h8m>9 z;w~HSEjx&Gy#a)S>GvlGohFq8?93ScZ6D5uJCebz{chVZRX`zi9j9ENkoTOlVB*oK zu=*QL_8-sdb<|(Q!m$2D~KaDv<-3wwkfNiT7K4qAEa>72fKz1 zZPoLAVixhMEXr;KgX2TqBM%qDS5{0Lm$>y&HtIv468f;wj_4X%8k%a)UEPz@Q)Y@7 z=455&rzE9Sn_}v@9rfWCM|1@o9s?P8R({XM%7n@Soe1RwFPQ!5%2R3f0dj3rFLx4) z0vP$vThXz_^4KRHBzFoFx7Syvfo&PzlYh$zg@o(mW^489+izA)KCKUQ52RW(Z2W=k zo55~Y#dVy`#OX*EGiv_d7#`jp$*;}i{-;@gr~3s1E2DnL%9=fiys4m4<$mA+>|9Po zEn}zKTX2yRoro?JfSZ9?i$qwNF6k*O>y#r3KKKFKa%emfO`RQ=*%iFo4)aLHopA=A zvj7GpVA584!ZG6We==qa;SvOIVK8->nEq*__8R5vdTw6*#?w95$UD`i(WcH{xl1QR z==3msaY7BNCZ&6{Tq2TOdcIXZVF!Zwe>HgL1!!?Li?w*@G@0T41(XA}c6K2{b!QD? zCjz$5GKCx`z$Nnz6hL@oOiWA~5HnPgum7~BpL{}Jp?;hQ#+OJsO?!)hyCVqc>~XI9 z5?=-f6sKxX1bu3{^J-vb{gqsRnn?9>yLd{du%)G~?Hg}Z{Si&>h|ljH@nsq{QV!}S zT&fH_r~j{=CZ{*E#$Y)I$fOn@n%(B!m|R|Z6g!sXP=bqeRkaR!3h(N+y2YYbd#-SzLP}fHo42;FTz! z2v^CBzRdq#KFibXf7tZ}E?=_h3oy)l0))5zF?h15X7dFy#r@t8W2`B&--&XRC1p;1rT`s%J;P46mniEm;d)D8!qVi z@p2J17!yJwrPTvbQi@vf+FvN@U`*%(cDg?PtjH%I9;4108xez!w$|*YBDd?n_evrK z+%>kJLJ6+uMUz^}-Rdi)8sz!<^=TB{vBS?voNacBy(VZYcH=mQ-zZBmNC0cKIglS} zcSK%jY5dsl&Z)|Zw(c~Vh-6>6yeP!wpBnXJ&is>XMX+kx816hwx*LH%JRevs5eZF} z1p-go{K`LK1c`WJsk#Z(1gA9zh56ra+*TNg$UHz$0MPsQp|LZ|q~n0b5Y3Y`|4wSaA6+;cXH&e^1<4xH#Q#+8yFo96%=$6P`YT+`Ie zo!9LKvV$6$ywK5KT7y+l9Mm_jaI`*|h1J-Rv}PIM6}(N;j%8HoPU`PU?e9z;=u)Lfhs!_v73v_32PJa8ZU*QoExZ!fl!*KXB z3GsxfehD{OMyoJNP26@b8T#Q8dr#0*n^RpAodLTE=+|6m4$+CbgK|Jy9sbc$zGM4( zHN^qy^0(t#s(dP%FnS2)TVTnb>u?#DQj8Yr>Bvj?$_tqi>Vb#641g+NvD->aIc@3B zW0*(1A<$v(Xo#>;kS@m3)d~=+qnol}E@40mx~(Icn>bv?W*UXr?sya@V{>xmbrq!Y2om^FBmUh(6SFNbiQ}?_%8ID~^ zNlC3~d@AQ5_9Z+`egP4=yce}R37Q|zI@AW_UW$yY6Of>SnLC#jv?cw)FXu59uo9Q{ z*Q4idi8VMv`wKr9*Cy|4W^*uxJ~EGR0Y^bd+{al<4ROzGx4Q`TY71%7h`x_P3D`N}rPV^hgpy~Em2l+-pt0^rfx^S8U3<@kWp z9T*7C(TZ;8Co)k!CXQB1_Htr}*#nZKH%ysJ@^QIcRPH}$9hY_V_0?p>>V(*e9*v*B zkU+j_KKgEAe(L>MUEB%XwOk$O1DQq-mV>@ZqMi#EW0kA#jfR-TeVzKY@{<{gr5}-T z_CW#nFSDnlR`bWU^wG{I@J_IreDO~)0!AvNlA3n|C_~%=ioFon!59}8*Zl2ze}7hQ zJ8_oZ5QBGYHKB4;^@k?=^1p1uj>$)!W!edadE^lO z?l1V1JBG|MttNGUY;*R9w@m4gwuIY=+|~HWGB{xzxqkfb_!l0hzV2ugIKpy(w|7$G zM}@Ho{4i(aPt7?pH@K$3k4v+ARP@z$-$K0@w@gQx@ys(CtYX^JVaK#)@>M&>D`2FZ zXbcbsfPmA8Fj&ERqE>njKq=W4AefetI=fpu4AinW?#6 zq+(L?2Fr~dPqgLHvy5;h+`qC7__9@`#0(uyT~1z95Y@}h&S0mGoS`lA(zWc+El4Rr zAyGFp=QUB&el9+bM%%PURq<(BTS!UcbDSE}h=jN|q*!)v9d0*vDgDaaW zmH2J2r>@L!k>A>cJ`%LkqCj8l>a@o`EG zU;1VPRtz_mVhX*8QU1aB+uLEA% zk_|MJbr%0n_KYW|#V-|8jJpcUCO~<5VcI`yrJlKTtJMz4VJcbW4r;BK2q`X2?NH)$*l8acR!+YpK857A3zvo?0OV^&mxM!r7DS?fPFZ ziI@q%+8HQXhTla_XeOHJ{gs)eM-4ln@ZHyHYNQZ9KTnvay_zc5+_I-3v&o}h$Y3Ek zu4cPHv51|oZxo_-1Ox4`PX9^297^Kp=f!3Me z@)aq+8yl77>02OrYK6;_kI@9DRZO8F!8G_~cPM5m$SsV+{G>(ls-ppl9iql?LklJy zk8^B;)G8hDsw9`ei>73oEBAC|%?&hgow=o}vhFA(ld?GmDRtD_>B6+{w>4MMMp)Jy zL~Zj;Hg(QSYmTUx(|&X#Po8p0;i+g2Rb6@D7c$pxW*{qqaKutDof@2>&MjYfg0h*X86K>Ocx~(9@)D(~9EmZMkKMS>N0Z=a?T!a?2tj6ZBJ*DxdEuGH~mT^t2HEOw0HrKQ zNXxeA!0LUdV-WuwF1DnqSoIKwIiK8m%glF_!yL>e~c16^jJ5dRDESq@5_N)WE zrmGUIs)J1DkqEfH{#s_CBl6$Qcd^Gyc24e(-(HCq5JtcGc1OhWsY^c^3(Y*IqR}|L zI!@&L*8}NLc3OknB&u8;IB>@ zJbw7Y<;8QW0Pe;GLvtZ~?X(qw`MglivIQp~lJ7l%H+7gNI9dyVJD0*h%w#KpQD>!h z{oGbIs&OTM{fZ#uCX|#zw!^N5FakNnF54DDUJ*BieLva(CyTfL zifk$&VuI>z#AC!IB6fKiz`9}~2gjK=V&}Cce#bmYfjEn@*%Qd-tQek+_F{&q7Bw#i zgy=(X5i2(F1A(ypj$BcI3L6+kESk53>|(4(0f6*}X{d+gJyRZ*IDqZU4DEUq&Lg4@ z9XZ=zEDCxdCniQslUerQ8;xqN>5!`rt$1%#ZnHe_G3FecVhj{`9--SfT@$9wepkP(8euM zkR~|vDB0fS^ws{o+l#Lo69dSce?v|3S7E3w9^y&y2T=}->$UpIS<)Rd#8ViH+C z)m8o8pOrR0y|(BIZh`dp52C4ZDTrJFFFm4p))!NoA8mpctA&~YNC<~F`}6O&qkj;h zGI|V73*pwDj8RPjgqE?6`k6cgK<795BVLFe&k|4x^S|5-7OUI^VAkay3_ua}I&!F; z+I%CFGiN;vv?46%iQn6PvTfb((36i2oYg&edZn@J1?;!Q?#iM6(oMkTN%dAbV*7gw zr6unNc6$9QZL$?Qf&y3tE!POwl~etx zM#x1g7V7Kkk0qn>hiaprkIFYs^@XZ}X*`9xoa`_R!>ZY{01y8!_=+84{ueh;zg=!I z`me*X6MgAR)~yAWRZrOjVjm-sz|D^GIvo*ZFq4+Jci2LMACYb9fApHNrB0vnO)2+W zj8}7kkVlwlxb;%bXC8CWOgD9UcFQ8!gs(iqSzXRfemJM2&)z-q$ppNT9YDM#&l!VE z8=9&ZG=6>jcY*+rk~qF=;lTh37vJNknAVF4c~12qN(?$wifg%7??gD!hd6CDV|(1@ z2y90;Z}XsdQ%uyJwdj&BFc(|jnXLzTC|-%~LHFTwMcGFd;&r3vH!2^Q_*1Sd# z|1bT}GyDZHaq-jhAE4yPos&4;Z0k~Z8P_i|d50IF-}_{A9W`-xn&sLa`nsrv(v$X> z-TbB>TeMSD$N*GGJ3PDHmbFNP)(qkQ`}NAc&lpL~?BShIY4yTtt8l9{7)NuEh` z1}XfNp+{jo-l>SR&rx3cp{_u3{05Nn)i~k^q2^SWQwI8jUR`NMw#(8{=9}wJRb6kb z`CYSOM>bt7DN)P$Osl}7p!*0j6(1-LU5qppe;hDlB_h54V4C#m8vybMmTT#b%zMae zuzRQnfM){IKeMd=Y`Gzw`-*B>oQAi=u$%^VyY&*)=SQp8o*G?fyzzu!l;BQTeLrgM})e=b!x;R6<{Yd=8E z@kv1k^{e7#HvqyBeH-}Ubg8=d?;pRljZFVsdgInP z|F(3pmFS``4KRaCmGnFxiGz8&==&yo6#IKzO%|eJa^Bt!tna|B_2xs`H;jLh^t4b^_k@sWzj^A&+@ z>=!Kn0X;MHj5p{`9^6#nWVcmm6bUkU%Pq&(m@5`<%pZ{wiT6vmA>TWXsDi6Pcsu!j zQ~Y9FZjYmqLDLuuaYnYPQP8~+u{(5MJYwhR4>-@TWuWWhb=#*kTVxYcBPJ1uraaR6 z1sRjlp!@;& zUa|mpyPLaB%k+JHC|~bPC!jgAbQj+HR14v5CYkVCFn8DuLE0)-6mqx%8g6sOwmev^ zbICZOuQ0%g!UmBy7d^lYnAVb?9E zd@K7ia!%iDsd8=&&N>&Y?>saOn3)4W>vu|1e%ZP_i6c8?%RmCfVp-}WSH%*$fCn|w z*1oSYpBaE?chFDy;b}GYGDM=#;??P zANi^Ox{2~`3kn9BySS(~P^%Sbim8)E{cb)L*C~s#e5`SuhDC;%JOfs4AB@k$x;+#cH>`xfg>95cmM)V zwa|yJ!kt$X)Z2*(nV;9(TT^U}tvMy*FrTO$>p1J)hbO-I9f($zdUI$OF*l=E>pl@T zlHFW!J;JrTh#iM?bO`Gc)N8xh8iz&CQ4_3I>?R}Yzpmp9KU+l0nuwPFa=VA6PJP1g zbOYpsJeI0f&^0kK0%Y97a+F*{`;HRc01FFDcX#)+%v`1a$YmwIE57(H*GnJrdVhsB z;8K0r5v64{9a5^U9fgA2`R2QiWb@ujBu4-s%I-11n#<_jR=u#;a*?WucHzbaDj-P- zkVZJ~gr$~*qMf;nBa?36rAZfccYeEpf~ti(UDp4PrK^mKvg!H)(h|~*fJ%3FcStRr zBAvn#(%}ly%1w7SNSCyLbazR2D81A>Jiqs|ALN>yIdkUx%arbk16+>~S7iz8Z{6OK z)4urMw-zY5IoQ#)Eb2@5{|Mk?X>K1)m2X!d>V!)3xT7<{O}!{c@rp*x-w3jdu+Syz%PAQsGy^9JuS8|e`0b*;lETx5@|-{zAA~(Eu!=y| z#|`uHa9(Rnw#fY!fZgwjbboQwQ|(M|gl^qKcbxi=ceRp9wzX}Rj*3sd+MS!gt(!0Z zctL>{uy@9qW|%S)q@Oe$;+wqyBQ+`T_;g4w$}w;_Y4r*8#}^dCQFg2op-&ICKAADO zKFk^z)d`pnFHv_Aq=Jmrdjel{g>LT~mZAF)Z%HY^MlmS6>#9HLR|Uj^!O#CyA2Vm^ z-Mh9d+c-K-lV^Y02LJ*HibsPtmCL(7hlp#S)r zJvJW6Z)jYxn*+e`E6oN44y>a+sM`xTK>h*=$I5eyG34>GG>0%94jn`u;zc1p!l4_Z zla<27;7VA=w})-Q&5Tb-fDHG6Hp<{TWNki^pJn6S&Pav#%GZ9y&s>aXt4z~d1Ub~L zNttyf*!yB`kFD=$)0Jq5qCR?!%&FS=dwY5|RMh%|+_%&Icm8+*J12LP4*N%1wk;T= zs6K%WQ6Cz~*Gldi8fjBL3)6qx9iwqrZg!tOn=D(e(tbW}BsU7Tj4Y=Vjc%)PL5`fE zL4h)R(q~)u&eCd`NrW!Or2C8UfIg3$v(xb@&EuN`34JFCKo?7iZgg1@tI;C~ld(ss z1CTEVt<5Q^QQR66UJdnOn;RFk-X?44F)1{x{Cv@*E=*@ktAo-m2B*8*?>X6v-Bs(# z#{I6N26Kc_p63(&1wK`S)kRMtDzG?}r4FPPp7I;m33U5aD}O@97uyO3h0IZ!voqp+ z*@!PsR%4>0qamZ>nTmhjyP{_sIkLvSFI(_+g7fJ4t6jEr?2n4pZ5v`HcKpLeX%eIY zEyynyFOc8WOJRM^o#ROm8H99RT|J4F9{a~~rL0J&%Z*fZ|FJ#>%Pq_V^?&?2-uHA+ zpR2^tty1s^uq$A?AGI1?%PnlHP;)ko6652H{?G7PMptRU2Z3zE=E);=li|w9)h9(8-@<$z7U!=qMsnQ>dVAI`CxFhHpHzmjlX_s@_zQ#(fCh zf}|eI&%Fe&naPqzy5K8SR6ph0T)NNBGl8vd}kz`V|Y4q^b5XnW7>h>uaoEp!m6*OzisJwCFNe99(Vh5 zZV!6kyFFRM4Xv|B9BiGLM5V)a&R`780G`ytuk z7pw{hlQb{f^UF_#CUCtqd}n%QVq((kDkFMQ%k!+WPB%+gzXhRr$cl!DcE5xl>=2mV z&|A)muCu(JqeW8YFO|cl&j%NY89Hx=Fns8QFcYr&FDd`mMt0L=gaBMzt@CJcei8Id za{FGV&#w8bWz9qbT&GWck<;15>C&a4^mt-8k$#5iN)1Cw?E)rskiAv?pTS6d;xW{h zrKdw$X?05QW^wlym#~inhI90%>qI76J#<2{2iZEy2CtR4|USrr&+%CPg?`XY@I} zzccLZ_|#ljP*6}X8&}-d4FjQIBP@0bv$7OGlkLLPtnhhPbGG0+ndal)Hgv1L`09V# zz{N`qwkv}96mTUW^&m#lf*#tm_4<&Fqh6B0Y@(is=jm_V;i6u_ydLDB3@)i=SKBO| zFdb+RNSxnKts{S^3gHmD_YW(07B0yqf3Y5PsGAWINpQt^?wy%x*u;Iwr|l;u$P24w z1f$svy|OATB-l=lM~g7ph0#H@_1O5#mz9+j$S8FUS2Mo65zKGtXL{AT!kl1QrS1TmuL_O_;*p_@RtqIB`NnB<6f0XS z9#`C8X30U)eb=Or3cFS8Ncj?J%kcp5sQ1inI~phNn_ifp5w;KFftMC}iIk~I z&l49lAvqJ(!nwQEVo-r{)NwQHGa(XkNY_TNJA6NdEmVbQX0q?q33>1*XLC1*z9Yxg zdAENtva1K_R>5LgDHc;L(UB^MXP^;E(ZGrvK5yWWj8^H^Sv2TR!`gHzuWlGM4M{T! z1j@4^R8NasKwJ#i#b^k(j2s#odW9Du<#6!j)cc23b^XDQC~Xum^YwRjj+E%K2OwV4zfttEMH#_$}P-IiFbnYnV(&c|xF)goU-R1BTvmRZRT zZsW`f3lNYs>$j*TL@MA(#t{2l9!kFY0cQw@IUwQ#ZwZQE4jt`ESfN?z4u0+zc`1Uy zT-N(*=Q!sMMIu*4QN-Ng)9$Y}y|*@8k;?s+PgeoHGMxam>1OUr@R_v3`dOG%LqWuK zTp+!yisq3Fh{eF8TB#y8GQ3Kd`*=|ITP3rMxI3Y%>&xxXlfls>%Q{ucCEnE<9B@U` zY%pq5!1n04e@wkiLBd5cW>R-9wMnX2YzDb3Ev@>fd!875&Ogc^qtXD@gc38U6uvTc z*w?!{bhq1jP{(n)q(^e zQG}nxm1zriJ%U6snuObSoo+g}i(v(sFdlR$v>_YG+Ra9trMxABd z10vwZt;dx9O?ZahK7T*hr|`sAn4dpU+L>cMZ5mIp%gGkC&iL7MBY< zUe?{TpzKXl)6~F_HTi>~PiSvN_c<@h-qJv;Z_$8YT>ma>x?1;^D^0QrEd}<$Sao^1 zYCudp@iMW+#%%2GVx$L3Rz3a_gTkE-va}Gk4aKB2f(36wAY;-Vvjpbk%Wa~l$7Y>+ zRrgzWvH+JUZ?k&-f*-HGW+m6xcALfD=hjD~l!Y*bI!a~3{*3t4)eD0=xzS(sPks{k zp-moLPt6-Y2T^%dp<)T(WQ2k`&--4x>l%y01JxJW82MTKB)J4qVW(Ycl_&S1t^rM9qI#4Qd4VTS@qGirmIs|eyFhl=d6QuX z)VpG&r0;uOy+n90l(eZ}Cr``J6J#IXbqF`OvQuI7+vdJXV@kECA|WCoI=5wtmtsqt zoE|wlJA?n%gTt6e9X4XHT(S~VbW`8sco;`N6{8vtVh?uZuiyRWyhpz!{wI0U>51}y>i631B`EF zLaFLreINWEyH+dv#(tdf;(}s(`b->fa0YHbrsCcE(FgKJKeQ+kc??b$PlhMhzPJ4$ zau?WRdxDo^)9(PsS0Mw~h*r^WQGEWui{*hLkWd1)ZZBKj{!;nW=$uR*vJ5mS=JzLw z8b?l__dZx!2|>Uz<)#64CDZSd&wycDUVlej7+my%RZc(Pl`M^rBQs>LckPG+qtR(; zz|W7t*Vx2FjxQCqcqAu6)wjM*3$lr!?t;kMP^!V|^H00&d-fQ^#i|~We+UV6gK?vj zRh7s(&KYO=+T7C!4a>F*b+axHN^EO4#*GxQre@ZqkAzaY)?p(SAgnc;yxDLsedIek z3g4SL7=N@9Yb3u^wgwF9B2xZgm05FdAN&`Dc8Sa?g)r3jkFS2!n=H>PQ)PS}Dl1Rz zPG2&LXCw2)Est`+xbhOz6u(fp@ExCDiO$Z>ezM`nUfKG9HrND^(|pObH^T$RYtJa9A9QM)eWQIw z1j$W{N}h?9T{g6*QTJw!q&=w`@I2H;`JeQJ#=AAW)Q51+LyX$ijn#!ey^=iZBzC(= zFTll{h~NGS#UCrOJUHh0e-YQVn0xf1W@Wz^u00+v!2Uaa4w$Ml;Rel05y1CKDY4_oWnn}>z1h?1W z{Yl@p{Bd`_8viyTBDW-xt*+R=(guM2SPdQ=6=HRdnQC3PPPnEs{f6P{85m5ELzuK& z*eV*P^3Sw(Ri`&=$49gDw+WIO>g&5su2zef3Ihrs{e?^93Qs0SGt$##EiCBtZgy{d zKE6aR$#Xg_7Pdo*AbG7G2mAwBjEY%=;6fZ696$EwZHYJ`eVUDZB+OVPm-jXEZrAjw zC-MN9dFw5T6^%J&8l|#EFXWk{$yOYD7rOTGCob8nxfpBYdPf=vXIlMc#t%}1HliQf zh$TNv--gA7fu>y#LRx2j$IGGf3|xj0&dxeiRQyJu`UClgUSDXHj*v_MoMh8an&ZT{ z6(f3CNjH)Hd+Zt0p>ebW1B)p)+;|qpA^c&)Q`<}_<)GZKuv9`kc_PT47xln+B}=U6 zX8zKNGNFwmrDG!^@nWy|T0{x9&EB#SAR~(6u@!QDHmlB)P~N=|$U4^kUys>lc^-Q{ z;`uyEcIjBRnDnylh-jcv{HFGswh7?JG3cD3sP-tgb~Ai5vuD46W0MOk4JiuwX3u$H z+u9mxrj^=hm05wtWnAqE{;0;${`_@*t5p`LdN=A@;;cr$KdCGin@c-fB;nO1W<_`z z#5nEl?Ma%N0?*#j`TC5|)M|S(a{>$A!*|wdYt&dvx>s&vwrba+QOg2}dY|J&DuWMP zvp+toB{>H%GveCqS6}mA0tMhxuUHRaP_E$*)ShRMp;Xe?x<4+lzky~;DY)yb z&+Ax_nEr3t2)qeWh-Nu3pE6Z0>9T-JNe74!ogjD@VOsQPe4bGH zc4CW|7tSUq%%+Vf5y9ubQJjer(Tfq=piJe`h9zMpl56t`KQJCBTv+0n+87Dw2~@OT zoWmrAvHxwyO}5MDYP4!h^QUXtv`x4!Z~WbjLi|{QQi$PJf?HS_(wX(Z55EC~vUZI? zfBKxjv5*mI@qm1IYwTD$;poy5Mc2y<5^VT6?GLQ%yF0d9X^7@+>PuDHEbOkMl~(fQ zdr*&Q3d^9ofq`zH|&BT4IRK3dT)0#WzZ5a=TeRyFmPK{?3S%tB2npO@h zptoo#NWz4xfUPMNAF7M}%ykCpQN7tZh3+gO2da)g+0 zdN5QAwp*(KcnNz4ZSBRpb@^QughUm!gq5<+I7zmg9ZI-ucxjUXT3}Jb{kDAJS=E-y z*QCref_28G#Y}~PX;L3C`u0|x`4iJdv{;ziL&sKo2kd=)jYRX_;~Mfwd-0QHDJz&y z&7`sce{fQs#(NO>bIXcpfBZo#wL?beOI{D*Ze%=J-Td}5O0P0YZZZ}Absnc~$qXal zzI3=8^Zv)w*^OH^r?1~J?MmeEdpX!y1h&1;D4?z!R?V z3kA=&GFkx<>jTg%Ci8S->v7nBq@{OzHoo$FmY!Nl4SDSVW4t*3_Kcn;k(T3x6p@e2 zN#L{7v=}FjV9j&-X5+f%3Z-{>W|Qs(O~?5qn!5eE5$y=TTQxM(f1Byok zxvSkv^#EbmN=}37<9$xC7esbDW7s?QRk}gTU_Xr4IAgnPktqwSC#Rv7v=UicFheOH zdipLv75jF}KzfeS#&B}$ zVRnz0lYEpq(d%wEg2Xhb=bbnl)Xaa@s(_O!dV3Wh`Qna@#E>4`ZNK5y7pZ>V@sXf3$9^sdHLnL)Y!o zyUTX+_`wjFQK6x-!7BjOu*)O$hg*FGwBWQUNk{4v29WNW+UBcG{~`FIC7?!gGcoa9 zjD48d6n$dvB>S;oP!ml}QVx3vZS>HR4 znVt3V)aBVZtU|?kA~60Z%{-jTE*)a0PyBoSkHMcHr2`;46-9whTD<%Hzt2x^TB!}S zAX6K3g{OtqrgHmy7NSIS=DrWfCuUx*FEYJOUslKg8b7Tj!qUzguFU($xdG?(`7dBF z`PNFB&EC3WtE9|MeOJ|OygYGKDthF-=zclbz1*UQ4oAwB>huaf;)RP;gGgeHvZv$K zF`x0SgN>ZM*@a7T*9gq=;!K1bO{941_CJhP$lN>AgZ6Z(b5%+Xp;UxAQc4)48$X1t zus*&7di#seua%e3pp;o5e^*F^#VSy3`sd{2Bz$KAoOP`?H%gX*u+1s4`)$FE^VM1n z1vT@VfizdUPPMgLrIw5MXZMt|(+Hh67#L%F@G|Y?nh8T}7zej_WtW?Hc$y@M%Uy*GpeH7 z&1tcXhDX|XRAY7JR;T4^QM=H`*a^zVmpk78e~`&Ot?U0<2gzDhn`wRnos?`}&}qwM zo?z70O&wp&-r=nzDp@s(_qWgyI+AC~%RfzFYLED8)tM>s2@eohoA=X7x^6Q#I zk`zXD3Bjq5Hwos(uU4qOOy1bPG`7Zg#6+icmfI;DoLCQuw}a{k)y9krjl4=zj|mCl z>o}48J2iG&E<={_fyl7lp*xyVSVz1izQ>Cxd}|3V^qZr1W(PD-EGLC69>Q!A;za4= zKpn|hA1tP!?)g1)Z>6SS@V;d>^A}Dp`)7i5x0LuP6z&8i@1m@3wIP? zUJ}m{daj4M)c(6ukQo`&PFY{3p!2w;u5`$ol2@~=%k5Y2X2WLTRx)EXmS?t3@rEiV zHdp>y?#=8way9(VGVL!UqNH}!c`!(qI+V$fcamA&RCkWQWbMS91l{u(_kISse@zDM1a3J0;xF| zTzZ=;YK(Ocl%!@?#UX?o3al8K4nhcNI}*aF1l~NY3l~5 z^S~kwW#T|s;NA2tkKyvJqi&{Se-Py{guL`FAgALGtyLk5o z=&a)sC2ofOpvI4ZY^A{y7L#%EQywY}bNJ}RA*2v#_oa!-@JP|Auf=VO$_MBho8t24 z2ThZ79B&g(ci%m@gBcwXY0TFj18&`7NHm!p2lr0b)tadL%O9Hx2B3D!YN(fV-xQW> zX$mpUii;VSn;)EPBoWeM7UpqYy!g4X%4k%G0WC2r#7&Q%x&9@vp3Ea-E&kd1$XU@= zJkis8Y1?5+%G!MYJq?~M53*s^CpUA*n~goRUQP24jfm-0)jpg&L3!SX$ST$zZjAqB zX9PSZU=aBqEZBn%r#G!&l>FC;Q-FhN;tf2X|CHX^M2btdS?c#%zrDA`n8o$x)P!;0 zX?dv4Y3GG9bDV(@THC@HfrSvE(a zs@d44Ql;sxXyB?2f{-{q5OKMsh_76{-Mh&nA6Sp*M2Y01fKXhK9RRy{=m;~gX97_6FC7W*%$V!vt-u3 zU(nb2cj(mWW6{FNrQ{1J;sdX?kyh%C9)_i?Te7H=!r)B&$QO;J#P@cqEA#WkrRBw3 zEmZ1t0?5^{A5(rH5Zzt)s==+vNJzl6*e>C_MUZ9|j1t6q!u@l53^*blJ^rII&A)Ey zdAn}gIE9e4l0n78UrF`bSnYp~f_iM-*@PB^u?a+MrG2lEP<}>vKhbj2x&k{Y&nbW9 zvf4AF-OR+}3y299i0z*VpMO^_0RBd(ejMUnqs>H~+`R+)u0!PaU{;257p^WR6@GEi zWmVRtQ!?&ucnMMeQ-Sm{bc}Gf>}^=|+axeeyvX)h!I(7mbgrd^Br(-kI)$iEpZ{d@ zf{al|#aoFy8`gZQ4Ku-UGhp1UpB-zCA|lpnNd6SyrtAV2O57lt&zCTMRuLKbO1`DQ zd#NI{t@4S-GZc$834v;bF{!E=bHad@U;7;q@qri?9>ZVNup=35#t< z{yUjgQ&kP9@R3u4K4g*8KOm*!(^r`2^Y%&=Kg(P7KQ5t7ihGDhb_+X+w}&%nYt8!- zdJ7igSalsBEU|r1K5b!q=45Lt2`=smi9($%HTn1!`{K>Qvz21mTP0*6*v|`>MF`?@ zh1;1Mu0Oww8?L?Vn&5J%=MnC!NMG_RCTkY?HEtCOG2h0lXMUWizktp$ujlryMf|_= z8|B{%cMq{5V^dE?07K}(1>~knXH~qg9p{mljrMdeD$IToy|1jYG>J-Dff6!?bVH&e zB(nWUam72k)H0&fw-hAI_)iDbm65#klV_CQS8hk>%SsB7L?d7t16*iwmrQN9eYv0DXm6eJB4*`lVlNy003fEp|H%LK?o&%@1 z_d-d=1_tXG=Xm-CCcR4&IoPAJKTiL?Vt$dL!LH+j$xOoyq2jV;ZTv1M>T2E>P3ir) z!;V2BQ=tz^4qzS2&*eNLJ;WV>R}D@mU~6ta2c*H5hc9Z&_qa?2t}sSeRoxCQ@#4cUMQnM2zX$y*eFsdo&#$fy>bD1(%Ajx-=agG;Yk5%m zSAKCyZEI^2{t_>i`EC`YbesMjwXm0--ihO$46~f_U!5pA7U7?>+J(F86yoyjg)0^2mUT9_A(t^?DCMfm|EbZs5-fWX!J4<5c`8_z`v1r zi(S6wYDy{grP9jjFBuXf&VFK zu|>X!O0wWUl->Cim zyW}vFggVr+Bt{I2%8as)fkC)IXR&ZkW!m>HWV@zs@)TWHa!oLM{|o@`=&6eh+&Z?E zvH}wHQ&^kQj!$%m$mtTD2I9=c`zqc5=ry~uSVaB@iZbZtCawB@#d{~>A$c4SW4`s; zB-~s4@}yHQxqOBBJ7E+7+L;00woETu2L3O*L?Fv@{v{NERlfi%@eOO5ZSGS(Th70+ z#aBac;UJl!@#Z0$CLfmdlLvjBvG2P*J*A2E#*b; zQjNYdZ~z6d&of_{CGwsROaHu4=ZjbC4Y&W)V8047Q}mm78+8&NK}j=F(wS@dZ$9J zSD52rRTR=b_QWWtsJfy)atleCYd5ZJFd+Ynge=8tqX82VkQ-k_oZt;|D)5NED|>zX zgt%%-)pPTUR{n`wW;e5XARH-gIHQC64|EJ&0fK)W9=NfX+q6+!+0snWjP11j9<*YL zkkIo5-*%HQV;I8h+Nm%6eMurAPji$Hm@1fJT^)Ky95<@aRy%5kXH{Ce6qt(&s5$z^YrNe*&V7y3 zmnru;4v{&?2G5l-yjnf;HtN<>x|R>@6SgJuSXw4cL?Axx{=X@lNXYw&Y(wROhjFG2Z3>@$&G{(aqCB_Gr~V$N^0W zB(qSH5s+|ytbdiEB}q*SJaX%Hu+!7EcUMLhs_w9Ro;+M^s*xTcC-WK`Oyd_D9Hl)y zuS#}e^0ksoOe9$JgQH0ztK2`m|MalO%>MB)-E{#S@W*_~mJr?~^ePWT^Ki&5Ngm`f#Lk zu4>1ZkiL($a{!G_;Ob?P*3AZCrxm*S{9A@Uog10bvFm-76l8N1N^sM+;}g4pz#99r zElRV?7{Y^Q&gD^UQBcXHGG}I+$+!p{lbHRLeX)8=)2C&({-e5~&YF^1KjE(he{^(V z3k_O@pQl+0@sd(fr5!#}ofgmLZ%aT%JgmzyB1>Ha=~%C2j{Nhmw=@MvQ@anok&4dV z%LCJ4y$g`d1s)52|H$69HSTQ8?KvAyZrL2)C~N7SE8y!GxzZs18Z?}C7O5r9DfO58 z%b^p(!nk=N<16lg9tpvHVg{+wz0OlTL3_M{M*UKaDXisGS`9)mdUi!7` z&);Qpn?tViEn?vh*hw+KytK?4J*?28E#`rZq?7}+1;6WEF~4xE?~?|imT~AXGo&Tq zgj6kll83;*GXIb-&}~htg`gEli6#z4$>cyC2a2$6X37oS1A+L#SUumq)v$P~fd#~= zQDhOzzfF~7p!kxPr6}q&W1+fs6Rs^MK2s9jOjY56INYhT6!3WOo!x6!u>Mj-F7aDI z1)BB+Uby7jNq9X$)WcolOCW)=oVNJ;lxv|2$IedaDSOez=ZTP+Di&2*%mMOYNh_Y> zHFZLPR{jZ7uL7rX_~zqXA-{j%mNrFMBe+^(Yt(hv@VDtu&lxSs>{PeQ4l4tzm;k|p zG~YL9-t28pH#2GJ8-$8u3s{pzJe6XH_uup75U3V%wFypM>>I2x#v}l-+9^^+^mJfs z0A@j;8h1-^l<=^O^?`f@E>U8_4-%|SV6x7zb7p5i8CzW7PFIWaD&vd%z5bIA&8-2A z6}|>bKK}1z3U7`4USATfZzG|*wH%$BJyenLlaDDvUrluoym_&-pR(JNw={kNq`2o7 z-k0fw4aRNoQ|?pLIXzYii=)NpfoYHFJCRW3Z2jea7~xmAF9Utzxoy+e3=o)x+Cfrk zKdNhwV;tVJ0aPq1RjymS(>t6MM~+6imPct;{d zJy8FdVUNbqP96{(D;68I)Zfu~7jKsfqqB-+JxgDRcJpfc_}7bKwzuzFt*eCc4__K~ zcW;S!QP0v4nfHw0D2b?`4QJY=r6Ej|f(kEN^_z{4ZJnVL(4I zoiz^55y@$pQ~sxA_AvDiLixn&wK!yTGXE2B1V_V0Zkd5Lt@>=Bdiib=aOotM=B2a5 z!P3z8DvT_5&N?nOaCA*Wxq9cYG{eOse8l0%96E2V33HcTcR?UI9V9KngcAEq=V<&f zW)1X{`$&I479R%Mh;vrL9}r`8St)p7p4Mf6(V`rLXa-_mpnG^-YaFP%$a=Q!?TwAU z5xifH9SI2XA`cz}tD~|Ggkleb`NleF_ocFn#)+l`_HD@p1O!MXCM(Ktk{L^d6TMox zp+A^qq|7Ug+R5eC5lj58Y>7T*8wo3?I+*&p9*i;6&f|t{nkE?4m=3&uz|9^bhU{D9 z$n0P3W}}*TJ2E|aeUCp%o1z#*sK^6s*JSfafdAFGM_@g9-nfy65UY>5|nD1voGEhx0-mO;>efh}4 z74j8IFeoUN1klt-!u5<^CtusqzFF--DDTg}RsVa&A$lg_OQC3tk$j9XW|&xL3eGXe zT(N_}f=s_$?+u7*aCneYWF@A?#>G+?apan2$6Grm6PLDfy$R9&NLbBV z@=8=e(p#|fk+hWhsn2DR80X3)Q8Y^nxJoR2EHsZoPzgKb6~7dyh{S zOKzD}^*#HK_nT*M(d&U=&w5T*zUO&dMXv!{2iJJmEE4j@yZXMh=r;GM2z3KwS#hzE zMTU^_w`Ey$f3d9IXy%k@L?;zV$TEiq){~tV`uI2^z>{?Xpb;K~35J!%3_oRk5M)Ah zWA4LC)eRE_{u~@1&t!jkjVJjw4#Y@2(+*O*!d$jWPfzDR&P_dGF4^3%duYi;Mzhe0 zQz#ylmNU%t-qBA{!}R>1(QTo@JLx7vi=_pF6J&sKW~KMT2euFxl;Yb(eRSS2$vFj4 zyU8~-%4fb5dE7cG;xANRnOnZ(m~E5zT`-$$L>3q%>SIfLyr*nGc7&e z?j$J%DXZ5}m0PgBZF0{6` z0-a7zJhLt|tKYHzBwFfU{QORpp2}!!A<-?gMZ@qa9@lS+Zxi=^1i;}x8h<`-8As(- z{|X~#64Sj(H5oNRZuyBu|0fOwn?fKkI(mBNgf$;ODGY`?SWrdN9|=UJ!BA?{y+}e0 zorUXcjCS#f`no@ZELPvIYvya1IK*T6RR}lm3jRAU)z4>> z;Ni~{CYp6)$my5jF(WnhaXU%v|7<&aH=lv#$zI0l$n?1d1oTd>aqp?5H1V`3y9G!~ zwYX9O{XVXzuf8jFiEOLnOI=ve^tn0!T3d`~BACnLHZ!e$4`dD)!^*y4d9i|`D)m_m2VI8>A|~ z5<2-9GJ=gF5g{B(%Ibyv%YbtY$xjmMx{R^tj2Q(7p$8lUbeh5`MWDt3|MsHJ+4MqW ze3R8&Nc-7`=Sbej%x=--Zo~A%;R`u_5JAi@vsiW!RoyEQ@BI>EEa(Uu$Qw)=zlD3DT+(?bL7dSaP=5|vA8zkrxFKsxWGVs6wC zSvz$OS6W-LVB8VKjVopEseKKK6=L45mM9d;ih_<=kkqfVUvFYy&5kKg>YpWGl=b09 z(2=~82P0(g#Rq37fAd>`$5HR7W&6znzJQ>3blNbbIo}7yl*dR7tR=Qa|Lv-OF(8eC zP)c-uU^N1~Zdo~WdcdH9JEo6n)JjIr6-1QD&KFiz?A^hU0v8UCbwE6_S)J-AZ@UW? zQJoFDwT=JTYHa@=j%)7)(|LXU#IY_wzs4Pxy4PIv5pP}fk|oyJU*BVlHz zL4G(G!O2-&Y%Cb`J3dstKB3Q)=h6)xcXXE-VL{qsYQoy9x$(h~dVXDlWdYd-ZmbrM z7?BV`&}=#=`m*4uh?8oXI)Mv;c2kPPl{5Gvwq`JP80+S8v8+H_+QuJKTEcr-Xg6QI zWeP#)o+_nDgo`U!o#FGyh}u+1d|(Qbm#x2uF*`=<-YqLDE4&?qAVFK;;6d+q%f`yO z2g4u;*Ai$m$QQbIz*yhof*L5)ohrqS(0Ka?mmXrBsJ)|&&?i#wZo;7<#339yVfjWF)M6LzfGlfK z##Ll|>wMzHF6JBhjmc9A)W4m%Nj(j8k-vLE zQi=dnjGxm=qYW5(_v8Gm96=FkyxV;-l#SrDWrE~1#KZ3Cb19|Xv9BX(kK#SzQLOQX zx6Y~Xnct^RO>dFT0DF@XSoejLS5;}bp)aOSLtn@ zg-RFkq`Uk8Sot+Py~_;wd%^Hc!OX)Pn~?@WV%W6rHe7vyNT$*MOf)e>4=tB-ENii$ z@{85mn9sz}+e;%sX8jt$R|J!z>CJUM${`pNqWfX5+gHFA0Eb}z!h%WQacd>SUTgtQ z9W4%uKYuXM+Q^rZ_bWG8`+DiO&TYDsxThhUI_?SitPkcgz8}ydrvcxRSwhLeD2&P5 zqEj!O@x#N7v8C&eN|jC$*BJ`i!ZGukUxuITfSHNqn6m-H0&#?ucy~BbeNVA5N!s-F;c&1fUJ28*kTmm zqm|tMsH}9JuAz6;Pt#A4WQdoLUE!QKlqbwNc$MB)Qo zIhAZ~SWhhk=wID>1d(?{MMZ@~OwlL#!~I7Rp;pJ?A*7zTjcc4WSGIBK!2U!-=yz#d{zsAjRt~aUTLXvgGb@weumHS_^Q2w z!&J=}qglhOUb+MgeDTDv5OojgXT7rnMPngH!rk{T<4Nu>-a%KsXN~_F>ppujlU+$I0TFV8lV3YVc-b_Zn)uyD$jPVh>^z6`! z0$m;AXY(P2#e%7dE@?^9(s#j#>Ba~oEhw*u>Gp?v(H!g!+M+qqOa7H@3Pc{Iz7o0n z&wz=Qjr|f;=rq=*r~NVhk^D0E*7?TsQ|q$J@aG+7qXLXEZao7NsdkJ|W#gA>jzFrR z4ko>Jx=uQhjW-_5ki{vF^pX)&RMhctjSQSlsE>M`u%okl11Y2gh&)RE6wchjv-dq|!NLOk>V+ zbl3?wAU?LOt_5S#ogeGz&;WJnZ*Aw}N2e3>JDEPUvgs2lAF`USXmezcjh|DF z2_3*#gOGk9)vWD=WA{|Nb?--L-z-(ZLGo@KNAuz<;9?)X@on2*WPvN|9nN#%m)cho z%9X9*t4giqv~deu^50|QGd}(Vba2;X7EPwec*|>Cr8Tg-*hex%OBessVrbR;WbA08 zbNe(f_fjMLGaX1Ipnh;2&v&i;P0gIsPc`D@u?KZUEtDQAF3^PG=ix!LrtrMF2K^Kw zA75Sj=CgZiyib#}&xLT50-|ILPK)Qs%akN>_c4(9`tOf{pDXPMD*J#)-@+-uJ7cwp>xI_q3KB|K98I z^p8Pw-~TBT|HzLq6-C|Bt|SIPUnMR4r)v?`GSTd}Snt?ukXSNS z+B2|PKk+Xe@#6x!t&`Al2D2o5xvVzO(=JS{q3`!^G}nTnYGm2{CffKfSrY9_H%;Dt zxK#uOWQ*rvfl|$4L$7I*qC>ycm;B&eAD+7S{`Ezyc7tOsHJZ*&FbG-!J8PTbmTckg z^nGTR!T8eIs`%eB@0fHX=Cm}UvXO)%{+EV6W{0HDEG}f!ScyEu2#d&%2yG8CbVA?z z)9pW80}g=P$1W`zrtLKde#Yn#A60#|a?aJ6>2h7rc!M0WQ~g|Nzrc!_DKKxy06*47p@Z-s@0?wJ4V zq&vBWZBI^4Doal9JQ$vV%H7h^;_jsqf1j_0M@NNLR)K-U zIduaWNRZzVN+Rr&C&tm)J}#b;XQTbvw2@m-L_WK$8WdIx^d6dxeQ&;!SFMV$-G_fH zXiv5=fbsmsrulDZi&&&iqB9snOxCb(n6|zE!Q zB0(j&$6GB=uoc{1Jigk#{~dr}!5e9+&m?PBh7$3H6jW2~0<9!5p}0)W8n4h~{n^rS z&X!-2aCadN(-++)>J9n=2#RL|(^tV!taAZgs6u|FsQCm+CdrK`irj}6IFQH8A)`vM zO35#QdEr(Kxk^JEKtc13JdUJ!tEk-D^*QN(Zb`-6%3rK-*Oupy0I@42p_+ zV7L1n`sit%RO*|b|E?rjEu!&xLc4p5(D%Q~jXQLi$QHc7ohSU; z&h#WPY&=K0I)f{ndSNp6UIXN8g&+?TBW^#h?pDdYDK9Gx(G4#2t?lR& zd8^ntZ$l1X+o_mgj1hOym~HXptxCtIPp_fRSHJ`ZW07vCP^+MVDf+l>g=cx_u6B?(uP97U zPL$0@sH74xP);n58~2#2{{MKo3a==;uPaCnNXO71-5^MJE8Qp!DoA&CBP~+WAuyzf zfHVvWl0$b02*V65L)Z84{?_{b0M=sebMJ|L_Su`NW%{-qJY`F2&m+%NZez_;6&d)x zUBy(r{8>Qy@wBPDgmL6Ut((~Jf};wpVm%y zU@ZMLE6?ur8JM6v&rz9P5j5p$7en<@T>JkTBnw=>IUjJKl; z_-w&>RvIMC0gbYz_C>E$E$1Y}G_JLTA*IL#zWo^Z=SZmL#&Fj;w@;VLrp ze*p+zc1AENRQ$rF=5%WEzp3DuHskrd-J$HGze8gZ>(`+NweyBE;@I9TWkzF@fA@r$ zN5M2Sk;gnd)|83in3=xefbx%HPrt~T`gix9Kaa$0XcC>@_n-NBd=zF*@^JAUgLrhn zC1DW0B;ibhnO5jYD6m>FotekU5 zPa7Ur%&X{6Z#xJ#?C^qmGaCSrEn~H3)+n0hX_kjMhkZwb1Ra;~MEKQ@`%I&T^KuL zEK^eMRkNKER$Ufx1^Xv&WcV1y&vh*<>O_&k0>DtWrP{QeQjzs)XNN;YMU@fJ3Qf1% zUwR&3rlTw-?aDKEQX5eBO?kuK0VxNlzs=@ir@N!D*aYdgi=wNYB6LsNC}*;u@X2~# z0NeWSPMhtcME;QOu0Dew-7siNrD)RzlS~+(@3B=PgPx`y{4{oZ!0_7 zbV#=qLZlSS0faR3WRy3^Z+hz^_AXgO?mG#vaTXNPg$^J)W6dk{O;_Q__4bwjj@?TjRBp%=F7{5HEAXOrkw`DR+v8(*^R$A?Tf2~11^fx1 z*dvaMVxrzv$gxvo9c_B-rMYW%87W0x;!R40QHgQ>)DwP zO=4hkc!S;-)py%gRPe&;U(UypOaon|RyA>MVO@Grsd4mT1mTjsVD11};>zkrs@TZs zgZ$>f9PV(5cM5Y_+koFjC1>pAwmAai3aYGkFz2(KBmC%nBxA}{rwH;CsVtHU5)r0M z@S`Yc0J7C)mR0`Xtx}&y7EK11lBfp`9?YU6tSA6UmN~t@gm>`hP-5q7rK;6kvfnRJQr7xvId-GIBYqypK@^}FXc#C}mvfO(LDva<5*lD3NC4xSd| zQKw0J{-9%|SG~Hg%{tZpj&Lujqnu85_1NOOc(CWykAXm~_4}?11~9=Eg+NA`y_C6k z0Z-~HxUmq1(_x#W`Kw{zHbTS9;DxX{MSfnYG`*V_znczst!jQBIn03ch{vhIq4@yQ zBv+Qs%yQ;m{!SI@IA=lqepgbF?Ceu3>Y--s7RC~fC$1aR_xA80C>bf}R z=me4o!x%@MYzbY-yjPJJ4T9xO%P~n{BFV}urD>$sbf4e;87KP4M}uF*GnRgKDH~oO zX`dFiU7V{Pw8l2$>B~`7xJyk#5a~#hqm5y#6_?oNs10C{GJlDuXOzle_u`~;oQ{JBvEv*yK6do zcDyituF&{Bg+VsC$#D*81Sb=KorZ9hA)T)*5K(B0l}$3y6F`r!e~PySAKzaxyhvG$ z>2Mv^q~nQlMQDT5Cd7Uk5Y3RE+Pk{K5WeEnxHc@ba!(DzG8j*uOEQ>G?vNZhr5=nO zrd$5kKg~~Gbv8?1@N0UyPTa0%oo`P6B_c7x-PeY5Ju~&T_&q@F@Z{GI*XwUyq_#Yk zuf(x-B0e-{p0S*My3)T<>Ny@SjA(e8QL!ceMgA>@-E=4*)MP7KqBr;EP6g)=af=X=9^)p;YQ2Foxr^Xu}z{j;!Ph z6A3{2E9sG6>B(j(*?f(#4kp3L2d5>t++(Qpg_ON{FSS}P&#ck^jxm;vNwwVjROX?5 zeAskyFxm`lS_^Nz8X$#j)~85?t9_eq7az?pyWN{WKnY;v)Xv$%0i?FSc;-MT)`HMF z3Zo-Jw1L$%Y+GeOWuBajGw`c+CyEeo#r+V!g0(p|`U-|q=ay02zZv=dO})|n{;r|` zaU@T(ajnuzU&^9CK{ij%DKt*x(Y^;MTA5f{*7qR?2M0T9yvqONDWE*zV~AJ#(FTTH zI;h3~Df;i#KJalU5$DYqh|4ML4R&J|M_LRs+T8)4?r69J|v z5hlnwEH9mEikfEUrSj0&g}hvPWml`0yjQ<^Q)PPXCCU^~5LCH>x6);o^?x7LD8CLQ z)qE>`l;bH`eqnL$9<+C43x0y#V`0o=2&bCfC&E&ts`1Dw&UQd3Gw3)3@}31jUj3UO6NQULPg&Nc z%0X)ZBKu@nf0d_mu`oSdw$zf6l*Cl{gphEveeJfc&Zm56PY-tj0Wb5pCMPH7;N~7~ zFO(z$Aqa@q4bV%LOYhI{(DV7}OrZ^!gJeH7)LJ$xjBZ+iNN>%ur}`28+#;c%J%ZTk zjRakpe&D&>8-#Y)1LqoSJKSHM?0NTgin;Pz`!>p*vV7o36&6hMSiY&=WHC6;f-+hD zEQL~ernQ`&X1lCBA895f5645}?i+1(0@hI<#KWhpQ z$4Oa<^1n~sy(4XMuJ;J^jw?twC>U02b&4h(+j2OA6C)=D5_e;k@zO4XL_afVS@Sey zmaBh`EP(CU#jBKF^b&@IB~bDsU#(v*&jtxj91iUJ>=NZeQ9+=I$X-Ua{sHTSDfURR z#7~$ngqpJ)2->ceeVVMA-i=ZN(x7iT{l%yT-ri%d*;s$gEdqJ1tOBKdWICb>1%~|q zteJpk>t(ryBG@|`b8ZG0tlifr(ecf~G6QU%=1z>yC5gV z@C{HGf)vs@%cgr}qIV2%yXGs0Zd7`uq8o&<^Z6hj#S9a zPJhK{eLV{3lu)W@KDZ<)(YFh07{o1tKK8={unZ*Gd)`qfi8Q@orK54^7_es229nOr z@gwUDe}7@C`;~Uw)6lA!E7%I%!;j0{y3g=`$sO|-lSWX`Xs}-;TT7j$@%7R0+5lc8 zW!fJCEyOE$u(E-7f&4ElU)ONAMc@kqE_%;Wmo8K%zE|!q>683Zr!8pI`w6I-=DN#z zytt(pdk9sZ_U#VBD_fhrQ>Wf3c2>}mivzy$o9<@F{b(;T1X?<1>ElmdCu$}#7l*7U&=nPrZ%^uz3mF#i|5HdW z(;`U^Ih}^f8yg#Ig%VD;1&i&e@-#(xE+o+1-c}#wZA&9_Ql^*){)hhln-4^a{&8(z zO>Dh_4pJVS4;21-BOLWn1^C?!$RLXRGUu*ag3o9;{n@YEcIXEYKK&bpg#)ZVEive> zLnsq7IB4cSq<>AW+6gzhO>p|bZ=3ifr@f6)`>aPo78uQlUUBIj){b1clYJl{Ae3KB z@SSbq@Pz8Y?$X@+CeTr4eVz(44LP7!4oupBzbOU`*jb*Rym1DMUPg@ur}jB}MmuCl z|4w$l2!1A~(efG=B-1v6v7A8V5jzt+3#>$S!+<*d_ICU<=reH_87%>JRdw}g@Qd@4 zm-%9HGw?_Ub;GLb&JjaDw6>?rf;cUY-S{?2GRSRbUh>r!>PU(M;iUk< zk>{1=AKrmRITJNmNF;>otEwhXCOA3HFM|Ryg+^%sv63W(e;Qlj(K{$Xl(-rjPsLKS zoi={a8mnQ_l*qGZ9$SvQ!Z%-#CwF%tbOoZ%pKr|s9eq)dWw^D#$uCN+_3Y830)yqm z$YLg5S^h9Y^CJ5Ejp7_%B@|=!%_CmN7TcLBL-F9>a(-B(4XFnzy%fYvz7^t#JAonv zuNBU4IQz4Q2DP?w=HO+J?TMt$ThCF%W&xF0%KhWS_oySt?nVCw?C|%P#BV;5CcH3rl{`$M1M3)z1MXSm_5Yq_ zpF2fw4zTNNo%QT`+1(O>%&%jwn4EuId$5{mkr$c9Rm$Zk{J{*BA+H;IF6Zj#I2FD( zJ^krU3^Xk2yjT|L4I0JmEZj~a1r0mTUFPd#2R4laodP|&{=B4=R88L;FiBV`31woE z2;Ku+5ytYwOGD3QXJ@&4Lj+6Gi7QJA57Z@Ig)JTeNtZV;PK#Xyb$TxEmq+wGLB!PY zEk^01q&SnBiO0npH9AVZ%0;BQ-&*$ydoB@z7q$(0=2&I{!n*}klL+5h1+R5)_f%kq z6QHA@SMBpjb)V>dTH5ycc!Sy0eWMDyrD?m}WDT5z(B>E=u>6ue$xr5YK5WBFY?ynM z73<+))Ibbs)Q ziEPJWrxkzL{3rPi@{gt1p4ylZi5*u0oO_Bdn2+Ocxm;cq6oL!_NLG~)NGpjG|M!=9 zKx2Nc)XR^FB3sn|y?=h~jd}U!Pf;CPg;i!qOly#48!zTDSxHt_*6!K%FJuz?Go3gs z*~6bdqh!|Kw02=%EzuU{X|xQS@$Lz>PkPb#@~pmiWW$A{3VIOy(@Oa@XaZ)omfcwH z3P$MWsy7W537Myj$xA#G%k$sd!E9Y!h>C66f+^gJ(4)N;w*U{E!Kd{dU3EHS9?yyh zYScG>qHP}V%K4w2KJ9<`qKeGt8%;Y?n? zziF!DGc#YR-w6qg*HiZM_mAvrG)dfcIL_56tE(pvj3HRit2hb^3TByjJ2k_PN4wr_ zukF}ANT{y>y5#BTAAqhqgngPkEQH^S0kEsQqMM9=j1pNfR=*XRI(%>Af!=G6QDT7! z^C895RQnC-DKVT(z?GFkUOEGUq~hKgV7+yJOF8LTU@b!PqbO9ca}e+atn*4e;MyQ< z`meV#0@D@Wob<=K+bQF1Bl#_Fi3{jDiq5h~Ii3T_T{^Q;32Z81b#Q=gyXQ?-Ff8X{ zj{WOqsl_tl?Ty3l&T~$I{<=TFS*a;Xv6rE$t1(xxGXO5&n6Ym1_Lzqs?E*YsH%x*Y zBu5b-mz6Y?Y%o7*IGvQ_DQhY`_3bu^ciJ^Z!8o?BIMUeRu~x7DyF2k)Q?{V7?@_Rz z{4a9FUk5fPvVhLBO(DN(0;s@64uysC>((^3Wr~&xr2{D>efEA#hTJ(A-%CZub=_Al z!RQyI>5Hmw7Ki;utv4?eSa_T4I(kG7;#m^y z&F<+$g@ym3m~7J?^_MV}`Q2UqYu8JwqhOY5ly6&lpvD7_&2JCdv*y!@nKE6|Yj%6N z&hW>SGMSOcvcSdEUorZv2UjI`-OK38f6ySfd0S7I;F$#X+$CH_G;#4#N&R2c6_+fH zm#hbMVuPbwSONRcUYh5`k497T6#}48v>}Ivvyt<)rb(CWRERHxBQ{(c)Y);s2#1Z! zq32}fsh_KCIKP&2?N@WJaz?R|73qTuAu^VAJ(ozMLsYPTrv=^K)F~;u)}QfWMF|s_ zQBug|zqc1bGUkS%r`JLtX{GMh<%@X-Ew%&6>(85N%^Lw^OYVG4&!7|_oFB*Is%1Sc z;kk|%z?8>adqy;yAabIT0+A!DBl~LIhb4{0MAQ8mXAe)SuaKhwvo|O`Cyo9Gm)bem zpl$9$h3^y{T=bSbq`_Ti8+pQBIAE6|8yT-g#M<`}sQZp5tD8j+R^Yk^ zp0+YGa1z*H*`}SsYD*$;RHPIp@A@c)fB4xwLWe!neR;;0t1JFIKf z-atyu$0QG@g%%s5p=5W3lD-7&+65i|ZtY~)JVVK8uKXcf%#0uXwsweteKZs774_rm zQkO?U;g59oxvNsss>a{v-m|9?aYgy!(0u;QosRaq zn6pU}{qAdk0%xyy?teW`!r!^i&Fg*n=T>!h4>;jY@qhq0Pkt~PSMe+*>m9&3CLdW6 zAqxSP4k@VeclgcY-cu2w+t0z{=@K+@}pd_}nC|I*QI4iW^9P ztwPCXt~9QPopScnC0ju@_x)4B*t1>ZVOFUMDmb7a^tyD4)zD6#^(tw^F7ryS^idvH zz{9`^$VLFVsZ#m_pauheTnWzvx~cQu97R?X{CrV}d7n&h5$r=N>8}8bwA>@g*U~C7 zI$k%REvcd{G3nJ|y+)jnJJVvl5Kg*;B{oe^~x{y>D`Y z5zWZVuZBp1ZDn6`G6*b&-T`GITL$4oM|c@4RLC=UFU_O-gI&hXf$GyG9Y zKv;(Alv@N$ucrl64KvAg`w?3!Dv-HjN~CeB8wcA}b$de0W*9_GHG@0D#Pl__gvP3? ztG$=b%w%1=N(y5`G69t4!Txm3SX80dospdGB9al9R2mr1_{WwaCP9Io+|hH*ShtjDeh3M4t}(*XdgRW$&()j{<-S70DGBI)Ho~N1m2BLr>>wqfPEPol zb2$WL3AKInO>QZy@a8aO(WdFp2;vosT0k*8h5Gj|4XFsLr&!fwT#g`f7Occ;%lenb z3Hfb$|Kl5(+qTpbYaaw1=JlO4>N877bqfnw+^qXMG$Vu93>McfXSK3hY98C}No?T$ z9&YtE7M*>2I9-XVR?fSp_5g(7P3pwtU_yEy9(t^&hFWM6d&tlQeNm9~?XkUkiz}>% zYG;tFq`_p2;Ug}ro?slJkjpg+d6^sGS=Cl{;YC*Y&v1^*l~awqk7qIf8@o@#UOhFxH349g(AALc06UNM3Mb9 z#*8g%PG3YmXcV}BZo=lpQqORO$bR)F(M$l!Hm803Rnfi7wzmO3(%g*3Exr}X+T>Nc z2aj1hjvlC|f|(5sHPn{NKM2W1;CV82ru#Jat5a(|h{56w23KM7DB5g}%XC5I49aFz z7w)mmm;DsIW{WMmAXKhooy=yWJX~?@ku8tiGE{mEXBYRh@V=%_94Pmc`#JG1qMp3{ z9l~~n8o@7tute43|4M$1u219JeU+0xwukqG>~nX6-pab$j>yV%T^`q1G)ME~pE=!A z@fB?^8@c8okG6U?c6M*pfLE_xP2_0+&TGfW>iP|ZP=>z+eBfr$8%>BZPH7WC=KH5t zNO4-JU|OL;4*J%6-m8%8!#P?ix^UDFR9>46>-*Uzkdm_htF{u$Zv9CrE`+*-J8NcC z*@rh(QYrmjnDN=TeM}mFcx)B{E@=bf6?!@jtB0(6%ogcsJK+F|ZNL2BchOS&RHo@J z;8+U0r;?3E@uQ^CL%E7-bkEcENNhL1GSz*W0p0H(Ie(|U4<4K@p3U_Erof_L+qo>y z>$616@s|Y#SOHD%0iJ>CpH}SckoC9oSr; zG_iLPHPo8ARXjH=j@?0B%X)6(Tlk$4DXB6io;uHU7hf$2zII@uWg~&*{~RP`x2n3{ z>so0kZf4tE7@R92CGTfv6WAiGlzl@O73f0F8m)r$e!EF=#kbkZsxHEVY3eX*V&RWj z^tQ1vU4k4LeLLyHvW_{)H>xMtf@HXi7NADD1ak#xc6KP@GJ{X`szFh{t&Jz1kLy3! z%74a(lHl-8Sy)UeyPA+;IpuLyN`>S&dG(-Nn$~a&Y&P{`n5KluuHmC}}aSo(ErqUjsVCZK}6(Nz3TR1*`Nwoi|QF<2K6SqI7bL z!0zFp#*$B4?TU-4r)T48rE?%>6aaiam7II&(AI75;|r{$XoUxM?e6aOdGdG0b(Mj3 z7k6DkY){XbsjIA)o6){?kx-5m4s2o<4lESQj3@)L%i~8x&_NCiLtL`J+)jg+{BF9> z+wfJqb6p(W%wS@3o|6!VzsSrLWE=5^5-7ayA(Ri&E#ODrg7Gz(GdIK+m078^Teiy1 z4!$RiJtC{|`~r}hfs(}F%V;#eYtS2u^-I|TKl=Nk=0SV+q~$gRI?3O6xCF5Jrw&8b zfx9jO{Vs{n6SG3m%C8$(V^|M1H}XGNo-*KGf>~C9T9W%I7rhEyUL|R(VUhOeNDtT9 zkyPKe)NeTf7QP&0lf3iwDu<<9NTAx_78->Z20A;}y1dmQU-%NE1unV{*P>1pAEDP` zOK)??U0j~R#g2X*tz`~M?mD9$L-{Z_HPe?gNK~Yh`Wz^Lap+nf5YDHpyz@^;t7$%a zUik&woSvUDztcbW-&&^Z2kCJhbOIu1jEC38MR3(_TRqb}ZJ zs#(lzFcb)uCqb&^=1+&bSm>iLmu?{13$W zQR=0|^M=zhUoV?i+N!B+_xfu*#VMpOmIdl$@0Up?uYkURBFfn1Un*Jx*ZA#2blxa? z1h`CPi08E{%X|2`66%eSh{L$wL&B7Miu`9L90|X23P^h+$qGX6{PCYWLG*MK7o*kI z*0yPSamK)-xPmw<^VBcY45w97=*?%jP`?ON9|lx?1IbX!tqA+7nC!l|#}g))w(61} z$>076g3#3GONFNm9_MwbcCV!jA~>btIVuF7@VR5pkiz%N4DvZs(Wkp}%#>3NaTXkj zUr4N2!{}AN`$?>88#A6(celsOwY^^1pt$?*(P#j(;kA}K;j@{VelzI_Un~Ym6gVbA z8C;;cks7>7TVNUMe{KZMUFrq5x0)@r(1^OMtvdO_vi$RF?Z1ZLW zyg|`&$|sI$QOQ7*PNlAtrYhNlj4a$%ZvKXd% z-~clmC8@8)FT@PvTwnpFAn`yfEXMo=S5y3`zH%c*4W*I^?wN_czCMYa;L{mnL8=7C zEgwb*1!W>htynj`!+@6OANYre9iZ*87Jq{t0;;5>E|CSSiN)N{@Si>*4>(3^_LriV z9snoJ2prDGDMiz$YUlUh#S++fc>IfDAW8ToP;$@+=0(@FcimhWa2(e_$>Df6H344l zLjfnB0%$b`m~pDQz19CgU%yY6kji@O5MHJljj$DJv%y`SEqr(={v%;Mg|%IjhY-t@ zR+VDeJ?W(Tv%hoh?g2GFpq}JDgR2c*u}nVZ+kGun(M&)t616j@AKQ;1xK3JZ!3M(W zJx2e0#4*Ue;A3Wx#=c6-LkfaECe8TTm)6I0_U8}dm1?XyFdxddfE;8gJu7}}?J#q9 z0~m^4K=i?)`d^!ynYTSRW*RHp>ndozBrCjkFxNIUG$h~tpryC7zds{?2bXreIL)3`E8Um%nay)S+bJ}SiUXAU^HqzBUh z7TIsvJ@Lnrl4EFYr_Ou9rew55a7e1-;if~ycVJQ92!j^6Rc>+HqT;+TesfaLWuX>` zYxo*wRHSMv=;iCX38^UT&pELpeh-Ois=)jBE^6kJt(DayPVZd3EZnm91Odv*nA?%i zjdXTx8wUr6SKGhsxF!%ExhCKwW!RDOQfC*NU_4Alpj)_*G7)GaG=APQ--Z?c4BJBD z3RDofe!08)y}MghTCdj_ORVw!NmiZ-x`;YpTjK5Q{mPYI>!)GdfvVWx4CkJNJxh-U zM<-FWhz92ALv{gr=r0q*qYS>QNoy_(Nv$PEH(8-atY&8^%mrK@1y#6&=NiZS$zYlY zL7Ns=3|k&aahdeM>$9=lf=^G^Q}3T7HYhiQ{bL1)O>R2-c`ks8F1c6*!FYZfx^%7_ zV_1T8mUXK-cV#++1zIdXz~E25ZbCNrT$6}qkrBz|)zq*s^WhB)CmAyCd%Ng;6hOhG z#H&LlH`Q@rFZ@BrK4kRuj;9Xh6?c6{1fV1$ZQAwio5zbjlt!GErR3bgKI;5vlfCdK z;vgYaSjfekh9X_Yc7f=tme=NZysa9mzTZ(VcK!GM3Ws27cA_s|Tly=ivFDqVIIyz5 z+V;gfIl2sb*3`R?(ZPq4A{6{zJK%(QW3fwr6SBmc zT|L)-b-bhYn=dD(@Yj<#SI+ZOC&8}JKSj+RzbxU2+^kmol zJ-~(O0DT_7|9x1A$0u%`NG=sCpu^|nmaw<}MvS(9=i&nT+JF+Aqk;U;R|$%&U}I04 zuZ%cZF(*ngG(NzdFsQZrB6~bCB_3PwMzuYjFDLzHzN?t-w^V=`(eCf2;P#?4 zoH@F{_nMJ{&GtZFWNW0*h2gR}26zpOUZG_4Rs+XbvmsNrlp-m z>yjnI7?LL9b1_4aPH(DWlvbiHzq)U#h|w-c+Kv1y&5nyn`}(2Go7Gh2xD1dZ6`I@{ z->VtrNI`!XHP25LPlbdQy9>#5ZOe=d#9FSV7^?&jy(GJkNszF}>a<0W(!A129Ia-Y zZHf@g%x(Sd+I@WE8A(V)WvT?kl7`gtb!)YoM?fg~W5y*`AEs&kE_8v4imDf9)Rgax z!jcp3afQ=n_4|B_1Fw_W3gNrG=H`Z2Dda7hmDcqQ}(|=uVoprz>Si> zbQfmvT;5KX$&%ib()}hbO0}=W0W~{;*%ow}b4^!AFX7dl!XY5m3g2*L!lwhyQl<|U z>VX{-(N6%(o0+ZMxhxFD^(D}Lt3RcGG%z_cugpzaaGuml{W+|%KEi)D0L~P+A;PUEgWJ|8iz)ILkX4rDqVYZ=zMZPRJmWC}cNgHTwB^8%ZEg-vA7HMN`wI z461eYt-pimeh1NTLE0yBE{F)-CaD2Al*y+h31W#5s+GuFw}rj0T+`vEU$FO{aG()J ztqW*)`S@(#SeGJwk=ja(y?Aa;#5$O5i!9ze#iZubY(unC-2a6WyfeI{+@lds%(y!(-;9SD6ChAY-zuw30R~+hHm+=YoZ^x+6XMcz6dbpv_w#t|wNsOf_SAGPy z8*LLDx+Z-xlFowcqVXben`s z^7wV1MX zB`8nJplD%ddNO~zOuzm7!v_!m?PizbDn=V6NT(NYty=;t6*FpDIfvjziLffHku67R zC2Xf>Z<)9Ph-tvFUI8=orM%|uOg})o zdtN)lJqOmmc}JdGUu{{NG%h6pi&j2UZ1hH109V|p_u*ui4oy`$8g65!4G91nPt0Lv zjZb*F0TYAtT}g>aOf2ch%IXWQ@wZQL#f8xpq)UuhfA;%i=Nchq7sU0kkEfo64|jI9 z=`Hd=uMsL7mE|HpAD~(~JcX62;01VyWJm(Uhf6@Eqjt@wedWkcip#W__cf554j1eS ze{t7$eXxoKbxrqvbmOt!J=-l#NB`{LrR?}DEh6uqV&wquUb7dY-4O;OFN9(mDroR& zuBfz>d3V$n!oqhoAV`kKzr^v#sKnl=ZLxAHp8ocakK7NtJ};Nj|JH(eR*hETLrw0- zJFvup@BW@#j5OE@5aX5*AtuCh>6xS~ZJdQhTGo2mErgnXS0b0oc(Z8K_b>tdAzqM$ zJm({ac<=7TeZQ8rp-wPxH1S;Hj#11Nbe(uvd`!S%jvW8D58@zLpvsI$O~L5H)9l@G zm@wS(WLl*0x-LJS&^50uRChDR=O`nH?Gy9bA?hC(Xjp#6y96AIVb>G|C>nLHL=Kqx z@?(MEh5p-A8u|%`yt`J@MwLAQm=p{Kzsq4hB2UUJ+ACF`=rl>cc4X>s5jbrDtM^c- zmjv5N8r56mpPuz9;ScmTBV3JQwbt_f}g*@1HOEWBeVR$biOAQiya?OIICAX%^oabN~AwUGLk?6xrk{ z_!!ioI&xz5>$e0#q~k78@W5-jzr*2zCRs6z%<4-L-By*?y)J0GbM>mh!NIe++P|Le z;A+`O^rK0b;_Au1JLQ4(a!PA!8=GjxhMA!MSG^t9<%jy+4FE*Vd$$e$L}hdi4o>xb zN==2TU={)xr5v5)XV*o{iPT_WI$NiBTwtmFdhMaA2Na-je#Qa}rxTzBMpx?ff3n8` zD@Mf263MZojib5i7HuBd)6O2Nw|Pr8{SxupROM_c9cKwP^m=}fZZ;sCYLKJFBymgAG+W4hB?Sxy`-Wo)daKT$ z^IdGNW6PA9GXg$!(c?^4Vl^j^r3i@-Ag`?Vs_N=^9jkk`VDxy7m_D?q^ktHdwZfpO zO@u(O8ZVeoqm2Lkx>${ZPXc8Mz=mniQ*ggjoXNk~D8Gz`haMwSj(+L$+lUEADm-Ps zNxj`{Go>{JZ)QFba5W|DH?a^X3txNuUu@t9Z#W`l&HnuYbBkq2$ibXrJn!XXZDM$m zFX3GF850}<9q`FFXTl&%CG7KrekiO-=$3P z{L4^Si=9FMwZz)teSB!ecplUfG{JdPLABp%G&Hpj9NAGRdqIyq4@QmigtfO!@j$J6 zd%%r|QH3Ik89t*hwX99XL5(zatbFvXqXr-7)1!vBhOU@w*BP-Cr{dWB!;`nDs^ltO zqS6pvm=uN;DWf|^{_pOVwRKxSo;UVY;J>8aWRpqzU4~;DN6Luk}?t! z9{}N78;UVQ4HvyHHlD9N~*yt6|%&?He*T_rcgWa}AsPd#jDdMU56O)|% zFGa1UGy6JhOS{U_6>3OVQj1Bk{w ze}a|^>og7-Vrf6W>`u8$GR=J;bSQ7hK6ipdhy zrrw|1A;si9sBAKEIt+Zo!T;Enr{2%j{pLA<004+?U_n#D4;DvPNa{y)h-W_m?=wE97q8S<3_$deKy=6lQ0SXL$wgvf&A5@tH$C`Mct%+OJxH z2G8%r>o{C%`Jse4^oh%qFzItRMnD$*ppc+E5_O>admS;?2RJnY|2vNaGUR&*vrPM){0JQ9|5q<=<-Ij@aIRZ~+hVH5 zHgUfy+Xz_)UeghIXILd}F`yk6eAu~rZk0Gn14#HNfj452P(mO=5{Wdm8}VuQrAhz! z!i792pl6}Oq{U*_OYQ9Ynrsnvl+o)0GXf}Q)si;27A`Z?n1;m+;sIpEI>(sVWD*Qg zemcfQrv5r~a*gwxv=tNwUQmVR!+3hwx|>Ptju-kP!&1vb+Z~qh()!MrgvXs~%`W~zahGnFtcB-#sBW z)RzASilQekPpK4;u7sk|qwy>PAj&1PdwP_I3$`cE>QLsKy=kF|oN5J(1 z13*2Hw!H0PI5l!=A2JHCCg<<~Zq4~n8Z*viUQbm(PsXh)D?{98kL!K4FTB4el>y;fzYR-0yWJ2$duYHhZzqAN?Y{ z;R1d*wB93HMXgE!E-l+{BF{m}MjcH`{`GC=J_;qwcWM^1^}TfbHz)ZA3)(2!{0RFx z!(JQ8#O44V&UV~&cV-S!v6~@of88&g;L3k_+u`SDXWJ_eyZy0ON^geVzB{IW^ z<9E;Y&CSgLI75@w-LHm{bb=AgErUFiCc~WG_-;`~|JeYB&2;)^j6LC@dc2I$GQ7WS zHa*I6WatUfSvpIhT}~Oki|=q*(qcc;9iXKbZ&accnGZqw^b7z2k^52kr&(30UWVsk zf+BVcq)#%9YsxNE``Fg%>zcy=;#(d~3<$RG_F>O?0g`cgF-`U+JinJGmfg@#rVQpF zDnVmi<2u#5@r}cisi}&9G@W$k@Nl5Yuv+JL$wXz{?=H7>^nb*o^1$6UP?lM1Js~Tz ztD66>d`5Of`FXM?vXLt+;W@Qv@a6%SAkp;X9U!5Fbd{$@2Ns8MQmXx`iKo}vc&!n$ z`>i9ggse6@Pqb{f`kk3yrIjip5iBg2J1!LgNgfKH#3$!kCE|g5Z>p<+xyoP>%pcO? zO}MRST);4PGIS!Rv>%I+a38fw{%*DXNl7VgVuOOUFplYl46$F&Fexdh{Qsf((Yl-_BNY!kP{TR>yn0O&iszVm^rQq``UiSBX@ zYR<-MQW0ov4i0l$uwHFLV>2uWTrn|H=&7vlQFpB`4)J`;bIPtTM-0^9s;^K?jBfT063 zu^~b!NiC$0X5}7WGi)WA{-9)B7br+`_kZ~xFKAew4{WdGasq>RpC9euyg@T495z9p zh0y;FcL(!w2`_i@(Se;Xl+=L+pJDUmbr)6tFntYjgbo0>FhC?j?_TRJ(g}dz zHq9%oca6TAn&ZT%5D2cb_Xz+nuhtWIDhks5QIn1XI|uJrYfmr#j?bQgHtEc|CC z0X2O}NpVQ%)6A&3{n;6Ks}xp5JO`Vt^-tb{-rp~2oBS$98DhLD=K?94e0}9ApJ+8v znGO8}(jG4x;Ib~;cqiY{67qO^v)&>YJ@kB0e}{0}dh7Ddqi5i)1Cilh!dBNav5ZxX zg>oa{VN;(bpA&1>y&k#o)^p%E-U7l&X#Wyo*@7nqx}HY=eUvYl*)n8RqARAu^exs- zUtfQRjy}R`R65m6>lsmucL$A!6Lo)^NVcy_3k|^Xr*aa=KV0xX=!3x z^XEm)$kw$#&z@dgF{EW`OJuXcV6Jp@_zA=nkE`svKf*BzLTZ2C`(pM3CfPpE0?)1+4yiTY-EYa&z@g=M zGzE{521^lnNM9%db3{Ns3q9l6Ptq7xc1E4Y0A#MHWsN-Q;EBD2lc^+YYj3+^1`I{y zfqEpS@DM3G{_tjOpQn~_&oy!K_xk{drNL+awVxDyjWFmZL{E}5(XOibarh%FEG)O< z4do~WCNh$%SBVQoyAAUu0RM#sf3R(YMm#9Y*W}nn}`|Hvm6Q%>Un;-e`CfAhtT} zLM<*(cvyHqDB*AAXG06=AM)D0w_(9h3ML=HK9>W^ozJK~i8(@XeZTG#(Grm~KwD(a z%cle|B-!0pg@z=mD&nx)rv7=9-%w+(o444Ybuo1^8T(H3b1wade=#>rBdx}Z9gscMnY{w()r`eLK8#6rR&v^m2fb(2zt`9XxA*|^FvGk zRQiG58Ab)YXa=#aR@F%WYis`fwdg1h>qyNvvg63z!#*V?-ek#tyKi5p816h5KwFTk z3SneV+^@EFIgpciCembkqt@a=*kwCFz#|G86tWfvs9~wDElb|1TE&9Va79H$H8wn7 z0^w-*G*p@-e&@qgYYD|QAU+qDjl3&4^Y!GU%!kcJ{IVa&U(6Dm+$7qrC^o|im{A!X(nN8N&c|Z> zEl?M|yL2L;UA;}PcN?icnTjPm7=UX@RcND+Vz+JNX%*RWEABhy1OjU!l*jIlX_Y#} zFeBY$XK1XmN^9GJ{$XROo-_HzsdqC~&QD>IoebiU%3-_O7qsbUv{@miKI?TeU;Xu% z+N|UGAw;*bvF6tV4-7*D`R@OLt`7^g3e&fx1af6R`P3|trl`Wf9b zYxv7!0E7ZIj89dHJ&Q{p= zf6mvEZv#zW=7+&IRdiXR@`{KvdZmX17DBjQf(Z_|jKWS?rtC_bwg z32x~%$>?@u{Nj?~wy9f+c^>DmZ2Nb1B^l}kT3VVJ_^6sV7uro$9txw@UE&DPz1S7y zQN=>y$MFYlT!-@ioj+#5&3nDaHkcnqRKo%=g5TQN}V&XPp#yiJ86QL2k^}_B(P3>%dcvLAoE`5F-ZDo z7@JK61y+Ya6@lGRyJD-n*UkFRrAM7BGWSugsFTYencGHyl3xMK`mUEMLjF_ogX`I4 zl1`jUWjE97yN{1I5pKf$Z5thp$#Y@Kl5 zysNMk(WwA=4>%PaxR-NJ?<0e;umL}s{tuMNf(OZk-FwVsXs1)|l6-+>`0M zW5r>5m+zGYCmC!+lrXmM3ebQD?WiepzG+8hsXDJ+UC^BK7tenr0P3@$IzYO+^88{fvrr}4Ib@3N{q|+W1N%1v zANGE%Rf0e@&r}Wmc)|>C32@#1hm8qm5Rg2|RT+5ByO8ygP2;}EEJ0S?{10QN@TV8Q z3H@!Z=VJ1%Lx9Jb1mr$SFBy+TQb{?(oo);u`;mGvT5|4H>gWDuVv`C*ko+G>XTcCv z*LGoP7+?VD5(N|l>Fx%V?rurx?(S9}y1PqY=uRbu8af@iyW=~&-%pr*&YZpXy4Ska zKZVG|7=r(4#u%POnrL1?dXRMnB3cr99V99i2=btNtsD~@Z(6EHnV~xD21jaNk z18V>q z3kE}JTm+<3u}c!NXjI%U?|~U4!UeyivnmJEYAJOl@b`m#RN7{J`6mZ@mw0h?wC&>Y z#@@CUJ;EcmPyftj^|{z=E^rOklKP&J591r~*gTurdv!)`qLlGIH#IwI2fb`cr`ff( zn{V4)=3rz;ES{!lI(l()(3og<)c?g{+R;lIa{bk{`@LZ>#4nye#v@MLnMdv>VNqm(U zXL|U3z%wa86V36&++0=_c+}J-D3%RhtgssK`%J~3()%tTbhchYRZE~b$(lqw{awh` zi9H284*Ouvaw+HvoIxrA9%hSdZ|2hOw<)t!H*XCoZ}G$ zbgz3FXob3Xw9i#8Y+tsIpzg-&FnRc&@j^ed*wwS%LoAnWO|&1nSi&j2aRO^!4=|!g zy*$#;4MGfHvILk_A2l5>R}%^i!BBJ+g}r9aw?}k+})Sx zsS^IH_f*Wn@6gD6AyLLURZ<>wyO!l5V5JSAmJRQ&fK6m^u~o9exLO951Bc6VQyRl)GriXsBPuOdN_1;hBqlOox> z6u{r0zb=nJhq4JQbU%msR_c9_og$Uu`on04eeF;@QrS%L@dVt7=1t72$Zvm8xum`C zHUbGUyV!ZFp0{GK%U!%T+XOw|69kjW=M^@S0v0ts-_M2)p!^>JRlhA_Hb36o7eH#) z=UJDzbW@Lh7gsx@5CLl}DDM4-O4pgiuiX=E3qE)A%J2S9^}p$j@w4!4J;$uhX{6z~ z4zjKEt?MR}=zY^I)Q6(^Kp%Ak0pY5SLAR!p@KO#VDY+bJN|wi1Ixi!|q1&@+F`pqs z)AwOW7u4%c1DzzX0{*J{`hbwAE5_XrE_Dtamb#L%0I?4a4jL=Fb!6nGzJEt&{&cP;!s!;a_ zV6{}G>SvXTb{_eDt5|9_zi^)ZPt(ZBghGXhIx>vmKL((PcwgeIbk;?{9W>E@2Whjh zRWyCGvyn5CMCAKZnp-IFi(U$;4C~lA1t}xRSs8E*Nk?=6O{zyQv(I7+Yo-FmfWJ8! zhlUF$tSbhssONw~jpcbwmRCe5$iw=UpZ1TV2yRr+Cd<0&;HH831iP@M}NKXc? zMNmHiY@wJ+dNeFf4B|b>E07CFKrveYDVm{eg9IPmQ6|ZM` z_Wbp*ZAvRR_m}V81~Mqw86$>6j4$_X8aHTQ*In|R`v+cV$QmcjLti9*3t4+@tuoqC zy;%p5dfb#W)vtMan_}p%+l*9@LrcMUdRaf9?IvVYO-Xlq0OnC}@96yC5bWI@Ia9$u zKfZCOB^xaH|aYPSiBlDvq?1anP}`NCE- z=|3D^_2Q-Zj~y<_BwI8NT0W z|NJ_sEXJ}z3sUokl>!yi3x3;UIXPej^(@9R2(He72~<6*=IpfN`zE42?ski7jeEoP zSGHo+w?f42%I?NSNQ$oxQoo+?9klq|MDsR3j9+c}MArbNKKF0NxN)=NuDE6S3h}ad!LCde8I?*0&OH&_Yr&!(D5& zDwN23hQ@V+f|dK1pInvIpYk|dYLei=-2tY?rJqw@$V?~Rq6Tp=h;BpY+487EJX?9usmB1jRM z0or|)N%$(w`w1d+S6~itDmiPz?FG{C6P;xp{#BAJDAGu@Pc!(w2atrRXL7F!R#U0C z__OGt7+}&7_)DRYn`sG$1xdVX9`)k=YB$|@f>qvuR6f!UtDH8sc% z6!dadc+cGmkMTz4CqE*G9Rx_iLZOOmj`=HuE() z6VOqM#gsFB^ppgqP~isT)l~|`+w|Kup)H_E6qH>sxn>FAp3<#Z{F{DK$#krz@1shZy@Krm6NU!w2pXh!ZUzJL8$4&vGB4h5fTt-C_)$>p-nFMFQjr+M||z=0`g~wP6>WQpsc( z;5Z-HeM}VdiY1$UR&1Kjsk~sc&wQkfew+ ze8rU4_ydIza$lbFzdz0EWef zxYQ1hJt?1yanqekMR601phL;mPSmgZe(n02YbeD4>hx+GzKxmh$db~@x+m(W-E-5O z=M$?dY6a~9=cijF!;4xGoTfK-&a4^*FRLha-wX9Hz4y;a%3B2ps=5dS89Y6u7P%H2 zoSY$ly0EZadsPxXHk1ghhr2=)!*`@rv13LlaRIrWV1d|A=4AhMQOohLUP@G$!`+3| zlH(wy=1Zt2qsO+z&o!|*1D=N#3I~Q5J3Bk$?>td`DM7;=w)69fdH2yrt>?Z%=$A2K z+~aUZKlS%g66g`^Xp+vZEt~oEF7I17kz>>^y|`DSLU#6`;wAlJjmxEq;6E|cO{qX8 zIk?*jn3Dr;yPuMQcQC@e3#!&s@VG^=ip_1T)dN-+Qc@xcbdPm=R;h2zD=GQ8dSrQ04Oi9G;oB8s1_rW`L^g>2;Fd_iw_Z2g3Yjpd zMk*x8e8s_!BCAjKeR|cd{|qXb%pk}!jc6P?Th2J{%vOl#lIH(L@idRx)JB6kaiwQ> z;=QK0E19x*GIPW1#&4)bDf#==#1OWDuk+be9ps&`-e&_xahe4DIJZdyi0+-<< zmFtXiZ15Dr-!!XvUg!;I`Nn9GsPpP3XFk;ALd+SvBbiK$h1%$S;90*rhPO#NUCT3% zM3t||DB--gP`+0O2hY6V=z&A z5Fkw25QXm8p~WQWr0i>osc9Cpe$Jo@!KxCKb-C0tDXCu6(hXNH*Vw#x*J~^?kS9jV z^kR+T5exK^M8Q+*=i$em7eMv2BKhr)=(L1`OtVCk46I0m{2`jZ^SQtlWtKZM2CnIM zqj)p@U6IAWqspLoV~zIPYLl?t`fNdug|H8K__pU72c)tDw_kf!D|ZHYciZ&6MWqh( z7Q*7U2GA1l0Q@ou#yfT13oh4KZ=ii9DNz$;d$gy}ExE3<4LL{*D?S!+tUuGAdH3z} z!>bB-^4q_9tZvn?_2vnkWV>HXQ6hq~SP06S!K?oP2F{SxW3*u^PG>)TP2vOQVvtF$aR_CN$c94}6 z+qnA!((Y{T1gX$|1|h;!!0n8_*A<_xc03uAmw^Xces;W{f`-oY*Eku9;$nF@RQoAMd!ZxBiBoM>>`ubk(VFal zffK)d2^-KsOr7as2CP3fBqZ}Yk~&E@V`qL1g09j?Jg8Wx#Bf4_>vV(rh3B9B5!-; z?}Kok1Kj6N2nzUQCuMxlqJt)j$P~`GLn8h3mawzX>&L z=RqBk$_B43&p%FcfRUVymp^EC*Gm6#b{Hazxws}U`emRjgow3>*Rn%Bi@U9Byb zq_5c>ZR+{sos%}LszVBQg?2lcf&CTf!LJEcz+r<<$2(^d!^G|tn=@ahM##&VYFC>B@dntn8cJ`fw1cS*Zur#Zx7}54>yWljW#j$vWCri>(vF|D<ru(5MvO(p zTUG@&9~0$Brk!qbl>Ie$zeJBR{r3^c98D|owPo0U{_tqZn{B6CEyqSZBPl>{qBiGM zea^|MYyxGnL2PyoCK-60_?h>255@Yv?sD&AzEkfq!;b0Y_atS<)iBn@2C{mr(}g9> zERmkuJT!njOcwjEClvGZ2Tw<#Y+g#R8qak1q0|1Jb-QK1U(iYOJ8MRxBfVlp^jvG- zeLbp*TB)={kGg8gsGfZnQ^1z>KO4Lv;ST%2I8~y<5w>R0K~jMz%#J@ zKZ&<5Zo9LeAF&=pY#Y~x)8B|S^L66?eLnoB-1Zz* zUZw3=r$SPK@SyV~38lKVreB!>woTe;1#u?yvU2{sCZg@QkCwFqw?{EWi2#;?o90bw zP`&WWJ@EtcKj$-0uV4(ctce!rMK?}D>d!e_uL}!tVGkRRedlg2;MgclJa6`EU=_^ob;{4j%WDAV6;b4k zV5IoOrAWV98*q~}XfG%q2QqctXEMH0TsrA#Y@EF4&nY0G59RO5>NNS2DfmeT_9y+} zMLOI0X5wX%$;n}X%$G56bxgbg>gUn$Jcq>tODn5C9|h@Xe!Tr)YHj?Jn1IX4FS-F$e$0WSC5fAZbkzC29A+^ zapHn%5xaK9e?%v>H)wu7MFIBA*Z6z>gbae!l6HifKqo_j83S(m_kb+9U82I7FPEnb z4g_3;xcZg2<$F`JLRFrjXHv3EapX*{HHqG=!ouJre{S~*vDijxA#YX+lvac?$zt60 zO7K97Mo!i1q^wBcgsjMph{QxrGDHsJnV<=D*%<67@z82D}^ajy_Xh zpFr#)+0(hU7UDl!@s`e6j|8Dv`cFIl$*d`RH*nvD=p1;o#2R*5& z&HDnCU8cLcFCstrJ?AW1kwy*n@F{II+vBh5JlL|>2DUbq8}x5BS{n0~xI2A)(wr5T z7PDWriiZz$OOvNg039Uplc}n}2+!fGn@xUuL6hnH|CI0V;)|GbQYLO_ZNZZ zdFZS{mS~Ly0U&xg@bTs};SsLvr0$H03o)V>?D*y3K2E;HO7~KrHw+>5Kjn>4BDi$5 z)v7a>SD<*KfWSa5n}wZASP-!;lq2rA${Nxh!J`0>!v z4`jb{NVhvz+K>SCZMCg#WE+)v4JYfKTR{>0?q{0|6;#fA` z%GeW*SBFp-Q+6y8^)Jb5kN(?RL{neJnB{%m&roU6%AGZul^xUXLJFrM(tW}2Sd+qU zVD*y7lsEYH6vrBxvw(n+6rh_@qQ=5lMKLz@<-@O?-NVqO8ce=i$YK7k&zV$8vrHs= zIpbPWSYk|T-~Ub}9vrlMqsEX$*)eEE+T7eUx-Bg$106C|?i}t0@-Ct|E=mI!Lfqwj zL>+76&)*@heaV2q_~p_#vT}RZpcgIJ`%~x@L|qGra?YfyUf#;mB(veei5`IDtt1_4 z=_5wF*^29Cn$De5nHfr>#Bn^ZP8Q|BMOmm>z!m+QemwpL%$8N@g8Wc3j4i`B}Vg;6QIOxZ=Kgm|ED|uvf ztW@(%=;a5yQydk?<7ADBFc4VqtI8R!#QIl6;^;p?21M(`k{ z6V2X0)Z;1i7t5M^))OkTLM(M@9QMXejF%s!qR61a4G;mJ#r^+V3Pa^(&}MRF5GLsm zgt~*OLer1^x-jXe^tdBpLo;g}L7){Qu(go&${V=;f@3yGh2K|(423-WR}<}CTAmIF zG|@vCNb(N3;CZAA55c&6nRvwJ5f26J8ZQ|meG0W|EpEcB#%{~X+4Ec}%J6RO(thjj zMik;qP2&_6$-^i~*&(x9 zx(6^OjMUap0(Uh|_YJN0W|tqm}d7Uw)2g80HGQ#UD%fv8%(`xbsRJY(lJJ}4X8;sz%!dg%e;R=j@bl2lg@kKx`UYU-BOVoaex6woNyZWTN?8~BfetS< zt-JXLg3rIXDG#rmyoJt<3DHkz-1uAR?mcr5vQHw)_f8sZbgz}r+Vpl3G6gJGG6HoP z$j-&=UHpIbMv7z*+&>uEgr0u&imX-H>Li6s`>Sg<2G@R)BaBjE-P4$*Cu%TXEtYWk z(V=iGTUuB{%M|{l^qDJnMb@5fI_Y0YJMFk>ffTPYlFZDaf}$e4r!ZP{K9+IZjQ*#c zJP#*AZ$9F=0$~u>toXPqn7~;Dff7Pa`forwncQE+Bx{9!T1t+A0}(^xr%;+n>o zC4Me!)c$E+Xd&*hsGi%h(gZ+YlAZzT7Jvfo2NFobUW*>IK_`6#JscdLrA@6fnIC;T z_*g?ld@sX2%-XUQ|9<1+aIXyYye6u7P=CkE`CeDq2Z6uAs@QcnqvR5l6`qAp7vkYB z7`GKQ1n(4p`_+)gRK5jXD7m?Fh?u?4vjvO+a7;V@>;Vi@AF+x{(%%pm5T?(9((V4} z7rI@fg`_+*+hFYv8O zB=Uyq6>&C6TY!kjohep`MZ((Smn2MAJ<|&Nz}jmKp_!=7YPY8DoK_8@^yzhHzSWtf z%tvHKx7h5jq#Bt#kmpY1%zK5v+LzjJ4A8f_%Uh0xr~o>I7N8FQ8T1mE(OHA3n%f|ni&oFHjmG-IZ7da-sM08*cSAstL< zki^~Px7BK)*q4qSmmxMK1DupJ=rCRPURb(F^aL_)QpV2nAL0p+;%V@KMJ29 zN`A-dfd!(7G*E;Sp$}i3W@DZ9#Wq2#m>W*_zMH8&TB9=~f!g8H%uE<0YZx8t@*Nw0 zrm$xSQqU5g4TEI0`ZcNR7i9pQSmEr;$_**MpY%MxXD<{s;YYUDPJI&E*n7C(eKu)f(Wsx+|q_0#kHW zAM%t#=!)@DmBl=0%gTp8N_M7J8~R{p3q2x+^)eWtg>Qf2$*ToH+*f;EWZ$IsLSb44rvoW@>n zsDyjL7_ba0d(N;K=vO~5)Qp}OQwlnuON7sZU0HXAUWeUHkHsIyD$>k;Ji;9Q4-EaN zhC@Ty60eKlQvSU@^M}~Q5W2J(zuY$DI5f0{xW4qH4Wn6|*WKOS)WwBBW7O3k$Pz06 zG@@MerZ7<$9he-QWDtqf5y$=3p3J*~ap1x^EUhgiolWi3>pzKn1F&mEUM5Bqzkzy= zyt<|4Oje!!DzyyVUPDNbsNT*@BcTp<5(Fb(ZEQhPGX6Wjh&e_xch+d~t1+oYD?4D9k*x zS1tMu2ru=8ViMz$W=LAYV!ROD9SE28lz?FAlCJe6EP`RFsIIq?<8c?aeGm0~cDkoH z;{AG+!?s%Bn=gH*XG3OkgLy!(Ith)wE$n98&fKB~ssTBF zqBA|Pc3C2e5k;l8tg*R)7wI}cYeZi?rc^6td4LUh-O;hlof1EX0~!XMB4Q3A`D8h>#WW_r{^Wh!pI33RonD5oV=8IO zgd4}UZXTep?dV*2@rQgKx2`uXM~|AUkQiWUVAgXNatD3m8v84ex+p2w<$Em=Va6&0 zgJXC}k48R4rgeB?+4!g+{eGdwDNeN45>vV3JbJ!vn4{VuLAH{>AaNz`01WJ`))Q)K}uFn>GT zh(N_QqHi*v)|_$JJqh-Yh5uMA-}GtN5(;*Q+sBCnpFcTDs!|~T=2NPchqMFdEH!FF zTXnHpp**r{5*R;kr-5je{JngS>tuPai%3?(htrA-wOp@PF-&w-!iIR(#%S5sQaCQi zdfw0eMmDJ@ct#kQ{MWuyYf)@`?&Wuyzn9ZNrW8u+!xHQ9IPx<9_OlyNzAZ>o%iT+H?>0*H-h;SG)G z;}k2#Ob${IWG$LYxRfaXjWllRHFg|84T};%QhBGl?@u#YVe+IY!>0yDaJ}rUbHn~g zr|z|0s=F}N>akF8r|=VJSdSmA#{*6}CZH}~EgB!_1^fIgW%X}Wj+cp@F|*QCfbG`{ znQ-nj0%gRkkF8F~tBuk>?9IRZTx??G;IYP0Giti?k@74|@IQ#8EP0{%BLLI$hXeh+ zPY;=Y+g9?bHjj6sZ4`Et;udNfOL)(T@(~*&zP3RvCjtX1Uxj^EZD^B``u(PXV+n!h zh_n1c&!z0E$R`VjF%{#M7KQaY+J7l30(r`pqFbMR&BkfUUc0rM(ZK#8j|v0m$ArB# zMWyc}QzYGoVeji+~iTU<3BAfx9d%3)V=iT z_c?8IhTv{3>lz z5k3*XwC5DBOyc~*ZlE};46>X0WitX@HhkOHx;Vw0s{K>(<);ERQW^AJR-pmMyWhDe z$RMaQAIWFns(yGVC5IxP#3*F1l8^Gal`=0Xcm?H`^+$6Cldv)G7DcHlN zkr#_jN9D-6fEUx0Fw364yhj{3O|WDM#EAYJe5z^YI9^6PuN2UH2>GOQom+M+6H5gA zU8(djreM;xDqL3 zpVdm<|J}7UVAZ29B?^l9*_U4Zd=H}?Dsvd8icP zS&CK6@_AESB#m3=vBH-w01Wa;uI+RuwtYZjB1Ud&Jh5>>)=~;myns2gA8k|TL3^(! z3T2mI!yaGwzZMhAbzz{PY@HEFm)%+!t#dQ>mO|`y<5*k}7XXw6Ai0F)oOk1Q%mvz) z^hU3ufpP|UT2UL!s>AW!$zdMR%H>EG;B{%X;rh~QRT1}02b1E3j|!!lqcwN-)u2fn z?I~WcNLVGw0PbMZ!iVWN9i!{AED~b>`U2`Yj>ny}dOp;cr`Xx`?E2miS{5 z;2hQAJM}wdkg%4!jIHcnD{BM;`2ayV>^(5(ZqE5S_sx#Z@UgFU7{)PwxM#^aCH}PM zb{0cOvv-pdYMVgcn%$WnT^dynSri)ckJfgY{gIAr3S`p0`O3Pj()~%J_(_ zDYHV50WX$L7v7EGO4Ieb%eFd{3Zs%ayze1s*HPAOu##Ro(~lJ(M_IzS?e6*shN) zCZ$TQ_;y!jT46sY=50=p8)(tf?an0O&T(hR*eVe5;xjE`JT%%WJa?8aMAr9PYV@Ky zGpbHRCMb{kKS^7CN~kakwQx30W@aWyR)(mN3)$}mH#0?kyxmi56{4#PX|a6TJ%(2; zin|sVQbq$m0sThQ*4B0=9>^b(pvxQc6K!g~B+!Ppu{q|e)#^LhBpc1jW;vYaIc(w0 zPMMp0{LPY~8$T87V)|f+a{UiAgcW)B=RN9@AC@hW6yhi9M&RiJ?CTdZRoBU zCihT#-P)53^9+BD(6REU64C9ww~si#icf|3qBb4$P`k_(Y3Yshe6yf^_dc~qFof-u zb|ae~jsatnfS1$EkRR%$Ig>muU zlvY>EXlti}XLh~R)e}3GRh&vaiq)IOk?FY6aUh?mj);{n%2+cQCRv7X$4VG0;8_4*j--fwO1{Gs@Y;?0>fjv%?qVLMt36E%2zp77mlN9WZ5j$lQW;|gL@BRzFB zuG;|~p-1CHh=3GVT8L_Y-?_+;EbUbEvrG$g-4N`9MjV$Z{#;rYA(fXBA>)#$A2p-6 zS03m^H(raHR|)3evbN}0$`?l#smQ9U2`hdZPB&%iV9}E>K_Zyt(q*8p9t(IfZPdE^ z78<_S`aODaa&he{4Vmk<6jPVU6<_^x2|}<9S!l=kLo)U*g1FKuqb}%#Cen(QHHzec zK`FjVC6|A;*h1nuUV@2rW3ngH4+@9FBl_V93dA9^nN_bgU7JIr>5Qq)^Qo)nHu*ZFC2CQ1Uk zE5PptB#_#+q+z+M^6lzXn-BM}*~`$BqmVU~Y@>d$^_kWOdc%jA z{d1}Kzt4t)nI7La89w!y`dXagSQzxor?T}`E=hB;UP<@BgKj%`#HKZjnuScJ%TIiC z#Tvh7N6U;7=_k}r+b&y7d%^3RUSF47E~;EsaFrg11J<1NZ)1&{d*_6-Kn~Vz&2qfi z9gRenmLjL_`)ADOZ2qEld42t-DGF^3&f_*zaNC#ZwM^;!EO&z{H=9w<4+(*yui*Ij zyq-r`Pq*IBamONluf!?G2Pi&NoUupDkVVA7a2aTM(!LwHaPN)(8v5L#FEF~C4>ynq zJ#>;p*~y>Ub^DHF`Z<$Qw8S5a80~Iic`+D17{9)2krg%zmr4f(Lu^;$WZw9Zcqcda zf|d={GFL#vN9^Tn=6$d@J;ZG@5(~3e%j`iM>=DYOg9M`rENhu!Ee;dp&ixqL*{s_N3Qq^quNen4xSeLhB zF+AB}BWP!!kEctvk-jxq(9-~b0IlQedUH37*7Hy9z*eF~J5;=0hw{IXXXbpo3=zJ1 z=Ch(Rli$>VXz8DN*VUx6-M7A_;s&6EV>VeHQNt>?TUjx%f~dipKZgUrmktL~j{8{P zKzoE4kxhnXirI(2-zq)Sa1AuSc8Qd8ZQulK66(wuqVOE?Sf89rb3jNp&+UM3>&Oe< zO4ULEUR&%RV`0b_l9~WA1*`xoE#QG7x0F#B?i<1}HLrIRBDZR%1GlX%ZkfWpcDBQb zN#D7x&jq-V03;Iaug}l0K_La4iIrG?1;LqfGBPyI6rw?Ia14(9&_r}A~1BNcHuDOkk z#NDRJM$_t||DFDM9ZgLFt44GADgM7AQkh?lG36+exQ$yc9awg9jY-*=^G^f*dCxm; zXa5#nJ(lfLtHPL9lqgxMR7pEYRvAAQJz9RWINq5b-fxiZbV}cUXl>hroAZ3>_V|=q zMO&=BiGusnFkUsq!*H4ZL4>K5XbJc57hQHQG4ze}TT&#`^P-vM5t*X^t?WmwZ)_9% zaD@9xDXx7VVPW7LhL?}vjgOXGP$;0@EIQWVm$MMorY=PGF$}|h`0EJTE zP@$M&0*&hT7%FRZdo>rvt~XQQBw{QORDI|^!m`VIX zkH8NWB9WCcuB-|^yaKFr9b!}udw-1<5qm~Q@{lSmrZvkd#i?w_7d7^g>_}q@plhmP zUN>u2$l&bWlP5e1-$O2+y{YVhF-n1&@3YLydzt2ZKm*f$>b`7bhbfO-6D-grI7%6k zvikvxjW8*mj13Ul;{K9m{1m$=+b1YOQ;ShJTrvGzDxb8Gx^;oCnmH6Bmm~M>nNk^Z z&r%TK3nf?%>`5G)f{|y#5X7LFYIm-$sfni1w$psN)a=ZwsJ_z<$JHOz3++&(0@ z%KAAQb|RIx6pLvD)Sq!i*<3^?&_F@Ly1aGabHc_;gE)&z-+d(6N7w|?~?OvIuJ!* z{;_ZP%zt&f8qa|wbQkk?dIsS33ari5fgSj2XxTYVcXF1DMR^Z)xldxPiybwIM`Y~6 z^_eoXl4Zu7t8j#pr5z5Mss7z7iUd-UbHfks-wc#NAQ|O|#N_IW2c42js zGHQA&jQKm7i}UlMkH1?)WWLGu?-df}PvuXPLM*{TRHf&Eu{uwSqqD>Xb3G*}OoLst zWj$Xwx$8d-|M_6Ex$HWtPjWNsANnlG^Uah{a`W$~I14I3HA?~IO~NI-z3sNa;E+ea zO!hA6D{wC-F_o1IZ|1>SBz}}NIG=&#-r6|WMa>33ria}AEBiIP>aclP{j+%}WNt=1 zS*648<0@{LA!W|+q(G{-@X;C9I-JrycVVkfTxs|Un&pW{v;OL*)0=Y~lHnX>6?fa9 z+oDa%`Fn(XN~%U%!ogPStjWV`Jr_yINzUAAV011ur#6^_#Tp zJ>X~GAR7p{VM&OGwz=B}>CJ-fA!CJ@W6n|{KpjJE3x>2T_68Hf+;Mi-cs~M`-cGN- z&%<*MQA}PBd#&?6ciVOQalh{BoyKyJ7hkY=+NnMhMTtgdJeZLDn1CpVy>zi%)%6a8 zv`dO~pgw9GO&z#(H)UlKs5E{lA^f5CV_?rEC{V(-`N8McuU{t~Z3%YaEe%=@<$^`X z)DDW$HASUJl$3ivv=)`WWX3YZ4_t6Y1h@m4L}^jw!j)!Mf|8PwGK4pN z`cnXvnEyUjaldrfN3A{}`NTDxOQaeP9&_0bLT4+dD4&P`ENxb=NTxpuMx>q9&i|kp= zx}VzZ%$zKyq>O{TE0J2-q6>xAk7bXpn=QgerypU-4<9H-x6JwjO8e z29Fq8H5#}p)Z0!wD# z^9i4z^Ud41Y{5ttztd>oq6u(IfsUxTI*WL_>!V9A<4>yOLZDcVz&o$?{>22lbfJo# zb7%GlJL_Ap$e6YmVG$kcM>u*pVRFL4YpMDeagOo)mQdfTx+A7xaaUXUyW(QLog+eF z&wTVb=J>n?_K6~K5$okK*mhy}u8YakK0{)g)5V~vvG|hiIcc0n(+44->8s*P7T6hQ4RJ3g?mG=5y_k*X=GQ9mU zVnz|rb`HYC$9xU12HtqL>gYW2)tJdt)*iE#o#d2A3{v!-1BhMV)CE5`d_e3tZ$Eyg zu`jVv*@$4jf}q@56Fgv+4}Ke>YFnQEYGLI%z)qIul8WWuCb6Q#mE!s)es%6sB$e^? zSfyhD>3;j_mfcLSD3Q#>d9SrpQVrDHC7)ZN*UpZjTvC z6&_+RyhAP^l%`{AmLu;$;Uw1s#M-j$0$66|o!_WA@4uIRJIk^Zh47bm8DIk)SMK&a z8@LV!wPyRSAG~`w57lfk6VRpLT*++!Z?ztMi4WKMK#}#o1qH0_1NLN;@0CLh#$nsx z(9zPnjD^O!HjA>$bRfA8<_#KaKSna;`}Zny`9H}$8tcv@a06`z$Y#DI*}#V9?`aJP z%0up4PEf49&C-7HTKK%Vb8wi>FxcN#sKR{-JOM*ocg)SfKsbdg;|k2Y`XyGNaBZ?< zBXZSw_qyY{cMuOJY~6`>e0n?rjmifXcweV%<31`84|Ce5k2$#SjEWZ4n6`fo@07>{)ZIK)kpdiWgwwl)>i9Av8Eb9dIduqWW)l3CrGi-icT->{qPk9 zPeTAp-l(^!DGj&XQUo?RcTQd&qHuPLh3)V)!44j`$Mo@UY`Y~hjq>(sv$xrDVUga{+teP{QbAy1Lu^@q(hsD2M`d=Mv_m-Axv7=oue2~EvA%TIHh2tQ?q4m`BS2umeBfh0ua`ldBN~Pf5j;JZ) z9V(iGTNBl386Jt50{ZruIy(VBX;c@ftjY3B0}!|w^14@|4hvkXuM!8`+`x(CHgHDe zFE*X_NL$qgje!Alg+WOXr&p`D*rN#v{3kV^agwj zm7?*1pTeNznl2uWvZyaL|JPUN!zENR!sYUI{wc%Wex;VQ;_ZZ8Z)@9WEel#8K%(X8 zM&KW|mKJ2+j1;`WGajPc8$D>3pKqI3I%nz$zfP0aqH)T`5fT)P@qaiIfv-|6#@A7! zpUzOS0uE7S?Sl2&%Q<cKPo9!IkdXpE%;sPBNyKJg-jpr+nWPznkW2vDLFi!Qibt? zHWh>*v8SZdp`R*rTiqb$cHK6JK81j6*a!U5L#Dlh5wYGJc?}eSCcURr2>+i^16v(abGfU`Vk1)t3-8 z;%!D!T={ma%&6TEZN9C$ z2I`wORdSOaaDe>bU(j}+X`2C>ZiHf?*EpC5xZ>t$DMjCR>V|M%Gj{VY z@-*(93-AOI3g$jt%dXr6I*iv|yLZjGo|MxMr%qWt8VEAmX=>gQnqVkh-X7+?KIYPK z@klEdxgViB$vpNaMv5ai*%AVvH)0-1A}6B)VgEmw$~o^&ko zy5?MtE@o0-JVsn*URt1@tb4Sw**&T`vtrV|tut6vh1J5t_kBR|<$<5~rTz8e=mdtZ z#*?FcQQ!bSFowzj{?s+P*|a&c{>5rS!Md<*#)EHl9#hutw>Rcne>p7;M5Wyl#ImAS zG8O4);Bhtm`~~xqxkweq|EN+GcWeSBw6pq);Fmc5C><#}2 zQaeda`Ji#fliNgDrGpkHPhKS z%dGWSYLQ-K32vbAmt*KNCKPs{Gr5@`{FFfzTIWZBs6%0#KN_x93h&&M1#$Xo_Jcf2 z_Mmu*S+yHO5WvaI^L`28EacJ1%*^ZCZ?LI*>4BTqDpPJK`DdxM*-~%!0&A8Y5`eQu z1jd&%Lww_$_m9UaR>e#DE{V5z$9}$2F6YedyFwe_qR0PU?ohYzKA=ubBt!jno71%d z=xt9`Eb6RZR7Bw@lu#=ou<~D6VuKkA=UZ$_yQoOpUwe4X*Ex^{o-I$wQ;Hxws6a8( zW$3U3DWr|2Y#W=JfK|39U_%dMl-jHqkdy%DdwTv`4F9rY?T!j-Od{X5JszoyGmc%8 z-rhPfQE*h9OmUyvQu|5h;G{PrXh=YFch$gtD{ArdWVMbV8&`m^)6qt1pxSIwoI@KXFj<~AR-6jo1{Q}0SL+{EE+{l4JQx|1L9)rQgDw|WZ*#hxDf76Ku)vE;OV^KAOOEZB0N_E$ z|NKtd(FP(baAvI*CTv+;ElZUS^uchRI%3EFO(80(RAh0qB!E>O4s9u+*sZsj*dxne zuGC+2p7zj?3>ESylG}=D4!;>8jaxTfEEgX{2%rH;-`TMe{TxP^NMQh3|2y$&EJvU~ z1&iB>qszxO?gO$U0CKNvE5=k(QUdfDq?nuaL0?!>|NGswUsZV?o%5}|G_Hg;RfAZk ziN`5s*Y8Y!CK3EAzAdr*ghZgrA^%?Ta-nZ*o#XS(=5CMsND$Z3q?fy`3gF_dBN4er zkoV$$fog4O=J`1_q!u*M?$#w5v1=G${Pj2efX(lXB+PIM>`u^-H8A(t`GYq*tn}7V zM`8Hb6x~{5U9f4VOJ|FcE-@)-Q;O;yXDmky)8$kf*g^w9ka++TWl|fPS6iyuDS}iJ z_i}>H@)Wf)1ns*Y$?Tc{YZN9IVcW3z2SpE)o?n+X=P+!_sHJUL8JUP`S!lBZ*-87U znEgtdR@A|?wofaGtzd5jrIN9klxWVp(1Y{$^pgPYcphJLu&F3`HSyr!imM4}8q3i> zq`+5`jbHVB(1(D8nT;Lc6!&~p`k(*@%o45OJbUopQl&V# zeVY*nX}CuU9frp=y8*-9S*{Ng8W{qtXzVEo#_h~F^ZitEq^!Ajnq&ysQ? z1EU_1aEgaS*hW$SiFzRNwAweeXt|XL0}PI4veskjyQe^rhvf$EqkXFW#YMO!;nQ%Y zk*>3G$Iw1&!esQO$v^I4V|4bFp}1NoQ+45s+3M>T+qgedKA6(J`e4ACXh~NcNM-g% zF`)p`O*JlZiP80ZU32JpN>Jvs+Cf^OUG>+k8%Vo=uD3B}KD7FNgR=gw z?*GUGgTa-MTgJjRkZ9h<&GCP=w<=rSMGaZifw^sY@3G=67nh*UTQ`{F?jPi>R{H z_-~u&&B`#RV>@gJR3&zQhzDn2Qw*&4N7V(r1R5>0SdYG5b{%1xFPE(PSKtE|cHOsD zebU0qpKz4jd$X(MA%%Mq(x;UgY4?InYMEj@2D#ONf3x{ml)+2kS2@>51$a%AH8nNE zk=5)X9`{V<&zx&62+Qx++v4{*MQRNDjBdqy)&)mmhK)*u33JHy`eAFFm2qL5&7P9; zJ{hNsiYV)i!pFS!69JaG2s7*Mna{&>O?QJn;p*3~X1XlT5F z-E42dj88u+4%6x-I+JEIg2r!GKo$xp+I{&;-r;i}C#sh5RV?aMnUw6@sd(E|X)aC$ z0`;p11OK!IcWc1;7AOSV4lX+>&g>p8^?dAG>Z}`n7TELOdkIYwF;=p=d~-u*>HqSA zt8TC3)D$|ZK&q8rvF4wfA}Yc--S+eguoP9;s7L_nz##H4ajgtMbrxVDkELW<;zz=hir)fLqzZ%$B$;ZU zJOOO0@x{)=>h@Z>2tLib7RMDKO&y&IY?Cg61th5_U1i>K>VVR_`^OduX_r3G8lhm= z2zTh)0WLa=Ow|xy`D3~$E!{kSsWRo=&oxWLJV_E9a=-B$UVD` z$o^at524^QAS96gP_6x6N5bxekqGU9Bj>08)d~1}!&#)QBTt3SyoKJJl-^$|(ii`b(tU0B!g=wq5jGk7kbB z+2hz_2S{)lM!WvIdp0NYm!zNtu-CCa_?Gpf2zyYoZLVX(O+lgJYJ0&1& zqZJ_j=dw%6^TkWu#xiyDYg7NmhOV{`C!(BINEZT3%1mhYdmb=XE|DU7(~P5=MtdSX zP5)wl;F&>TOxB!ZWPHpN;0if8aTIxmZ3-zu>#fjdtBooQIyIVnm6Q@z?VGoQVjZ<7 zeH+&MDC$2(?eLNX;Bb-GSv#vEJF7*y7vD#NX3AIm7^F)NiQbIB0)ep9=n#*u>PpeaT| zgv*%{sttE9&HE$KzV#r74Hq;{)kQdzcdh%D_aUb$2m71d*|ui4F^2)`e(N5}^)K8P zjiS5iw6q02)`Yh!Uipp>$_-P4pvSy`K4j>Tf^(J)m z$39+HV2M$bLQVU@5jys-UTP*dVRsrT)q$dDkm#13-sbhru~YWe{gRN@h$H?4&_kzP zGL9YmOqaX3({{rv#&wL5j@m#^Mdj$a_5}J<&3F<*NW1yX?3<_4vKEwaND;e&I{Cs5 zoHwkGKiIXCJwZ|D5*~NZp8p;}FRSj$RPvBDVc@%v?LWm>AmJHh6{Ez#2f0M@UY>FB zs4QFJf!P@x<5fXpT!KbbJFg6i!sQ| zN=Zx0*8z4E-J@Vm9Ux=+ht&ySM z(Du|UqH${A)d*9hX1o}y#V3s%&L8M2d< z+_i2?p6>4M!F6}wi?q$v703q4xJbmL+f%K-6{j};K7Qy&eM6+ZR5*_pP1C8!F@QW*Em>Q;*NHyddBoC9%`6HajLhkG0? z1$&YJ62Po0RbNDtdCjk1{b2A%2sZq26dUzFq& z$Q&2i5L84ScneGYu0!(6Y9`X~=vj#77{%YsHkxj>EfclN{^Jl!9*ln_Hv8|xO9V`g z`j;w%&c*dT3;r0wF1CQYipR`dN(@}Jkd1;0g-Vvw3ex4xvhq(OW?)t-UIu!?Ei(JH zlw%X>e%~ohdvG4TP|IW{6tOd|mwNAZOVAsCN#;6l<_FDA_I>2s3V+#wT(_E?>>OO; zl}eHvVo%ef?9s;}xHmI(C||nuwp=q126R%eGI}^~@Tp~a81oj~9ErYVzHnaQt6$2JKMPrpTJBA2yXWpOw2+0^)JwB{AY9j7cQR zYzWq4ULj*s4O{{u;2QsJ7A6i3IVq{IYUOU=)9XA;;q|?^80jCbQ|8{)rRV+15o##k zpQrD~PuOWRtVA$WwowmwR;iu%ZyM0fW)c zGn~X6F{DF9j`)jf9IMvc4z2htMUqUk0*eRcMo86fU%w!cScMQY#X(6C!GEz73;U4p z#R6MT@AJ7_FN=m!J(M5g^ihg;z=pS47lnZ!8zb}CqaTbb0Zn=ZN2$j2Jzw0Pk zDd~Rx7g5;PnJ|!9Ki^l2inDdfBK=N>Vdf@Txq`rAZ_($1^dlbmJ?6X3zeyJ9CPv84 z7|$COyxnNbthG;nI%~&nXk;2DoF>LrIxKidG?FrnR$40EmTR#sL7TrUv6Cjqi#>i)W` zoU2w%HV|lQ1;@^SCVM7Sk_ov0-a}9bQ&1prs|S%m6UGy`53igfRF2>Q8lS5%si{aW zKYjp`Uz0O4#Wo2V?Oxez;`t|UmmjKbUyzMq9NqEd0FXhS*aoh6BAVyv6G@WkcuAG+ z$^u$Og?qnJGpxgy$BCA%lMuC;ot?GryaSx^ z=u;L4+@ODVEiS$wN3)GE2Z*VO7Rqv*bKhAK3db6>U1jYq#St;pg|nIb&qH%xhXaE4 z;!5G`B~8=c7bp>PnwZKn%Of#;r#4*W-RwvQto%dwe5g$)A9*IB^Rp|ubD*LI-1rb7 zc7qL3tMlO(!6WZ8!rCu=fCGxxYWJ##^g1m`Z2d?cqaIw3p1$Aj9C(0W_FeBeZ-OFSb>{b?utP~nme*T+}UBrW9 zF%=B@D>Kft1;dDB9OAlPCNcKir#FgNou2+tG3C^GtxfU63Apcei$hg|yBwDI9&c)% zkj2hNgL*BCQPLreeHM3%GSgU5&Zl`xlMAbG?JHPAL}VV7Emphi0H4V0%8-gK_s7(v z>X)kJa;fOI5x$Ow8B@QQ-{6V^OAXSgX#aE_@pX3#2Cbm()fU3T{K9Ib}%@`6&#+`yygD zxD7;d*wO+h3;aC4@mKPJCbr#iTp7x2jK4oOV+|Ry?!2lxOw<^sKM)#m7R^mz7h0Ms}l) zXeUZP+!TYkzWOxT2iL1q^SUd1fKoaiKLG!q0mwX?PMqU|Hn{<&bYXE!dWm#-1-7!ybHSZoJHwluilgRTZm z_>)9
OfK=f3p%4Rn7u>@J64?)LsIFIfa zNx4E16W@G$cy6-}kR+bQte8MPkKDEleXt0xXI5taZBzU5NdXjF14^t2=d0-th`{FJ zsGciIBsWgToI$mm@z#a^9C1$HchKZ!$NT@P0zSrW!)~ekk-qO<+X-t3LrNYa(4MSosteiH-h){I$8pv>Mi}gY0 zBgxQt?DK}VwO@cdmw`Lai5&ag2FkPH$2+v^={`Rf$s6+@4wU{GNS)BdoEx*?e;-K}fOt;iTOI=*RvxJo$ZP)b6>C(0x^b z>VI>3Ha!>@?J+I&l~}FT=7YTLrZ_L!?iqpp4|J!XVKIul8h7GX+CpWU2u}(BvPgz1 z{9mjx%p49Cy_%Uk)t@kxVP+XUv!mIrpz)$|MA?2gpt?Fx$utQ2r`?L*HZLtD3YxA* zF%W&Hivc|lOB}u*Tbnr#%<~va&oJxpA5kWOILX&^Zp8q7j@rjThh%N zT6^@Nz*n3OKC$y9-0?wBXnKbs*F9ISv|LpJME4o$f#HPEiuY*7SKzR7|s`3PPV7P=!4BUKK z>T)%JM)GoxIj@xMc&X_2tDHMG)C1svEvgzBRV7kn+;Dj>RYBHoHQr{LJi`Y%-@K!n z;{^ax7YFY>jMa&@6j2Mk2v#Sl+iYK-H~XDT@vH%ig^>gy<)eiLl*vy70SB$r`<{J^ z^T;jr`SV&ofA|KbS@4uOXP7~YI6vGx$~r8+FnIv>n!?Tca9GXSt4>Sj;IZ`h zEbf=T+?7jOL@g*spzQBNCs>-%M?rz>&Lk_u95nl?b$(Lxx+w+JMo8MBk`>_wNRduZAJBa2gWPXyTSHnQa*Fu@tHK zxWa(jE?_#`%(BUoFfz)`%TS(KZ}&Qt)zC=ID^<>5*KxE!-RX0LY$h&E!+}>K&uqkF z0J@YUAhNb&0o@{g`2@(0F_sZD%Hqi8o1Td57iw|itA5?>8UhMyUF34O^@sl(?)`B1_@EIwcy41wb zL(zmFu@{iC66PDn^DYttYfYc$9V1RBy?n3rJMHz&n`58E#g?hl{T7U1E^WhUmOjzd zf#-+Nn*fzCLQ$Yx|B~e*K7Sl@@?rwN!NPk(rdR~J;`VUfm%icTB*7?zl%i;70c06M zfOd>DBwGb)X3NL=v#t4Fr}#|@;-i`FKV2Y-!_FQ-d=&s$>Uh-@%D>}6ZKIJSnJ+Y7 z=%7V`s8x!8?ZL^7bd^ZEDjNINBcSb;d(5OGn(75M>>J1(v3odM)qf0uRvUC_uzUh5 zoBV0rS1!fBU3MXUF1=rPzF{%x!^Z9m))gs`BS9VCh`Ldb{U}h<$9X@biYss0RD-
c#9b^`d>BvLgoybWYGrL!f8WR#K&~p*o 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 z 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 yhh$_83_@y?p2hP;-Gm*DP$Wkp8^$@c#j1bTVfE literal 0 HcmV?d00001 diff --git a/assets/svg/coin_icons/Bitcoincash.svg b/assets/svg/coin_icons/Bitcoincash.svg new file mode 100644 index 000000000..4e700f9e0 --- /dev/null +++ b/assets/svg/coin_icons/Bitcoincash.svg @@ -0,0 +1 @@ +bitcoin-cash-bch \ No newline at end of file diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 5a4d03c57..9bc785547 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -113,9 +113,9 @@ class _AddEditNodeViewState extends ConsumerState { break; case Coin.bitcoin: + case Coin.bitcoincash: case Coin.dogecoin: case Coin.firo: - case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 1fd4d9b66..eb209490f 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1938,6 +1938,7 @@ class BitcoinCashWallet extends CoinServiceAPI { unconfirmedCachedTransactions .removeWhere((key, value) => value.confirmedStatus); + print("CACHED_TRANSACTIONS_IS $cachedTransactions"); if (cachedTransactions != null) { for (final tx in allTxHashes.toList(growable: false)) { final txHeight = tx["height"] as int; diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 4099c34c1..eec2d8a8b 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -98,6 +98,16 @@ abstract class CoinServiceAPI { tracker: tracker, ); + case Coin.bitcoincash: + return BitcoinCashWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + client: client, + cachedClient: cachedClient, + tracker: tracker, + ); + case Coin.dogecoin: return DogecoinWallet( walletId: walletId, @@ -133,16 +143,6 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); - - case Coin.bitcoincash: - return BitcoinCashWallet( - walletId: walletId, - walletName: walletName, - coin: coin, - client: client, - cachedClient: cachedClient, - tracker: tracker, - ); } } diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index f251f7523..68029158b 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -41,6 +41,8 @@ class AddressUtils { switch (coin) { case Coin.bitcoin: return Address.validateAddress(address, bitcoin); + case Coin.bitcoincash: + return Address.validateAddress(address, bitcoincash); case Coin.dogecoin: return Address.validateAddress(address, dogecoin); case Coin.epicCash: @@ -50,8 +52,6 @@ class AddressUtils { case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); - case Coin.bitcoincash: - return Address.validateAddress(address, bitcoincash); case Coin.bitcoinTestNet: return Address.validateAddress(address, testnet); case Coin.firoTestNet: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 80e718883..30ad99e52 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -101,6 +101,7 @@ class _SVG { String get txExchangeFailed => "assets/svg/tx-exchange-icon-failed.svg"; String get bitcoin => "assets/svg/coin_icons/Bitcoin.svg"; + String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; String get firo => "assets/svg/coin_icons/Firo.svg"; @@ -110,12 +111,13 @@ class _SVG { String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; String get firoTestnet => "assets/svg/coin_icons/Firo.svg"; String get dogecoinTestnet => "assets/svg/coin_icons/Dogecoin.svg"; - String get bitcoincash => "assets/svg/coin_icons/Bitcoin.svg"; String iconFor({required Coin coin}) { switch (coin) { case Coin.bitcoin: return bitcoin; + case Coin.bitcoincash: + return bitcoincash; case Coin.dogecoin: return dogecoin; case Coin.epicCash: @@ -124,8 +126,6 @@ class _SVG { return firo; case Coin.monero: return monero; - case Coin.bitcoincash: - return bitcoincash; case Coin.bitcoinTestNet: return bitcoinTestnet; case Coin.firoTestNet: @@ -147,21 +147,22 @@ class _PNG { String get dogecoin => "assets/images/doge.png"; String get bitcoin => "assets/images/bitcoin.png"; String get epicCash => "assets/images/epic-cash.png"; - String get bitcoincash => "assets/images/bitcoin.png"; + String get bitcoincash => "assets/images/bitcoincash.png"; String imageFor({required Coin coin}) { switch (coin) { case Coin.bitcoin: case Coin.bitcoinTestNet: return bitcoin; + case Coin.bitcoincash: + return bitcoincash; case Coin.dogecoin: case Coin.dogecoinTestNet: return dogecoin; case Coin.epicCash: return epicCash; case Coin.firo: - case Coin.bitcoincash: - return bitcoincash; + return firo; case Coin.firoTestNet: return firo; case Coin.monero: diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index d50a2c7ea..67abf6b3f 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -6,17 +6,19 @@ class _CoinThemeColor { const _CoinThemeColor(); Color get bitcoin => const Color(0xFFFCC17B); + Color get bitcoincash => const Color(0xFFFCC17B); Color get firo => const Color(0xFFFF897A); Color get dogecoin => const Color(0xFFFFE079); Color get epicCash => const Color(0xFFC1C1FF); Color get monero => const Color(0xFFB1C5FF); - Color get bitcoincash => const Color(0xFFFCC17B); Color forCoin(Coin coin) { switch (coin) { case Coin.bitcoin: case Coin.bitcoinTestNet: return bitcoin; + case Coin.bitcoincash: + return bitcoincash; case Coin.dogecoin: case Coin.dogecoinTestNet: return dogecoin; @@ -27,8 +29,6 @@ class _CoinThemeColor { return firo; case Coin.monero: return monero; - case Coin.bitcoincash: - return bitcoincash; } } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 97b3d8896..b7816fa5e 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -39,13 +39,13 @@ abstract class Constants { final List values = []; switch (coin) { case Coin.bitcoin: + case Coin.bitcoincash: case Coin.dogecoin: case Coin.firo: case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: case Coin.firoTestNet: case Coin.epicCash: - case Coin.bitcoincash: values.addAll([24, 21, 18, 15, 12]); break; @@ -63,6 +63,9 @@ abstract class Constants { case Coin.bitcoinTestNet: return 600; + case Coin.bitcoincash: + return 600; + case Coin.dogecoin: case Coin.dogecoinTestNet: return 60; @@ -76,8 +79,6 @@ abstract class Constants { case Coin.monero: return 120; - case Coin.bitcoincash: - return 600; } } diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index e986c9a0b..2cd55bea2 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -31,6 +31,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get bitcoincash => NodeModel( + host: "electrum1.cipig.net", + port: 20055, + name: defaultName, + id: _nodeId(Coin.bitcoincash), + useSSL: true, + enabled: true, + coinName: Coin.bitcoincash.name, + isFailover: true, + isDown: false, + ); + static NodeModel get dogecoin => NodeModel( host: "dogecoin.stackwallet.com", port: 50022, @@ -81,18 +93,6 @@ abstract class DefaultNodes { isDown: false, ); - static NodeModel get bitcoincash => NodeModel( - host: "electrum1.cipig.net", - port: 20055, - name: defaultName, - id: _nodeId(Coin.bitcoincash), - useSSL: true, - enabled: true, - coinName: Coin.bitcoincash.name, - isFailover: true, - isDown: false, - ); - static NodeModel get bitcoinTestnet => NodeModel( host: "electrumx-testnet.cypherstack.com", port: 51002, @@ -134,6 +134,9 @@ abstract class DefaultNodes { case Coin.bitcoin: return bitcoin; + case Coin.bitcoincash: + return bitcoincash; + case Coin.dogecoin: return dogecoin; @@ -146,9 +149,6 @@ abstract class DefaultNodes { case Coin.monero: return monero; - case Coin.bitcoincash: - return bitcoincash; - case Coin.bitcoinTestNet: return bitcoinTestnet; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 647709190..3d01c6f61 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -10,11 +10,11 @@ import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart' enum Coin { bitcoin, + bitcoincash, dogecoin, epicCash, firo, monero, - bitcoincash, /// /// @@ -33,6 +33,8 @@ extension CoinExt on Coin { switch (this) { case Coin.bitcoin: return "Bitcoin"; + case Coin.bitcoincash: + return "Bitcoin Cash"; case Coin.dogecoin: return "Dogecoin"; case Coin.epicCash: @@ -41,8 +43,6 @@ extension CoinExt on Coin { return "Firo"; case Coin.monero: return "Monero"; - case Coin.bitcoincash: - return "Bitcoin Cash"; case Coin.bitcoinTestNet: return "tBitcoin"; case Coin.firoTestNet: @@ -56,6 +56,8 @@ extension CoinExt on Coin { switch (this) { case Coin.bitcoin: return "BTC"; + case Coin.bitcoincash: + return "BCH"; case Coin.dogecoin: return "DOGE"; case Coin.epicCash: @@ -64,8 +66,6 @@ extension CoinExt on Coin { return "FIRO"; case Coin.monero: return "XMR"; - case Coin.bitcoincash: - return "BCH"; case Coin.bitcoinTestNet: return "tBTC"; case Coin.firoTestNet: @@ -79,6 +79,8 @@ extension CoinExt on Coin { switch (this) { case Coin.bitcoin: return "bitcoin"; + case Coin.bitcoincash: + return "bitcoincash"; case Coin.dogecoin: return "dogecoin"; case Coin.epicCash: @@ -88,8 +90,6 @@ extension CoinExt on Coin { return "firo"; case Coin.monero: return "monero"; - case Coin.bitcoincash: - return "bitcoincash"; case Coin.bitcoinTestNet: return "bitcoin"; case Coin.firoTestNet: @@ -102,9 +102,9 @@ extension CoinExt on Coin { bool get isElectrumXCoin { switch (this) { case Coin.bitcoin: + case Coin.bitcoincash: case Coin.dogecoin: case Coin.firo: - case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -122,6 +122,9 @@ extension CoinExt on Coin { case Coin.bitcoinTestNet: return btc.MINIMUM_CONFIRMATIONS; + case Coin.bitcoincash: + return bch.MINIMUM_CONFIRMATIONS; + case Coin.firo: case Coin.firoTestNet: return firo.MINIMUM_CONFIRMATIONS; @@ -135,9 +138,6 @@ extension CoinExt on Coin { case Coin.monero: return xmr.MINIMUM_CONFIRMATIONS; - - case Coin.bitcoincash: - return bch.MINIMUM_CONFIRMATIONS; } } } @@ -147,6 +147,10 @@ Coin coinFromPrettyName(String name) { case "Bitcoin": case "bitcoin": return Coin.bitcoin; + case "Bitcoincash": + case "bitcoincash": + case "Bitcoin Cash": + return Coin.bitcoincash; case "Dogecoin": case "dogecoin": return Coin.dogecoin; @@ -159,10 +163,6 @@ Coin coinFromPrettyName(String name) { case "Monero": case "monero": return Coin.monero; - case "Bitcoincash": - case "bitcoincash": - case "Bitcoin Cash": - return Coin.bitcoincash; case "Bitcoin Testnet": case "tBitcoin": case "bitcoinTestNet": @@ -185,6 +185,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { switch (ticker.toLowerCase()) { case "btc": return Coin.bitcoin; + case "bch": + return Coin.bitcoincash; case "doge": return Coin.dogecoin; case "epic": @@ -193,8 +195,6 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.firo; case "xmr": return Coin.monero; - case "bch": - return Coin.bitcoincash; case "tbtc": return Coin.bitcoinTestNet; case "tfiro": diff --git a/pubspec.yaml b/pubspec.yaml index dafa6189e..089da284d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -185,6 +185,7 @@ flutter: - assets/images/doge.png - assets/images/bitcoin.png - assets/images/epic-cash.png + - assets/images/bitcoincash.png - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg @@ -261,6 +262,7 @@ flutter: - assets/svg/envelope.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg + - assets/svg/coin_icons/Bitcoincash.svg - assets/svg/coin_icons/Dogecoin.svg - assets/svg/coin_icons/EpicCash.svg - assets/svg/coin_icons/Firo.svg From 84d394f419730aa7e49840dc5f3436e89eef8ea4 Mon Sep 17 00:00:00 2001 From: Likho Date: Mon, 12 Sep 2022 09:44:04 +0200 Subject: [PATCH 06/20] WIP: TEsts for bitcoincash --- .../bitcoincash_history_sample_data.dart | 0 .../bitcoincash_transaction_data_samples.dart | 375 +++ .../bitcoincash_utxo_sample_data.dart | 84 + .../bitcoincash/bitcoincash_wallet_test.dart | 2925 +++++++++++++++++ .../bitcoincash_wallet_test.mocks.dart | 0 .../bitcoincash_wallet_test_parameters.dart | 14 + 6 files changed, 3398 insertions(+) create mode 100644 test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart create mode 100644 test/services/coins/bitcoincash/bitcoincash_transaction_data_samples.dart create mode 100644 test/services/coins/bitcoincash/bitcoincash_utxo_sample_data.dart create mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test.dart create mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart create mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart diff --git a/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart b/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart new file mode 100644 index 000000000..e69de29bb diff --git a/test/services/coins/bitcoincash/bitcoincash_transaction_data_samples.dart b/test/services/coins/bitcoincash/bitcoincash_transaction_data_samples.dart new file mode 100644 index 000000000..148818b37 --- /dev/null +++ b/test/services/coins/bitcoincash/bitcoincash_transaction_data_samples.dart @@ -0,0 +1,375 @@ +import 'package:stackwallet/models/paymint/transactions_model.dart'; + +final transactionData = TransactionData.fromMap({ + "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c": tx1, + "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba": tx2, + "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a": tx3, + "84aecde036ebe013aa3bd2fcb4741db504c7c040d34f7c33732c967646991855": tx4, +}); + +final tx1 = Transaction( + txid: "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c", + confirmedStatus: true, + confirmations: 187, + txType: "Received", + amount: 7000000, + fees: 742, + height: 756720, + address: "12QZH44735UHWAXFgb4hfdq756GjzXtZG7", + timestamp: 1662544771, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 1, + outputSize: 2, + inputs: [ + Input( + txid: "f716d010786225004b41e35dd5eebfb11a4e5ea116e1a48235e5d3a591650732", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "12QZH44735UHWAXFgb4hfdq756GjzXtZG7", + value: 7000000, + ), + Output( + scriptpubkeyAddress: "3E1n17NnhVmWTGNbvH6ffKVNFjYT4Jke7G", + value: 71445709, + ) + ], +); + +final tx2 = Transaction( + txid: "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + confirmedStatus: true, + confirmations: 175, + txType: "Sent", + amount: 3000000, + fees: 227000, + height: 756732, + address: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + timestamp: 1662553616, + worthNow: "0.00", + worthAtBlockTimestamp: "0.0", + inputSize: 1, + outputSize: 2, + inputs: [ + Input( + txid: "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + value: 3000000, + ), + Output( + scriptpubkeyAddress: "16GbR1Xau2hKFTr1STgB39NbP8CEkGZjYG", + value: 3773000, + ), + ], +); + +final tx3 = Transaction( + txid: "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + confirmedStatus: true, + confirmations: 177, + txType: "Received", + amount: 2000000, + fees: 227, + height: 756738, + address: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + timestamp: 1662555788, + worthNow: "0.00", + worthAtBlockTimestamp: "0.0", + inputSize: 1, + outputSize: 2, + inputs: [ + Input( + txid: "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + value: 2000000, + ), + Output( + scriptpubkeyAddress: "16GbR1Xau2hKFTr1STgB39NbP8CEkGZjYG", + value: 1772773, + ), + ], +); + +final tx4 = Transaction( + txid: "84aecde036ebe013aa3bd2fcb4741db504c7c040d34f7c33732c967646991855", + confirmedStatus: false, + confirmations: 0, + txType: "Received", + amount: 4000000, + fees: 400, + height: 757303, + address: "1PQaBto5KmiW3R2YeexYYoDWksMpEvhYZE", + timestamp: 1662893734, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 1, + outputSize: 2, + inputs: [ + Input( + txid: "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + vout: 0, + ), + Input( + txid: "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "1JHcZyhgctuDCznjkxR51pQzKEJUujuc2j", + value: 999600, + ), + Output( + scriptpubkeyAddress: "1PQaBto5KmiW3R2YeexYYoDWksMpEvhYZE", + value: 4000000, + ) + ], +); + +final tx1Raw = { + "in_mempool": false, + "in_orphanpool": false, + "txid": "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c", + "size": 372, + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": + "f716d010786225004b41e35dd5eebfb11a4e5ea116e1a48235e5d3a591650732", + "vout": 1, + "scriptSig": { + "asm": + "0 3045022100d80e1d056e8787d7fac8e59ce14d56a2dbb2aceb43da1fee47e687e318049abd02204bb06be6e8af85250b93e0f5377da535557176557563a4d0121b607ffbf3e7c1[ALL|FORKID] 304402200c528edd5f1c0aa169178f5a4c1ec5044559326f1608db6987398bdc0761aaae02205a94bb7f8dac69400823a0093e0303eaa2905b9fadbb8bbb111c3fef0a452ef0[ALL|FORKID] 522103ff1450283f08568acdb4d5f569f32e4cd4d8c1960ea049a205436f69f9916df8210230ee6aec65bc0db7e9cf507b33067f681047e180e91906e1fde1bb549f233b24210366058482ecccb47075be9d1d3edb46df331c04fa5126cd6fd9dc6cee071237b453ae", + "hex": + "00483045022100d80e1d056e8787d7fac8e59ce14d56a2dbb2aceb43da1fee47e687e318049abd02204bb06be6e8af85250b93e0f5377da535557176557563a4d0121b607ffbf3e7c14147304402200c528edd5f1c0aa169178f5a4c1ec5044559326f1608db6987398bdc0761aaae02205a94bb7f8dac69400823a0093e0303eaa2905b9fadbb8bbb111c3fef0a452ef0414c69522103ff1450283f08568acdb4d5f569f32e4cd4d8c1960ea049a205436f69f9916df8210230ee6aec65bc0db7e9cf507b33067f681047e180e91906e1fde1bb549f233b24210366058482ecccb47075be9d1d3edb46df331c04fa5126cd6fd9dc6cee071237b453ae" + }, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.07, + "n": 0, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 0f6ca2ddb50a473f809440f77d3d931335ac2940 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9140f6ca2ddb50a473f809440f77d3d931335ac294088ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["12QZH44735UHWAXFgb4hfdq756GjzXtZG7"] + } + }, + { + "value": 0.71445709, + "n": 1, + "scriptPubKey": { + "asm": "OP_HASH160 872dcab340b7a8500b2585781e51e9217f11dced OP_EQUAL", + "hex": "a914872dcab340b7a8500b2585781e51e9217f11dced87", + "reqSigs": 1, + "type": "scripthash", + "addresses": ["3E1n17NnhVmWTGNbvH6ffKVNFjYT4Jke7G"] + } + } + ], + "blockhash": + "00000000000000000529d5816d2f9c97cfbe8c06bb87e9a15d9e778281ff9225", + "confirmations": 187, + "time": 1662544771, + "blocktime": 1662544771, + "hex": + "010000000132076591a5d3e53582a4e116a15e4e1ab1bfeed55de3414b0025627810d016f701000000fdfd0000483045022100d80e1d056e8787d7fac8e59ce14d56a2dbb2aceb43da1fee47e687e318049abd02204bb06be6e8af85250b93e0f5377da535557176557563a4d0121b607ffbf3e7c14147304402200c528edd5f1c0aa169178f5a4c1ec5044559326f1608db6987398bdc0761aaae02205a94bb7f8dac69400823a0093e0303eaa2905b9fadbb8bbb111c3fef0a452ef0414c69522103ff1450283f08568acdb4d5f569f32e4cd4d8c1960ea049a205436f69f9916df8210230ee6aec65bc0db7e9cf507b33067f681047e180e91906e1fde1bb549f233b24210366058482ecccb47075be9d1d3edb46df331c04fa5126cd6fd9dc6cee071237b453aeffffffff02c0cf6a00000000001976a9140f6ca2ddb50a473f809440f77d3d931335ac294088accd2c42040000000017a914872dcab340b7a8500b2585781e51e9217f11dced8700000000" +}; + +final tx2Raw = { + "in_mempool": false, + "in_orphanpool": false, + "txid": "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + "size": 225, + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": + "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c", + "vout": 0, + "scriptSig": { + "asm": + "304402207b3301ec0ab0c7dbba32690b71e369a6872ff2d0c0cacaddc831bb04d22cef7102206e0bb6d039c408e301d49978aa34597f57d075b9a69e1a9f120e08af159b167a[ALL|FORKID] 02cb6cdf3e5758112206b4b02f21838b3d8a26c601a88030f3c5705e357d8e4ea8", + "hex": + "47304402207b3301ec0ab0c7dbba32690b71e369a6872ff2d0c0cacaddc831bb04d22cef7102206e0bb6d039c408e301d49978aa34597f57d075b9a69e1a9f120e08af159b167a412102cb6cdf3e5758112206b4b02f21838b3d8a26c601a88030f3c5705e357d8e4ea8" + }, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.03, + "n": 0, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 87f3c240183ef0f3efe4b056029dd16d3e3d5d4f OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a91487f3c240183ef0f3efe4b056029dd16d3e3d5d4f88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM"] + } + }, + { + "value": 0.03773, + "n": 1, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 7fa80c90c0f8aa021074702c06b3300c0b247244 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a9147fa80c90c0f8aa021074702c06b3300c0b24724488ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["1Cdz8cpH3ZRuZViuah32YaTkNGryCS3DZj"] + } + } + ], + "blockhash": + "000000000000000005d25c8d3722e4486c486bbf864f9261631993afab557832", + "confirmations": 175, + "time": 1662553616, + "blocktime": 1662553616, + "hex": + "02000000017c24830a6a1f67fd10bf60d8557d170dcb59ab85171952287d9194b93cdbfe61000000006a47304402207b3301ec0ab0c7dbba32690b71e369a6872ff2d0c0cacaddc831bb04d22cef7102206e0bb6d039c408e301d49978aa34597f57d075b9a69e1a9f120e08af159b167a412102cb6cdf3e5758112206b4b02f21838b3d8a26c601a88030f3c5705e357d8e4ea8ffffffff02c0c62d00000000001976a91487f3c240183ef0f3efe4b056029dd16d3e3d5d4f88ac48923900000000001976a9147fa80c90c0f8aa021074702c06b3300c0b24724488ac00000000" +}; + +final tx3Raw = { + "in_mempool": false, + "in_orphanpool": false, + "txid": "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + "size": 226, + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": + "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + "vout": 1, + "scriptSig": { + "asm": + "3045022100ed38dc64e40a5cfe137d38fbe9b7c4fe8a09ef923d7f999f35c65b029aa233ac02206f119c8d881a1b475697ec1eef815cde2e0e456ce4e234c5762fc7ddbe04ac27[ALL|FORKID] 029845663b31ebf3136039db97b3413b939b61c5bef45e4ee23544165a28ed452b", + "hex": + "483045022100ed38dc64e40a5cfe137d38fbe9b7c4fe8a09ef923d7f999f35c65b029aa233ac02206f119c8d881a1b475697ec1eef815cde2e0e456ce4e234c5762fc7ddbe04ac274121029845663b31ebf3136039db97b3413b939b61c5bef45e4ee23544165a28ed452b" + }, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.02, + "n": 0, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 87f3c240183ef0f3efe4b056029dd16d3e3d5d4f OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a91487f3c240183ef0f3efe4b056029dd16d3e3d5d4f88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM"] + } + }, + { + "value": 0.01772773, + "n": 1, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 39cb987d75cbe99ec577de2f1918ff2b3539491a OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a91439cb987d75cbe99ec577de2f1918ff2b3539491a88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["16GbR1Xau2hKFTr1STgB39NbP8CEkGZjYG"] + } + } + ], + "blockhash": + "00000000000000000227adf51d47ac640c7353e873a398901ecf9becbf5988d7", + "confirmations": 179, + "time": 1662555788, + "blocktime": 1662555788, + "hex": + "0200000001ba348354bc44422c189ad10ea05821cfe43d04aee686ecb5dfff42fa2ecc6597010000006b483045022100ed38dc64e40a5cfe137d38fbe9b7c4fe8a09ef923d7f999f35c65b029aa233ac02206f119c8d881a1b475697ec1eef815cde2e0e456ce4e234c5762fc7ddbe04ac274121029845663b31ebf3136039db97b3413b939b61c5bef45e4ee23544165a28ed452bffffffff0280841e00000000001976a91487f3c240183ef0f3efe4b056029dd16d3e3d5d4f88ace50c1b00000000001976a91439cb987d75cbe99ec577de2f1918ff2b3539491a88ac00000000" +}; + +final tx4Raw = { + "in_mempool": false, + "in_orphanpool": false, + "txid": "84aecde036ebe013aa3bd2fcb4741db504c7c040d34f7c33732c967646991855", + "size": 360, + "version": 1, + "locktime": 757301, + "vin": [ + { + "txid": + "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + "vout": 0, + "scriptSig": { + "asm": + "95a4d53e9059dc478b2f79dc486b4dd1ea2f34f3f2f870ba26a9c16530305ddc3e25b1d1d5adc42df75b4666b9fe6ec5b41813c0e82a579ce2167f6f7ed1b305[ALL|FORKID] 02d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fd", + "hex": + "4195a4d53e9059dc478b2f79dc486b4dd1ea2f34f3f2f870ba26a9c16530305ddc3e25b1d1d5adc42df75b4666b9fe6ec5b41813c0e82a579ce2167f6f7ed1b305412102d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fd" + }, + "sequence": 4294967294 + }, + { + "txid": + "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + "vout": 0, + "scriptSig": { + "asm": + "f2557ee7ae3eaf6488cc24972c73578ffc6ea2db047ffc4ff0b220f5d4efe491de01e1024ee77dc88d2cfa2f44b686bf394bd2a7114aac4fac48007547e2d313[ALL|FORKID] 02d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fd", + "hex": + "41f2557ee7ae3eaf6488cc24972c73578ffc6ea2db047ffc4ff0b220f5d4efe491de01e1024ee77dc88d2cfa2f44b686bf394bd2a7114aac4fac48007547e2d313412102d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fd" + }, + "sequence": 4294967294 + } + ], + "vout": [ + { + "value": 0.009996, + "n": 0, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 bd9e7c204b6d0d90ba018250fafa398d5ec1b39d OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914bd9e7c204b6d0d90ba018250fafa398d5ec1b39d88ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["1JHcZyhgctuDCznjkxR51pQzKEJUujuc2j"] + } + }, + { + "value": 0.04, + "n": 1, + "scriptPubKey": { + "asm": + "OP_DUP OP_HASH160 f5c809c469d24bc0bf4f6a17a9218df1a79cd247 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914f5c809c469d24bc0bf4f6a17a9218df1a79cd24788ac", + "reqSigs": 1, + "type": "pubkeyhash", + "addresses": ["1PQaBto5KmiW3R2YeexYYoDWksMpEvhYZE"] + } + } + ], + "blockhash": + "000000000000000005aa6b3094801ec56f36f74d4b25cad38b22dc3d24cd3e43", + "confirmations": 1, + "time": 1662893734, + "blocktime": 1662893734, + "hex": + "01000000028a2832369a6a7ab35900cb00aa9ac148f5f5c5e8cccca056583b2401d9450b0700000000644195a4d53e9059dc478b2f79dc486b4dd1ea2f34f3f2f870ba26a9c16530305ddc3e25b1d1d5adc42df75b4666b9fe6ec5b41813c0e82a579ce2167f6f7ed1b305412102d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fdfeffffffba348354bc44422c189ad10ea05821cfe43d04aee686ecb5dfff42fa2ecc6597000000006441f2557ee7ae3eaf6488cc24972c73578ffc6ea2db047ffc4ff0b220f5d4efe491de01e1024ee77dc88d2cfa2f44b686bf394bd2a7114aac4fac48007547e2d313412102d0825e4d527c9c24e0d423187055904f91218c82652b3fe575a212fef15531fdfeffffff02b0400f00000000001976a914bd9e7c204b6d0d90ba018250fafa398d5ec1b39d88ac00093d00000000001976a914f5c809c469d24bc0bf4f6a17a9218df1a79cd24788ac358e0b00" +}; diff --git a/test/services/coins/bitcoincash/bitcoincash_utxo_sample_data.dart b/test/services/coins/bitcoincash/bitcoincash_utxo_sample_data.dart new file mode 100644 index 000000000..54ab1c37b --- /dev/null +++ b/test/services/coins/bitcoincash/bitcoincash_utxo_sample_data.dart @@ -0,0 +1,84 @@ +import 'package:stackwallet/models/paymint/utxo_model.dart'; + +final Map>> batchGetUTXOResponse0 = { + "some id 0": [ + { + "tx_pos": 0, + "value": 7000000, + "tx_hash": + "61fedb3cb994917d2852191785ab59cb0d177d55d860bf10fd671f6a0a83247c", + "height": 756720 + }, + { + "tx_pos": 0, + "value": 3000000, + "tx_hash": + "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + "height": 756732 + }, + ], + "some id 1": [ + { + "tx_pos": 1, + "value": 2000000, + "tx_hash": + "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + "height": 756738 + }, + ], + "some id 2": [], +}; + +final utxoList = [ + UtxoObject( + txid: "9765cc2efa42ffdfb5ec86e6ae043de4cf2158a00ed19a182c4244bc548334ba", + vout: 0, + status: Status( + confirmed: true, + confirmations: 175, + blockHeight: 756732, + blockTime: 1662553616, + blockHash: + "000000000000000005d25c8d3722e4486c486bbf864f9261631993afab557832", + ), + value: 3000000, + fiatWorth: "\$0", + txName: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + blocked: false, + isCoinbase: false, + ), + UtxoObject( + txid: "f716d010786225004b41e35dd5eebfb11a4e5ea116e1a48235e5d3a591650732", + vout: 1, + status: Status( + confirmed: true, + confirmations: 11867, + blockHeight: 745443, + blockTime: 1655792385, + blockHash: + "000000000000000000065c982f4d86a402e7182d0c6a49fa6cfbdaf67a57f566", + ), + value: 78446451, + fiatWorth: "\$0", + txName: "3E1n17NnhVmWTGNbvH6ffKVNFjYT4Jke7G", + blocked: false, + isCoinbase: false, + ), + UtxoObject( + txid: "070b45d901243b5856a0cccce8c5f5f548c19aaa00cb0059b37a6a9a3632288a", + vout: 0, + status: Status( + confirmed: true, + confirmations: 572, + blockHeight: 756738, + blockTime: 1662555788, + blockHash: + "00000000000000000227adf51d47ac640c7353e873a398901ecf9becbf5988d7", + ), + value: 2000000, + fiatWorth: "\$0", + txName: "1DPrEZBKKVG1Pf4HXeuCkw2Xk65EunR7CM", + blocked: false, + isCoinbase: false, + ), +]; diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart new file mode 100644 index 000000000..0e39892b1 --- /dev/null +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -0,0 +1,2925 @@ +import 'package:bitcoindart/bitcoindart.dart'; +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.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; +import 'package:stackwallet/services/price.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; + +// import '../../../cached_electrumx_test.mocks.dart'; +// import '../../../screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart'; +// import '../firo/firo_wallet_test.mocks.dart'; + +import 'bitcoincash_history_sample_data.dart'; +import 'bitcoincash_transaction_data_samples.dart'; +import 'bitcoincash_utxo_sample_data.dart'; +import 'bitcoincash_wallet_test.mocks.dart'; +import 'bitcoincash_wallet_test_parameters.dart'; + +@GenerateMocks( + [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +void main() { + group("bitcoincash constants", () { + test("bitcoincash minimum confirmations", () async { + expect(MINIMUM_CONFIRMATIONS, 3); + }); + test("bitcoincash dust limit", () async { + expect(DUST_LIMIT, 1000000); + }); + test("bitcoincash mainnet genesis block hash", () async { + expect(GENESIS_HASH_MAINNET, + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + }); + }); + + test("bitcoincash DerivePathType enum", () { + expect(DerivePathType.values.length, 1); + expect(DerivePathType.values.toString(), "[DerivePathType.bip44]"); + }); + + group("bip32 node/root", () { + test("getBip32Root", () { + final root = getBip32Root(TEST_MNEMONIC, bitcoincash); + expect(root.toWIF(), ROOT_WIF); + }); + + test("basic getBip32Node", () { + final node = + getBip32Node(0, 0, TEST_MNEMONIC, bitcoincash, DerivePathType.bip44); + expect(node.toWIF(), NODE_WIF_44); + }); + }); + + group("validate mainnet bitcoincash addresses", () { + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + BitcoinCashWallet? mainnetWallet; + + setUp(() { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + mainnetWallet = BitcoinCashWallet( + walletId: "validateAddressMainNet", + walletName: "validateAddressMainNet", + coin: Coin.bitcoincash, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("valid mainnet legacy/p2pkh address type", () { + expect( + mainnetWallet?.addressType( + address: "DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), + DerivePathType.bip44); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("invalid base58 address type", () { + expect( + () => mainnetWallet?.addressType( + address: "mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("invalid bech32 address type", () { + expect( + () => mainnetWallet?.addressType( + address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("address has no matching script", () { + expect( + () => mainnetWallet?.addressType( + address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("valid mainnet bitcoincash legacy/p2pkh address", () { + expect( + mainnetWallet?.validateAddress("DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), + true); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("invalid mainnet bitcoincash legacy/p2pkh address", () { + expect( + mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + false); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + }); + + group("testNetworkConnection", () { + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + BitcoinCashWallet? bch; + + setUp(() { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + bch = BitcoinCashWallet( + walletId: "testNetworkConnection", + walletName: "testNetworkConnection", + coin: Coin.bitcoincash, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("attempted connection fails due to server error", () async { + when(client?.ping()).thenAnswer((_) async => false); + final bool? result = await bch?.testNetworkConnection(); + expect(result, false); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("attempted connection fails due to exception", () async { + when(client?.ping()).thenThrow(Exception); + final bool? result = await bch?.testNetworkConnection(); + expect(result, false); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("attempted connection test success", () async { + when(client?.ping()).thenAnswer((_) async => true); + final bool? result = await bch?.testNetworkConnection(); + expect(result, true); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + }); + + group("basic getters, setters, and functions", () { + final bchcoin = Coin.bitcoincash; + // final dtestcoin = Coin.bitcoincashTestNet; + final testWalletId = "DOGEtestWalletID"; + final testWalletName = "DOGEWallet"; + + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + BitcoinCashWallet? bch; + + setUp(() async { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("get networkType main", () async { + expect(bch?.coin, bchcoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("get networkType test", () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + expect(bch?.coin, bchcoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("get cryptoCurrency", () async { + expect(Coin.bitcoincash, Coin.bitcoincash); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("get coinName", () async { + expect(Coin.bitcoincash, Coin.bitcoincash); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("get coinTicker", () async { + expect(Coin.bitcoincash, Coin.bitcoincash); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("get and set walletName", () async { + expect(Coin.bitcoincash, Coin.bitcoincash); + bch?.walletName = "new name"; + expect(bch?.walletName, "new name"); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("estimateTxFee", () async { + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 1), 356); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 900), 356); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 999), 356); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 1000), 356); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 1001), 712); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); + expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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_TESTNET, + "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 bch?.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); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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_TESTNET, + "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 bch?.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); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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 bch?.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); + verifyNoMoreInteractions(priceAPI); + }); + }); + + group("DogeWallet service class functions that depend on shared storage", () { + final bchcoin = Coin.bitcoincash; + // final dtestcoin = Coin.bitcoincashTestNet; + final testWalletId = "DOGEtestWalletID"; + final testWalletName = "DOGEWallet"; + + bool hiveAdaptersRegistered = false; + + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + BitcoinCashWallet? bch; + + setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + // Registering Transaction Model Adapters + Hive.registerAdapter(TransactionDataAdapter()); + Hive.registerAdapter(TransactionChunkAdapter()); + Hive.registerAdapter(TransactionAdapter()); + Hive.registerAdapter(InputAdapter()); + Hive.registerAdapter(OutputAdapter()); + + // Registering Utxo Model Adapters + Hive.registerAdapter(UtxoDataAdapter()); + Hive.registerAdapter(UtxoObjectAdapter()); + Hive.registerAdapter(StatusAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', testWalletName); + } + + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + // test("initializeWallet no network", () async { + // when(client?.ping()).thenAnswer((_) async => false); + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // expect(bch?.initializeNew(), false); + // expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("initializeExisting no network exception", () async { + // when(client?.ping()).thenThrow(Exception("Network connection failed")); + // // bch?.initializeNew(); + // expect(bch?.initializeExisting(), false); + // expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + test("initializeNew mainnet throws bad network", () 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": [] + }); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + expectLater(() => bch?.initializeNew(), throwsA(isA())) + .then((_) { + expect(secureStore?.interactions, 0); + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + test("initializeNew throws mnemonic overwrite exception", () 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"); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + expectLater(() => bch?.initializeNew(), throwsA(isA())) + .then((_) { + expect(secureStore?.interactions, 2); + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + test("initializeExisting testnet 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": [] + }); + + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + expectLater(() => bch?.initializeNew(), throwsA(isA())) + .then((_) { + expect(secureStore?.interactions, 0); + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + // test("getCurrentNode", () async { + // // when(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // 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 DebugService.instance.init(); + // expect(bch?.initializeExisting(), true); + // + // bool didThrow = false; + // try { + // await bch?.getCurrentNode(); + // } catch (_) { + // didThrow = true; + // } + // // expect no nodes on a fresh wallet unless set in db externally + // expect(didThrow, true); + // + // // 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("activeNodeName", "default"); + // + // // try fetching again + // final node = await bch?.getCurrentNode(); + // expect(node.toString(), + // "ElectrumXNode: {address: some address, port: 9000, name: default, useSSL: true}"); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("initializeWallet new main net wallet", () async { + // when(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")) + // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // 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": [] + // }); + // expect(await bch?.initializeWallet(), true); + // + // final wallet = await Hive.openBox(testWalletId); + // + // expect(await wallet.get("addressBookEntries"), {}); + // expect(await wallet.get('notes'), null); + // expect(await wallet.get("id"), testWalletId); + // expect(await wallet.get("preferredFiatCurrency"), null); + // expect(await wallet.get("blocked_tx_hashes"), ["0xdefault"]); + // + // final changeAddressesP2PKH = await wallet.get("changeAddressesP2PKH"); + // expect(changeAddressesP2PKH, isA>()); + // expect(changeAddressesP2PKH.length, 1); + // expect(await wallet.get("changeIndexP2PKH"), 0); + // + // final receivingAddressesP2PKH = + // await wallet.get("receivingAddressesP2PKH"); + // expect(receivingAddressesP2PKH, isA>()); + // expect(receivingAddressesP2PKH.length, 1); + // expect(await wallet.get("receivingIndexP2PKH"), 0); + // + // final p2pkhReceiveDerivations = jsonDecode(await secureStore?.read( + // key: "${testWalletId}_receiveDerivationsP2PKH")); + // expect(p2pkhReceiveDerivations.length, 1); + // + // final p2pkhChangeDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2PKH")); + // expect(p2pkhChangeDerivations.length, 1); + // + // expect(secureStore?.interactions, 10); + // expect(secureStore?.reads, 7); + // expect(secureStore?.writes, 3); + // expect(secureStore?.deletes, 0); + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // // test("initializeWallet existing main net wallet", () async { + // // when(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // // when(client?.ping()).thenAnswer((_) async => true); + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((_) 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": [] + // // }); + // // // init new wallet + // // expect(bch?.initializeNew(), true); + // // + // // // fetch data to compare later + // // final newWallet = await Hive.openBox(testWalletId); + // // + // // final addressBookEntries = await newWallet.get("addressBookEntries"); + // // final notes = await newWallet.get('notes'); + // // final wID = await newWallet.get("id"); + // // final currency = await newWallet.get("preferredFiatCurrency"); + // // final blockedHashes = await newWallet.get("blocked_tx_hashes"); + // // + // // final changeAddressesP2PKH = await newWallet.get("changeAddressesP2PKH"); + // // final changeIndexP2PKH = await newWallet.get("changeIndexP2PKH"); + // // + // // final receivingAddressesP2PKH = + // // await newWallet.get("receivingAddressesP2PKH"); + // // final receivingIndexP2PKH = await newWallet.get("receivingIndexP2PKH"); + // // + // // final p2pkhReceiveDerivations = jsonDecode(await secureStore?.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH")); + // // + // // final p2pkhChangeDerivations = jsonDecode(await secureStore?.read( + // // key: "${testWalletId}_changeDerivationsP2PKH")); + // // + // // // exit new wallet + // // await bch?.exit(); + // // + // // // open existing/created wallet + // // bch = BitcoinCashWallet( + // // walletId: testWalletId, + // // walletName: testWalletName, + // // coin: dtestcoin, + // // client: client!, + // // cachedClient: cachedClient!, + // // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); + // // + // // // init existing + // // expect(bch?.initializeExisting(), true); + // // + // // // compare data to ensure state matches state of previously closed wallet + // // final wallet = await Hive.openBox(testWalletId); + // // + // // expect(await wallet.get("addressBookEntries"), addressBookEntries); + // // expect(await wallet.get('notes'), notes); + // // expect(await wallet.get("id"), wID); + // // expect(await wallet.get("preferredFiatCurrency"), currency); + // // expect(await wallet.get("blocked_tx_hashes"), blockedHashes); + // // + // // expect(await wallet.get("changeAddressesP2PKH"), changeAddressesP2PKH); + // // expect(await wallet.get("changeIndexP2PKH"), changeIndexP2PKH); + // // + // // expect( + // // await wallet.get("receivingAddressesP2PKH"), receivingAddressesP2PKH); + // // expect(await wallet.get("receivingIndexP2PKH"), receivingIndexP2PKH); + // // + // // expect( + // // jsonDecode(await secureStore?.read( + // // key: "${testWalletId}_receiveDerivationsP2PKH")), + // // p2pkhReceiveDerivations); + // // + // // expect( + // // jsonDecode(await secureStore?.read( + // // key: "${testWalletId}_changeDerivationsP2PKH")), + // // p2pkhChangeDerivations); + // // + // // expect(secureStore?.interactions, 12); + // // expect(secureStore?.reads, 9); + // // expect(secureStore?.writes, 3); + // // expect(secureStore?.deletes, 0); + // // verify(client?.ping()).called(2); + // // verify(client?.getServerFeatures()).called(1); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + + test("get current receiving addresses", () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + expect( + Address.validateAddress( + await bch!.currentReceivingAddress, bitcoincashtestnet), + true); + expect( + Address.validateAddress( + await bch!.currentReceivingAddress, bitcoincashtestnet), + true); + expect( + Address.validateAddress( + await bch!.currentReceivingAddress, bitcoincashtestnet), + true); + + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get allOwnAddresses", () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + final addresses = await bch?.allOwnAddresses; + expect(addresses, isA>()); + expect(addresses?.length, 2); + + for (int i = 0; i < 2; i++) { + expect( + Address.validateAddress(addresses![i], bitcoincashtestnet), true); + } + + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + // test("get utxos and balances", () async { + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => batchGetUTXOResponse0); + // + // 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); + // + // when(cachedClient?.getTransaction( + // txHash: tx1.txid, + // coin: Coin.bitcoincashTestNet, + // )).thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // txHash: tx2.txid, + // coin: Coin.bitcoincashTestNet, + // )).thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // txHash: tx3.txid, + // coin: Coin.bitcoincashTestNet, + // )).thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: tx4.txid, + // coin: Coin.bitcoincashTestNet, + // )).thenAnswer((_) async => tx4Raw); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // final utxoData = await bch?.utxoData; + // expect(utxoData, isA()); + // expect(utxoData.toString(), + // r"{totalUserCurrency: $103.2173, satoshiBalance: 1032173000, bitcoinBalance: null, unspentOutputArray: [{txid: 86198a91805b6c53839a6a97736c434a5a2f85d68595905da53df7df59b9f01a, vout: 0, value: 800000000, fiat: $80, blocked: false, status: {confirmed: true, blockHash: e52cabb4445eb9ceb3f4f8d68cc64b1ede8884ce560296c27826a48ecc477370, blockHeight: 4274457, blockTime: 1655755742, confirmations: 100}}, {txid: a4b6bd97a4b01b4305d0cf02e9bac6b7c37cda2f8e9dfe291ce4170b810ed469, vout: 0, value: 72173000, fiat: $7.2173, blocked: false, status: {confirmed: false, blockHash: bd239f922b3ecec299a90e4d1ce389334e8df4b95470fb5919966b0b650bb95b, blockHeight: 4270459, blockTime: 1655500912, confirmations: 0}}, {txid: 68c159dcc2f962cbc61f7dd3c8d0dcc14da8adb443811107115531c853fc0c60, vout: 1, value: 100000000, fiat: $10, blocked: false, status: {confirmed: false, blockHash: 9fee9b9446cfe81abb1a17bec56e6c160d9a6527e5b68b1141a827573bc2649f, blockHeight: 4255659, blockTime: 1654553247, confirmations: 0}}, {txid: 628a78606058ce4036aee3907e042742156c1894d34419578de5671b53ea5800, vout: 0, value: 60000000, fiat: $6, blocked: false, status: {confirmed: true, blockHash: bc461ab43e3a80d9a4d856ee9ff70f41d86b239d5f0581ffd6a5c572889a6b86, blockHeight: 4270352, blockTime: 1652888705, confirmations: 100}}]}"); + // + // final outputs = await bch?.unspentOutputs; + // expect(outputs, isA>()); + // expect(outputs?.length, 4); + // + // final availableBalance = await bch?.availableBalance; + // expect(availableBalance, Decimal.parse("8.6")); + // + // final totalBalance = await bch?.totalBalance; + // expect(totalBalance, Decimal.parse("10.32173")); + // + // final pendingBalance = await bch?.pendingBalance; + // expect(pendingBalance, Decimal.parse("1.72173")); + // + // final balanceMinusMaxFee = await bch?.balanceMinusMaxFee; + // expect(balanceMinusMaxFee, Decimal.parse("7.6")); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.estimateFee(blocks: 1)).called(1); + // verify(client?.estimateFee(blocks: 5)).called(1); + // verify(client?.estimateFee(blocks: 20)).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx1.txid, + // coin: Coin.bitcoincashTestNet, + // )).called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx2.txid, + // coin: Coin.bitcoincashTestNet, + // )).called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx3.txid, + // coin: Coin.bitcoincashTestNet, + // )).called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx4.txid, + // coin: Coin.bitcoincashTestNet, + // )).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // // test("get utxos - multiple batches", () async { + // // bch = BitcoinCashWallet( + // // walletId: testWalletId, + // // walletName: testWalletName, + // // coin: dtestcoin, + // // client: client!, + // // cachedClient: cachedClient!, + // // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); + // // 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?.getBatchUTXOs(args: anyNamed("args"))) + // // .thenAnswer((_) async => {}); + // // + // // when(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // // + // // await bch?.initializeWallet(); + // // + // // // add some extra addresses to make sure we have more than the single batch size of 10 + // // final wallet = await Hive.openBox(testWalletId); + // // final addresses = await wallet.get("receivingAddressesP2PKH"); + // // addresses.add("DQaAi9R58GXMpDyhePys6hHCuif4fhc1sN"); + // // addresses.add("DBVhuF8QgeuxU2pssxzMgJqPhGCx5qyVkD"); + // // addresses.add("DCAokB2CXXPWC2JPj6jrK6hxANwTF2m21x"); + // // addresses.add("D6Y9brE3jUGPrqLmSEWh6yQdgY5b7ZkTib"); + // // addresses.add("DKdtobt3M5b3kQWZf1zRUZn3Ys6JTQwbPL"); + // // addresses.add("DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"); + // // addresses.add("DE5ffowvbHPzzY6aRVGpzxR2QqikXxUKPG"); + // // addresses.add("DA97TLg1741J2aLK6z9bVZoWysgQbMR45K"); + // // addresses.add("DGGmf9q4PKcJXauPRstsFetu9DjW1VSBYk"); + // // addresses.add("D9bXqnTtufcb6oJyuZniCXbst8MMLzHxUd"); + // // addresses.add("DA6nv8M4kYL4RxxKrcsPaPUA1KrFA7CTfN"); + // // await wallet.put("receivingAddressesP2PKH", addresses); + // // + // // final utxoData = await bch?.utxoData; + // // expect(utxoData, isA()); + // // + // // final outputs = await bch?.unspentOutputs; + // // expect(outputs, isA>()); + // // expect(outputs?.length, 0); + // // + // // verify(client?.ping()).called(1); + // // verify(client?.getServerFeatures()).called(1); + // // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(2); + // // verify(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")).called(1); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + // + test("get utxos fails", () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + when(client?.getBatchUTXOs(args: anyNamed("args"))) + .thenThrow(Exception("some exception")); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + final utxoData = await bch?.utxoData; + expect(utxoData, isA()); + expect(utxoData.toString(), + r"{totalUserCurrency: 0.00, satoshiBalance: 0, bitcoinBalance: 0, unspentOutputArray: []}"); + + final outputs = await bch?.unspentOutputs; + expect(outputs, isA>()); + expect(outputs?.length, 0); + + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("chain height fetch, update, and get", () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: bchcoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + // get stored + expect(await bch?.storedChainHeight, 0); + + // fetch fails + when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); + expect(await bch?.chainHeight, -1); + + // fetch succeeds + when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { + "height": 100, + "hex": "some block hex", + }); + expect(await bch?.chainHeight, 100); + + // update + await bch?.updateStoredChainHeight(newHeight: 1000); + + // fetch updated + expect(await bch?.storedChainHeight, 1000); + + verifyNever(client?.ping()).called(0); + verify(client?.getServerFeatures()).called(1); + verify(client?.getBlockHeadTip()).called(2); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("getTxCount succeeds", () async { + when(client?.getHistory( + scripthash: + "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + .thenAnswer((realInvocation) async => [ + { + "height": 4270352, + "tx_hash": + "7b34e60cc37306f866667deb67b14096f4ea2add941fd6e2238a639000642b82" + }, + { + "height": 4274457, + "tx_hash": + "9cd994199f9ee58c823a03bab24da87c25e0157cb42c226e191aadadbb96e452" + } + ]); + + final count = + await bch?.getTxCount(address: "D6biRASajCy7GcJ8R6ZP4RE94fNRerJLCC"); + + expect(count, 2); + + verify(client?.getHistory( + scripthash: + "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + .called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("getTxCount fails", () async { + when(client?.getHistory( + scripthash: + "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + .thenThrow(Exception("some exception")); + + bool didThrow = false; + try { + await bch?.getTxCount(address: "D6biRASajCy7GcJ8R6ZP4RE94fNRerJLCC"); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + + verify(client?.getHistory( + scripthash: + "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + .called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("_checkCurrentReceivingAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + .thenAnswer((realInvocation) async => [ + { + "height": 4270385, + "tx_hash": + "c07f740ad72c0dd759741f4c9ab4b1586a22bc16545584364ac9b3d845766271" + }, + { + "height": 4270459, + "tx_hash": + "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + } + ]); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + bool didThrow = false; + try { + await bch?.checkCurrentReceivingAddressesForTransactions(); + } catch (_) { + didThrow = true; + } + expect(didThrow, false); + + verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + verify(client?.getServerFeatures()).called(1); + verifyNever(client?.ping()).called(0); + + expect(secureStore?.interactions, 11); + expect(secureStore?.reads, 7); + expect(secureStore?.writes, 4); + expect(secureStore?.deletes, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("_checkCurrentReceivingAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + .thenThrow(Exception("some exception")); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + bool didThrow = false; + try { + await bch?.checkCurrentReceivingAddressesForTransactions(); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + + verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + verify(client?.getServerFeatures()).called(1); + verifyNever(client?.ping()).called(0); + + expect(secureStore?.interactions, 8); + expect(secureStore?.reads, 5); + expect(secureStore?.writes, 3); + expect(secureStore?.deletes, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("_checkCurrentChangeAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + .thenAnswer((realInvocation) async => [ + { + "height": 4286283, + "tx_hash": + "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b" + }, + { + "height": 4286295, + "tx_hash": + "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + } + ]); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + bool didThrow = false; + try { + await bch?.checkCurrentChangeAddressesForTransactions(); + } catch (_) { + didThrow = true; + } + expect(didThrow, false); + + verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + verify(client?.getServerFeatures()).called(1); + verifyNever(client?.ping()).called(0); + + expect(secureStore?.interactions, 11); + expect(secureStore?.reads, 7); + expect(secureStore?.writes, 4); + expect(secureStore?.deletes, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("_checkCurrentChangeAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + .thenThrow(Exception("some exception")); + + await Hive.openBox(testWalletId); + await Hive.openBox(DB.boxNamePrefs); + + await bch?.initializeNew(); + await bch?.initializeExisting(); + + bool didThrow = false; + try { + await bch?.checkCurrentChangeAddressesForTransactions(); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + + verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + verify(client?.getServerFeatures()).called(1); + verifyNever(client?.ping()).called(0); + + expect(secureStore?.interactions, 8); + expect(secureStore?.reads, 5); + expect(secureStore?.writes, 3); + expect(secureStore?.deletes, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + // test("getAllTxsToWatch", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // var notifications = {"show": 0}; + // const MethodChannel('dexterous.com/flutter/local_notifications') + // .setMockMethodCallHandler((call) async { + // notifications[call.method]++; + // }); + // + // bch?.pastUnconfirmedTxs = { + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // "b39bac02b65af46a49e2985278fe24ca00dd5d627395d88f53e35568a04e10fa", + // }; + // + // await bch?.getAllTxsToWatch(transactionData); + // expect(notifications.length, 1); + // expect(notifications["show"], 3); + // + // expect(bch?.unconfirmedTxs, { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // 'dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3', + // }); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("refreshIfThereIsNewData true A", () async { + // when(client?.getTransaction( + // txHash: + // "a4b6bd97a4b01b4305d0cf02e9bac6b7c37cda2f8e9dfe291ce4170b810ed469", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // txHash: + // "86198a91805b6c53839a6a97736c434a5a2f85d68595905da53df7df59b9f01a", + // )).thenAnswer((_) async => tx1Raw); + // + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // + // await wallet.put('changeAddressesP2PKH', []); + // + // bch?.unconfirmedTxs = { + // "a4b6bd97a4b01b4305d0cf02e9bac6b7c37cda2f8e9dfe291ce4170b810ed469", + // "86198a91805b6c53839a6a97736c434a5a2f85d68595905da53df7df59b9f01a" + // }; + // + // final result = await bch?.refreshIfThereIsNewData(); + // + // expect(result, true); + // + // verify(client?.getTransaction( + // txHash: + // "a4b6bd97a4b01b4305d0cf02e9bac6b7c37cda2f8e9dfe291ce4170b810ed469", + // )).called(1); + // verify(client?.getTransaction( + // txHash: + // "86198a91805b6c53839a6a97736c434a5a2f85d68595905da53df7df59b9f01a", + // )).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("refreshIfThereIsNewData true B", () async { + // // when(priceAPI.getbitcoincashPrice(baseCurrency: "USD")) + // // .thenAnswer((_) async => Decimal.fromInt(10)); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // final uuids = Map>.from(realInvocation + // .namedArguments.values.first as Map) + // .keys + // .toList(growable: false); + // return { + // uuids[0]: [ + // { + // "tx_hash": + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9", + // "height": 4286305 + // }, + // { + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // "height": 4286295 + // } + // ], + // uuids[1]: [ + // { + // "tx_hash": + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // "height": 4286283 + // } + // ], + // }; + // }); + // + // when(client?.getTransaction( + // txHash: + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // txHash: + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // )).thenAnswer((_) async => tx1Raw); + // + // when(cachedClient?.getTransaction( + // txHash: + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "4493caff0e1b4f248e3c6219e7f288cfdb46c32b72a77aec469098c5f7f5154e", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx5Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "e095cbe5531d174c3fc5c9c39a0e6ba2769489cdabdc17b35b2e3a33a3c2fc61", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx6Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "d3054c63fe8cfafcbf67064ec66b9fbe1ac293860b5d6ffaddd39546658b72de", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx7Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "7b34e60cc37306f866667deb67b14096f4ea2add941fd6e2238a639000642b82", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx4Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "a70c6f0690fa84712dc6b3d20ee13862fe015a08cf2dc8949c4300d49c3bdeb5", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx8Raw); + // + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // + // await wallet.put('changeAddressesP2PKH', []); + // + // bch?.unconfirmedTxs = { + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // }; + // + // final result = await bch?.refreshIfThereIsNewData(); + // + // expect(result, true); + // + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(2); + // verify(client?.getTransaction( + // txHash: + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // )).called(1); + // verify(cachedClient?.getTransaction( + // txHash: anyNamed("tx_hash"), + // verbose: true, + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .called(9); + // // verify(priceAPI?.getbitcoincashPrice(baseCurrency: "USD")).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("refreshIfThereIsNewData false A", () async { + // // when(priceAPI.getbitcoincashPrice(baseCurrency: "USD")) + // // .thenAnswer((_) async => Decimal.fromInt(10)); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // final uuids = Map>.from(realInvocation + // .namedArguments.values.first as Map) + // .keys + // .toList(growable: false); + // return { + // uuids[0]: [ + // { + // "tx_hash": + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9", + // "height": 4286305 + // }, + // { + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // "height": 4286295 + // } + // ], + // uuids[1]: [ + // { + // "tx_hash": + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // "height": 4286283 + // } + // ], + // }; + // }); + // + // when(client?.getTransaction( + // txHash: + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // txHash: + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // )).thenAnswer((_) async => tx1Raw); + // + // when(cachedClient?.getTransaction( + // txHash: + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "4493caff0e1b4f248e3c6219e7f288cfdb46c32b72a77aec469098c5f7f5154e", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx5Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "7b34e60cc37306f866667deb67b14096f4ea2add941fd6e2238a639000642b82", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx4Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "e095cbe5531d174c3fc5c9c39a0e6ba2769489cdabdc17b35b2e3a33a3c2fc61", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx6Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "d3054c63fe8cfafcbf67064ec66b9fbe1ac293860b5d6ffaddd39546658b72de", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx7Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "a70c6f0690fa84712dc6b3d20ee13862fe015a08cf2dc8949c4300d49c3bdeb5", + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx8Raw); + // + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // + // await wallet.put('changeAddressesP2PKH', []); + // + // bch?.unconfirmedTxs = { + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // "351a94874379a5444c8891162472acf66de538a1abc647d4753f3e1eb5ec66f9" + // }; + // + // final result = await bch?.refreshIfThereIsNewData(); + // + // expect(result, false); + // + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(2); + // verify(client?.getTransaction( + // txHash: + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // )).called(1); + // verify(cachedClient?.getTransaction( + // txHash: anyNamed("tx_hash"), + // verbose: true, + // coin: Coin.bitcoincashTestNet, + // callOutSideMainIsolate: false)) + // .called(15); + // // verify(priceAPI.getbitcoincashPrice(baseCurrency: "USD")).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // // test("refreshIfThereIsNewData false B", () async { + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenThrow(Exception("some exception")); + // // + // // when(client?.getTransaction( + // // txHash: + // // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // // )).thenAnswer((_) async => tx2Raw); + // // + // // bch = BitcoinCashWallet( + // // walletId: testWalletId, + // // walletName: testWalletName, + // // coin: dtestcoin, + // // client: client!, + // // cachedClient: cachedClient!, + // // tracker: tracker!, + // // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); + // // final wallet = await Hive.openBox(testWalletId); + // // await wallet.put('receivingAddressesP2PKH', []); + // // + // // await wallet.put('changeAddressesP2PKH', []); + // // + // // bch?.unconfirmedTxs = { + // // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a", + // // }; + // // + // // final result = await bch?.refreshIfThereIsNewData(); + // // + // // expect(result, false); + // // + // // verify(client?.getBatchHistory(args: anyNamed("args"))).called(1); + // // verify(client?.getTransaction( + // // txHash: + // // "a4b6bd97a4b01b4305d0cf02e9bac6b7c37cda2f8e9dfe291ce4170b810ed469", + // // )).called(1); + // // + // // expect(secureStore?.interactions, 0); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + + 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(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .thenAnswer((realInvocation) async {}); + + final wallet = await Hive.openBox(testWalletId); + + // restore so we have something to rescan + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + // fetch valid wallet data + final preReceivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final preUtxoData = await wallet.get('latest_utxo_model'); + final preReceiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final preChangeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + // destroy the data that the rescan will fix + await wallet.put( + 'receivingAddressesP2PKH', ["some address", "some other address"]); + await wallet + .put('changeAddressesP2PKH', ["some address", "some other address"]); + + await wallet.put('receivingIndexP2PKH', 123); + await wallet.put('changeIndexP2PKH', 123); + await secureStore?.write( + key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + await secureStore?.write( + key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + + bool hasThrown = false; + try { + await bch?.fullRescan(2, 1000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + + // fetch wallet data again + final receivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final utxoData = await wallet.get('latest_utxo_model'); + final receiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final changeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + expect(preChangeIndexP2PKH, changeIndexP2PKH); + expect(preUtxoData, utxoData); + expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .called(1); + + expect(secureStore?.writes, 9); + expect(secureStore?.reads, 12); + expect(secureStore?.deletes, 2); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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); + + final wallet = await Hive.openBox(testWalletId); + + // add maxNumberOfIndexesToCheck and height + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + expect(await bch?.mnemonic, TEST_MNEMONIC.split(" ")); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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 bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, true); + + verify(client?.getServerFeatures()).called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test( + "recoverFromMnemonic using empty seed on testnet fails due to bad genesis hash match", + () async { + bch = BitcoinCashWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: Coin.bitcoincashTestNet, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + bool hasThrown = false; + try { + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, true); + + verify(client?.getServerFeatures()).called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + 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 bch?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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); + + final wallet = await Hive.openBox(testWalletId); + + bool hasThrown = false; + try { + await bch?.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); + + expect(secureStore?.interactions, 6); + expect(secureStore?.writes, 3); + expect(secureStore?.reads, 3); + expect(secureStore?.deletes, 0); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .thenAnswer((realInvocation) async {}); + + final wallet = await Hive.openBox(testWalletId); + + // restore so we have something to rescan + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + // fetch valid wallet data + final preReceivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final preUtxoData = await wallet.get('latest_utxo_model'); + final preReceiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final preChangeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + // destroy the data that the rescan will fix + await wallet.put( + 'receivingAddressesP2PKH', ["some address", "some other address"]); + await wallet + .put('changeAddressesP2PKH', ["some address", "some other address"]); + + await wallet.put('receivingIndexP2PKH', 123); + await wallet.put('changeIndexP2PKH', 123); + await secureStore?.write( + key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + await secureStore?.write( + key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + + bool hasThrown = false; + try { + await bch?.fullRescan(2, 1000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, false); + + // fetch wallet data again + final receivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final utxoData = await wallet.get('latest_utxo_model'); + final receiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final changeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + expect(preChangeIndexP2PKH, changeIndexP2PKH); + expect(preUtxoData, utxoData); + expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .called(1); + + expect(secureStore?.writes, 9); + expect(secureStore?.reads, 12); + expect(secureStore?.deletes, 2); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .thenAnswer((realInvocation) async {}); + + final wallet = await Hive.openBox(testWalletId); + + // restore so we have something to rescan + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + // fetch wallet data + final preReceivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + + final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final preUtxoData = await wallet.get('latest_utxo_model'); + final preReceiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final preChangeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + when(client?.getBatchHistory(args: historyBatchArgs0)) + .thenThrow(Exception("fake exception")); + + bool hasThrown = false; + try { + await bch?.fullRescan(2, 1000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, true); + + // fetch wallet data again + final receivingAddressesP2PKH = + await wallet.get('receivingAddressesP2PKH'); + + final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + final utxoData = await wallet.get('latest_utxo_model'); + final receiveDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_receiveDerivationsP2PKH"); + final changeDerivationsStringP2PKH = await secureStore?.read( + key: "${testWalletId}_changeDerivationsP2PKH"); + + expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + expect(preChangeIndexP2PKH, changeIndexP2PKH); + expect(preUtxoData, utxoData); + expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + .called(1); + + expect(secureStore?.writes, 7); + expect(secureStore?.reads, 12); + expect(secureStore?.deletes, 4); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + // // test("fetchBuildTxData 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(cachedClient.getTransaction( + // // tx_hash: + // // "339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .thenAnswer((_) async => tx9Raw); + // // when(cachedClient.getTransaction( + // // tx_hash: + // // "c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .thenAnswer((_) async => tx10Raw); + // // when(cachedClient.getTransaction( + // // tx_hash: + // // "d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .thenAnswer((_) async => tx11Raw); + // // + // // // recover to fill data + // // await bch.recoverFromMnemonic( + // // mnemonic: TEST_MNEMONIC, + // // maxUnusedAddressGap: 2, + // // maxNumberOfIndexesToCheck: 1000, + // // height: 4000); + // // + // // // modify addresses to trigger all change code branches + // // final chg44 = + // // await secureStore.read(key: testWalletId + "_changeDerivationsP2PKH"); + // // await secureStore.write( + // // key: testWalletId + "_changeDerivationsP2PKH", + // // value: chg44.replaceFirst("1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U", + // // "D5cQWPnhM3RRJVDz8wWC5jWt3PRCfg1zA6")); + // // + // // final data = await bch.fetchBuildTxData(utxoList); + // // + // // expect(data.length, 3); + // // expect( + // // data["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // .length, + // // 2); + // // expect( + // // data["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // .length, + // // 3); + // // expect( + // // data["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // .length, + // // 2); + // // expect( + // // data["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // ["output"], + // // isA()); + // // expect( + // // data["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["output"], + // // isA()); + // // expect( + // // data["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // ["output"], + // // isA()); + // // expect( + // // data["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["redeemScript"], + // // isA()); + // // + // // // modify addresses to trigger all receiving code branches + // // final rcv44 = await secureStore.read( + // // key: testWalletId + "_receiveDerivationsP2PKH"); + // // await secureStore.write( + // // key: testWalletId + "_receiveDerivationsP2PKH", + // // value: rcv44.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", + // // "D5cQWPnhM3RRJVDz8wWC5jWt3PRCfg1zA6")); + // // + // // final data2 = await bch.fetchBuildTxData(utxoList); + // // + // // expect(data2.length, 3); + // // expect( + // // data2["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // .length, + // // 2); + // // expect( + // // data2["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // .length, + // // 3); + // // expect( + // // data2["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // .length, + // // 2); + // // expect( + // // data2["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // ["output"], + // // isA()); + // // expect( + // // data2["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["output"], + // // isA()); + // // expect( + // // data2["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // ["output"], + // // isA()); + // // expect( + // // data2["339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data2["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data2["d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c"] + // // ["keyPair"], + // // isA()); + // // expect( + // // data2["c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e"] + // // ["redeemScript"], + // // isA()); + // // + // // verify(client.getServerFeatures()).called(1); + // // verify(cachedClient.getTransaction( + // // tx_hash: + // // "339dac760e4c9c81ed30a7fde7062785cb20712b18e108accdc39800f884fda9", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .called(2); + // // verify(cachedClient.getTransaction( + // // tx_hash: + // // "c2edf283df75cc2724320b866857a82d80266a59d69ab5a7ca12033adbffa44e", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .called(2); + // // verify(cachedClient.getTransaction( + // // tx_hash: + // // "d0c451513bee7d96cb88824d9d720e6b5b90073721b4985b439687f894c3989c", + // // coinName: "bitcoincash", + // // callOutSideMainIsolate: false)) + // // .called(2); + // // verify(client.getBatchHistory(args: historyBatchArgs0)).called(1); + // // verify(client.getBatchHistory(args: historyBatchArgs1)).called(1); + // // + // // expect(secureStore.interactions, 38); + // // expect(secureStore.writes, 13); + // // expect(secureStore.reads, 25); + // // expect(secureStore.deletes, 0); + // // + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + + // test("fetchBuildTxData throws", () 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(cachedClient?.getTransaction( + // txHash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenThrow(Exception("some exception")); + // + // // recover to fill data + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // bool didThrow = false; + // try { + // await bch?.fetchBuildTxData(utxoList); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 14); + // expect(secureStore?.writes, 7); + // expect(secureStore?.reads, 7); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("build transaction 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(cachedClient?.getTransaction( + // txHash: + // "e9673acb3bfa928f92a7d5a545151a672e9613fdf972f3849e16094c1ed28268", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "fa5bfa4eb581bedb28ca96a65ee77d8e81159914b70d5b7e215994221cc02a63", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "694617f0000499be2f6af5f8d1ddbcf1a70ad4710c0cee6f33a13a64bba454ed", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await bch?.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", + // "D5cQWPnhM3RRJVDz8wWC5jWt3PRCfg1zA6")); + // + // final data = await bch?.fetchBuildTxData(utxoList); + // + // final txData = await bch?.buildTransaction( + // utxosToUse: utxoList, + // utxoSigningData: data!, + // recipients: ["DS7cKFKdfbarMrYjFBQqEcHR5km6D51c74"], + // satoshiAmounts: [13000]); + // + // expect(txData?.length, 2); + // expect(txData?["hex"], isA()); + // expect(txData?["vSize"], isA()); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "d3054c63fe8cfafcbf67064ec66b9fbe1ac293860b5d6ffaddd39546658b72de", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "fa5bfa4eb581bedb28ca96a65ee77d8e81159914b70d5b7e215994221cc02a63", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: + // "694617f0000499be2f6af5f8d1ddbcf1a70ad4710c0cee6f33a13a64bba454ed", + // coin: Coin.bitcoincash, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + test("confirmSend error 1", () async { + bool didThrow = false; + try { + await bch?.confirmSend(txData: 1); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend error 2", () async { + bool didThrow = false; + try { + await bch?.confirmSend(txData: 2); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend some other error code", () async { + bool didThrow = false; + try { + await bch?.confirmSend(txData: 42); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend no hex", () async { + bool didThrow = false; + try { + await bch?.confirmSend(txData: {"some": "strange map"}); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend fails due to vSize being greater than fee", () async { + bool didThrow = false; + try { + await bch + ?.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); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + 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 bch + ?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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); + + final wallet = await Hive.openBox(testWalletId); + + // recover to fill data + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + bch?.refreshMutex = true; + + await bch?.refresh(); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + + expect(secureStore?.interactions, 6); + expect(secureStore?.writes, 3); + expect(secureStore?.reads, 3); + expect(secureStore?.deletes, 0); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("refresh wallet throws", () async { + when(client?.getBlockHeadTip()).thenThrow(Exception("some exception")); + 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?.getHistory(scripthash: anyNamed("scripthash"))) + .thenThrow(Exception("some exception")); + + final wallet = await Hive.openBox(testWalletId); + + // recover to fill data + await bch?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + await bch?.refresh(); + + verify(client?.getServerFeatures()).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + verify(client?.getBlockHeadTip()).called(1); + verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + + expect(secureStore?.interactions, 6); + expect(secureStore?.writes, 3); + expect(secureStore?.reads, 3); + expect(secureStore?.deletes, 0); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + // 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?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => []); + // when(client?.estimateFee(blocks: anyNamed("blocks"))) + // .thenAnswer((_) async => Decimal.one); + // // when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")) + // // .thenAnswer((_) async => Decimal.one); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // // recover to fill data + // await bch?.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 bch?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(2); + // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); + // verify(client?.getBlockHeadTip()).called(1); + // // verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2); + // + // expect(secureStore?.interactions, 6); + // expect(secureStore?.writes, 2); + // expect(secureStore?.reads, 2); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + }); + + tearDown(() async { + await tearDownTestHive(); + }); +} diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart new file mode 100644 index 000000000..e69de29bb diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart new file mode 100644 index 000000000..3fbfbdaaa --- /dev/null +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart @@ -0,0 +1,14 @@ +const TEST_MNEMONIC = + "orbit claim toss supreme sweet kit puppy gown lounge letter illness planet"; + +const ROOT_WIF = "KxpP3c45bRPbPzecYfistA5u3Z4SSGCCiCS4pjWrS5LF1g4mXC66"; +const NODE_WIF_44 = "KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o"; + +const ISOLATE_DERIVE_EXPECTED_TESTNET = + "{receive: {0: {p2pkh: {publicKey: 02846b24e2489b571e1eacae66dad5144f0194fa97fd33f9969674e6c031718191, wif: cVtvaWqNU1rzTwydotGXCwyxTB3x1KWTH67wu3xDoZT86WNQmBid, address: mpMk94ETazqonHutyC1v6ajshgtP8oiFKU}, p2sh: {publicKey: 03ef135d7987f25e5f904b5a7f1e9dcb3d4b787ae5b315afea51314e23b7ca7c54, wif: cUtApy1f3uon5zmauEGPMiXEX6no1p4tnHKR796VV5aQdo1ECzRh, address: 2N3GrCAFdpgJ4pJLepapKqiSmNwDYLkrU8W}, p2wpkh: {publicKey: 03f7a172264d89eb8c706bb887d411430fa8111f07348b537131ce6d83109e2f46, wif: cU1E2J1eHbQh36TCtsqneAsKaXRwRhSs74GEDPXWCGY7f1xpsymM, address: tb1qnf2qmetsr53tezq8qwspzdjnxplmjny4n5pxz3}}, 1: {p2pkh: {publicKey: 02057e72d0dc2b448b846a96a88b2ce9cfb9b9b0657a9634e7112cf080aabe3473, wif: cQNo7vPrfJ7qEPCKoSe7T1iST117Lm8dSJUvYwuC9vQpZ2zwbgv6, address: mpaW4y1dv42R9RTHgPQv1XSqJfYw52NJYy}, p2sh: {publicKey: 03027202d42383a043f1a70f1e8fc634391e08e097f66fabb88288e59f48e70a38, wif: cUnEnjizxjEgXgJMxR1SPxQBXC3mRBcQ9bEoY9ApxSD8ihVcQceh, address: 2Mvi3i2QYkrRrQd22WW4brHVtF2zAhdVwi3}, p2wpkh: {publicKey: 02bfbf552a8d5bb4bca1284a138119719a1a92b22aecef7e3967d5c3208ad5f152, wif: cPa85quZsdsXk17BP3pq2CuWKDp93JquL71tQgKYA27B2grQcic9, address: tb1qc6krkzn284rv2ugch2p9te0xw7egq4jk8gzqe5}}}, change: {0: {p2pkh: {publicKey: 03068667e3c60e6e77470a30724f0641b44d20e333c045a49a8c79ce8d453fc10b, wif: cQPpPV8136qx1KzYVSPmgY7CtJdV1QNU2bg9gAhn8E76Z23avf1i, address: mup6rf8QxcVQgUMQoGxHj8wQxskhrA4QiN}, p2sh: {publicKey: 02c9977033bac46cfd3847be81ec392582a10cb924efdfe433c7d35ffb3066c334, wif: cUtApy1f3uon5zmauEGPMiXEX6no1p4tnHKR796VV5aQdo1ECzRh, address: 2N4iiKohmeKYtiexoj3p6cshEY3DKdbaK9G}, p2wpkh: {publicKey: 030d81edda8fb383fffc8c2654e82b68dd6d074c6e97324de2f20a4c8eb73d9d79, wif: cQAZ6KVEnpDMfcgDx9M4mZYtknSUYRrCKbREfEYvdQus5hfuPKGr, address: tb1qhndp7nnlamv3w9sqjhz7tdlk2es0nyaj0dwnsh}}, 1: {p2pkh: {publicKey: 0354205a454c3a20813069bda67f7be96748a2a88550070697c0d61568212481cf, wif: cV3fSc57dkYmRvDQtAFJBSDJGMosGPYEyV4GbhP2AMw8A8YxgUyT, address: n24iGgnpskxApuVRzJ72q85ALQxPjBr8iW}, p2sh: {publicKey: 02309f858514e761c88153c9af3d44b70b5e8d06e76afcb73203da101e252fb1ce, wif: cUnEnjizxjEgXgJMxR1SPxQBXC3mRBcQ9bEoY9ApxSD8ihVcQceh, address: 2MzB56pivdE59u5mG9dcrno8UBVEHjpx8Md}, p2wpkh: {publicKey: 03b9d6f01c3303073a51992f4eaeb9be2db46f2696eb8bc336b4459959a4e63ad1, wif: cSss3AT1v5KyFNXbqQo9qFCLCmKPX2eJnBoQe2Hk9jzZFzMz5MdG, address: tb1qh45hl46hvnafr6hkcs6a5xgtkukjuh6v4cnssv}}}}"; + +const FILL_ADDRESSES_EXPECTED_CHANGE_MAINNET = + "{0: {p2pkh: {publicKey: 02a1a969c63ed41662a23afc640f76ee4a53c35fba4a13ff871931849abdc0670c, wif: Kws4oRj4Z3uT4WNejbuLsWvqTeso4dGciVu75jjd5fa36Tst48ER, address: 1JfZd6cSNr924p4ZwewG1CeRw7AqoYQ6uE}, p2sh: {publicKey: 020cebf05d41da01c00fbb7f7c04cf85741188da0e5ee22ae9fac6e7b5098b8cac, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3ANTVqufTH1tLAuoQHhng8jndRsA9hcNy7}, p2wpkh: {publicKey: 0301def02c073852fc8c28182749be431ac22a2699b51c896fbc48457107c10c65, wif: L3UwhkWiXSAyCWYVjaFCyMhGcqeDU8soCmqhuFYFadcVQepKKcjy, address: bc1qwt76574cgnhjv0nx4f52qylyla0t50d60znk5u}}, 1: {p2pkh: {publicKey: 02617b5f7868bd0402a3e3ff6fa224aede55f13218bbef9987467455d068d629cd, wif: KzDwZXPyNhPix2nV5wri4QaGj6Tge7znvf49D5tgL7EvAnYV6Pas, address: 1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U}, p2sh: {publicKey: 03b4c1e17b7257850fd2c0cf69acd397616835de29867cfe047bf1064b9803f773, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3LNBen8sNa6kXsWuMc6tynHBQr3vUHn3nL}, p2wpkh: {publicKey: 038b646c426ed75204bd92f55f0519fb89c41a503844aecf187a55eb00a5d8b5eb, wif: L1jQHTyFP4sNyqdKeooJzw3AUghPeSmGoW7UmZwDsS1Js4UxiGyh, address: bc1qn2x7h96kufgfjxtkhsnq03jqwqde8zasffqvd2}}, 2: {p2pkh: {publicKey: 031643235b0355ac366ade3ec125e466be42602a6167ba1cc68f39c38929f3c785, wif: KzqPH523RyAKwzRDg8PqjvNVDV8Fex2qrKxwwLGcvc9XZHfvaErb, address: 1EwEMkfeivF8nEkG3tWhBSqibnDjoREBoF}, p2sh: {publicKey: 02312b44be7de07c21bed9bd8b0c2f3d64be41c5f5120a463fcb85371b3f44cce1, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3MPyLcCkGPKyZULKSzkYDZQxxMmG8HNRPi}, p2wpkh: {publicKey: 031a10a60de8563b1be55e8274538d6ec6375d19764e81303999de2605634ea15a, wif: L2u4AYxEKkDamcXjAfXviuTPuRuWe1aN9zXEX1PSpzrmqGtSHQqe, address: bc1qpcn5wd2cx7syc28r0t4w4ym8yy6fck87nrxn4p}}, 3: {p2pkh: {publicKey: 02f552c7b15e90df9ff99f35e8b5bd84eff0bdef6cb6c75e13d1df81d334c6b786, wif: Kxi7qVxEqwaBhR3tuewpfi8EDqqR7fBzgQUDambVWGPEP3oG9JUM, address: 1MifgAa6CzqmTF4euVSD2DivD3xUGDbuA5}, p2sh: {publicKey: 02a7ba8279be4c182292b855cbf4349dba68f8a776b9b8e04e05585ac9f605e7f1, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 34pa7nXRZ3XahoFKhhnaLy4DqzfC9v5aSm}, p2wpkh: {publicKey: 03d712c240ccd578b8fb59acf88a8fbacf2ab14301a900e7907c739244e61029bd, wif: L3ezWHMUvdwprhChDVn7iVV8otpLUBrdkZB7X97GEbiVZbxJkDPc, address: bc1qemkha74vvmlz8yg56tesswpz77wg07m9wu23sc}}, 4: {p2pkh: {publicKey: 03d86c453c6b8ef6239db8fc270c76a9b08faf2c58f3de74cbe2cb0bc0cef7139a, wif: KzNpx8vgcNbYcqPZxHFwvKSRwPpyZNhmF4Eqa4k1wuWJZovwaXNC, address: 1BxYVw7u85QWVoVCHJAJkCCZZjWeXYW6EA}, p2sh: {publicKey: 020e9095a31b9c1e580b3773d34d1f752f9c4de42feb1c3480f5234a2392762fbc, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 33tciSXesiShRULW4bds5BE8358GsJc8CA}, p2wpkh: {publicKey: 02908673d298d4929a724ff9627c3fc710e27f0d727fbb0f2741c00dac1d792120, wif: L5QYEMxjY42c9JnpEQCDUbqCUfZ1vKoELe41rREuGzszcZgzVepx, address: bc1qpl7rh3ykw5h9823wv3uw0jrehlty6dfjp65ykt}}, 5: {p2pkh: {publicKey: 0368fda0552db99cfac045f57327cecd9b365d8813982dda051f1156ceefacd80b, wif: Ky8KYn2vMmWiMw6QEnE6JsM3uwQgAZBjiUHB938MVtnSgpiDjh7o, address: 14g637PwocgKr3S949WDitY7Bj3L1Bttcb}, p2sh: {publicKey: 03a1c4977ec9e9e02ba011cf56cee34c1617a7e05e85183a4207efd6425079a914, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 34Z5gAgPiUsfA9VHdAss2AD5h5Xk5TEp9K}, p2wpkh: {publicKey: 028c44701f2e918ba9853737aa1c6c7b5302a4c9198bf0eb22a75fb6fd3ef4dc9e, wif: Kx7wWb9xvNTaiWkzEmLqJ7gsQEaD6VPJTodr71rQwtpCU5dZDbEP, address: bc1q0ja83w4jwplrxjggcy8ltnrz70qrhnsmfwjv47}}, 6: {p2pkh: {publicKey: 03844bb44c430540dd2fd42630200a059f06480e024b7a06ea628b5094f48b944f, wif: Kwe6hMa7t9qcWW7fHBFTpBDDyzBdWzkHjhngiTLRKZzBjUqqppRJ, address: 1JNghjiaNWrWgvAJnmGyyFEbQMWV35QT1D}, p2sh: {publicKey: 03add39947c251a1f42f2fe4dbf41ea6dfed2bcf56bbe5ab6c44213235b68a1d0a, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3AnduCZuFcXFtBjnz7Y6vRJBwYvfeNKJtR}, p2wpkh: {publicKey: 02883642dad55a3f5003440ff7e066c97624539841643e900db80eaac7ebab7270, wif: L4sisqYU1xSzbnx25xuyLefifGL5BoGDXh5Vsex9UCBSDHhFYt8h, address: bc1qdjjssjd6vl6e3egqts7qxcj6dqrs6krgj4dv3q}}, 7: {p2pkh: {publicKey: 03487700edb11cf39b095b17da1f44f702398bd707dfdace4d88ee0227a48c8a0c, wif: L3UmMi9h8RQ8Y8jKATFjum77kc9ng6Jr71fq1H9rGxxdspKGXt12, address: 1Q1FMnBJxQ5uh2UDQbvwQ1y1KMpqVd3whq}, p2sh: {publicKey: 03a600d4880ce8a53287f25b6534f97a689ba7006f665ad1fe3e2bb8d3c9620ec5, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 3J9GqZ2b5raBNyGmuBg8ttyg5x8zaqSrMp}, p2wpkh: {publicKey: 035b91a2c439d6cc34a706449b8d9843721cc499aec4b38ebdd211978a52431fc0, wif: KzJZE96Hjwn2dtuYpBgMoR9s86TgQ3apRsNTxGZTpyW2RVA6HppV, address: bc1qs2df496wpp4339qjuypc2l2r6e0z7vndz70ac7}}, 8: {p2pkh: {publicKey: 0254ff6bad342ae9af2e04ee9d876682771ad86a76336a817eadb2e4e5bf68070a, wif: KxhFsZNv8XDUxLECR9y965eWFSGBbc2FrTkRvuyRPwPM9xxeCfV4, address: 1JD8uzAKQo9DgR2om6gS98S92cgqscZ3Ur}, p2sh: {publicKey: 02f574d6a16300e4d497647814fd2b0fa2eaba32af19fcf65821f70743bc566f9d, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3BqGdLjubq2vAGRDNRkxk5uMqd6FPoMg7N}, p2wpkh: {publicKey: 0227cb798c29365364027e5df67f6890985fd89ecbca571e51bb6260272cc66ec1, wif: Kxrpxu5kNAdy9jqz7etcCg716gsEk1K5weiMLKg1fhLPdxitvzsy, address: bc1qhpvcc08dxa7m5ztkxsfsplmj69yzsg6p3dna63}}, 9: {p2pkh: {publicKey: 02b8b1a447c71bb4ed82fd3719ec9e665df5c9c6e31c7a14beb56c65f355a08bc4, wif: Ky9kWxSwqBQP9ShkZNCfLwDqpzqnDEvQG3NjfYY3A3VFE4nPLqrs, address: 16EpYJQoM1K2ZKFUEMBAJubiNsMghkvBUx}, p2sh: {publicKey: 03f6cc36b17f382490d1a08bc9f7c28302c704c69cb6c153d2828710cd93a1c215, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3HBjEYyPH9H8JvuVbibNaZ2bJyD8564r1Y}, p2wpkh: {publicKey: 030f021f31d9eac4279f8ae12a512885769978dc93a74e0e4c91eba994a7d02cda, wif: L26bxcZZ8PLaa23X9myLgzh3hAYxjQ9MsSfiz49yiEdys7dzr66z, address: bc1q6hxsggrtcrvcdxpzu7e8qv2m9md3zaghjjsxut}}}"; + +const FILL_ADDRESSES_EXPECTED_RECEIVING_MAINNET = + "{0: {p2pkh: {publicKey: 0359bc5f4918d68ab3730886c4fdebec1cfdeba9db1170582da7072183d7afce99, wif: KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o, address: 1GDtrDP62zQETQESHt48Z82afXWcvX8qNv}, p2sh: {publicKey: 02addc76ef5845c0e74e5ada1d97568d0ee76856031e60a084f6f6a0e7be51d84b, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk}, p2wpkh: {publicKey: 02e5b585efaec9e6486f4b8afb3415ffd71a89566c1f84bb5331c4cddc905b7555, wif: KzCvDQSH6qqUFhPnbM42zbLK3qPeHXKVVpc6A5bAqADanHQFqypQ, address: bc1qz36w7sv4lnt09saurf94lwk5tc6qsyhky9yfgm}}, 1: {p2pkh: {publicKey: 036070efb466bfbf689efe1e7a27fa405bdbb16fa4c836f7b71feeb7ec9f5c5db4, wif: L3aA8PRqcj1iuZnQMJ3M5x68zhywgwgGbzPtDsQMBWRmqUaKyyBQ, address: 1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw}, p2sh: {publicKey: 02b8f46d7741d709bc1dc81386edaf4e6dad8b78b1fb9ec0c5b1f8e08e7d72f395, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3Ggn4xZmXjMCDUdQSTjxTi1PdVrtDGUy5Z}, p2wpkh: {publicKey: 03627f4374d7c992c40d743786028f94b1e1ca354b94ffa776b51ba50c80fba1d7, wif: L5gAb3ABSYTBTL85FhoRxHa7dJMtz9sVLHWkEY7kYKXT2cDg71Ym, address: bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0}}, 2: {p2pkh: {publicKey: 03f2171f073a8fbf5b844a20e5b6ae356a07e9e6dc5af24cd4b3e54a1599eb4137, wif: L27AJt6ZRU4D3sf7Ls1HLq2ruF8c6iYq9iJVvW8Xm2kMKrKcPwzZ, address: 1Mu5SqaUBHB1HkyvEtjHjrNQVaai2MoFFz}, p2sh: {publicKey: 024e6d7230df59dc28f94ab7bd45c5aed53ac0ea96e8a1ee46edcca5594fa3ec66, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3K8CQxXWp2kjbGcnub4sh935H5s1AfrFmp}, p2wpkh: {publicKey: 0365b55bdae2d0ada31bfc73371f7106e0e20563dff0763c48da8ce2ca70346bd8, wif: KxttkPfRdY9uwEqgHGPo9d7Bb3qTsBzHSgmv8EVTS2Gbh88GgJZQ, address: bc1qsegc09enu2ts4dg7lnxee6tv8fy78m89cfzdy9}}, 3: {p2pkh: {publicKey: 020d00dd73194f8f087a3740594f2eeaa545224fb634462e06d281ad5759e77cbd, wif: L5Mdvj7tkY4Sx5JTiB1WtQJPogqXGLunaFAubAQMvuAK3TjuXXxD, address: 16d7AeqhmspaMyeJKF3cDwxjkLUTFZQdTn}, p2sh: {publicKey: 038d2aec8c0da1bafc3e51fe7fdd3aa60f121983071d97ed754c05f24c54d7d09b, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 3QwDpsqwb5VfKQVUVLzaDwgC51pea1ymHr}, p2wpkh: {publicKey: 02f144cd8c1d6966db09da86bf662933480ad6e7f2862f7878780f21bc1448d563, wif: Ky9zeEcFB7td3dPYCiXxrMPCQkKyfm4feaWp5PAWxY7kBwCccUxz, address: bc1q3h5llpmhvr89el03dktd936jqsn7t6pytr2nlr}}, 4: {p2pkh: {publicKey: 038d6142ee0db16d4ff23c95d1c157428d48e11fae7752ddacaf6cccce6db61fc6, wif: Ky41iFhmo2Y5mYp1UALsYqRhhKajEy7AxLJcSDYRVdwW7iv3vmTK, address: 1EFyHH7G4Hk8aCjGWPer1Qc9e8iZsbfZC8}, p2sh: {publicKey: 03a1624a73009c17ceefe299ca998ee941b501692b2f3a032e037b50726415c78b, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 38YQK65m9A278nCbWKTx3EJtyK9Vega1Z9}, p2wpkh: {publicKey: 031e816e4a35c686ee30387e01c26341927806674986a57ee5755114f3ee59d560, wif: L5AiWbctHW9EyaD5vyzCrxKmKppdMwdRFTvB1TUKLWvibo5R3QXa, address: bc1qj69ku48uu6lqu6uhd62a37zvuwta2dlc28frxc}}, 5: {p2pkh: {publicKey: 024697dbd5ad644c285fb9ce25a38d0f8b48de9dfbda146eba8f5659184fb2779c, wif: L3yze1iu58vmF7GyoUHqnmYCm4qLsBoGiyRgBPbt68gB3kKnQfCv, address: 16ZbDHYV97o6xeXoQMWus1a8NMNyxqHZon}, p2sh: {publicKey: 021b442ef944676b70e6ef23ecb932c015b6934ff5d094db88820735be36f6d807, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 3LFx71JddSzs8qE51P4JHHjxsgiSTZdk4z}, p2wpkh: {publicKey: 0307ffe6b1c0dfca40b6126140691a4d907a053dcfd97b816cc7ff86775486e612, wif: L3F1KPjtaA4uyfcmMSH2sFWs2TW2aX6Lcn8vetrdqtwgKgvfvVGg, address: bc1ql66zvg82lk425g8j4jx5flt255n8a9up328z3l}}, 6: {p2pkh: {publicKey: 02a5087e407ad853720a7010c1870bbc35a9ee09d90777c617801c983aaccc8523, wif: L1fLMxyhSKSsLskB3iBcfbfCinVr6MRE9YYVKXfCbLjPLZLAUpb7, address: 1FzukoiU7vXb7invLNLCCLAFdfK2gmP45A}, p2sh: {publicKey: 02d4f8bc88178a215b5c95e079b7c1ff0b62ec2884d5ffa71122683557a16615f3, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3GpAXutnaxCYuV2MSgSiXQTSafE7Aouifs}, p2wpkh: {publicKey: 03f7eb3138cbaa7baabdef7b903ff1a3369a8d7937c15ae17dac7084aa4cfd8afe, wif: KyWW73wu11xVnmFKMzq9AuxQ3TXw2KjwCzxUa9WbYx2Ee5oh2Nk1, address: bc1q4ev5cas22cn8rna3a7je238p3plgqqdjv9xt0e}}, 7: {p2pkh: {publicKey: 0366efa2d1624fd9ff0d9feffa39ff47c10ef196fe209421dcebf5d07dcb8907ce, wif: KwxWpA4scQ7RVKDFKjXvKWn6PdVXpr6nzW9T6ju6VP3MmjBRb5eK, address: 13qhwGjBzshL724ZZNv2C2XmUNpFoDLx4Z}, p2sh: {publicKey: 03b08cabbcaa97c20bf30e0830c8c7a2361ba63d1ba987032ee7cd97d666b62692, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 39ZZhvQJGwohf2ndBGXYWW31t1E91j9wcB}, p2wpkh: {publicKey: 03a353139ce07e31b33f1c5b0ece6f08ac117339a1a56aa1b3af011216cf2cbf88, wif: KwL9LaYjNvwWiYgDiUBLnVW7W5j2FyWqmzrioTA2QRgwdz6bUTQH, address: bc1q96wmh0qc2lwauzxv7la206d03zysc9zfw3ajkd}}, 8: {p2pkh: {publicKey: 032234e3903ebcd72c081119fe4b4c2d98e3fd3940b5b6cd7737f5f4d3240dcb7d, wif: L5kwyvViXwBcFqzJL5cffcJR7S1vEHxHuKHAQrAq6FtknvJG4Cx3, address: 1LPGD9sExsBMTLGxnTTEDYBHrtRSZCKNrN}, p2sh: {publicKey: 02c1ca19626e4784b2ce19b68d2887e4c9c733176db189e536b71a79c07178b4af, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3FXRAzFAgVMDcqbv6WE9Ki7yBACrRAh2Wn}, p2wpkh: {publicKey: 020fd4e2cd43f98366dd60fcc8902564c532c9396c35ebb840b63c965deed16330, wif: L4EgRsvLsju7uVXsTEchYnGdwmFrxhbE9SkMuvnaMDSMxmopNg8C, address: bc1q354ttmgwjzjf7jmym2aw9p5geeq9lhk8vcffap}}, 9: {p2pkh: {publicKey: 03dd5e2f3b828bdc2653ce7a7ce34ebfa8118077b647bdf4922c5d15f4546dc419, wif: Ky4f8LEYkXMkLiYX4t2PR6V2vJigyCoYTYATu4B5pnjDDywUD2U7, address: 13GXUQDr91Z5prRxAQQFHUvKNqs2hEWrkJ}, p2sh: {publicKey: 034a4cf3a57083ade346ecaab24dc931e2a991107c7c42cf170d1c6e3b84560e68, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3FstRy5yCE7a3wCNS8SgFKi4SF3zWK8vB6}, p2wpkh: {publicKey: 020cb048fc44a2db09be875ccd938634c557b19e2062fbc55372783be1116737e7, wif: KyDq3LHf9NkUWChPxVEAV3dQkX89GJWUkSw8cSVLooXronEwKouy, address: bc1qn99xewnyhqcleyj03f2y0crdhqmxhm5cmextw8}}}"; From 0b660d943361902135d289673977e7be55617851 Mon Sep 17 00:00:00 2001 From: Likho Date: Mon, 12 Sep 2022 10:20:46 +0200 Subject: [PATCH 07/20] Remove wallet test parameters --- .../bitcoincash_wallet_test_parameters.dart | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart deleted file mode 100644 index 3fbfbdaaa..000000000 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart +++ /dev/null @@ -1,14 +0,0 @@ -const TEST_MNEMONIC = - "orbit claim toss supreme sweet kit puppy gown lounge letter illness planet"; - -const ROOT_WIF = "KxpP3c45bRPbPzecYfistA5u3Z4SSGCCiCS4pjWrS5LF1g4mXC66"; -const NODE_WIF_44 = "KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o"; - -const ISOLATE_DERIVE_EXPECTED_TESTNET = - "{receive: {0: {p2pkh: {publicKey: 02846b24e2489b571e1eacae66dad5144f0194fa97fd33f9969674e6c031718191, wif: cVtvaWqNU1rzTwydotGXCwyxTB3x1KWTH67wu3xDoZT86WNQmBid, address: mpMk94ETazqonHutyC1v6ajshgtP8oiFKU}, p2sh: {publicKey: 03ef135d7987f25e5f904b5a7f1e9dcb3d4b787ae5b315afea51314e23b7ca7c54, wif: cUtApy1f3uon5zmauEGPMiXEX6no1p4tnHKR796VV5aQdo1ECzRh, address: 2N3GrCAFdpgJ4pJLepapKqiSmNwDYLkrU8W}, p2wpkh: {publicKey: 03f7a172264d89eb8c706bb887d411430fa8111f07348b537131ce6d83109e2f46, wif: cU1E2J1eHbQh36TCtsqneAsKaXRwRhSs74GEDPXWCGY7f1xpsymM, address: tb1qnf2qmetsr53tezq8qwspzdjnxplmjny4n5pxz3}}, 1: {p2pkh: {publicKey: 02057e72d0dc2b448b846a96a88b2ce9cfb9b9b0657a9634e7112cf080aabe3473, wif: cQNo7vPrfJ7qEPCKoSe7T1iST117Lm8dSJUvYwuC9vQpZ2zwbgv6, address: mpaW4y1dv42R9RTHgPQv1XSqJfYw52NJYy}, p2sh: {publicKey: 03027202d42383a043f1a70f1e8fc634391e08e097f66fabb88288e59f48e70a38, wif: cUnEnjizxjEgXgJMxR1SPxQBXC3mRBcQ9bEoY9ApxSD8ihVcQceh, address: 2Mvi3i2QYkrRrQd22WW4brHVtF2zAhdVwi3}, p2wpkh: {publicKey: 02bfbf552a8d5bb4bca1284a138119719a1a92b22aecef7e3967d5c3208ad5f152, wif: cPa85quZsdsXk17BP3pq2CuWKDp93JquL71tQgKYA27B2grQcic9, address: tb1qc6krkzn284rv2ugch2p9te0xw7egq4jk8gzqe5}}}, change: {0: {p2pkh: {publicKey: 03068667e3c60e6e77470a30724f0641b44d20e333c045a49a8c79ce8d453fc10b, wif: cQPpPV8136qx1KzYVSPmgY7CtJdV1QNU2bg9gAhn8E76Z23avf1i, address: mup6rf8QxcVQgUMQoGxHj8wQxskhrA4QiN}, p2sh: {publicKey: 02c9977033bac46cfd3847be81ec392582a10cb924efdfe433c7d35ffb3066c334, wif: cUtApy1f3uon5zmauEGPMiXEX6no1p4tnHKR796VV5aQdo1ECzRh, address: 2N4iiKohmeKYtiexoj3p6cshEY3DKdbaK9G}, p2wpkh: {publicKey: 030d81edda8fb383fffc8c2654e82b68dd6d074c6e97324de2f20a4c8eb73d9d79, wif: cQAZ6KVEnpDMfcgDx9M4mZYtknSUYRrCKbREfEYvdQus5hfuPKGr, address: tb1qhndp7nnlamv3w9sqjhz7tdlk2es0nyaj0dwnsh}}, 1: {p2pkh: {publicKey: 0354205a454c3a20813069bda67f7be96748a2a88550070697c0d61568212481cf, wif: cV3fSc57dkYmRvDQtAFJBSDJGMosGPYEyV4GbhP2AMw8A8YxgUyT, address: n24iGgnpskxApuVRzJ72q85ALQxPjBr8iW}, p2sh: {publicKey: 02309f858514e761c88153c9af3d44b70b5e8d06e76afcb73203da101e252fb1ce, wif: cUnEnjizxjEgXgJMxR1SPxQBXC3mRBcQ9bEoY9ApxSD8ihVcQceh, address: 2MzB56pivdE59u5mG9dcrno8UBVEHjpx8Md}, p2wpkh: {publicKey: 03b9d6f01c3303073a51992f4eaeb9be2db46f2696eb8bc336b4459959a4e63ad1, wif: cSss3AT1v5KyFNXbqQo9qFCLCmKPX2eJnBoQe2Hk9jzZFzMz5MdG, address: tb1qh45hl46hvnafr6hkcs6a5xgtkukjuh6v4cnssv}}}}"; - -const FILL_ADDRESSES_EXPECTED_CHANGE_MAINNET = - "{0: {p2pkh: {publicKey: 02a1a969c63ed41662a23afc640f76ee4a53c35fba4a13ff871931849abdc0670c, wif: Kws4oRj4Z3uT4WNejbuLsWvqTeso4dGciVu75jjd5fa36Tst48ER, address: 1JfZd6cSNr924p4ZwewG1CeRw7AqoYQ6uE}, p2sh: {publicKey: 020cebf05d41da01c00fbb7f7c04cf85741188da0e5ee22ae9fac6e7b5098b8cac, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3ANTVqufTH1tLAuoQHhng8jndRsA9hcNy7}, p2wpkh: {publicKey: 0301def02c073852fc8c28182749be431ac22a2699b51c896fbc48457107c10c65, wif: L3UwhkWiXSAyCWYVjaFCyMhGcqeDU8soCmqhuFYFadcVQepKKcjy, address: bc1qwt76574cgnhjv0nx4f52qylyla0t50d60znk5u}}, 1: {p2pkh: {publicKey: 02617b5f7868bd0402a3e3ff6fa224aede55f13218bbef9987467455d068d629cd, wif: KzDwZXPyNhPix2nV5wri4QaGj6Tge7znvf49D5tgL7EvAnYV6Pas, address: 1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U}, p2sh: {publicKey: 03b4c1e17b7257850fd2c0cf69acd397616835de29867cfe047bf1064b9803f773, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3LNBen8sNa6kXsWuMc6tynHBQr3vUHn3nL}, p2wpkh: {publicKey: 038b646c426ed75204bd92f55f0519fb89c41a503844aecf187a55eb00a5d8b5eb, wif: L1jQHTyFP4sNyqdKeooJzw3AUghPeSmGoW7UmZwDsS1Js4UxiGyh, address: bc1qn2x7h96kufgfjxtkhsnq03jqwqde8zasffqvd2}}, 2: {p2pkh: {publicKey: 031643235b0355ac366ade3ec125e466be42602a6167ba1cc68f39c38929f3c785, wif: KzqPH523RyAKwzRDg8PqjvNVDV8Fex2qrKxwwLGcvc9XZHfvaErb, address: 1EwEMkfeivF8nEkG3tWhBSqibnDjoREBoF}, p2sh: {publicKey: 02312b44be7de07c21bed9bd8b0c2f3d64be41c5f5120a463fcb85371b3f44cce1, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3MPyLcCkGPKyZULKSzkYDZQxxMmG8HNRPi}, p2wpkh: {publicKey: 031a10a60de8563b1be55e8274538d6ec6375d19764e81303999de2605634ea15a, wif: L2u4AYxEKkDamcXjAfXviuTPuRuWe1aN9zXEX1PSpzrmqGtSHQqe, address: bc1qpcn5wd2cx7syc28r0t4w4ym8yy6fck87nrxn4p}}, 3: {p2pkh: {publicKey: 02f552c7b15e90df9ff99f35e8b5bd84eff0bdef6cb6c75e13d1df81d334c6b786, wif: Kxi7qVxEqwaBhR3tuewpfi8EDqqR7fBzgQUDambVWGPEP3oG9JUM, address: 1MifgAa6CzqmTF4euVSD2DivD3xUGDbuA5}, p2sh: {publicKey: 02a7ba8279be4c182292b855cbf4349dba68f8a776b9b8e04e05585ac9f605e7f1, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 34pa7nXRZ3XahoFKhhnaLy4DqzfC9v5aSm}, p2wpkh: {publicKey: 03d712c240ccd578b8fb59acf88a8fbacf2ab14301a900e7907c739244e61029bd, wif: L3ezWHMUvdwprhChDVn7iVV8otpLUBrdkZB7X97GEbiVZbxJkDPc, address: bc1qemkha74vvmlz8yg56tesswpz77wg07m9wu23sc}}, 4: {p2pkh: {publicKey: 03d86c453c6b8ef6239db8fc270c76a9b08faf2c58f3de74cbe2cb0bc0cef7139a, wif: KzNpx8vgcNbYcqPZxHFwvKSRwPpyZNhmF4Eqa4k1wuWJZovwaXNC, address: 1BxYVw7u85QWVoVCHJAJkCCZZjWeXYW6EA}, p2sh: {publicKey: 020e9095a31b9c1e580b3773d34d1f752f9c4de42feb1c3480f5234a2392762fbc, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 33tciSXesiShRULW4bds5BE8358GsJc8CA}, p2wpkh: {publicKey: 02908673d298d4929a724ff9627c3fc710e27f0d727fbb0f2741c00dac1d792120, wif: L5QYEMxjY42c9JnpEQCDUbqCUfZ1vKoELe41rREuGzszcZgzVepx, address: bc1qpl7rh3ykw5h9823wv3uw0jrehlty6dfjp65ykt}}, 5: {p2pkh: {publicKey: 0368fda0552db99cfac045f57327cecd9b365d8813982dda051f1156ceefacd80b, wif: Ky8KYn2vMmWiMw6QEnE6JsM3uwQgAZBjiUHB938MVtnSgpiDjh7o, address: 14g637PwocgKr3S949WDitY7Bj3L1Bttcb}, p2sh: {publicKey: 03a1c4977ec9e9e02ba011cf56cee34c1617a7e05e85183a4207efd6425079a914, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 34Z5gAgPiUsfA9VHdAss2AD5h5Xk5TEp9K}, p2wpkh: {publicKey: 028c44701f2e918ba9853737aa1c6c7b5302a4c9198bf0eb22a75fb6fd3ef4dc9e, wif: Kx7wWb9xvNTaiWkzEmLqJ7gsQEaD6VPJTodr71rQwtpCU5dZDbEP, address: bc1q0ja83w4jwplrxjggcy8ltnrz70qrhnsmfwjv47}}, 6: {p2pkh: {publicKey: 03844bb44c430540dd2fd42630200a059f06480e024b7a06ea628b5094f48b944f, wif: Kwe6hMa7t9qcWW7fHBFTpBDDyzBdWzkHjhngiTLRKZzBjUqqppRJ, address: 1JNghjiaNWrWgvAJnmGyyFEbQMWV35QT1D}, p2sh: {publicKey: 03add39947c251a1f42f2fe4dbf41ea6dfed2bcf56bbe5ab6c44213235b68a1d0a, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3AnduCZuFcXFtBjnz7Y6vRJBwYvfeNKJtR}, p2wpkh: {publicKey: 02883642dad55a3f5003440ff7e066c97624539841643e900db80eaac7ebab7270, wif: L4sisqYU1xSzbnx25xuyLefifGL5BoGDXh5Vsex9UCBSDHhFYt8h, address: bc1qdjjssjd6vl6e3egqts7qxcj6dqrs6krgj4dv3q}}, 7: {p2pkh: {publicKey: 03487700edb11cf39b095b17da1f44f702398bd707dfdace4d88ee0227a48c8a0c, wif: L3UmMi9h8RQ8Y8jKATFjum77kc9ng6Jr71fq1H9rGxxdspKGXt12, address: 1Q1FMnBJxQ5uh2UDQbvwQ1y1KMpqVd3whq}, p2sh: {publicKey: 03a600d4880ce8a53287f25b6534f97a689ba7006f665ad1fe3e2bb8d3c9620ec5, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 3J9GqZ2b5raBNyGmuBg8ttyg5x8zaqSrMp}, p2wpkh: {publicKey: 035b91a2c439d6cc34a706449b8d9843721cc499aec4b38ebdd211978a52431fc0, wif: KzJZE96Hjwn2dtuYpBgMoR9s86TgQ3apRsNTxGZTpyW2RVA6HppV, address: bc1qs2df496wpp4339qjuypc2l2r6e0z7vndz70ac7}}, 8: {p2pkh: {publicKey: 0254ff6bad342ae9af2e04ee9d876682771ad86a76336a817eadb2e4e5bf68070a, wif: KxhFsZNv8XDUxLECR9y965eWFSGBbc2FrTkRvuyRPwPM9xxeCfV4, address: 1JD8uzAKQo9DgR2om6gS98S92cgqscZ3Ur}, p2sh: {publicKey: 02f574d6a16300e4d497647814fd2b0fa2eaba32af19fcf65821f70743bc566f9d, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3BqGdLjubq2vAGRDNRkxk5uMqd6FPoMg7N}, p2wpkh: {publicKey: 0227cb798c29365364027e5df67f6890985fd89ecbca571e51bb6260272cc66ec1, wif: Kxrpxu5kNAdy9jqz7etcCg716gsEk1K5weiMLKg1fhLPdxitvzsy, address: bc1qhpvcc08dxa7m5ztkxsfsplmj69yzsg6p3dna63}}, 9: {p2pkh: {publicKey: 02b8b1a447c71bb4ed82fd3719ec9e665df5c9c6e31c7a14beb56c65f355a08bc4, wif: Ky9kWxSwqBQP9ShkZNCfLwDqpzqnDEvQG3NjfYY3A3VFE4nPLqrs, address: 16EpYJQoM1K2ZKFUEMBAJubiNsMghkvBUx}, p2sh: {publicKey: 03f6cc36b17f382490d1a08bc9f7c28302c704c69cb6c153d2828710cd93a1c215, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3HBjEYyPH9H8JvuVbibNaZ2bJyD8564r1Y}, p2wpkh: {publicKey: 030f021f31d9eac4279f8ae12a512885769978dc93a74e0e4c91eba994a7d02cda, wif: L26bxcZZ8PLaa23X9myLgzh3hAYxjQ9MsSfiz49yiEdys7dzr66z, address: bc1q6hxsggrtcrvcdxpzu7e8qv2m9md3zaghjjsxut}}}"; - -const FILL_ADDRESSES_EXPECTED_RECEIVING_MAINNET = - "{0: {p2pkh: {publicKey: 0359bc5f4918d68ab3730886c4fdebec1cfdeba9db1170582da7072183d7afce99, wif: KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o, address: 1GDtrDP62zQETQESHt48Z82afXWcvX8qNv}, p2sh: {publicKey: 02addc76ef5845c0e74e5ada1d97568d0ee76856031e60a084f6f6a0e7be51d84b, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk}, p2wpkh: {publicKey: 02e5b585efaec9e6486f4b8afb3415ffd71a89566c1f84bb5331c4cddc905b7555, wif: KzCvDQSH6qqUFhPnbM42zbLK3qPeHXKVVpc6A5bAqADanHQFqypQ, address: bc1qz36w7sv4lnt09saurf94lwk5tc6qsyhky9yfgm}}, 1: {p2pkh: {publicKey: 036070efb466bfbf689efe1e7a27fa405bdbb16fa4c836f7b71feeb7ec9f5c5db4, wif: L3aA8PRqcj1iuZnQMJ3M5x68zhywgwgGbzPtDsQMBWRmqUaKyyBQ, address: 1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw}, p2sh: {publicKey: 02b8f46d7741d709bc1dc81386edaf4e6dad8b78b1fb9ec0c5b1f8e08e7d72f395, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3Ggn4xZmXjMCDUdQSTjxTi1PdVrtDGUy5Z}, p2wpkh: {publicKey: 03627f4374d7c992c40d743786028f94b1e1ca354b94ffa776b51ba50c80fba1d7, wif: L5gAb3ABSYTBTL85FhoRxHa7dJMtz9sVLHWkEY7kYKXT2cDg71Ym, address: bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0}}, 2: {p2pkh: {publicKey: 03f2171f073a8fbf5b844a20e5b6ae356a07e9e6dc5af24cd4b3e54a1599eb4137, wif: L27AJt6ZRU4D3sf7Ls1HLq2ruF8c6iYq9iJVvW8Xm2kMKrKcPwzZ, address: 1Mu5SqaUBHB1HkyvEtjHjrNQVaai2MoFFz}, p2sh: {publicKey: 024e6d7230df59dc28f94ab7bd45c5aed53ac0ea96e8a1ee46edcca5594fa3ec66, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3K8CQxXWp2kjbGcnub4sh935H5s1AfrFmp}, p2wpkh: {publicKey: 0365b55bdae2d0ada31bfc73371f7106e0e20563dff0763c48da8ce2ca70346bd8, wif: KxttkPfRdY9uwEqgHGPo9d7Bb3qTsBzHSgmv8EVTS2Gbh88GgJZQ, address: bc1qsegc09enu2ts4dg7lnxee6tv8fy78m89cfzdy9}}, 3: {p2pkh: {publicKey: 020d00dd73194f8f087a3740594f2eeaa545224fb634462e06d281ad5759e77cbd, wif: L5Mdvj7tkY4Sx5JTiB1WtQJPogqXGLunaFAubAQMvuAK3TjuXXxD, address: 16d7AeqhmspaMyeJKF3cDwxjkLUTFZQdTn}, p2sh: {publicKey: 038d2aec8c0da1bafc3e51fe7fdd3aa60f121983071d97ed754c05f24c54d7d09b, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 3QwDpsqwb5VfKQVUVLzaDwgC51pea1ymHr}, p2wpkh: {publicKey: 02f144cd8c1d6966db09da86bf662933480ad6e7f2862f7878780f21bc1448d563, wif: Ky9zeEcFB7td3dPYCiXxrMPCQkKyfm4feaWp5PAWxY7kBwCccUxz, address: bc1q3h5llpmhvr89el03dktd936jqsn7t6pytr2nlr}}, 4: {p2pkh: {publicKey: 038d6142ee0db16d4ff23c95d1c157428d48e11fae7752ddacaf6cccce6db61fc6, wif: Ky41iFhmo2Y5mYp1UALsYqRhhKajEy7AxLJcSDYRVdwW7iv3vmTK, address: 1EFyHH7G4Hk8aCjGWPer1Qc9e8iZsbfZC8}, p2sh: {publicKey: 03a1624a73009c17ceefe299ca998ee941b501692b2f3a032e037b50726415c78b, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 38YQK65m9A278nCbWKTx3EJtyK9Vega1Z9}, p2wpkh: {publicKey: 031e816e4a35c686ee30387e01c26341927806674986a57ee5755114f3ee59d560, wif: L5AiWbctHW9EyaD5vyzCrxKmKppdMwdRFTvB1TUKLWvibo5R3QXa, address: bc1qj69ku48uu6lqu6uhd62a37zvuwta2dlc28frxc}}, 5: {p2pkh: {publicKey: 024697dbd5ad644c285fb9ce25a38d0f8b48de9dfbda146eba8f5659184fb2779c, wif: L3yze1iu58vmF7GyoUHqnmYCm4qLsBoGiyRgBPbt68gB3kKnQfCv, address: 16ZbDHYV97o6xeXoQMWus1a8NMNyxqHZon}, p2sh: {publicKey: 021b442ef944676b70e6ef23ecb932c015b6934ff5d094db88820735be36f6d807, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 3LFx71JddSzs8qE51P4JHHjxsgiSTZdk4z}, p2wpkh: {publicKey: 0307ffe6b1c0dfca40b6126140691a4d907a053dcfd97b816cc7ff86775486e612, wif: L3F1KPjtaA4uyfcmMSH2sFWs2TW2aX6Lcn8vetrdqtwgKgvfvVGg, address: bc1ql66zvg82lk425g8j4jx5flt255n8a9up328z3l}}, 6: {p2pkh: {publicKey: 02a5087e407ad853720a7010c1870bbc35a9ee09d90777c617801c983aaccc8523, wif: L1fLMxyhSKSsLskB3iBcfbfCinVr6MRE9YYVKXfCbLjPLZLAUpb7, address: 1FzukoiU7vXb7invLNLCCLAFdfK2gmP45A}, p2sh: {publicKey: 02d4f8bc88178a215b5c95e079b7c1ff0b62ec2884d5ffa71122683557a16615f3, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3GpAXutnaxCYuV2MSgSiXQTSafE7Aouifs}, p2wpkh: {publicKey: 03f7eb3138cbaa7baabdef7b903ff1a3369a8d7937c15ae17dac7084aa4cfd8afe, wif: KyWW73wu11xVnmFKMzq9AuxQ3TXw2KjwCzxUa9WbYx2Ee5oh2Nk1, address: bc1q4ev5cas22cn8rna3a7je238p3plgqqdjv9xt0e}}, 7: {p2pkh: {publicKey: 0366efa2d1624fd9ff0d9feffa39ff47c10ef196fe209421dcebf5d07dcb8907ce, wif: KwxWpA4scQ7RVKDFKjXvKWn6PdVXpr6nzW9T6ju6VP3MmjBRb5eK, address: 13qhwGjBzshL724ZZNv2C2XmUNpFoDLx4Z}, p2sh: {publicKey: 03b08cabbcaa97c20bf30e0830c8c7a2361ba63d1ba987032ee7cd97d666b62692, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 39ZZhvQJGwohf2ndBGXYWW31t1E91j9wcB}, p2wpkh: {publicKey: 03a353139ce07e31b33f1c5b0ece6f08ac117339a1a56aa1b3af011216cf2cbf88, wif: KwL9LaYjNvwWiYgDiUBLnVW7W5j2FyWqmzrioTA2QRgwdz6bUTQH, address: bc1q96wmh0qc2lwauzxv7la206d03zysc9zfw3ajkd}}, 8: {p2pkh: {publicKey: 032234e3903ebcd72c081119fe4b4c2d98e3fd3940b5b6cd7737f5f4d3240dcb7d, wif: L5kwyvViXwBcFqzJL5cffcJR7S1vEHxHuKHAQrAq6FtknvJG4Cx3, address: 1LPGD9sExsBMTLGxnTTEDYBHrtRSZCKNrN}, p2sh: {publicKey: 02c1ca19626e4784b2ce19b68d2887e4c9c733176db189e536b71a79c07178b4af, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3FXRAzFAgVMDcqbv6WE9Ki7yBACrRAh2Wn}, p2wpkh: {publicKey: 020fd4e2cd43f98366dd60fcc8902564c532c9396c35ebb840b63c965deed16330, wif: L4EgRsvLsju7uVXsTEchYnGdwmFrxhbE9SkMuvnaMDSMxmopNg8C, address: bc1q354ttmgwjzjf7jmym2aw9p5geeq9lhk8vcffap}}, 9: {p2pkh: {publicKey: 03dd5e2f3b828bdc2653ce7a7ce34ebfa8118077b647bdf4922c5d15f4546dc419, wif: Ky4f8LEYkXMkLiYX4t2PR6V2vJigyCoYTYATu4B5pnjDDywUD2U7, address: 13GXUQDr91Z5prRxAQQFHUvKNqs2hEWrkJ}, p2sh: {publicKey: 034a4cf3a57083ade346ecaab24dc931e2a991107c7c42cf170d1c6e3b84560e68, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3FstRy5yCE7a3wCNS8SgFKi4SF3zWK8vB6}, p2wpkh: {publicKey: 020cb048fc44a2db09be875ccd938634c557b19e2062fbc55372783be1116737e7, wif: KyDq3LHf9NkUWChPxVEAV3dQkX89GJWUkSw8cSVLooXronEwKouy, address: bc1qn99xewnyhqcleyj03f2y0crdhqmxhm5cmextw8}}}"; From 6a418c421579a9f57f650b570000b5e4834a0c0a Mon Sep 17 00:00:00 2001 From: Likho Date: Mon, 12 Sep 2022 10:30:32 +0200 Subject: [PATCH 08/20] WIP : Adding name coin --- .../coins/namecoin/namecoin_wallet.dart | 3051 +++++++++++++++++ lib/utilities/enums/coin_enum.dart | 3 + 2 files changed, 3054 insertions(+) create mode 100644 lib/services/coins/namecoin/namecoin_wallet.dart diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart new file mode 100644 index 000000000..30394cd62 --- /dev/null +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -0,0 +1,3051 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +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:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:http/http.dart'; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/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/node_service.dart'; +import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/services/price.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/assets.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/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:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +const int MINIMUM_CONFIRMATIONS = 3; +const int DUST_LIMIT = 1000000; + +const String GENESIS_HASH_MAINNET = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const String GENESIS_HASH_TESTNET = + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + +enum DerivePathType { bip44 } + +bip32.BIP32 getBip32Node(int chain, int index, String mnemonic, + NetworkType network, DerivePathType derivePathType) { + final root = getBip32Root(mnemonic, network); + + final node = getBip32NodeFromRoot(chain, index, root, derivePathType); + return node; +} + +/// wrapper for compute() +bip32.BIP32 getBip32NodeWrapper( + Tuple5 args, +) { + return getBip32Node( + args.item1, + args.item2, + args.item3, + args.item4, + args.item5, + ); +} + +bip32.BIP32 getBip32NodeFromRoot( + int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { + String coinType; + switch (root.network.wif) { + case 0x80: // bch mainnet wif + coinType = "145"; // bch mainnet + break; + default: + throw Exception("Invalid Bitcoincash network type used!"); + } + switch (derivePathType) { + case DerivePathType.bip44: + return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + default: + throw Exception("DerivePathType must not be null."); + } +} + +/// wrapper for compute() +bip32.BIP32 getBip32NodeFromRootWrapper( + Tuple4 args, +) { + return getBip32NodeFromRoot( + args.item1, + args.item2, + args.item3, + args.item4, + ); +} + +bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { + final seed = bip39.mnemonicToSeed(mnemonic); + final networkType = bip32.NetworkType( + wif: network.wif, + bip32: bip32.Bip32Type( + public: network.bip32.public, + private: network.bip32.private, + ), + ); + + final root = bip32.BIP32.fromSeed(seed, networkType); + return root; +} + +/// wrapper for compute() +bip32.BIP32 getBip32RootWrapper(Tuple2 args) { + return getBip32Root(args.item1, args.item2); +} + +class NamecoinCashWallet extends CoinServiceAPI { + static const integrationTestFlag = + bool.fromEnvironment("IS_INTEGRATION_TEST"); + final _prefs = Prefs.instance; + + Timer? timer; + late Coin _coin; + + late final TransactionNotificationTracker txTracker; + + NetworkType get _network { + switch (coin) { + case Coin.bitcoincash: + return bitcoincash; + default: + throw Exception("Bitcoincash network type not set!"); + } + } + + List outputsList = []; + + @override + Coin get coin => _coin; + + @override + Future> get allOwnAddresses => + _allOwnAddresses ??= _fetchAllOwnAddresses(); + Future>? _allOwnAddresses; + + Future? _utxoData; + Future get utxoData => _utxoData ??= _fetchUtxoData(); + + @override + Future> get unspentOutputs async => + (await utxoData).unspentOutputArray; + + @override + Future get availableBalance async { + final data = await utxoData; + return Format.satoshisToAmount( + data.satoshiBalance - data.satoshiBalanceUnconfirmed); + } + + @override + Future get pendingBalance async { + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed); + } + + @override + Future get balanceMinusMaxFee async => + (await availableBalance) - + (Decimal.fromInt((await maxFee)) / Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(); + + @override + Future get totalBalance async { + if (!isActive) { + final totalBalance = DB.instance + .get(boxName: walletId, key: 'totalBalance') as int?; + if (totalBalance == null) { + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalance); + } else { + return Format.satoshisToAmount(totalBalance); + } + } + final data = await utxoData; + return Format.satoshisToAmount(data.satoshiBalance); + } + + @override + Future get currentReceivingAddress => + _currentReceivingAddressP2PKH ??= + _getCurrentAddressForChain(0, DerivePathType.bip44); + + Future? _currentReceivingAddressP2PKH; + + @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; + final satsFee = + Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin); + return satsFee.floor().toBigInt().toInt(); + } + + @override + Future> get mnemonic => _getMnemonicList(); + + Future get chainHeight async { + try { + final result = await _electrumXClient.getBlockHeadTip(); + return result["height"] as int; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return -1; + } + } + + Future get storedChainHeight async { + final storedHeight = DB.instance + .get(boxName: walletId, key: "storedChainHeight") as int?; + return storedHeight ?? 0; + } + + Future updateStoredChainHeight({required int newHeight}) async { + DB.instance.put( + boxName: walletId, key: "storedChainHeight", value: newHeight); + } + + 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; + } + throw ArgumentError('Invalid version or Network mismatch'); + } else { + try { + decodeBech32 = segwit.decode(address); + } 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'); + } + } + throw ArgumentError('$address has no matching Script'); + } + + bool longMutex = false; + + @override + Future recoverFromMnemonic({ + required String mnemonic, + 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) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.bitcoincash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a BitcoinCashWallet using a non bch coin type: ${coin.name}"); + } + } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic.trim(), + 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 _recoverWalletFromBIP32SeedPhrase({ + required String mnemonic, + int maxUnusedAddressGap = 20, + int maxNumberOfIndexesToCheck = 1000, + }) async { + longMutex = true; + + Map> p2pkhReceiveDerivations = {}; + Map> p2pkhChangeDerivations = {}; + + final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + + List p2pkhReceiveAddressArray = []; + int p2pkhReceiveIndex = -1; + + List p2pkhChangeAddressArray = []; + int p2pkhChangeIndex = -1; + + // The gap limit will be capped at [maxUnusedAddressGap] + int receivingGapCounter = 0; + int changeGapCounter = 0; + + // actual size is 12 due to p2pkh so 12x1 + const txCountBatchSize = 12; + + try { + // receiving addresses + Logging.instance + .log("checking receiving addresses...", level: LogLevel.Info); + for (int index = 0; + index < maxNumberOfIndexesToCheck && + receivingGapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + Logging.instance.log( + "index: $index, \t receivingGapCounter: $receivingGapCounter", + level: LogLevel.Info); + + final receivingP2pkhID = "k_$index"; + Map txCountCallArgs = {}; + final Map receivingNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + // bip44 / P2PKH + final node44 = await compute( + getBip32NodeFromRootWrapper, + Tuple4( + 0, + index + j, + root, + DerivePathType.bip44, + ), + ); + final p2pkhReceiveAddress = P2PKH( + data: PaymentData(pubkey: node44.publicKey), + network: _network) + .data + .address!; + receivingNodes.addAll({ + "${receivingP2pkhID}_$j": { + "node": node44, + "address": p2pkhReceiveAddress, + } + }); + txCountCallArgs.addAll({ + "${receivingP2pkhID}_$j": p2pkhReceiveAddress, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: txCountCallArgs); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!; + if (p2pkhTxCount > 0) { + final node = receivingNodes["${receivingP2pkhID}_$k"]; + // add address to array + p2pkhReceiveAddressArray.add(node["address"] as String); + // set current index + p2pkhReceiveIndex = index + k; + // reset counter + receivingGapCounter = 0; + // add info to derivations + p2pkhReceiveDerivations[node["address"] as String] = { + "pubKey": Format.uint8listToString( + (node["node"] as bip32.BIP32).publicKey), + "wif": (node["node"] as bip32.BIP32).toWIF(), + }; + } + + // increase counter when no tx history found + if (p2pkhTxCount == 0) { + receivingGapCounter++; + } + } + } + + Logging.instance + .log("checking change addresses...", level: LogLevel.Info); + // change addresses + for (int index = 0; + index < maxNumberOfIndexesToCheck && + changeGapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + Logging.instance.log( + "index: $index, \t changeGapCounter: $changeGapCounter", + level: LogLevel.Info); + final changeP2pkhID = "k_$index"; + Map args = {}; + final Map changeNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + // bip44 / P2PKH + final node44 = await compute( + getBip32NodeFromRootWrapper, + Tuple4( + 1, + index + j, + root, + DerivePathType.bip44, + ), + ); + final p2pkhChangeAddress = P2PKH( + data: PaymentData(pubkey: node44.publicKey), + network: _network) + .data + .address!; + changeNodes.addAll({ + "${changeP2pkhID}_$j": { + "node": node44, + "address": p2pkhChangeAddress, + } + }); + args.addAll({ + "${changeP2pkhID}_$j": p2pkhChangeAddress, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: args); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!; + if (p2pkhTxCount > 0) { + final node = changeNodes["${changeP2pkhID}_$k"]; + // add address to array + p2pkhChangeAddressArray.add(node["address"] as String); + // set current index + p2pkhChangeIndex = index + k; + // reset counter + changeGapCounter = 0; + // add info to derivations + p2pkhChangeDerivations[node["address"] as String] = { + "pubKey": Format.uint8listToString( + (node["node"] as bip32.BIP32).publicKey), + "wif": (node["node"] as bip32.BIP32).toWIF(), + }; + } + + // increase counter when no tx history found + if (p2pkhTxCount == 0) { + changeGapCounter++; + } + } + } + + // save the derivations (if any) + if (p2pkhReceiveDerivations.isNotEmpty) { + await addDerivations( + chain: 0, + derivePathType: DerivePathType.bip44, + derivationsToAdd: p2pkhReceiveDerivations); + } + if (p2pkhChangeDerivations.isNotEmpty) { + await addDerivations( + chain: 1, + derivePathType: DerivePathType.bip44, + derivationsToAdd: p2pkhChangeDerivations); + } + + // 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); + p2pkhReceiveIndex = 0; + } + + // 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); + p2pkhChangeIndex = 0; + } + + await DB.instance.put( + boxName: walletId, + key: 'receivingAddressesP2PKH', + value: p2pkhReceiveAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'changeAddressesP2PKH', + value: p2pkhChangeAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'receivingIndexP2PKH', + value: p2pkhReceiveIndex); + await DB.instance.put( + boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); + await DB.instance + .put(boxName: walletId, key: "id", value: _walletId); + await DB.instance + .put(boxName: walletId, key: "isFavorite", value: false); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Info); + + 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; + Logging.instance.log( + "notified unconfirmed transactions: ${txTracker.pendings}", + level: LogLevel.Info); + 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); + var confirmations = txn["confirmations"]; + if (confirmations is! int) continue; + bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + // unconfirmedTxs = {}; + needsRefresh = true; + break; + } + } + if (!needsRefresh) { + var allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = + await _fetchHistory(allOwnAddresses); + final txData = await transactionData; + for (Map transaction in allTxs) { + if (txData.findTransaction(transaction['tx_hash'] as String) == + 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.Info); + rethrow; + } + } + + Future getAllTxsToWatch( + TransactionData txData, + ) async { + if (_hasCalledExit) return; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; + + // Get all unconfirmed incoming transactions + for (final chunk in txData.txChunks) { + for (final tx in chunk.transactions) { + if (tx.confirmedStatus) { + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + } else { + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); + } + } + } + } + + // notify on new incoming transaction + for (final tx in unconfirmedTxnsToNotifyPending) { + if (tx.txType == "Received") { + NotificationApi.showNotification( + title: "Incoming transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ); + await txTracker.addNotifiedPending(tx.txid); + } else if (tx.txType == "Sent") { + NotificationApi.showNotification( + title: "Sending transaction", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, + coinName: coin.name, + txid: tx.txid, + confirmations: tx.confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ); + await txTracker.addNotifiedPending(tx.txid); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.txType == "Received") { + NotificationApi.showNotification( + title: "Incoming transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ); + + await txTracker.addNotifiedConfirmed(tx.txid); + } else if (tx.txType == "Sent") { + NotificationApi.showNotification( + title: "Outgoing transaction confirmed", + body: walletName, + walletId: walletId, + iconAssetName: Assets.svg.iconFor(coin: coin), + date: DateTime.now(), + shouldWatchForUpdates: false, + coinName: coin.name, + ); + await txTracker.addNotifiedConfirmed(tx.txid); + } + } + } + + bool refreshMutex = false; + + 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(); + } + } + } + + //TODO Show percentages properly/more consistently + /// Refreshes display data for the wallet + @override + Future refresh() async { + final bchaddr = Bitbox.Address.toCashAddress(await currentReceivingAddress); + print("bchaddr: $bchaddr ${await currentReceivingAddress}"); + + 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) { + if (currentHeight != -1) { + // -1 failed to fetch current height + updateStoredChainHeight(newHeight: currentHeight); + } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + await _checkChangeAddressForTransactions(DerivePathType.bip44); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + await _checkCurrentReceivingAddressesForTransactions(); + + final newTxData = _fetchTransactionData(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.50, walletId)); + + final newUtxoData = _fetchUtxoData(); + final feeObj = _getFees(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.60, walletId)); + + _transactionData = Future(() => newTxData); + + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.70, walletId)); + _feeObject = Future(() => feeObj); + _utxoData = Future(() => newUtxoData); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.80, walletId)); + + await getAllTxsToWatch(await newTxData); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.90, walletId)); + } + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + refreshMutex = false; + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + // 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 int satoshiAmount, + Map? args, + }) async { + try { + final feeRateType = args?["feeRate"]; + final feeRateAmount = args?["feeRateAmount"]; + 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; + } + rate = fee; + } else { + rate = feeRateAmount as int; + } + // check for send all + bool isSendAll = false; + final balance = Format.decimalAmountToSatoshis(await availableBalance); + if (satoshiAmount == balance) { + isSendAll = true; + } + + final result = + await coinSelection(satoshiAmount, rate, address, isSendAll); + Logging.instance.log("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 { + 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({dynamic txData}) async { + try { + Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + final txHash = await _electrumXClient.broadcastTransaction( + rawTx: txData["hex"] as String); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future send({ + required String toAddress, + required int amount, + Map args = const {}, + }) async { + try { + final txData = await prepareSend( + address: toAddress, satoshiAmount: amount, args: args); + final txHash = await confirmSend(txData: txData); + return txHash; + } catch (e, s) { + Logging.instance + .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + Future testNetworkConnection() async { + 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(); + _isConnected = hasNetwork; + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + GlobalEventBus.instance + .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); + } + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + bool _isConnected = false; + + @override + bool get isConnected => _isConnected; + + @override + Future initializeNew() async { + Logging.instance + .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); + + if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + throw Exception( + "Attempted to initialize a new wallet using an existing wallet ID!"); + } + await _prefs.init(); + try { + await _generateNewWallet(); + } catch (e, s) { + Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", + level: LogLevel.Fatal); + rethrow; + } + await Future.wait([ + DB.instance.put(boxName: walletId, key: "id", value: _walletId), + DB.instance + .put(boxName: walletId, key: "isFavorite", value: false), + ]); + } + + @override + Future initializeExisting() async { + Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + level: LogLevel.Info); + + if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + await _prefs.init(); + final data = + DB.instance.get(boxName: walletId, key: "latest_tx_model") + as TransactionData?; + if (data != null) { + _transactionData = Future(() => data); + } + } + + @override + Future get transactionData => + _transactionData ??= _fetchTransactionData(); + Future? _transactionData; + + @override + bool validateAddress(String address) { + try { + // 0 for bitcoincash: address scheme, 1 for legacy address + final format = Bitbox.Address.detectFormat(address); + print("format $format"); + return true; + } catch (e, s) { + return false; + } + } + + @override + String get walletId => _walletId; + late String _walletId; + + @override + String get walletName => _walletName; + late String _walletName; + + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; + + late ElectrumX _electrumXClient; + + ElectrumX get electrumXClient => _electrumXClient; + + late CachedElectrumX _cachedElectrumXClient; + + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; + + late FlutterSecureStorageInterface _secureStore; + + late PriceAPI _priceAPI; + + BitcoinCashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + PriceAPI? priceAPI, + FlutterSecureStorageInterface? secureStore, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + + _priceAPI = priceAPI ?? PriceAPI(Client()); + _secureStore = + secureStore ?? const SecureStorageWrapper(FlutterSecureStorage()); + } + + @override + Future updateNode(bool shouldRefresh) async { + final failovers = NodeService() + .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(); + _cachedElectrumXClient = CachedElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + _electrumXClient = ElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + + if (shouldRefresh) { + refresh(); + } + } + + Future> _getMnemonicList() async { + final mnemonicString = + await _secureStore.read(key: '${_walletId}_mnemonic'); + if (mnemonicString == null) { + return []; + } + final List data = mnemonicString.split(' '); + return data; + } + + Future getCurrentNode() async { + final node = NodeService().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 List allAddresses = []; + + final receivingAddressesP2PKH = DB.instance.get( + boxName: walletId, key: 'receivingAddressesP2PKH') as List; + final changeAddressesP2PKH = + DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + as List; + + // for (var i = 0; i < receivingAddresses.length; i++) { + // if (!allAddresses.contains(receivingAddresses[i])) { + // allAddresses.add(receivingAddresses[i]); + // } + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // if (!allAddresses.contains(changeAddresses[i])) { + // allAddresses.add(changeAddresses[i]); + // } + // } + 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); + } + } + 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: Format.decimalAmountToSatoshis(fast), + medium: Format.decimalAmountToSatoshis(medium), + slow: Format.decimalAmountToSatoshis(slow), + ); + + 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() async { + Logging.instance + .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); + if (!integrationTestFlag) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.bitcoincash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a BitcoinWallet using a non bitcoin coin type: ${coin.name}"); + } + } + + // this should never fail + if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', + value: bip39.generateMnemonic(strength: 256)); + + // Set relevant indexes + await DB.instance + .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); + await DB.instance + .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); + await DB.instance.put( + boxName: walletId, + key: 'blocked_tx_hashes', + value: ["0xdefault"], + ); // A list of transaction hashes to represent frozen utxos in wallet + // initialize address book entries + await DB.instance.put( + boxName: walletId, + key: 'addressBookEntries', + value: {}); + + // Generate and add addresses to relevant arrays + // final initialReceivingAddress = + // await _generateAddressForChain(0, 0, DerivePathType.bip44); + // final initialChangeAddress = + // await _generateAddressForChain(1, 0, DerivePathType.bip44); + final initialReceivingAddressP2PKH = + await _generateAddressForChain(0, 0, DerivePathType.bip44); + final initialChangeAddressP2PKH = + await _generateAddressForChain(1, 0, DerivePathType.bip44); + + // await _addToAddressesArrayForChain( + // initialReceivingAddress, 0, DerivePathType.bip44); + // await _addToAddressesArrayForChain( + // initialChangeAddress, 1, DerivePathType.bip44); + await _addToAddressesArrayForChain( + initialReceivingAddressP2PKH, 0, DerivePathType.bip44); + await _addToAddressesArrayForChain( + initialChangeAddressP2PKH, 1, DerivePathType.bip44); + + // this._currentReceivingAddress = Future(() => initialReceivingAddress); + _currentReceivingAddressP2PKH = Future(() => initialReceivingAddressP2PKH); + + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + } + + /// Generates a new internal or external chain address for the wallet using a BIP44 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 _secureStore.read(key: '${_walletId}_mnemonic'); + final node = await compute( + getBip32NodeWrapper, + Tuple5( + chain, + index, + mnemonic!, + _network, + derivePathType, + ), + ); + final data = PaymentData(pubkey: node.publicKey); + String address; + + switch (derivePathType) { + case DerivePathType.bip44: + address = P2PKH(data: data, network: _network).data.address!; + break; + // default: + // // should never hit this due to all enum cases handled + // return null; + } + + // add generated address & info to derivations + await addDerivation( + chain: chain, + address: address, + pubKey: Format.uint8listToString(node.publicKey), + wif: node.toWIF(), + derivePathType: derivePathType, + ); + + return address; + } + + /// Increases the index for either the internal or external chain, depending on [chain]. + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _incrementAddressIndexForChain( + int chain, DerivePathType derivePathType) async { + // Here we assume chain == 1 if it isn't 0 + String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + + final newIndex = + (DB.instance.get(boxName: walletId, key: indexKey)) + 1; + await DB.instance + .put(boxName: walletId, key: indexKey, value: newIndex); + } + + /// Adds [address] to the relevant chain's address array, which is determined by [chain]. + /// [address] - Expects a standard native segwit address + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _addToAddressesArrayForChain( + String address, int chain, DerivePathType derivePathType) async { + String chainArray = ''; + if (chain == 0) { + chainArray = 'receivingAddresses'; + } else { + chainArray = 'changeAddresses'; + } + switch (derivePathType) { + case DerivePathType.bip44: + chainArray += "P2PKH"; + break; + } + + final addressArray = + DB.instance.get(boxName: walletId, key: chainArray); + if (addressArray == null) { + Logging.instance.log( + 'Attempting to add the following to $chainArray array for chain $chain:${[ + address + ]}', + level: LogLevel.Info); + await DB.instance + .put(boxName: walletId, key: chainArray, value: [address]); + } else { + // Make a deep copy of the existing list + final List newArray = []; + addressArray + .forEach((dynamic _address) => newArray.add(_address as String)); + newArray.add(address); // Add the address passed into the method + await DB.instance + .put(boxName: walletId, key: chainArray, value: newArray); + } + } + + /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] + /// and + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _getCurrentAddressForChain( + int chain, DerivePathType derivePathType) async { + // Here, we assume that chain == 1 if it isn't 0 + String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + switch (derivePathType) { + case DerivePathType.bip44: + arrayKey += "P2PKH"; + break; + } + final internalChainArray = + DB.instance.get(boxName: walletId, key: arrayKey); + return internalChainArray.last as String; + } + + 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; + } + 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 _fetchUtxoData() async { + final List allAddresses = await _fetchAllOwnAddresses(); + + try { + final fetchedUtxoList = >>[]; + + final Map>> batches = {}; + const batchSizeMax = 10; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scripthash = _convertToScriptHash(allAddresses[i], _network); + 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 priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final List> outputArray = []; + int satoshiBalance = 0; + int satoshiBalancePending = 0; + + for (int i = 0; i < fetchedUtxoList.length; i++) { + for (int j = 0; j < fetchedUtxoList[i].length; j++) { + int value = fetchedUtxoList[i][j]["value"] as int; + satoshiBalance += value; + + final txn = await cachedElectrumXClient.getTransaction( + txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + final Map utxo = {}; + final int confirmations = txn["confirmations"] as int? ?? 0; + final bool confirmed = txn["confirmations"] == null + ? false + : txn["confirmations"] as int >= MINIMUM_CONFIRMATIONS; + if (!confirmed) { + satoshiBalancePending += value; + } + + utxo["txid"] = txn["txid"]; + utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; + utxo["value"] = value; + + utxo["status"] = {}; + utxo["status"]["confirmed"] = confirmed; + utxo["status"]["confirmations"] = confirmations; + utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; + utxo["status"]["block_hash"] = txn["blockhash"]; + utxo["status"]["block_time"] = txn["blocktime"]; + + final fiatValue = ((Decimal.fromInt(value) * currentPrice) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2); + utxo["rawWorth"] = fiatValue; + utxo["fiatWorth"] = fiatValue.toString(); + outputArray.add(utxo); + } + } + + Decimal currencyBalanceRaw = + ((Decimal.fromInt(satoshiBalance) * currentPrice) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2); + + final Map result = { + "total_user_currency": currencyBalanceRaw.toString(), + "total_sats": satoshiBalance, + "total_btc": (Decimal.fromInt(satoshiBalance) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: Constants.decimalPlaces) + .toString(), + "outputArray": outputArray, + "unconfirmed": satoshiBalancePending, + }; + + final dataModel = UtxoData.fromJson(result); + + final List allOutputs = dataModel.unspentOutputArray; + Logging.instance + .log('Outputs fetched: $allOutputs', level: LogLevel.Info); + await _sortOutputs(allOutputs); + await DB.instance.put( + boxName: walletId, key: 'latest_utxo_model', value: dataModel); + await DB.instance.put( + boxName: walletId, + key: 'totalBalance', + value: dataModel.satoshiBalance); + return dataModel; + } catch (e, s) { + Logging.instance + .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); + final latestTxModel = + DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + + if (latestTxModel == null) { + final emptyModel = { + "total_user_currency": "0.00", + "total_sats": 0, + "total_btc": "0", + "outputArray": [] + }; + return UtxoData.fromJson(emptyModel); + } else { + Logging.instance + .log("Old output model located", level: LogLevel.Warning); + return latestTxModel as models.UtxoData; + } + } + } + + /// 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 = {}; + for (final entry in addresses.entries) { + args[entry.key] = [_convertToScriptHash(entry.value, _network)]; + } + final response = await electrumXClient.getBatchHistory(args: args); + + 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( + DerivePathType derivePathType) async { + try { + final String currentExternalAddr = + await _getCurrentAddressForChain(0, derivePathType); + final int txCount = await getTxCount(address: currentExternalAddr); + Logging.instance.log( + 'Number of txs for current receiving address $currentExternalAddr: $txCount', + level: LogLevel.Info); + + if (txCount >= 1) { + // First increment the receiving index + await _incrementAddressIndexForChain(0, derivePathType); + + // Check the new receiving index + String indexKey = "receivingIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, derivePathType); + + // Add that new receiving address to the array of receiving addresses + await _addToAddressesArrayForChain( + newReceivingAddress, 0, derivePathType); + + // Set the new receiving address that the service + + switch (derivePathType) { + case DerivePathType.bip44: + _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); + break; + } + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkChangeAddressForTransactions( + DerivePathType derivePathType) async { + try { + final String currentExternalAddr = + await _getCurrentAddressForChain(1, derivePathType); + final int txCount = await getTxCount(address: currentExternalAddr); + Logging.instance.log( + 'Number of txs for current change address $currentExternalAddr: $txCount', + level: LogLevel.Info); + + if (txCount >= 1) { + // First increment the change index + await _incrementAddressIndexForChain(1, derivePathType); + + // Check the new change index + String indexKey = "changeIndex"; + switch (derivePathType) { + case DerivePathType.bip44: + indexKey += "P2PKH"; + break; + } + final newChangeIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new change address + final newChangeAddress = + await _generateAddressForChain(1, newChangeIndex, derivePathType); + + // Add that new receiving address to the array of change addresses + await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkChangeAddressForTransactions($derivePathType): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(type); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Info); + 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(type); + } + } 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 bch address + String _convertToScriptHash(String bchAddress, NetworkType network) { + try { + final output = Address.addressToOutputScript(bchAddress, network); + 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 = 10; + 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 = 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 _fetchTransactionData() async { + final List allAddresses = await _fetchAllOwnAddresses(); + + final changeAddressesP2PKH = + DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + as List; + + final List> allTxHashes = + await _fetchHistory(allAddresses); + + final cachedTransactions = + DB.instance.get(boxName: walletId, key: 'latest_tx_model') + as TransactionData?; + int latestTxnBlockHeight = + DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + as int? ?? + 0; + + final unconfirmedCachedTransactions = + cachedTransactions?.getAllTransactions() ?? {}; + unconfirmedCachedTransactions + .removeWhere((key, value) => value.confirmedStatus); + + print("CACHED_TRANSACTIONS_IS $cachedTransactions"); + if (cachedTransactions != null) { + for (final tx in allTxHashes.toList(growable: false)) { + final txHeight = tx["height"] as int; + if (txHeight > 0 && + txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { + if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { + allTxHashes.remove(tx); + } + } + } + } + + List> allTransactions = []; + + for (final txHash in allTxHashes) { + Logging.instance.log("bch: $txHash", level: LogLevel.Info); + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + // TODO fix this for sent to self transactions? + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = txHash["address"]; + 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); + + final priceData = + await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); + Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; + final List> midSortedArray = []; + + for (final txObject in allTransactions) { + List sendersArray = []; + List recipientsArray = []; + + // Usually only has value when txType = 'Send' + int inputAmtSentFromWallet = 0; + // Usually has value regardless of txType due to change addresses + int outputAmtAddressedToWallet = 0; + int fee = 0; + + Map midSortedTx = {}; + + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, coin: coin); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + final address = out["scriptPubKey"]["addresses"][0] as String?; + if (address != null) { + sendersArray.add(address); + } + } + } + } + + Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); + + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0] as String?; + if (address != null) { + recipientsArray.add(address); + } + } + + Logging.instance + .log("recipientsArray: $recipientsArray", level: LogLevel.Info); + + final foundInSenders = + allAddresses.any((element) => sendersArray.contains(element)); + Logging.instance + .log("foundInSenders: $foundInSenders", level: LogLevel.Info); + + // If txType = Sent, then calculate inputAmtSentFromWallet + if (foundInSenders) { + int totalInput = 0; + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + inputAmtSentFromWallet += + (Decimal.parse(out["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + } + } + } + totalInput = inputAmtSentFromWallet; + int totalOutput = 0; + + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0]; + final value = output["value"]; + final _value = (Decimal.parse(value.toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + totalOutput += _value; + if (changeAddressesP2PKH.contains(address)) { + inputAmtSentFromWallet -= _value; + } else { + // change address from 'sent from' to the 'sent to' address + txObject["address"] = address; + } + } + // calculate transaction fee + fee = totalInput - totalOutput; + // subtract fee from sent to calculate correct value of sent tx + inputAmtSentFromWallet -= fee; + } else { + // counters for fee calculation + int totalOut = 0; + int totalIn = 0; + + // add up received tx value + for (final output in txObject["vout"] as List) { + final address = output["scriptPubKey"]["addresses"][0]; + if (address != null) { + final value = (Decimal.parse(output["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + totalOut += value; + if (allAddresses.contains(address)) { + outputAmtAddressedToWallet += value; + } + } + } + + // calculate fee for received tx + for (int i = 0; i < (txObject["vin"] as List).length; i++) { + final input = txObject["vin"][i] as Map; + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final out in tx["vout"] as List) { + if (prevOut == out["n"]) { + totalIn += (Decimal.parse(out["value"].toString()) * + Decimal.fromInt(Constants.satsPerCoin)) + .toBigInt() + .toInt(); + } + } + } + fee = totalIn - totalOut; + } + + // create final tx map + midSortedTx["txid"] = txObject["txid"]; + midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && + (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); + midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; + midSortedTx["timestamp"] = txObject["blocktime"] ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000); + + if (foundInSenders) { + midSortedTx["txType"] = "Sent"; + midSortedTx["amount"] = inputAmtSentFromWallet; + final String worthNow = + ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2) + .toStringAsFixed(2); + midSortedTx["worthNow"] = worthNow; + midSortedTx["worthAtBlockTimestamp"] = worthNow; + } else { + midSortedTx["txType"] = "Received"; + midSortedTx["amount"] = outputAmtAddressedToWallet; + final worthNow = + ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / + Decimal.fromInt(Constants.satsPerCoin)) + .toDecimal(scaleOnInfinitePrecision: 2) + .toStringAsFixed(2); + midSortedTx["worthNow"] = worthNow; + } + midSortedTx["aliens"] = []; + midSortedTx["fees"] = fee; + midSortedTx["address"] = txObject["address"]; + midSortedTx["inputSize"] = txObject["vin"].length; + midSortedTx["outputSize"] = txObject["vout"].length; + midSortedTx["inputs"] = txObject["vin"]; + midSortedTx["outputs"] = txObject["vout"]; + + final int height = txObject["height"] as int; + midSortedTx["height"] = height; + + if (height >= latestTxnBlockHeight) { + latestTxnBlockHeight = height; + } + + midSortedArray.add(midSortedTx); + } + + // sort by date ---- //TODO not sure if needed + // shouldn't be any issues with a null timestamp but I got one at some point? + midSortedArray + .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); + // { + // final aT = a["timestamp"]; + // final bT = b["timestamp"]; + // + // if (aT == null && bT == null) { + // return 0; + // } else if (aT == null) { + // return -1; + // } else if (bT == null) { + // return 1; + // } else { + // return bT - aT; + // } + // }); + + // buildDateTimeChunks + final Map result = {"dateTimeChunks": []}; + final dateArray = []; + + for (int i = 0; i < midSortedArray.length; i++) { + final txObject = midSortedArray[i]; + final date = extractDateFromTimestamp(txObject["timestamp"] as int); + final txTimeArray = [txObject["timestamp"], date]; + + if (dateArray.contains(txTimeArray[1])) { + result["dateTimeChunks"].forEach((dynamic chunk) { + if (extractDateFromTimestamp(chunk["timestamp"] as int) == + txTimeArray[1]) { + if (chunk["transactions"] == null) { + chunk["transactions"] = >[]; + } + chunk["transactions"].add(txObject); + } + }); + } else { + dateArray.add(txTimeArray[1]); + final chunk = { + "timestamp": txTimeArray[0], + "transactions": [txObject], + }; + result["dateTimeChunks"].add(chunk); + } + } + + final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; + transactionsMap + .addAll(TransactionData.fromJson(result).getAllTransactions()); + + final txModel = TransactionData.fromMap(transactionsMap); + + await DB.instance.put( + boxName: walletId, + key: 'storedTxnDataHeight', + value: latestTxnBlockHeight); + await DB.instance.put( + boxName: walletId, key: 'latest_tx_model', value: txModel); + + return txModel; + } + + 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(int satoshiAmountToSend, int selectedTxFeeRate, + String _recipientAddress, bool isSendAll, + {int additionalOutputs = 0, List? utxos}) async { + Logging.instance + .log("Starting coinSelection ----------", level: LogLevel.Info); + final List availableOutputs = utxos ?? outputsList; + final List spendableOutputs = []; + int spendableSatoshiValue = 0; + + // Build list of spendable outputs and totaling their satoshi amount + for (var i = 0; i < availableOutputs.length; i++) { + if (availableOutputs[i].blocked == false && + availableOutputs[i].status.confirmed == true) { + spendableOutputs.add(availableOutputs[i]); + spendableSatoshiValue += availableOutputs[i].value; + } + } + + // sort spendable by age (oldest first) + spendableOutputs.sort( + (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + + 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 = []; + + 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; + } + + Logging.instance + .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); + Logging.instance + .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); + Logging.instance + .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); + Logging.instance + .log('satoshiAmountToSend $satoshiAmountToSend', 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [_recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + int feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + if (feeForOneOutput < (vSizeForOneOutput + 1)) { + feeForOneOutput = (vSizeForOneOutput + 1); + } + + final int amount = satoshiAmountToSend - feeForOneOutput; + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: [amount], + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": amount, + "fee": feeForOneOutput, + "vSize": txn["vSize"], + }; + return transactionObject; + } + + final int vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [_recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + final int vSizeForTwoOutPuts = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [ + _recipientAddress, + await _getCurrentAddressForChain(1, DerivePathType.bip44), + ], + satoshiAmounts: [ + satoshiAmountToSend, + satoshisBeingUsed - satoshiAmountToSend - 1, + ], // dust limit is the minimum amount a change output should be + ))["vSize"] as int; + debugPrint("vSizeForOneOutput $vSizeForOneOutput"); + debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); + + // Assume 1 output, only for recipient and no change + var feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + // Assume 2 outputs, one for recipient and one for change + var feeForTwoOutputs = estimateTxFee( + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ); + + Logging.instance + .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); + Logging.instance + .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); + if (feeForOneOutput < (vSizeForOneOutput + 1)) { + feeForOneOutput = (vSizeForOneOutput + 1); + } + if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1))) { + feeForTwoOutputs = ((vSizeForTwoOutPuts + 1)); + } + + 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) { + // 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 > 546 satoshis, we perform the mechanics required to properly generate and use a new + // change address. + if (changeOutputSize > DUST_LIMIT && + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == + feeForTwoOutputs) { + // generate new change address if current change address has been used + await _checkChangeAddressForTransactions(DerivePathType.bip44); + final String newChangeAddress = + await _getCurrentAddressForChain(1, DerivePathType.bip44); + + 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( + utxosToUse: utxoObjectsToUse, + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + } + + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": feeBeingPaid, + "vSize": txn["vSize"], + }; + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + }; + return transactionObject; + } + } else { + // No additional outputs needed since adding one would mean that it'd be smaller than 546 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + }; + 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( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": recipientsAmtArray[0], + "fee": feeForOneOutput, + "vSize": txn["vSize"], + }; + 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, selectedTxFeeRate, + _recipientAddress, isSendAll, + additionalOutputs: additionalOutputs + 1, utxos: utxos); + } + return 2; + } + } + + Future> fetchBuildTxData( + List utxosToUse, + ) async { + // return data + Map results = {}; + Map> addressTxid = {}; + + // addresses to check + List addressesP2PKH = []; + + try { + // Populating the addresses to check + for (var i = 0; i < utxosToUse.length; i++) { + 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) { + final address = output["scriptPubKey"]["addresses"][0] as String; + if (!addressTxid.containsKey(address)) { + addressTxid[address] = []; + } + (addressTxid[address] as List).add(txid); + switch (addressType(address: address)) { + case DerivePathType.bip44: + addressesP2PKH.add(address); + break; + } + } + } + } + + // p2pkh / bip44 + final p2pkhLength = addressesP2PKH.length; + if (p2pkhLength > 0) { + final receiveDerivations = await _fetchDerivations( + chain: 0, + derivePathType: DerivePathType.bip44, + ); + final changeDerivations = await _fetchDerivations( + chain: 1, + derivePathType: DerivePathType.bip44, + ); + for (int i = 0; i < p2pkhLength; i++) { + // receives + final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; + // if a match exists it will not be null + if (receiveDerivation != null) { + final data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + receiveDerivation["pubKey"] as String)), + network: _network, + ).data; + + for (String tx in addressTxid[addressesP2PKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + receiveDerivation["wif"] as String, + network: _network, + ), + }; + } + } else { + // if its not a receive, check change + final changeDerivation = changeDerivations[addressesP2PKH[i]]; + // if a match exists it will not be null + if (changeDerivation != null) { + final data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + changeDerivation["pubKey"] as String)), + network: _network, + ).data; + + for (String tx in addressTxid[addressesP2PKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + changeDerivation["wif"] as String, + network: _network, + ), + }; + } + } + } + } + } + + return results; + } catch (e, s) { + Logging.instance + .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); + rethrow; + } + } + + /// Builds and signs a transaction + Future> buildTransaction({ + required List utxosToUse, + required Map utxoSigningData, + required List recipients, + required List satoshiAmounts, + }) async { + final builder = Bitbox.Bitbox.transactionBuilder(); + + // retrieve address' utxos from the rest api + List _utxos = + []; // await Bitbox.Address.utxo(address) as List; + utxosToUse.forEach((element) { + _utxos.add(Bitbox.Utxo( + element.txid, + element.vout, + Bitbox.BitcoinCash.fromSatoshi(element.value), + element.value, + 0, + MINIMUM_CONFIRMATIONS + 1)); + }); + Logger.print("bch utxos: ${_utxos}"); + + // placeholder for input signatures + final signatures = []; + + // placeholder for total input balance + int totalBalance = 0; + + // iterate through the list of address _utxos and use them as inputs for the + // withdrawal transaction + _utxos.forEach((Bitbox.Utxo utxo) { + // add the utxo as an input for the transaction + builder.addInput(utxo.txid, utxo.vout); + final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; + + final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF()); + + // add a signature to the list to be used later + signatures.add({ + "vin": signatures.length, + "key_pair": bitboxEC, + "original_amount": utxo.satoshis + }); + + totalBalance += utxo.satoshis; + }); + + // calculate the fee based on number of inputs and one expected output + final fee = + Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); + + // calculate how much balance will be left over to spend after the fee + final sendAmount = totalBalance - fee; + + // add the output based on the address provided in the testing data + for (int i = 0; i < recipients.length; i++) { + String recipient = recipients[i]; + int satoshiAmount = satoshiAmounts[i]; + builder.addOutput(recipient, satoshiAmount); + } + + // sign all inputs + signatures.forEach((signature) { + builder.sign( + signature["vin"] as int, + signature["key_pair"] as Bitbox.ECPair, + signature["original_amount"] as int); + }); + + // build the transaction + final tx = builder.build(); + final txHex = tx.toHex(); + final vSize = tx.virtualSize(); + Logger.print("bch raw hex: $txHex"); + + return {"hex": txHex, "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 + _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + + // back up data + await _rescanBackup(); + + try { + final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + ); + + longMutex = false; + 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 _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); + + // 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"); + + // 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); + + // 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"); + + // 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); + } + + @override + set isFavorite(bool markFavorite) { + DB.instance.put( + boxName: walletId, key: "isFavorite", value: markFavorite); + } + + @override + bool get isFavorite { + try { + return DB.instance.get(boxName: walletId, key: "isFavorite") + as bool; + } catch (e, s) { + Logging.instance + .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + @override + bool get isRefreshing => refreshMutex; + + bool isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => + (isActive) => this.isActive = isActive; + + @override + Future estimateFeeFor(int satoshiAmount, int feeRate) async { + final available = Format.decimalAmountToSatoshis(await availableBalance); + + if (available == satoshiAmount) { + return satoshiAmount - sweepAllEstimate(feeRate); + } else if (satoshiAmount <= 0 || satoshiAmount > available) { + return roughFeeEstimate(1, 2, feeRate); + } + + int runningBalance = 0; + int inputCount = 0; + for (final output in outputsList) { + runningBalance += output.value; + inputCount++; + if (runningBalance > satoshiAmount) { + break; + } + } + + final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); + final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); + + if (runningBalance - satoshiAmount > oneOutPutFee) { + if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - satoshiAmount - twoOutPutFee; + if (change > DUST_LIMIT && + runningBalance - satoshiAmount - change == twoOutPutFee) { + return runningBalance - satoshiAmount - change; + } else { + return runningBalance - satoshiAmount; + } + } else { + return runningBalance - satoshiAmount; + } + } else if (runningBalance - satoshiAmount == oneOutPutFee) { + return oneOutPutFee; + } else { + return twoOutPutFee; + } + } + + // TODO: correct formula for bch? + int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return ((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil(); + } + + int sweepAllEstimate(int feeRate) { + int available = 0; + int inputCount = 0; + for (final output in outputsList) { + if (output.status.confirmed) { + available += output.value; + inputCount++; + } + } + + // transaction will only have 1 output minus the fee + final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); + + return available - estimatedFee; + } +} + +// Bitcoincash Network +final bitcoincash = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80); diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 3d01c6f61..f8a12c0a3 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -15,6 +15,7 @@ enum Coin { epicCash, firo, monero, + namecoin, /// /// @@ -43,6 +44,8 @@ extension CoinExt on Coin { return "Firo"; case Coin.monero: return "Monero"; + case Coin.namecoin: + return "Namecoin"; case Coin.bitcoinTestNet: return "tBitcoin"; case Coin.firoTestNet: From 84694fa1ddcf5119393cfee6846117ccc9f7b096 Mon Sep 17 00:00:00 2001 From: Likho Date: Mon, 12 Sep 2022 14:01:42 +0200 Subject: [PATCH 09/20] WIP: Add namecoin --- assets/svg/coin_icons/Namecoin.svg | 1 + .../add_edit_node_view.dart | 2 + .../coins/bitcoincash/bitcoincash_wallet.dart | 30 +++ lib/services/coins/coin_service.dart | 11 + .../coins/namecoin/namecoin_wallet.dart | 192 ++++++++---------- lib/utilities/address_utils.dart | 3 + lib/utilities/assets.dart | 7 + lib/utilities/block_explorers.dart | 2 + lib/utilities/cfcolors.dart | 3 + lib/utilities/constants.dart | 4 + lib/utilities/default_nodes.dart | 17 ++ lib/utilities/enums/coin_enum.dart | 14 ++ pubspec.yaml | 1 + 13 files changed, 183 insertions(+), 104 deletions(-) create mode 100644 assets/svg/coin_icons/Namecoin.svg diff --git a/assets/svg/coin_icons/Namecoin.svg b/assets/svg/coin_icons/Namecoin.svg new file mode 100644 index 000000000..2cda6aaf0 --- /dev/null +++ b/assets/svg/coin_icons/Namecoin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 9bc785547..0cb47b7ff 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -116,6 +116,7 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.bitcoincash: case Coin.dogecoin: case Coin.firo: + case Coin.namecoin: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -528,6 +529,7 @@ class _NodeFormState extends ConsumerState { case Coin.bitcoin: case Coin.dogecoin: case Coin.firo: + case Coin.namecoin: case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index eb209490f..bb241b494 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -3040,6 +3040,36 @@ class BitcoinCashWallet extends CoinServiceAPI { return available - estimatedFee; } + + @override + Future generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip44); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + boxName: walletId, + key: 'receivingIndexP2PKH') as int; // Check the new receiving index + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + DerivePathType + .bip44); // Use new index to derive a new receiving address + await _addToAddressesArrayForChain( + newReceivingAddress, + 0, + DerivePathType + .bip44); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddressP2PKH = Future(() => + newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } // Bitcoincash Network diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 629912f00..5db6bd8bc 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -134,6 +135,16 @@ abstract class CoinServiceAPI { // tracker: tracker, ); + case Coin.namecoin: + return NamecoinWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + tracker: tracker, + cachedClient: cachedClient, + client: client, + ); + case Coin.dogecoinTestNet: return DogecoinWallet( walletId: walletId, diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 30394cd62..067b26c0f 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -46,9 +46,7 @@ const int MINIMUM_CONFIRMATIONS = 3; const int DUST_LIMIT = 1000000; const String GENESIS_HASH_MAINNET = - "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; -const String GENESIS_HASH_TESTNET = - "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"; enum DerivePathType { bip44 } @@ -77,11 +75,11 @@ bip32.BIP32 getBip32NodeFromRoot( int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { String coinType; switch (root.network.wif) { - case 0x80: // bch mainnet wif - coinType = "145"; // bch mainnet + case 0x80: // nmc mainnet wif + coinType = "7"; // nmc mainnet break; default: - throw Exception("Invalid Bitcoincash network type used!"); + throw Exception("Invalid Namecoin network type used!"); } switch (derivePathType) { case DerivePathType.bip44: @@ -122,7 +120,7 @@ bip32.BIP32 getBip32RootWrapper(Tuple2 args) { return getBip32Root(args.item1, args.item2); } -class NamecoinCashWallet extends CoinServiceAPI { +class NamecoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; @@ -134,10 +132,10 @@ class NamecoinCashWallet extends CoinServiceAPI { NetworkType get _network { switch (coin) { - case Coin.bitcoincash: - return bitcoincash; + case Coin.namecoin: + return namecoin; default: - throw Exception("Bitcoincash network type not set!"); + throw Exception("Namecoin network type not set!"); } } @@ -298,14 +296,14 @@ class NamecoinCashWallet extends CoinServiceAPI { final features = await electrumXClient.getServerFeatures(); Logging.instance.log("features: $features", level: LogLevel.Info); switch (coin) { - case Coin.bitcoincash: + 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 BitcoinCashWallet using a non bch coin type: ${coin.name}"); + "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); } } // check to make sure we aren't overwriting a mnemonic @@ -730,9 +728,6 @@ class NamecoinCashWallet extends CoinServiceAPI { /// Refreshes display data for the wallet @override Future refresh() async { - final bchaddr = Bitbox.Address.toCashAddress(await currentReceivingAddress); - print("bchaddr: $bchaddr ${await currentReceivingAddress}"); - if (refreshMutex) { Logging.instance.log("$walletId $walletName refreshMutex denied", level: LogLevel.Info); @@ -1048,14 +1043,7 @@ class NamecoinCashWallet extends CoinServiceAPI { @override bool validateAddress(String address) { - try { - // 0 for bitcoincash: address scheme, 1 for legacy address - final format = Bitbox.Address.detectFormat(address); - print("format $format"); - return true; - } catch (e, s) { - return false; - } + return Address.validateAddress(address, _network); } @override @@ -1082,7 +1070,7 @@ class NamecoinCashWallet extends CoinServiceAPI { late PriceAPI _priceAPI; - BitcoinCashWallet({ + NamecoinWallet({ required String walletId, required String walletName, required Coin coin, @@ -1222,14 +1210,14 @@ class NamecoinCashWallet extends CoinServiceAPI { final features = await electrumXClient.getServerFeatures(); Logging.instance.log("features: $features", level: LogLevel.Info); switch (coin) { - case Coin.bitcoincash: + 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 BitcoinWallet using a non bitcoin coin type: ${coin.name}"); + "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); } } @@ -1840,10 +1828,10 @@ class NamecoinCashWallet extends CoinServiceAPI { /// attempts to convert a string to a valid scripthash /// - /// Returns the scripthash or throws an exception on invalid bch address - String _convertToScriptHash(String bchAddress, NetworkType network) { + /// Returns the scripthash or throws an exception on invalid namecoin address + String _convertToScriptHash(String namecoinAddress, NetworkType network) { try { - final output = Address.addressToOutputScript(bchAddress, network); + final output = Address.addressToOutputScript(namecoinAddress, network); final hash = sha256.convert(output.toList(growable: false)).toString(); final chars = hash.split(""); @@ -1937,7 +1925,6 @@ class NamecoinCashWallet extends CoinServiceAPI { unconfirmedCachedTransactions .removeWhere((key, value) => value.confirmedStatus); - print("CACHED_TRANSACTIONS_IS $cachedTransactions"); if (cachedTransactions != null) { for (final tx in allTxHashes.toList(growable: false)) { final txHeight = tx["height"] as int; @@ -1953,7 +1940,6 @@ class NamecoinCashWallet extends CoinServiceAPI { List> allTransactions = []; for (final txHash in allTxHashes) { - Logging.instance.log("bch: $txHash", level: LogLevel.Info); final tx = await cachedElectrumXClient.getTransaction( txHash: txHash["tx_hash"] as String, verbose: true, @@ -2325,8 +2311,8 @@ class NamecoinCashWallet extends CoinServiceAPI { vSize: vSizeForOneOutput, feeRatePerKB: selectedTxFeeRate, ); - if (feeForOneOutput < (vSizeForOneOutput + 1)) { - feeForOneOutput = (vSizeForOneOutput + 1); + if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { + feeForOneOutput = (vSizeForOneOutput + 1) * 1000; } final int amount = satoshiAmountToSend - feeForOneOutput; @@ -2382,11 +2368,11 @@ class NamecoinCashWallet extends CoinServiceAPI { .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); Logging.instance .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); - if (feeForOneOutput < (vSizeForOneOutput + 1)) { - feeForOneOutput = (vSizeForOneOutput + 1); + if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { + feeForOneOutput = (vSizeForOneOutput + 1) * 1000; } - if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1))) { - feeForTwoOutputs = ((vSizeForTwoOutPuts + 1)); + if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1) * 1000)) { + feeForTwoOutputs = ((vSizeForTwoOutPuts + 1) * 1000); } Logging.instance @@ -2686,76 +2672,45 @@ class NamecoinCashWallet extends CoinServiceAPI { required List recipients, required List satoshiAmounts, }) async { - final builder = Bitbox.Bitbox.transactionBuilder(); + Logging.instance + .log("Starting buildTransaction ----------", level: LogLevel.Info); - // retrieve address' utxos from the rest api - List _utxos = - []; // await Bitbox.Address.utxo(address) as List; - utxosToUse.forEach((element) { - _utxos.add(Bitbox.Utxo( - element.txid, - element.vout, - Bitbox.BitcoinCash.fromSatoshi(element.value), - element.value, - 0, - MINIMUM_CONFIRMATIONS + 1)); - }); - Logger.print("bch utxos: ${_utxos}"); + final txb = TransactionBuilder(network: _network); + txb.setVersion(1); - // placeholder for input signatures - final signatures = []; - - // placeholder for total input balance - int totalBalance = 0; - - // iterate through the list of address _utxos and use them as inputs for the - // withdrawal transaction - _utxos.forEach((Bitbox.Utxo utxo) { - // add the utxo as an input for the transaction - builder.addInput(utxo.txid, utxo.vout); - final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; - - final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF()); - - // add a signature to the list to be used later - signatures.add({ - "vin": signatures.length, - "key_pair": bitboxEC, - "original_amount": utxo.satoshis - }); - - totalBalance += utxo.satoshis; - }); - - // calculate the fee based on number of inputs and one expected output - final fee = - Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); - - // calculate how much balance will be left over to spend after the fee - final sendAmount = totalBalance - fee; - - // add the output based on the address provided in the testing data - for (int i = 0; i < recipients.length; i++) { - String recipient = recipients[i]; - int satoshiAmount = satoshiAmounts[i]; - builder.addOutput(recipient, satoshiAmount); + // Add transaction inputs + for (var i = 0; i < utxosToUse.length; i++) { + final txid = utxosToUse[i].txid; + txb.addInput(txid, utxosToUse[i].vout, null, + utxoSigningData[txid]["output"] as Uint8List); } - // sign all inputs - signatures.forEach((signature) { - builder.sign( - signature["vin"] as int, - signature["key_pair"] as Bitbox.ECPair, - signature["original_amount"] as int); - }); + // Add transaction output + for (var i = 0; i < recipients.length; i++) { + txb.addOutput(recipients[i], satoshiAmounts[i]); + } - // build the transaction - final tx = builder.build(); - final txHex = tx.toHex(); - final vSize = tx.virtualSize(); - Logger.print("bch raw hex: $txHex"); + try { + // Sign the transaction accordingly + for (var i = 0; i < utxosToUse.length; i++) { + final txid = utxosToUse[i].txid; + txb.sign( + vin: i, + keyPair: utxoSigningData[txid]["keyPair"] as ECPair, + witnessValue: utxosToUse[i].value, + redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + ); + } + } catch (e, s) { + Logging.instance.log("Caught exception while signing transaction: $e\n$s", + level: LogLevel.Error); + rethrow; + } - return {"hex": txHex, "vSize": vSize}; + final builtTx = txb.build(); + final vSize = builtTx.virtualSize(); + + return {"hex": builtTx.toHex(), "vSize": vSize}; } @override @@ -3018,7 +2973,7 @@ class NamecoinCashWallet extends CoinServiceAPI { } } - // TODO: correct formula for bch? + // TODO: correct formula for nmc? int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { return ((181 * inputCount) + (34 * outputCount) + 10) * (feeRatePerKB / 1000).ceil(); @@ -3039,10 +2994,39 @@ class NamecoinCashWallet extends CoinServiceAPI { return available - estimatedFee; } + + Future generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip44); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + boxName: walletId, + key: 'receivingIndexP2PKH') as int; // Check the new receiving index + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + DerivePathType + .bip44); // Use new index to derive a new receiving address + await _addToAddressesArrayForChain( + newReceivingAddress, + 0, + DerivePathType + .bip44); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddressP2PKH = Future(() => + newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } -// Bitcoincash Network -final bitcoincash = NetworkType( +// Namecoin Network +final namecoin = NetworkType( messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bc', bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 68029158b..75a1f51f5 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -6,6 +6,7 @@ import 'package:flutter_libepiccash/epic_cash.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -52,6 +53,8 @@ class AddressUtils { case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); + case Coin.namecoin: + return Address.validateAddress(address, namecoin); case Coin.bitcoinTestNet: return Address.validateAddress(address, testnet); case Coin.firoTestNet: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 304a2ff98..850de104f 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -1,3 +1,4 @@ +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class Assets { @@ -110,6 +111,7 @@ class _SVG { String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; String get firo => "assets/svg/coin_icons/Firo.svg"; String get monero => "assets/svg/coin_icons/Monero.svg"; + String get namecoin => "assets/svg/coin_icons/Namecoin.svg"; // TODO provide proper assets String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; @@ -130,6 +132,8 @@ class _SVG { return firo; case Coin.monero: return monero; + case Coin.namecoin: + return namecoin; case Coin.bitcoinTestNet: return bitcoinTestnet; case Coin.firoTestNet: @@ -152,6 +156,7 @@ class _PNG { String get bitcoin => "assets/images/bitcoin.png"; String get epicCash => "assets/images/epic-cash.png"; String get bitcoincash => "assets/images/bitcoincash.png"; + String get namecoin => "assets/images/bitcoincash.png"; String imageFor({required Coin coin}) { switch (coin) { @@ -171,6 +176,8 @@ class _PNG { return firo; case Coin.monero: return monero; + case Coin.namecoin: + return namecoin; } } } diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index e1073e018..c89f79432 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -24,5 +24,7 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://testexplorer.firo.org/tx/$txid"); case Coin.bitcoincash: return Uri.parse("https://blockchair.com/bitcoin-cash/transaction/$txid"); + case Coin.namecoin: + return Uri.parse("uri"); } } diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index b72ddb4bf..93d1f2f03 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -11,6 +11,7 @@ class _CoinThemeColor { Color get dogecoin => const Color(0xFFFFE079); Color get epicCash => const Color(0xFFC5C7CB); Color get monero => const Color(0xFFFF9E6B); + Color get namecoin => const Color(0xFFFCC17B); Color forCoin(Coin coin) { switch (coin) { @@ -29,6 +30,8 @@ class _CoinThemeColor { return firo; case Coin.monero: return monero; + case Coin.namecoin: + return namecoin; } } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 16a6c83c8..843194ab9 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -46,6 +46,7 @@ abstract class Constants { case Coin.dogecoinTestNet: case Coin.firoTestNet: case Coin.epicCash: + case Coin.namecoin: values.addAll([24, 21, 18, 15, 12]); break; @@ -79,6 +80,9 @@ abstract class Constants { case Coin.monero: return 120; + + case Coin.namecoin: + return 600; } } diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 2cd55bea2..8f0a0f905 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class DefaultNodes { @@ -14,6 +15,7 @@ abstract class DefaultNodes { monero, epicCash, bitcoincash, + namecoin, bitcoinTestnet, dogecoinTestnet, firoTestnet, @@ -93,6 +95,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get namecoin => NodeModel( + host: "46.229.238.187", + port: 57002, + name: defaultName, + id: _nodeId(Coin.namecoin), + useSSL: true, + enabled: true, + coinName: Coin.namecoin.name, + isFailover: true, + isDown: false, + ); + static NodeModel get bitcoinTestnet => NodeModel( host: "electrumx-testnet.cypherstack.com", port: 51002, @@ -149,6 +163,9 @@ abstract class DefaultNodes { case Coin.monero: return monero; + case Coin.namecoin: + return namecoin; + case Coin.bitcoinTestNet: return bitcoinTestnet; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index f8a12c0a3..1b642603e 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -7,6 +7,8 @@ import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as firo; import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart' as bch; +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' + as nmc; enum Coin { bitcoin, @@ -69,6 +71,8 @@ extension CoinExt on Coin { return "FIRO"; case Coin.monero: return "XMR"; + case Coin.namecoin: + return "NMC"; case Coin.bitcoinTestNet: return "tBTC"; case Coin.firoTestNet: @@ -93,6 +97,8 @@ extension CoinExt on Coin { return "firo"; case Coin.monero: return "monero"; + case Coin.namecoin: + return "namecoin"; case Coin.bitcoinTestNet: return "bitcoin"; case Coin.firoTestNet: @@ -108,6 +114,7 @@ extension CoinExt on Coin { case Coin.bitcoincash: case Coin.dogecoin: case Coin.firo: + case Coin.namecoin: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -141,6 +148,8 @@ extension CoinExt on Coin { case Coin.monero: return xmr.MINIMUM_CONFIRMATIONS; + case Coin.namecoin: + return nmc.MINIMUM_CONFIRMATIONS; } } } @@ -166,6 +175,9 @@ Coin coinFromPrettyName(String name) { case "Monero": case "monero": return Coin.monero; + case "Namecoin": + case "namecoin": + return Coin.namecoin; case "Bitcoin Testnet": case "tBitcoin": case "bitcoinTestNet": @@ -198,6 +210,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.firo; case "xmr": return Coin.monero; + case "nmc": + return Coin.namecoin; case "tbtc": return Coin.bitcoinTestNet; case "tfiro": diff --git a/pubspec.yaml b/pubspec.yaml index f4e6ecee8..21bea3968 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -271,6 +271,7 @@ flutter: - assets/svg/coin_icons/EpicCash.svg - assets/svg/coin_icons/Firo.svg - assets/svg/coin_icons/Monero.svg + - assets/svg/coin_icons/Namecoin.svg # lottie animations - assets/lottie/test.json - assets/lottie/test2.json From ea8c6290f449d69dd760e4f27e394b238a97536e Mon Sep 17 00:00:00 2001 From: Likho Date: Mon, 12 Sep 2022 17:53:25 +0200 Subject: [PATCH 10/20] Update test mocks --- test/cached_electrumx_test.mocks.dart | 10 + test/electrumx_test.mocks.dart | 10 + ...d_rate_exchange_form_state_test.mocks.dart | 78 ++-- ...d_address_book_view_screen_test.mocks.dart | 4 + ..._entry_details_view_screen_test.mocks.dart | 4 + ...ess_book_entry_view_screen_test.mocks.dart | 4 + .../exchange/exchange_view_test.mocks.dart | 70 ++-- .../lockscreen_view_screen_test.mocks.dart | 4 + .../main_view_screen_testA_test.mocks.dart | 4 + .../main_view_screen_testB_test.mocks.dart | 4 + .../main_view_screen_testC_test.mocks.dart | 4 + .../backup_key_view_screen_test.mocks.dart | 4 + ...up_key_warning_view_screen_test.mocks.dart | 4 + .../create_pin_view_screen_test.mocks.dart | 4 + ...restore_wallet_view_screen_test.mocks.dart | 4 + ...ify_backup_key_view_screen_test.mocks.dart | 4 + .../currency_view_screen_test.mocks.dart | 4 + ...dd_custom_node_view_screen_test.mocks.dart | 4 + .../node_details_view_screen_test.mocks.dart | 4 + .../wallet_backup_view_screen_test.mocks.dart | 4 + ...rescan_warning_view_screen_test.mocks.dart | 4 + ...elete_mnemonic_view_screen_test.mocks.dart | 4 + ...allet_settings_view_screen_test.mocks.dart | 4 + .../settings_view_screen_test.mocks.dart | 4 + ...search_results_view_screen_test.mocks.dart | 4 + .../confirm_send_view_screen_test.mocks.dart | 4 + .../receive_view_screen_test.mocks.dart | 4 + .../send_view_screen_test.mocks.dart | 4 + .../wallet_view_screen_test.mocks.dart | 4 + .../bitcoincash_wallet_test.mocks.dart | 352 ++++++++++++++++++ test/services/coins/manager_test.mocks.dart | 99 ++++- .../transaction_card_test.mocks.dart | 4 + 32 files changed, 656 insertions(+), 67 deletions(-) diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 1c72d43c2..f2bba01cc 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -358,6 +358,16 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { super.noSuchMethod(Invocation.setter(#lastAutoBackup, lastAutoBackup), returnValueForMissingStub: null); @override + bool get hideBlockExplorerWarning => + (super.noSuchMethod(Invocation.getter(#hideBlockExplorerWarning), + returnValue: false) as bool); + @override + set hideBlockExplorerWarning(bool? hideBlockExplorerWarning) => + super.noSuchMethod( + Invocation.setter( + #hideBlockExplorerWarning, hideBlockExplorerWarning), + returnValueForMissingStub: null); + @override bool get hasListeners => (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart index 4ef0b01ec..9a6457f53 100644 --- a/test/electrumx_test.mocks.dart +++ b/test/electrumx_test.mocks.dart @@ -207,6 +207,16 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { super.noSuchMethod(Invocation.setter(#lastAutoBackup, lastAutoBackup), returnValueForMissingStub: null); @override + bool get hideBlockExplorerWarning => + (super.noSuchMethod(Invocation.getter(#hideBlockExplorerWarning), + returnValue: false) as bool); + @override + set hideBlockExplorerWarning(bool? hideBlockExplorerWarning) => + super.noSuchMethod( + Invocation.setter( + #hideBlockExplorerWarning, hideBlockExplorerWarning), + returnValueForMissingStub: null); + @override bool get hasListeners => (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index 27aa1a772..2e496569f 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -8,18 +8,20 @@ import 'package:decimal/decimal.dart' as _i7; import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i12; + as _i13; import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' as _i2; +import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' + as _i9; import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i6; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' as _i8; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i10; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' as _i11; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i12; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i9; + as _i10; import 'package:stackwallet/services/change_now/change_now.dart' as _i3; // ignore_for_file: type=lint @@ -98,38 +100,42 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { as _i5 .Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>); @override - _i5.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>> - getEstimatedFixedRateExchangeAmount( + _i5.Future<_i2.ChangeNowResponse<_i9.CNExchangeEstimate>> + getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, - _i7.Decimal? fromAmount, - bool? useRateId = true, + _i9.CNEstimateType? fromOrTo, + _i7.Decimal? amount, + String? fromNetwork, + String? toNetwork, + _i9.CNFlowType? flow = _i9.CNFlowType.standard, String? apiKey}) => (super.noSuchMethod( - Invocation.method(#getEstimatedFixedRateExchangeAmount, [], { + Invocation.method(#getEstimatedExchangeAmountV2, [], { #fromTicker: fromTicker, #toTicker: toTicker, - #fromAmount: fromAmount, - #useRateId: useRateId, + #fromOrTo: fromOrTo, + #amount: amount, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse< - _i8.EstimatedExchangeAmount>>.value( - _FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>())) - as _i5 - .Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>); + _i2.ChangeNowResponse<_i9.CNExchangeEstimate>>.value( + _FakeChangeNowResponse_0<_i9.CNExchangeEstimate>())) + as _i5.Future<_i2.ChangeNowResponse<_i9.CNExchangeEstimate>>); @override - _i5.Future<_i2.ChangeNowResponse>> + _i5.Future<_i2.ChangeNowResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( Invocation.method( #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); @override - _i5.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>> createStandardExchangeTransaction( {String? fromTicker, String? toTicker, @@ -155,11 +161,11 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5 - .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + _i2.ChangeNowResponse<_i11.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) as _i5 + .Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>>); @override - _i5.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>> createFixedRateExchangeTransaction( {String? fromTicker, String? toTicker, @@ -187,26 +193,26 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5 - .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + _i2.ChangeNowResponse<_i11.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) as _i5 + .Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>>); @override - _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>> + _i5.Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>.value( - _FakeChangeNowResponse_0<_i11.ExchangeTransactionStatus>())) as _i5 - .Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>); + Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>>.value( + _FakeChangeNowResponse_0<_i12.ExchangeTransactionStatus>())) as _i5 + .Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>>); @override - _i5.Future<_i2.ChangeNowResponse>> + _i5.Future<_i2.ChangeNowResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); } diff --git a/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart index 31130ec28..c28b76d74 100644 --- a/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart @@ -334,6 +334,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart index 618cef5d8..7829cd299 100644 --- a/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart @@ -315,6 +315,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart index 034e4edf8..0aa30d54f 100644 --- a/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart @@ -313,6 +313,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index aa517d09b..6be0f9c74 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -9,18 +9,20 @@ import 'package:decimal/decimal.dart' as _i15; import 'package:http/http.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i19; + as _i20; import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' as _i2; +import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' + as _i17; import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i14; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' as _i16; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' as _i10; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' - as _i18; + as _i19; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i17; + as _i18; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' as _i5; import 'package:stackwallet/services/change_now/change_now.dart' as _i12; @@ -183,6 +185,16 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { super.noSuchMethod(Invocation.setter(#lastAutoBackup, lastAutoBackup), returnValueForMissingStub: null); @override + bool get hideBlockExplorerWarning => + (super.noSuchMethod(Invocation.getter(#hideBlockExplorerWarning), + returnValue: false) as bool); + @override + set hideBlockExplorerWarning(bool? hideBlockExplorerWarning) => + super.noSuchMethod( + Invocation.setter( + #hideBlockExplorerWarning, hideBlockExplorerWarning), + returnValueForMissingStub: null); + @override bool get hasListeners => (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); @@ -386,36 +398,40 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { as _i7 .Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>); @override - _i7.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>> - getEstimatedFixedRateExchangeAmount( + _i7.Future<_i2.ChangeNowResponse<_i17.CNExchangeEstimate>> + getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, - _i15.Decimal? fromAmount, - bool? useRateId = true, + _i17.CNEstimateType? fromOrTo, + _i15.Decimal? amount, + String? fromNetwork, + String? toNetwork, + _i17.CNFlowType? flow = _i17.CNFlowType.standard, String? apiKey}) => (super.noSuchMethod( - Invocation.method(#getEstimatedFixedRateExchangeAmount, [], { + Invocation.method(#getEstimatedExchangeAmountV2, [], { #fromTicker: fromTicker, #toTicker: toTicker, - #fromAmount: fromAmount, - #useRateId: useRateId, + #fromOrTo: fromOrTo, + #amount: amount, + #fromNetwork: fromNetwork, + #toNetwork: toNetwork, + #flow: flow, #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse< - _i16.EstimatedExchangeAmount>>.value( - _FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>())) - as _i7 - .Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>); + _i2.ChangeNowResponse<_i17.CNExchangeEstimate>>.value( + _FakeChangeNowResponse_0<_i17.CNExchangeEstimate>())) + as _i7.Future<_i2.ChangeNowResponse<_i17.CNExchangeEstimate>>); @override - _i7.Future<_i2.ChangeNowResponse>> + _i7.Future<_i2.ChangeNowResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( Invocation.method( #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); @override _i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> createStandardExchangeTransaction( @@ -479,22 +495,22 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7 .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); @override - _i7.Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>> + _i7.Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>.value( - _FakeChangeNowResponse_0<_i18.ExchangeTransactionStatus>())) as _i7 - .Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>); + Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>>.value( + _FakeChangeNowResponse_0<_i19.ExchangeTransactionStatus>())) as _i7 + .Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>>); @override - _i7.Future<_i2.ChangeNowResponse>> + _i7.Future<_i2.ChangeNowResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); } diff --git a/test/screen_tests/lockscreen_view_screen_test.mocks.dart b/test/screen_tests/lockscreen_view_screen_test.mocks.dart index 81369cdf1..d7d0e193c 100644 --- a/test/screen_tests/lockscreen_view_screen_test.mocks.dart +++ b/test/screen_tests/lockscreen_view_screen_test.mocks.dart @@ -483,6 +483,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i9.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart index 910b5404b..5d1ac1f5d 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart @@ -373,6 +373,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart index 1c298aa7e..c2ac8447a 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart @@ -373,6 +373,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart index 4f00f5f8e..04a0dac53 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart @@ -373,6 +373,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart b/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart index bab971459..80c9677f1 100644 --- a/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart b/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart index da239f761..c34ced0de 100644 --- a/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart @@ -371,6 +371,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart index 429b5b6b6..f37383e4b 100644 --- a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart @@ -483,6 +483,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i9.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart index 7f0d56002..48de40a42 100644 --- a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart @@ -399,6 +399,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override void addListener(_i11.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart b/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart index 769a9263f..e93ed2ce7 100644 --- a/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart index ec7ea5959..b95f2cc22 100644 --- a/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart index b12808703..17fb394ac 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart @@ -360,6 +360,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart index ca45f0303..5c08dc466 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart @@ -360,6 +360,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart index 6f6475108..10d20aa58 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart index 3c9a86351..d5760cd45 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart index c3301a326..969da34a1 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart @@ -371,6 +371,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart index c5645ad09..336bb7222 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart @@ -527,6 +527,10 @@ class MockManager extends _i1.Mock implements _i15.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override void addListener(_i14.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart index 379ceacd1..f74cec132 100644 --- a/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart @@ -371,6 +371,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i6.Future); @override + _i6.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i6.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart b/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart index d26dfc10c..337f73285 100644 --- a/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart +++ b/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart @@ -250,6 +250,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart index 623613b53..0fa3baaa5 100644 --- a/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart @@ -249,6 +249,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart index 86b635747..745dab4f3 100644 --- a/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart @@ -248,6 +248,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart index c7e4bbe9a..72eb2f746 100644 --- a/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart @@ -270,6 +270,10 @@ class MockManager extends _i1.Mock implements _i8.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart index 0ca83f21e..8cbf518ee 100644 --- a/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart @@ -250,6 +250,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index e69de29bb..f24f8be4c 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -0,0 +1,352 @@ +// Mocks generated by Mockito 5.2.0 from annotations +// in stackwallet/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i6; + +import 'package:decimal/decimal.dart' as _i2; +import 'package:http/http.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; +import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/services/transaction_notification_tracker.dart' + as _i11; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; +import 'package:stackwallet/utilities/prefs.dart' as _i3; +import 'package:tuple/tuple.dart' as _i10; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeDecimal_0 extends _i1.Fake implements _i2.Decimal {} + +class _FakePrefs_1 extends _i1.Fake implements _i3.Prefs {} + +class _FakeClient_2 extends _i1.Fake implements _i4.Client {} + +/// A class which mocks [ElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { + MockElectrumX() { + _i1.throwOnMissingStub(this); + } + + @override + set failovers(List<_i5.ElectrumXNode>? _failovers) => + super.noSuchMethod(Invocation.setter(#failovers, _failovers), + returnValueForMissingStub: null); + @override + int get currentFailoverIndex => + (super.noSuchMethod(Invocation.getter(#currentFailoverIndex), + returnValue: 0) as int); + @override + set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod( + Invocation.setter(#currentFailoverIndex, _currentFailoverIndex), + returnValueForMissingStub: null); + @override + String get host => + (super.noSuchMethod(Invocation.getter(#host), returnValue: '') as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i6.Future request( + {String? command, + List? args = const [], + Duration? connectionTimeout = const Duration(seconds: 60), + String? requestID, + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#request, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #requestID: requestID, + #retries: retries + }), + returnValue: Future.value()) as _i6.Future); + @override + _i6.Future>> batchRequest( + {String? command, + Map>? args, + Duration? connectionTimeout = const Duration(seconds: 60), + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#batchRequest, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #retries: retries + }), + returnValue: Future>>.value( + >[])) + as _i6.Future>>); + @override + _i6.Future ping({String? requestID, int? retryCount = 1}) => + (super.noSuchMethod( + Invocation.method( + #ping, [], {#requestID: requestID, #retryCount: retryCount}), + returnValue: Future.value(false)) as _i6.Future); + @override + _i6.Future> getBlockHeadTip({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBlockHeadTip, [], {#requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getServerFeatures({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getServerFeatures, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i6 + .Future>); + @override + _i6.Future broadcastTransaction({String? rawTx, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#broadcastTransaction, [], + {#rawTx: rawTx, #requestID: requestID}), + returnValue: Future.value('')) as _i6.Future); + @override + _i6.Future> getBalance( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBalance, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future>> getHistory( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getHistory, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) + as _i6.Future>>); + @override + _i6.Future>>> getBatchHistory( + {Map>? args}) => + (super.noSuchMethod( + Invocation.method(#getBatchHistory, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i6 + .Future>>>); + @override + _i6.Future>> getUTXOs( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getUTXOs, [], {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) as _i6 + .Future>>); + @override + _i6.Future>>> getBatchUTXOs( + {Map>? args}) => + (super.noSuchMethod(Invocation.method(#getBatchUTXOs, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i6 + .Future>>>); + @override + _i6.Future> getTransaction( + {String? txHash, bool? verbose = true, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #verbose: verbose, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getAnonymitySet( + {String? groupId = r'1', + String? blockhash = r'', + String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], { + #groupId: groupId, + #blockhash: blockhash, + #requestID: requestID + }), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future getMintData({dynamic mints, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getMintData, [], {#mints: mints, #requestID: requestID}), + returnValue: Future.value()) as _i6.Future); + @override + _i6.Future> getUsedCoinSerials( + {String? requestID, int? startNumber}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#requestID: requestID, #startNumber: startNumber}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + Invocation.method(#getLatestCoinId, [], {#requestID: requestID}), + returnValue: Future.value(0)) as _i6.Future); + @override + _i6.Future> getFeeRate({String? requestID}) => (super + .noSuchMethod(Invocation.method(#getFeeRate, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i6 + .Future>); + @override + _i6.Future<_i2.Decimal> estimateFee({String? requestID, int? blocks}) => + (super.noSuchMethod( + Invocation.method( + #estimateFee, [], {#requestID: requestID, #blocks: blocks}), + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); + @override + _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + Invocation.method(#relayFee, [], {#requestID: requestID}), + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); +} + +/// A class which mocks [CachedElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { + MockCachedElectrumX() { + _i1.throwOnMissingStub(this); + } + + @override + String get server => + (super.noSuchMethod(Invocation.getter(#server), returnValue: '') + as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i3.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), + returnValue: _FakePrefs_1()) as _i3.Prefs); + @override + List<_i5.ElectrumXNode> get failovers => + (super.noSuchMethod(Invocation.getter(#failovers), + returnValue: <_i5.ElectrumXNode>[]) as List<_i5.ElectrumXNode>); + @override + _i6.Future> getAnonymitySet( + {String? groupId, String? blockhash = r'', _i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], + {#groupId: groupId, #blockhash: blockhash, #coin: coin}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getTransaction( + {String? txHash, _i8.Coin? coin, bool? verbose = true}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #coin: coin, #verbose: verbose}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getUsedCoinSerials( + {_i8.Coin? coin, int? startNumber = 0}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#coin: coin, #startNumber: startNumber}), + returnValue: Future>.value([])) + as _i6.Future>); + @override + _i6.Future clearSharedTransactionCache({_i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#clearSharedTransactionCache, [], {#coin: coin}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); +} + +/// A class which mocks [PriceAPI]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { + MockPriceAPI() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Client get client => (super.noSuchMethod(Invocation.getter(#client), + returnValue: _FakeClient_2()) as _i4.Client); + @override + void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( + Invocation.method(#resetLastCalledToForceNextCallToUpdateCache, []), + returnValueForMissingStub: null); + @override + _i6.Future>> + getPricesAnd24hChange({String? baseCurrency}) => (super.noSuchMethod( + Invocation.method( + #getPricesAnd24hChange, [], {#baseCurrency: baseCurrency}), + returnValue: + Future>>.value( + <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{})) + as _i6.Future>>); +} + +/// A class which mocks [TransactionNotificationTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTransactionNotificationTracker extends _i1.Mock + implements _i11.TransactionNotificationTracker { + MockTransactionNotificationTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get walletId => + (super.noSuchMethod(Invocation.getter(#walletId), returnValue: '') + as String); + @override + List get pendings => + (super.noSuchMethod(Invocation.getter(#pendings), returnValue: []) + as List); + @override + List get confirmeds => (super + .noSuchMethod(Invocation.getter(#confirmeds), returnValue: []) + as List); + @override + bool wasNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedPending, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedPending, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + bool wasNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedConfirmed, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedConfirmed, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); +} diff --git a/test/services/coins/manager_test.mocks.dart b/test/services/coins/manager_test.mocks.dart index 600707e90..3ac360ffc 100644 --- a/test/services/coins/manager_test.mocks.dart +++ b/test/services/coins/manager_test.mocks.dart @@ -232,6 +232,22 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { super.noSuchMethod(Invocation.method(#stopNetworkAlivePinging, []), returnValueForMissingStub: null); @override + _i8.Future> prepareSendPublic( + {String? address, int? satoshiAmount, Map? args}) => + (super.noSuchMethod( + Invocation.method(#prepareSendPublic, [], { + #address: address, + #satoshiAmount: satoshiAmount, + #args: args + }), + returnValue: + Future>.value({})) + as _i8.Future>); + @override + _i8.Future confirmSendPublic({dynamic txData}) => (super.noSuchMethod( + Invocation.method(#confirmSendPublic, [], {#txData: txData}), + returnValue: Future.value('')) as _i8.Future); + @override _i8.Future> prepareSend( {String? address, int? satoshiAmount, Map? args}) => (super.noSuchMethod( @@ -257,6 +273,47 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { #send, [], {#toAddress: toAddress, #amount: amount, #args: args}), returnValue: Future.value('')) as _i8.Future); @override + int estimateTxFee({int? vSize, int? feeRatePerKB}) => (super.noSuchMethod( + Invocation.method( + #estimateTxFee, [], {#vSize: vSize, #feeRatePerKB: feeRatePerKB}), + returnValue: 0) as int); + @override + dynamic coinSelection(int? satoshiAmountToSend, int? selectedTxFeeRate, + String? _recipientAddress, bool? isSendAll, + {int? additionalOutputs = 0, List<_i4.UtxoObject>? utxos}) => + super.noSuchMethod(Invocation.method(#coinSelection, [ + satoshiAmountToSend, + selectedTxFeeRate, + _recipientAddress, + isSendAll + ], { + #additionalOutputs: additionalOutputs, + #utxos: utxos + })); + @override + _i8.Future> fetchBuildTxData( + List<_i4.UtxoObject>? utxosToUse) => + (super.noSuchMethod(Invocation.method(#fetchBuildTxData, [utxosToUse]), + returnValue: + Future>.value({})) + as _i8.Future>); + @override + _i8.Future> buildTransaction( + {List<_i4.UtxoObject>? utxosToUse, + Map? utxoSigningData, + List? recipients, + List? satoshiAmounts}) => + (super.noSuchMethod( + Invocation.method(#buildTransaction, [], { + #utxosToUse: utxosToUse, + #utxoSigningData: utxoSigningData, + #recipients: recipients, + #satoshiAmounts: satoshiAmounts + }), + returnValue: + Future>.value({})) + as _i8.Future>); + @override _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod(Invocation.method(#updateNode, [shouldRefresh]), returnValue: Future.value(), @@ -293,8 +350,8 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: >[]) as List>); @override - _i8.Future autoMint() => - (super.noSuchMethod(Invocation.method(#autoMint, []), + _i8.Future anonymizeAllPublicFunds() => + (super.noSuchMethod(Invocation.method(#anonymizeAllPublicFunds, []), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i8.Future); @override @@ -325,6 +382,11 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i8.Future); @override + _i8.Future checkChangeAddressForTransactions() => (super.noSuchMethod( + Invocation.method(#checkChangeAddressForTransactions, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i8.Future); + @override _i8.Future fillAddresses(String? suppliedMnemonic, {int? perBatch = 50, int? numberOfThreads = 4}) => (super.noSuchMethod( @@ -387,11 +449,11 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValueForMissingStub: Future.value()) as _i8.Future); @override _i8.Future getCoinsToJoinSplit(int? required) => - (super.noSuchMethod(Invocation.method(#GetCoinsToJoinSplit, [required]), + (super.noSuchMethod(Invocation.method(#getCoinsToJoinSplit, [required]), returnValue: Future.value()) as _i8.Future); @override _i8.Future estimateJoinSplitFee(int? spendAmount) => (super.noSuchMethod( - Invocation.method(#EstimateJoinSplitFee, [spendAmount]), + Invocation.method(#estimateJoinSplitFee, [spendAmount]), returnValue: Future.value(0)) as _i8.Future); @override _i8.Future estimateFeeFor(int? satoshiAmount, int? feeRate) => @@ -399,6 +461,21 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i8.Future); @override + _i8.Future estimateFeeForPublic(int? satoshiAmount, int? feeRate) => + (super.noSuchMethod( + Invocation.method(#estimateFeeForPublic, [satoshiAmount, feeRate]), + returnValue: Future.value(0)) as _i8.Future); + @override + int roughFeeEstimate(int? inputCount, int? outputCount, int? feeRatePerKB) => + (super.noSuchMethod( + Invocation.method( + #roughFeeEstimate, [inputCount, outputCount, feeRatePerKB]), + returnValue: 0) as int); + @override + int sweepAllEstimate(int? feeRate) => + (super.noSuchMethod(Invocation.method(#sweepAllEstimate, [feeRate]), + returnValue: 0) as int); + @override _i8.Future> getJMintTransactions( _i6.CachedElectrumX? cachedClient, List? transactions, @@ -418,6 +495,20 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: Future>.value(<_i4.Transaction>[])) as _i8.Future>); + @override + _i8.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i8.Future); + @override + _i8.Future<_i3.Decimal> availablePrivateBalance() => + (super.noSuchMethod(Invocation.method(#availablePrivateBalance, []), + returnValue: Future<_i3.Decimal>.value(_FakeDecimal_1())) + as _i8.Future<_i3.Decimal>); + @override + _i8.Future<_i3.Decimal> availablePublicBalance() => + (super.noSuchMethod(Invocation.method(#availablePublicBalance, []), + returnValue: Future<_i3.Decimal>.value(_FakeDecimal_1())) + as _i8.Future<_i3.Decimal>); } /// A class which mocks [ElectrumX]. diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index 5757ab5dd..012df8061 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -250,6 +250,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { Invocation.method(#estimateFeeFor, [satoshiAmount, feeRate]), returnValue: Future.value(0)) as _i7.Future); @override + _i7.Future generateNewAddress() => + (super.noSuchMethod(Invocation.method(#generateNewAddress, []), + returnValue: Future.value(false)) as _i7.Future); + @override void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); From 567d549747b7dcaa8d924875f4cbc90d2c2746a9 Mon Sep 17 00:00:00 2001 From: Likho Date: Tue, 13 Sep 2022 12:58:04 +0200 Subject: [PATCH 11/20] WIP: Update namecoin network --- lib/services/coins/bitcoin/bitcoin_wallet.dart | 2 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 2 +- lib/services/coins/dogecoin/dogecoin_wallet.dart | 2 +- lib/services/coins/namecoin/namecoin_wallet.dart | 11 ++++++----- lib/services/price.dart | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index ae6d7b70a..f0900f274 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -2250,7 +2250,7 @@ class BitcoinWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); - final id = const Uuid().v1(); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ id: [scripthash] diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index bb241b494..e5da66fc8 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1875,7 +1875,7 @@ class BitcoinCashWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); - final id = const Uuid().v1(); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ id: [scripthash] diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index c5a3ed8ac..0235a0c02 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1914,7 +1914,7 @@ class DogecoinWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); - final id = const Uuid().v1(); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ id: [scripthash] diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 067b26c0f..ef2b60237 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -75,7 +75,7 @@ bip32.BIP32 getBip32NodeFromRoot( int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { String coinType; switch (root.network.wif) { - case 0x80: // nmc mainnet wif + case 0xb4: // nmc mainnet wif coinType = "7"; // nmc mainnet break; default: @@ -159,6 +159,7 @@ class NamecoinWallet extends CoinServiceAPI { @override Future get availableBalance async { final data = await utxoData; + print("DATA IN GET BALANCE IS $data"); return Format.satoshisToAmount( data.satoshiBalance - data.satoshiBalanceUnconfirmed); } @@ -3027,9 +3028,9 @@ class NamecoinWallet extends CoinServiceAPI { // Namecoin Network final namecoin = NetworkType( - messagePrefix: '\x18Bitcoin Signed Message:\n', + messagePrefix: '\x18Namecoin Signed Message:\n', bech32: 'bc', bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80); + pubKeyHash: 0x34, //From 52 + scriptHash: 0x0d, //13 + wif: 0xb4); //from 180 diff --git a/lib/services/price.dart b/lib/services/price.dart index 5fd124e16..c79be324f 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -79,7 +79,7 @@ class PriceAPI { Map> result = {}; try { final uri = Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash&order=market_cap_desc&per_page=10&page=1&sparkline=false"); + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"); // final uri = Uri.parse( // "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero%2Cbitcoin%2Cepic-cash%2Czcoin%2Cdogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"); From 5bc4f25c154b5b8c4febe602e3f48c71bc7853c9 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 13 Sep 2022 21:12:21 +0800 Subject: [PATCH 12/20] namecoin receiving and sending --- .../coins/namecoin/namecoin_wallet.dart | 1441 +++++++++++++---- pubspec.yaml | 7 +- 2 files changed, 1111 insertions(+), 337 deletions(-) diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index ef2b60237..7c6706aab 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -42,16 +42,22 @@ import 'package:stackwallet/utilities/prefs.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; -const int MINIMUM_CONFIRMATIONS = 3; +const int MINIMUM_CONFIRMATIONS = 2; +// Find real dust limit const int DUST_LIMIT = 1000000; const String GENESIS_HASH_MAINNET = "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"; -enum DerivePathType { bip44 } +enum DerivePathType { bip44, bip49, bip84 } -bip32.BIP32 getBip32Node(int chain, int index, String mnemonic, - NetworkType network, DerivePathType derivePathType) { +bip32.BIP32 getBip32Node( + int chain, + int index, + String mnemonic, + NetworkType network, + DerivePathType derivePathType, +) { final root = getBip32Root(mnemonic, network); final node = getBip32NodeFromRoot(chain, index, root, derivePathType); @@ -72,7 +78,11 @@ bip32.BIP32 getBip32NodeWrapper( } bip32.BIP32 getBip32NodeFromRoot( - int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { + int chain, + int index, + bip32.BIP32 root, + DerivePathType derivePathType, +) { String coinType; switch (root.network.wif) { case 0xb4: // nmc mainnet wif @@ -84,6 +94,10 @@ bip32.BIP32 getBip32NodeFromRoot( switch (derivePathType) { case DerivePathType.bip44: return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + case DerivePathType.bip49: + return root.derivePath("m/49'/$coinType'/0'/$chain/$index"); + case DerivePathType.bip84: + return root.derivePath("m/84'/$coinType'/0'/$chain/$index"); default: throw Exception("DerivePathType must not be null."); } @@ -123,6 +137,7 @@ bip32.BIP32 getBip32RootWrapper(Tuple2 args) { class NamecoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); + final _prefs = Prefs.instance; Timer? timer; @@ -135,12 +150,30 @@ class NamecoinWallet extends CoinServiceAPI { case Coin.namecoin: return namecoin; default: - throw Exception("Namecoin network type not set!"); + throw Exception("Invalid network type!"); } } List outputsList = []; + @override + set isFavorite(bool markFavorite) { + DB.instance.put( + boxName: walletId, key: "isFavorite", value: markFavorite); + } + + @override + bool get isFavorite { + try { + return DB.instance.get(boxName: walletId, key: "isFavorite") + as bool; + } catch (e, s) { + Logging.instance + .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + @override Coin get coin => _coin; @@ -159,7 +192,6 @@ class NamecoinWallet extends CoinServiceAPI { @override Future get availableBalance async { final data = await utxoData; - print("DATA IN GET BALANCE IS $data"); return Format.satoshisToAmount( data.satoshiBalance - data.satoshiBalanceUnconfirmed); } @@ -193,12 +225,20 @@ class NamecoinWallet extends CoinServiceAPI { } @override - Future get currentReceivingAddress => + Future get currentReceivingAddress => _currentReceivingAddress ??= + _getCurrentAddressForChain(0, DerivePathType.bip84); + Future? _currentReceivingAddress; + + Future get currentLegacyReceivingAddress => _currentReceivingAddressP2PKH ??= _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; + Future get currentReceivingAddressP2SH => + _currentReceivingAddressP2SH ??= + _getCurrentAddressForChain(0, DerivePathType.bip49); + Future? _currentReceivingAddressP2SH; + @override Future exit() async { _hasCalledExit = true; @@ -218,9 +258,8 @@ class NamecoinWallet extends CoinServiceAPI { @override Future get maxFee async { - final fee = (await fees).fast; - final satsFee = - Format.satoshisToAmount(fee) * Decimal.fromInt(Constants.satsPerCoin); + final fee = (await fees).fast as String; + final satsFee = Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin); return satsFee.floor().toBigInt().toInt(); } @@ -238,14 +277,14 @@ class NamecoinWallet extends CoinServiceAPI { } } - Future get storedChainHeight async { + int get storedChainHeight { final storedHeight = DB.instance .get(boxName: walletId, key: "storedChainHeight") as int?; return storedHeight ?? 0; } Future updateStoredChainHeight({required int newHeight}) async { - DB.instance.put( + await DB.instance.put( boxName: walletId, key: "storedChainHeight", value: newHeight); } @@ -262,6 +301,10 @@ class NamecoinWallet extends CoinServiceAPI { // P2PKH return DerivePathType.bip44; } + if (decodeBase58[0] == _network.scriptHash) { + // P2SH + return DerivePathType.bip49; + } throw ArgumentError('Invalid version or Network mismatch'); } else { try { @@ -275,8 +318,9 @@ class NamecoinWallet extends CoinServiceAPI { if (decodeBech32.version != 0) { throw ArgumentError('Invalid address version'); } + // P2WPKH + return DerivePathType.bip84; } - throw ArgumentError('$address has no matching Script'); } bool longMutex = false; @@ -306,6 +350,15 @@ class NamecoinWallet extends CoinServiceAPI { throw Exception( "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); } + // if (_networkType == BasicNetworkType.main) { + // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + // throw Exception("genesis hash does not match main net!"); + // } + // } else if (_networkType == BasicNetworkType.test) { + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // } } // check to make sure we aren't overwriting a mnemonic // this should never fail @@ -335,6 +388,140 @@ class NamecoinWallet extends CoinServiceAPI { level: LogLevel.Info); } + Future> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int account) 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 $account ${type.name}: $gapCounter", + level: LogLevel.Info); + + final _id = "k_$index"; + Map txCountCallArgs = {}; + final Map receivingNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + final node = await compute( + getBip32NodeFromRootWrapper, + Tuple4( + account, + index + j, + root, + type, + ), + ); + String? address; + switch (type) { + case DerivePathType.bip44: + address = P2PKH( + data: PaymentData(pubkey: node.publicKey), + network: _network) + .data + .address!; + break; + case DerivePathType.bip49: + address = P2SH( + data: PaymentData( + redeem: P2WPKH( + data: PaymentData(pubkey: node.publicKey), + network: _network, + overridePrefix: namecoin.bech32!) + .data), + network: _network) + .data + .address!; + break; + case DerivePathType.bip84: + address = P2WPKH( + network: _network, + data: PaymentData(pubkey: node.publicKey), + overridePrefix: namecoin.bech32!) + .data + .address!; + break; + default: + throw Exception("No Path type $type exists"); + } + receivingNodes.addAll({ + "${_id}_$j": { + "node": node, + "address": address, + } + }); + txCountCallArgs.addAll({ + "${_id}_$j": address, + }); + } + + // 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"]; + // add address to array + addressArray.add(node["address"] as String); + iterationsAddressArray.add(node["address"] as String); + // set current index + returningIndex = index + k; + // reset counter + gapCounter = 0; + // add info to derivations + derivations[node["address"] as String] = { + "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, int maxUnusedAddressGap = 20, @@ -343,164 +530,100 @@ class NamecoinWallet extends CoinServiceAPI { longMutex = true; Map> p2pkhReceiveDerivations = {}; + Map> p2shReceiveDerivations = {}; + Map> p2wpkhReceiveDerivations = {}; Map> p2pkhChangeDerivations = {}; + Map> p2shChangeDerivations = {}; + Map> p2wpkhChangeDerivations = {}; final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _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; - // The gap limit will be capped at [maxUnusedAddressGap] - int receivingGapCounter = 0; - int changeGapCounter = 0; - - // actual size is 12 due to p2pkh so 12x1 + // 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); - for (int index = 0; - index < maxNumberOfIndexesToCheck && - receivingGapCounter < maxUnusedAddressGap; - index += txCountBatchSize) { - Logging.instance.log( - "index: $index, \t receivingGapCounter: $receivingGapCounter", - level: LogLevel.Info); + final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); - final receivingP2pkhID = "k_$index"; - Map txCountCallArgs = {}; - final Map receivingNodes = {}; + final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); - for (int j = 0; j < txCountBatchSize; j++) { - // bip44 / P2PKH - final node44 = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - 0, - index + j, - root, - DerivePathType.bip44, - ), - ); - final p2pkhReceiveAddress = P2PKH( - data: PaymentData(pubkey: node44.publicKey), - network: _network) - .data - .address!; - receivingNodes.addAll({ - "${receivingP2pkhID}_$j": { - "node": node44, - "address": p2pkhReceiveAddress, - } - }); - txCountCallArgs.addAll({ - "${receivingP2pkhID}_$j": p2pkhReceiveAddress, - }); - } - - // get address tx counts - final counts = await _getBatchTxCount(addresses: txCountCallArgs); - - // check and add appropriate addresses - for (int k = 0; k < txCountBatchSize; k++) { - int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!; - if (p2pkhTxCount > 0) { - final node = receivingNodes["${receivingP2pkhID}_$k"]; - // add address to array - p2pkhReceiveAddressArray.add(node["address"] as String); - // set current index - p2pkhReceiveIndex = index + k; - // reset counter - receivingGapCounter = 0; - // add info to derivations - p2pkhReceiveDerivations[node["address"] as String] = { - "pubKey": Format.uint8listToString( - (node["node"] as bip32.BIP32).publicKey), - "wif": (node["node"] as bip32.BIP32).toWIF(), - }; - } - - // increase counter when no tx history found - if (p2pkhTxCount == 0) { - receivingGapCounter++; - } - } - } + final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0); Logging.instance .log("checking change addresses...", level: LogLevel.Info); // change addresses - for (int index = 0; - index < maxNumberOfIndexesToCheck && - changeGapCounter < maxUnusedAddressGap; - index += txCountBatchSize) { - Logging.instance.log( - "index: $index, \t changeGapCounter: $changeGapCounter", - level: LogLevel.Info); - final changeP2pkhID = "k_$index"; - Map args = {}; - final Map changeNodes = {}; + final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); - for (int j = 0; j < txCountBatchSize; j++) { - // bip44 / P2PKH - final node44 = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - 1, - index + j, - root, - DerivePathType.bip44, - ), - ); - final p2pkhChangeAddress = P2PKH( - data: PaymentData(pubkey: node44.publicKey), - network: _network) - .data - .address!; - changeNodes.addAll({ - "${changeP2pkhID}_$j": { - "node": node44, - "address": p2pkhChangeAddress, - } - }); - args.addAll({ - "${changeP2pkhID}_$j": p2pkhChangeAddress, - }); - } + final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); - // get address tx counts - final counts = await _getBatchTxCount(addresses: args); + final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck, + maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1); - // check and add appropriate addresses - for (int k = 0; k < txCountBatchSize; k++) { - int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!; - if (p2pkhTxCount > 0) { - final node = changeNodes["${changeP2pkhID}_$k"]; - // add address to array - p2pkhChangeAddressArray.add(node["address"] as String); - // set current index - p2pkhChangeIndex = index + k; - // reset counter - changeGapCounter = 0; - // add info to derivations - p2pkhChangeDerivations[node["address"] as String] = { - "pubKey": Format.uint8listToString( - (node["node"] as bip32.BIP32).publicKey), - "wif": (node["node"] as bip32.BIP32).toWIF(), - }; - } + await Future.wait([ + resultReceive44, + resultReceive49, + resultReceive84, + resultChange44, + resultChange49, + resultChange84 + ]); - // increase counter when no tx history found - if (p2pkhTxCount == 0) { - changeGapCounter++; - } - } - } + 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) { @@ -509,12 +632,36 @@ class NamecoinWallet extends CoinServiceAPI { 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 @@ -524,6 +671,18 @@ class NamecoinWallet extends CoinServiceAPI { p2pkhReceiveAddressArray.add(address); p2pkhReceiveIndex = 0; } + if (p2shReceiveIndex == -1) { + final address = + await _generateAddressForChain(0, 0, DerivePathType.bip49); + p2shReceiveAddressArray.add(address); + p2shReceiveIndex = 0; + } + if (p2wpkhReceiveIndex == -1) { + final address = + await _generateAddressForChain(0, 0, DerivePathType.bip84); + p2wpkhReceiveAddressArray.add(address); + p2wpkhReceiveIndex = 0; + } // 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. @@ -533,7 +692,27 @@ class NamecoinWallet extends CoinServiceAPI { p2pkhChangeAddressArray.add(address); p2pkhChangeIndex = 0; } + if (p2shChangeIndex == -1) { + final address = + await _generateAddressForChain(1, 0, DerivePathType.bip49); + p2shChangeAddressArray.add(address); + p2shChangeIndex = 0; + } + if (p2wpkhChangeIndex == -1) { + final address = + await _generateAddressForChain(1, 0, DerivePathType.bip84); + p2wpkhChangeAddressArray.add(address); + p2wpkhChangeIndex = 0; + } + await DB.instance.put( + boxName: walletId, + key: 'receivingAddressesP2WPKH', + value: p2wpkhReceiveAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'changeAddressesP2WPKH', + value: p2wpkhChangeAddressArray); await DB.instance.put( boxName: walletId, key: 'receivingAddressesP2PKH', @@ -542,12 +721,34 @@ class NamecoinWallet extends CoinServiceAPI { boxName: walletId, key: 'changeAddressesP2PKH', value: p2pkhChangeAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'receivingAddressesP2SH', + value: p2shReceiveAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'changeAddressesP2SH', + value: p2shChangeAddressArray); + await DB.instance.put( + boxName: walletId, + key: 'receivingIndexP2WPKH', + value: p2wpkhReceiveIndex); + await DB.instance.put( + boxName: walletId, + key: 'changeIndexP2WPKH', + value: p2wpkhChangeIndex); + await DB.instance.put( + boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); await DB.instance.put( boxName: walletId, key: 'receivingIndexP2PKH', value: p2pkhReceiveIndex); await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); + boxName: walletId, + key: 'receivingIndexP2SH', + value: p2shReceiveIndex); + await DB.instance.put( + boxName: walletId, key: 'changeIndexP2SH', value: p2shChangeIndex); await DB.instance .put(boxName: walletId, key: "id", value: _walletId); await DB.instance @@ -557,7 +758,7 @@ class NamecoinWallet extends CoinServiceAPI { } catch (e, s) { Logging.instance.log( "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Info); + level: LogLevel.Error); longMutex = false; rethrow; @@ -571,9 +772,6 @@ class NamecoinWallet extends CoinServiceAPI { try { bool needsRefresh = false; - Logging.instance.log( - "notified unconfirmed transactions: ${txTracker.pendings}", - level: LogLevel.Info); Set txnsToCheck = {}; for (final String txid in txTracker.pendings) { @@ -584,8 +782,7 @@ class NamecoinWallet extends CoinServiceAPI { for (String txid in txnsToCheck) { final txn = await electrumXClient.getTransaction(txHash: txid); - var confirmations = txn["confirmations"]; - if (confirmations is! int) continue; + int confirmations = txn["confirmations"] as int? ?? 0; bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; if (!isUnconfirmed) { // unconfirmedTxs = {}; @@ -613,7 +810,7 @@ class NamecoinWallet extends CoinServiceAPI { } catch (e, s) { Logging.instance.log( "Exception caught in refreshIfThereIsNewData: $e\n$s", - level: LogLevel.Info); + level: LogLevel.Error); rethrow; } } @@ -625,15 +822,16 @@ class NamecoinWallet extends CoinServiceAPI { List unconfirmedTxnsToNotifyPending = []; List unconfirmedTxnsToNotifyConfirmed = []; - // Get all unconfirmed incoming transactions for (final chunk in txData.txChunks) { for (final tx in chunk.transactions) { if (tx.confirmedStatus) { + // 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); } @@ -641,24 +839,24 @@ class NamecoinWallet extends CoinServiceAPI { } } - // notify on new incoming transaction + // notify on unconfirmed transactions for (final tx in unconfirmedTxnsToNotifyPending) { if (tx.txType == "Received") { - NotificationApi.showNotification( + unawaited(NotificationApi.showNotification( title: "Incoming transaction", body: walletName, walletId: walletId, iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, coinName: coin.name, txid: tx.txid, confirmations: tx.confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, - ); + )); await txTracker.addNotifiedPending(tx.txid); } else if (tx.txType == "Sent") { - NotificationApi.showNotification( + unawaited(NotificationApi.showNotification( title: "Sending transaction", body: walletName, walletId: walletId, @@ -669,7 +867,7 @@ class NamecoinWallet extends CoinServiceAPI { txid: tx.txid, confirmations: tx.confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, - ); + )); await txTracker.addNotifiedPending(tx.txid); } } @@ -677,34 +875,31 @@ class NamecoinWallet extends CoinServiceAPI { // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { if (tx.txType == "Received") { - NotificationApi.showNotification( + unawaited(NotificationApi.showNotification( title: "Incoming transaction confirmed", body: walletName, walletId: walletId, iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), shouldWatchForUpdates: false, coinName: coin.name, - ); - + )); await txTracker.addNotifiedConfirmed(tx.txid); } else if (tx.txType == "Sent") { - NotificationApi.showNotification( + unawaited(NotificationApi.showNotification( title: "Outgoing transaction confirmed", body: walletName, walletId: walletId, iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), shouldWatchForUpdates: false, coinName: coin.name, - ); + )); await txTracker.addNotifiedConfirmed(tx.txid); } } } - bool refreshMutex = false; - bool _shouldAutoSync = false; @override @@ -725,6 +920,11 @@ class NamecoinWallet extends CoinServiceAPI { } } + @override + bool get isRefreshing => refreshMutex; + + bool refreshMutex = false; + //TODO Show percentages properly/more consistently /// Refreshes display data for the wallet @override @@ -761,14 +961,16 @@ class NamecoinWallet extends CoinServiceAPI { if (currentHeight != storedHeight) { if (currentHeight != -1) { // -1 failed to fetch current height - updateStoredChainHeight(newHeight: currentHeight); + unawaited(updateStoredChainHeight(newHeight: currentHeight)); } GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - await _checkChangeAddressForTransactions(DerivePathType.bip44); + final changeAddressForTransactions = + _checkChangeAddressForTransactions(DerivePathType.bip84); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - await _checkCurrentReceivingAddressesForTransactions(); + final currentReceivingAddressesForTransactions = + _checkCurrentReceivingAddressesForTransactions(); final newTxData = _fetchTransactionData(); GlobalEventBus.instance @@ -788,11 +990,20 @@ class NamecoinWallet extends CoinServiceAPI { GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - await getAllTxsToWatch(await newTxData); + final allTxsToWatch = getAllTxsToWatch(await newTxData); + await Future.wait([ + newTxData, + changeAddressForTransactions, + currentReceivingAddressesForTransactions, + newUtxoData, + feeObj, + allTxsToWatch, + ]); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } + refreshMutex = false; GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -801,10 +1012,12 @@ class NamecoinWallet extends CoinServiceAPI { coin, ), ); - refreshMutex = false; if (shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async { + 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()) { @@ -867,6 +1080,7 @@ class NamecoinWallet extends CoinServiceAPI { } else { rate = feeRateAmount as int; } + // check for send all bool isSendAll = false; final balance = Format.decimalAmountToSatoshis(await availableBalance); @@ -874,36 +1088,49 @@ class NamecoinWallet extends CoinServiceAPI { isSendAll = true; } - final result = + final txData = await coinSelection(satoshiAmount, rate, address, isSendAll); - Logging.instance.log("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"); + 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"); } - return result as Map; } else { - throw Exception("sent hex is not a String!!!"); + 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!"); @@ -916,12 +1143,15 @@ class NamecoinWallet extends CoinServiceAPI { } @override - Future confirmSend({dynamic txData}) async { + Future confirmSend({required Map txData}) async { try { Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); - final txHash = await _electrumXClient.broadcastTransaction( - rawTx: txData["hex"] as String); + + final hex = txData["hex"] as String; + + final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1004,6 +1234,7 @@ class NamecoinWallet extends CoinServiceAPI { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } + await _prefs.init(); try { await _generateNewWallet(); @@ -1013,7 +1244,7 @@ class NamecoinWallet extends CoinServiceAPI { rethrow; } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: _walletId), + DB.instance.put(boxName: walletId, key: "id", value: walletId), DB.instance .put(boxName: walletId, key: "isFavorite", value: false), ]); @@ -1044,7 +1275,7 @@ class NamecoinWallet extends CoinServiceAPI { @override bool validateAddress(String address) { - return Address.validateAddress(address, _network); + return Address.validateAddress(address, _network, namecoin.bech32!); } @override @@ -1118,7 +1349,7 @@ class NamecoinWallet extends CoinServiceAPI { ); if (shouldRefresh) { - refresh(); + unawaited(refresh()); } } @@ -1147,23 +1378,31 @@ class NamecoinWallet extends CoinServiceAPI { Future> _fetchAllOwnAddresses() async { 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]); - // } - // } - // for (var i = 0; i < changeAddresses.length; i++) { - // if (!allAddresses.contains(changeAddresses[i])) { - // allAddresses.add(changeAddresses[i]); - // } - // } + 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); @@ -1174,6 +1413,16 @@ class NamecoinWallet extends CoinServiceAPI { 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; } @@ -1232,10 +1481,18 @@ class NamecoinWallet extends CoinServiceAPI { value: bip39.generateMnemonic(strength: 256)); // Set relevant indexes + await DB.instance + .put(boxName: walletId, key: "receivingIndexP2WPKH", value: 0); + await DB.instance + .put(boxName: walletId, key: "changeIndexP2WPKH", value: 0); await DB.instance .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); await DB.instance .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); + await DB.instance + .put(boxName: walletId, key: "receivingIndexP2SH", value: 0); + await DB.instance + .put(boxName: walletId, key: "changeIndexP2SH", value: 0); await DB.instance.put( boxName: walletId, key: 'blocked_tx_hashes', @@ -1248,31 +1505,95 @@ class NamecoinWallet extends CoinServiceAPI { value: {}); // Generate and add addresses to relevant arrays - // final initialReceivingAddress = - // await _generateAddressForChain(0, 0, DerivePathType.bip44); - // final initialChangeAddress = - // await _generateAddressForChain(1, 0, DerivePathType.bip44); - final initialReceivingAddressP2PKH = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - final initialChangeAddressP2PKH = - await _generateAddressForChain(1, 0, DerivePathType.bip44); + await Future.wait([ + // P2WPKH + _generateAddressForChain(0, 0, DerivePathType.bip84).then( + (initialReceivingAddressP2WPKH) { + _addToAddressesArrayForChain( + initialReceivingAddressP2WPKH, 0, DerivePathType.bip84); + _currentReceivingAddress = + Future(() => initialReceivingAddressP2WPKH); + }, + ), + _generateAddressForChain(1, 0, DerivePathType.bip84).then( + (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain( + initialChangeAddressP2WPKH, + 1, + DerivePathType.bip84, + ), + ), - // await _addToAddressesArrayForChain( - // initialReceivingAddress, 0, DerivePathType.bip44); - // await _addToAddressesArrayForChain( - // initialChangeAddress, 1, DerivePathType.bip44); - await _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - await _addToAddressesArrayForChain( - initialChangeAddressP2PKH, 1, DerivePathType.bip44); + // P2PKH + _generateAddressForChain(0, 0, DerivePathType.bip44).then( + (initialReceivingAddressP2PKH) { + _addToAddressesArrayForChain( + initialReceivingAddressP2PKH, 0, DerivePathType.bip44); + _currentReceivingAddressP2PKH = + Future(() => initialReceivingAddressP2PKH); + }, + ), + _generateAddressForChain(1, 0, DerivePathType.bip44).then( + (initialChangeAddressP2PKH) => _addToAddressesArrayForChain( + initialChangeAddressP2PKH, + 1, + DerivePathType.bip44, + ), + ), - // this._currentReceivingAddress = Future(() => initialReceivingAddress); - _currentReceivingAddressP2PKH = Future(() => initialReceivingAddressP2PKH); + // P2SH + _generateAddressForChain(0, 0, DerivePathType.bip49).then( + (initialReceivingAddressP2SH) { + _addToAddressesArrayForChain( + initialReceivingAddressP2SH, 0, DerivePathType.bip49); + _currentReceivingAddressP2SH = + Future(() => initialReceivingAddressP2SH); + }, + ), + _generateAddressForChain(1, 0, DerivePathType.bip49).then( + (initialChangeAddressP2SH) => _addToAddressesArrayForChain( + initialChangeAddressP2SH, + 1, + DerivePathType.bip49, + ), + ), + ]); + + // // P2PKH + // _generateAddressForChain(0, 0, DerivePathType.bip44).then( + // (initialReceivingAddressP2PKH) { + // _addToAddressesArrayForChain( + // initialReceivingAddressP2PKH, 0, DerivePathType.bip44); + // this._currentReceivingAddressP2PKH = + // Future(() => initialReceivingAddressP2PKH); + // }, + // ); + // _generateAddressForChain(1, 0, DerivePathType.bip44) + // .then((initialChangeAddressP2PKH) => _addToAddressesArrayForChain( + // initialChangeAddressP2PKH, + // 1, + // DerivePathType.bip44, + // )); + // + // // P2SH + // _generateAddressForChain(0, 0, DerivePathType.bip49).then( + // (initialReceivingAddressP2SH) { + // _addToAddressesArrayForChain( + // initialReceivingAddressP2SH, 0, DerivePathType.bip49); + // this._currentReceivingAddressP2SH = + // Future(() => initialReceivingAddressP2SH); + // }, + // ); + // _generateAddressForChain(1, 0, DerivePathType.bip49) + // .then((initialChangeAddressP2SH) => _addToAddressesArrayForChain( + // initialChangeAddressP2SH, + // 1, + // DerivePathType.bip49, + // )); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } - /// Generates a new internal or external chain address for the wallet using a BIP44 derivation path. + /// 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( @@ -1298,9 +1619,24 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: address = P2PKH(data: data, network: _network).data.address!; break; - // default: - // // should never hit this due to all enum cases handled - // return null; + case DerivePathType.bip49: + address = P2SH( + data: PaymentData( + redeem: P2WPKH( + data: data, + network: _network, + overridePrefix: namecoin.bech32!) + .data), + network: _network) + .data + .address!; + break; + case DerivePathType.bip84: + address = P2WPKH( + network: _network, data: data, overridePrefix: namecoin.bech32!) + .data + .address!; + break; } // add generated address & info to derivations @@ -1325,6 +1661,12 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: indexKey += "P2PKH"; break; + case DerivePathType.bip49: + indexKey += "P2SH"; + break; + case DerivePathType.bip84: + indexKey += "P2WPKH"; + break; } final newIndex = @@ -1348,6 +1690,12 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: chainArray += "P2PKH"; break; + case DerivePathType.bip49: + chainArray += "P2SH"; + break; + case DerivePathType.bip84: + chainArray += "P2WPKH"; + break; } final addressArray = @@ -1382,26 +1730,42 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: arrayKey += "P2PKH"; break; + case DerivePathType.bip49: + arrayKey += "P2SH"; + break; + case DerivePathType.bip84: + arrayKey += "P2WPKH"; + break; } final internalChainArray = DB.instance.get(boxName: walletId, key: arrayKey); return internalChainArray.last as String; } - String _buildDerivationStorageKey( - {required int chain, required DerivePathType derivePathType}) { + 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; } return key; } - Future> _fetchDerivations( - {required int chain, required DerivePathType derivePathType}) async { + Future> _fetchDerivations({ + required int chain, + required DerivePathType derivePathType, + }) async { // build lookup key final key = _buildDerivationStorageKey( chain: chain, derivePathType: derivePathType); @@ -1487,7 +1851,7 @@ class NamecoinWallet extends CoinServiceAPI { final fetchedUtxoList = >>[]; final Map>> batches = {}; - const batchSizeMax = 10; + const batchSizeMax = 100; int batchNumber = 0; for (int i = 0; i < allAddresses.length; i++) { if (batches[batchNumber] == null) { @@ -1511,7 +1875,6 @@ class NamecoinWallet extends CoinServiceAPI { } } } - final priceData = await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; @@ -1532,9 +1895,7 @@ class NamecoinWallet extends CoinServiceAPI { final Map utxo = {}; final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = txn["confirmations"] == null - ? false - : txn["confirmations"] as int >= MINIMUM_CONFIRMATIONS; + final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; if (!confirmed) { satoshiBalancePending += value; } @@ -1592,7 +1953,8 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + DB.instance.get(boxName: walletId, key: 'latest_utxo_model') + as models.UtxoData?; if (latestTxModel == null) { final emptyModel = { @@ -1605,7 +1967,7 @@ class NamecoinWallet extends CoinServiceAPI { } else { Logging.instance .log("Old output model located", level: LogLevel.Warning); - return latestTxModel as models.UtxoData; + return latestTxModel; } } } @@ -1707,6 +2069,12 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: indexKey += "P2PKH"; break; + case DerivePathType.bip49: + indexKey += "P2SH"; + break; + case DerivePathType.bip84: + indexKey += "P2WPKH"; + break; } final newReceivingIndex = DB.instance.get(boxName: walletId, key: indexKey) as int; @@ -1725,13 +2093,14 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); break; + case DerivePathType.bip49: + _currentReceivingAddressP2SH = Future(() => newReceivingAddress); + break; + case DerivePathType.bip84: + _currentReceivingAddress = Future(() => newReceivingAddress); + break; } } - } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", - level: LogLevel.Error); - return; } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", @@ -1760,6 +2129,12 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: indexKey += "P2PKH"; break; + case DerivePathType.bip49: + indexKey += "P2SH"; + break; + case DerivePathType.bip84: + indexKey += "P2WPKH"; + break; } final newChangeIndex = DB.instance.get(boxName: walletId, key: indexKey) as int; @@ -1771,9 +2146,14 @@ class NamecoinWallet extends CoinServiceAPI { // Add that new receiving address to the array of change addresses await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + level: LogLevel.Error); + return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkChangeAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", level: LogLevel.Error); rethrow; } @@ -1787,7 +2167,7 @@ class NamecoinWallet extends CoinServiceAPI { } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", - level: LogLevel.Info); + level: LogLevel.Error); rethrow; } } @@ -1832,7 +2212,8 @@ class NamecoinWallet extends CoinServiceAPI { /// Returns the scripthash or throws an exception on invalid namecoin address String _convertToScriptHash(String namecoinAddress, NetworkType network) { try { - final output = Address.addressToOutputScript(namecoinAddress, network); + final output = Address.addressToOutputScript( + namecoinAddress, network, namecoin.bech32!); final hash = sha256.convert(output.toList(growable: false)).toString(); final chars = hash.split(""); @@ -1856,14 +2237,14 @@ class NamecoinWallet extends CoinServiceAPI { final Map>> batches = {}; final Map requestIdToAddressMap = {}; - const batchSizeMax = 10; + 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 = const Uuid().v1(); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ id: [scripthash] @@ -1903,12 +2284,61 @@ class NamecoinWallet extends CoinServiceAPI { 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 _fetchTransactionData() async { final List allAddresses = await _fetchAllOwnAddresses(); + final changeAddresses = DB.instance.get( + boxName: walletId, key: 'changeAddressesP2WPKH') as List; final changeAddressesP2PKH = DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') as List; + final changeAddressesP2SH = + DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') + as List; + + for (var i = 0; i < changeAddressesP2PKH.length; i++) { + changeAddresses.add(changeAddressesP2PKH[i] as String); + } + for (var i = 0; i < changeAddressesP2SH.length; i++) { + changeAddresses.add(changeAddressesP2SH[i] as String); + } final List> allTxHashes = await _fetchHistory(allAddresses); @@ -1938,6 +2368,11 @@ class NamecoinWallet extends CoinServiceAPI { } } + Set hashes = {}; + for (var element in allTxHashes) { + hashes.add(element['tx_hash'] as String); + } + await fastFetch(hashes.toList()); List> allTransactions = []; for (final txHash in allTxHashes) { @@ -1967,6 +2402,16 @@ class NamecoinWallet extends CoinServiceAPI { Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; final List> midSortedArray = []; + 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()); + for (final txObject in allTransactions) { List sendersArray = []; List recipientsArray = []; @@ -1980,12 +2425,14 @@ class NamecoinWallet extends CoinServiceAPI { Map midSortedTx = {}; for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; + final input = txObject["vin"]![i] as Map; final prevTxid = input["txid"] as String; final prevOut = input["vout"] as int; final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, coin: coin); + txHash: prevTxid, + coin: coin, + ); for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { @@ -2018,7 +2465,7 @@ class NamecoinWallet extends CoinServiceAPI { if (foundInSenders) { int totalInput = 0; for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; + final input = txObject["vin"]![i] as Map; final prevTxid = input["txid"] as String; final prevOut = input["vout"] as int; final tx = await _cachedElectrumXClient.getTransaction( @@ -2029,7 +2476,7 @@ class NamecoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { inputAmtSentFromWallet += - (Decimal.parse(out["value"].toString()) * + (Decimal.parse(out["value"]!.toString()) * Decimal.fromInt(Constants.satsPerCoin)) .toBigInt() .toInt(); @@ -2047,7 +2494,7 @@ class NamecoinWallet extends CoinServiceAPI { .toBigInt() .toInt(); totalOutput += _value; - if (changeAddressesP2PKH.contains(address)) { + if (changeAddresses.contains(address)) { inputAmtSentFromWallet -= _value; } else { // change address from 'sent from' to the 'sent to' address @@ -2218,9 +2665,14 @@ class NamecoinWallet extends CoinServiceAPI { /// 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(int satoshiAmountToSend, int selectedTxFeeRate, - String _recipientAddress, bool isSendAll, - {int additionalOutputs = 0, List? utxos}) async { + dynamic coinSelection( + int satoshiAmountToSend, + int selectedTxFeeRate, + String _recipientAddress, + bool isSendAll, { + int additionalOutputs = 0, + List? utxos, + }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); final List availableOutputs = utxos ?? outputsList; @@ -2288,8 +2740,6 @@ class NamecoinWallet extends CoinServiceAPI { .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); Logging.instance .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); - Logging.instance - .log('satoshiAmountToSend $satoshiAmountToSend', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray List recipientsArray = [_recipientAddress]; @@ -2312,8 +2762,11 @@ class NamecoinWallet extends CoinServiceAPI { vSize: vSizeForOneOutput, feeRatePerKB: selectedTxFeeRate, ); - if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { - feeForOneOutput = (vSizeForOneOutput + 1) * 1000; + + final int roughEstimate = + roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate); + if (feeForOneOutput < roughEstimate) { + feeForOneOutput = roughEstimate; } final int amount = satoshiAmountToSend - feeForOneOutput; @@ -2344,38 +2797,25 @@ class NamecoinWallet extends CoinServiceAPI { utxoSigningData: utxoSigningData, recipients: [ _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip44), + await _getCurrentAddressForChain(1, DerivePathType.bip84), ], satoshiAmounts: [ satoshiAmountToSend, - satoshisBeingUsed - satoshiAmountToSend - 1, + satoshisBeingUsed - satoshiAmountToSend - 1 ], // dust limit is the minimum amount a change output should be ))["vSize"] as int; - debugPrint("vSizeForOneOutput $vSizeForOneOutput"); - debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); // Assume 1 output, only for recipient and no change - var feeForOneOutput = estimateTxFee( + final feeForOneOutput = estimateTxFee( vSize: vSizeForOneOutput, feeRatePerKB: selectedTxFeeRate, ); // Assume 2 outputs, one for recipient and one for change - var feeForTwoOutputs = estimateTxFee( + final feeForTwoOutputs = estimateTxFee( vSize: vSizeForTwoOutPuts, feeRatePerKB: selectedTxFeeRate, ); - Logging.instance - .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); - Logging.instance - .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); - if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) { - feeForOneOutput = (vSizeForOneOutput + 1) * 1000; - } - if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1) * 1000)) { - feeForTwoOutputs = ((vSizeForTwoOutPuts + 1) * 1000); - } - Logging.instance .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); Logging.instance @@ -2389,15 +2829,15 @@ class NamecoinWallet extends CoinServiceAPI { 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 > 546 satoshis, we perform the mechanics required to properly generate and use a new + // 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 && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip44); + await _checkChangeAddressForTransactions(DerivePathType.bip84); final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip44); + await _getCurrentAddressForChain(1, DerivePathType.bip84); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2464,7 +2904,7 @@ class NamecoinWallet extends CoinServiceAPI { 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. + // 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); @@ -2491,7 +2931,7 @@ class NamecoinWallet extends CoinServiceAPI { return transactionObject; } } else { - // No additional outputs needed since adding one would mean that it'd be smaller than 546 sats + // 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); @@ -2573,6 +3013,9 @@ class NamecoinWallet extends CoinServiceAPI { // addresses to check List addressesP2PKH = []; + List addressesP2SH = []; + List addressesP2WPKH = []; + Logging.instance.log("utxos: $utxosToUse", level: LogLevel.Info); try { // Populating the addresses to check @@ -2582,6 +3025,8 @@ class NamecoinWallet extends CoinServiceAPI { txHash: txid, coin: coin, ); + Logging.instance.log("tx: ${json.encode(tx)}", + level: LogLevel.Info, printFullLength: true); for (final output in tx["vout"] as List) { final n = output["n"]; @@ -2595,6 +3040,12 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: addressesP2PKH.add(address); break; + case DerivePathType.bip49: + addressesP2SH.add(address); + break; + case DerivePathType.bip84: + addressesP2WPKH.add(address); + break; } } } @@ -2658,6 +3109,140 @@ class NamecoinWallet extends CoinServiceAPI { } } + // p2sh / bip49 + final p2shLength = addressesP2SH.length; + if (p2shLength > 0) { + final receiveDerivations = await _fetchDerivations( + chain: 0, + derivePathType: DerivePathType.bip49, + ); + final changeDerivations = await _fetchDerivations( + chain: 1, + derivePathType: DerivePathType.bip49, + ); + for (int i = 0; i < p2shLength; i++) { + // receives + final receiveDerivation = receiveDerivations[addressesP2SH[i]]; + // if a match exists it will not be null + if (receiveDerivation != null) { + final p2wpkh = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + receiveDerivation["pubKey"] as String)), + network: _network, + overridePrefix: namecoin.bech32!) + .data; + + final redeemScript = p2wpkh.output; + + final data = + P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data; + + for (String tx in addressTxid[addressesP2SH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + receiveDerivation["wif"] as String, + network: _network, + ), + "redeemScript": redeemScript, + }; + } + } else { + // if its not a receive, check change + final changeDerivation = changeDerivations[addressesP2SH[i]]; + // if a match exists it will not be null + if (changeDerivation != null) { + final p2wpkh = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + changeDerivation["pubKey"] as String)), + network: _network, + overridePrefix: namecoin.bech32!) + .data; + + final redeemScript = p2wpkh.output; + + final data = + P2SH(data: PaymentData(redeem: p2wpkh), network: _network) + .data; + + for (String tx in addressTxid[addressesP2SH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + changeDerivation["wif"] as String, + network: _network, + ), + "redeemScript": redeemScript, + }; + } + } + } + } + } + + // p2wpkh / bip84 + final p2wpkhLength = addressesP2WPKH.length; + if (p2wpkhLength > 0) { + final receiveDerivations = await _fetchDerivations( + chain: 0, + derivePathType: DerivePathType.bip84, + ); + final changeDerivations = await _fetchDerivations( + chain: 1, + derivePathType: DerivePathType.bip84, + ); + + for (int i = 0; i < p2wpkhLength; i++) { + // receives + final receiveDerivation = receiveDerivations[addressesP2WPKH[i]]; + // if a match exists it will not be null + if (receiveDerivation != null) { + final data = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + receiveDerivation["pubKey"] as String)), + network: _network, + overridePrefix: namecoin.bech32!) + .data; + + for (String tx in addressTxid[addressesP2WPKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + receiveDerivation["wif"] as String, + network: _network, + ), + }; + } + } else { + // if its not a receive, check change + final changeDerivation = changeDerivations[addressesP2WPKH[i]]; + // if a match exists it will not be null + if (changeDerivation != null) { + final data = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List( + changeDerivation["pubKey"] as String)), + network: _network, + overridePrefix: namecoin.bech32!) + .data; + + for (String tx in addressTxid[addressesP2WPKH[i]]!) { + results[tx] = { + "output": data.output, + "keyPair": ECPair.fromWIF( + changeDerivation["wif"] as String, + network: _network, + ), + }; + } + } + } + } + } + return results; } catch (e, s) { Logging.instance @@ -2677,18 +3262,18 @@ class NamecoinWallet extends CoinServiceAPI { .log("Starting buildTransaction ----------", level: LogLevel.Info); final txb = TransactionBuilder(network: _network); - txb.setVersion(1); + txb.setVersion(2); // Add transaction inputs for (var i = 0; i < utxosToUse.length; i++) { final txid = utxosToUse[i].txid; txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List); + utxoSigningData[txid]["output"] as Uint8List, namecoin.bech32!); } // Add transaction output for (var i = 0; i < recipients.length; i++) { - txb.addOutput(recipients[i], satoshiAmounts[i]); + txb.addOutput(recipients[i], satoshiAmounts[i], namecoin.bech32!); } try { @@ -2696,11 +3281,11 @@ class NamecoinWallet extends CoinServiceAPI { for (var i = 0; i < utxosToUse.length; i++) { final txid = utxosToUse[i].txid; txb.sign( - vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, - ); + vin: i, + keyPair: utxoSigningData[txid]["keyPair"] as ECPair, + witnessValue: utxosToUse[i].value, + redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + overridePrefix: namecoin.bech32!); } } catch (e, s) { Logging.instance.log("Caught exception while signing transaction: $e\n$s", @@ -2708,7 +3293,7 @@ class NamecoinWallet extends CoinServiceAPI { rethrow; } - final builtTx = txb.build(); + final builtTx = txb.build(namecoin.bech32!); final vSize = builtTx.virtualSize(); return {"hex": builtTx.toHex(), "vSize": vSize}; @@ -2730,7 +3315,7 @@ class NamecoinWallet extends CoinServiceAPI { ); // clear cache - _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data await _rescanBackup(); @@ -2809,6 +3394,72 @@ class NamecoinWallet extends CoinServiceAPI { 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"); @@ -2826,6 +3477,40 @@ class NamecoinWallet extends CoinServiceAPI { 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'); @@ -2878,6 +3563,80 @@ class NamecoinWallet extends CoinServiceAPI { 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"); @@ -2894,6 +3653,38 @@ class NamecoinWallet extends CoinServiceAPI { 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'); @@ -2905,27 +3696,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance.log("rescan backup complete", level: LogLevel.Info); } - @override - set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); - } - - @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; - } - } - - @override - bool get isRefreshing => refreshMutex; - bool isActive = false; @override @@ -2974,9 +3744,9 @@ class NamecoinWallet extends CoinServiceAPI { } } - // TODO: correct formula for nmc? + // TODO: Check if this is the correct formula for namecoin int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((181 * inputCount) + (34 * outputCount) + 10) * + return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * (feeRatePerKB / 1000).ceil(); } @@ -2996,24 +3766,25 @@ class NamecoinWallet extends CoinServiceAPI { return available - estimatedFee; } + @override Future generateNewAddress() async { try { await _incrementAddressIndexForChain( - 0, DerivePathType.bip44); // First increment the receiving index + 0, DerivePathType.bip84); // First increment the receiving index final newReceivingIndex = DB.instance.get( boxName: walletId, - key: 'receivingIndexP2PKH') as int; // Check the new receiving index + key: 'receivingIndexP2WPKH') as int; // Check the new receiving index final newReceivingAddress = await _generateAddressForChain( 0, newReceivingIndex, DerivePathType - .bip44); // Use new index to derive a new receiving address + .bip84); // Use new index to derive a new receiving address await _addToAddressesArrayForChain( newReceivingAddress, 0, DerivePathType - .bip44); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddressP2PKH = Future(() => + .bip84); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddress = Future(() => newReceivingAddress); // Set the new receiving address that the service return true; @@ -3029,7 +3800,7 @@ class NamecoinWallet extends CoinServiceAPI { // Namecoin Network final namecoin = NetworkType( messagePrefix: '\x18Namecoin Signed Message:\n', - bech32: 'bc', + bech32: 'nc', bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), pubKeyHash: 0x34, //From 52 scriptHash: 0x0d, //13 diff --git a/pubspec.yaml b/pubspec.yaml index 21bea3968..463d3d28a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: bitcoindart: git: url: https://github.com/cypherstack/bitcoindart.git - ref: a35968c2d2d900e77baa9f8b28c89b722c074039 + ref: 65eb920719c8f7895c5402a07497647e7fc4b346 stack_wallet_backup: git: @@ -74,7 +74,10 @@ dependencies: url: https://github.com/Quppy/bitbox-flutter.git ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 bip32: ^2.0.0 - bech32: ^0.2.1 + bech32: + git: + url: https://github.com/cypherstack/bech32.git + ref: 22279d4bb24ed541b431acd269a1bc50af0f36a0 bs58check: ^1.0.2 # Storage plugins From e065044476a121309cd1049b7e26b1b0a0dcebc6 Mon Sep 17 00:00:00 2001 From: Likho Date: Tue, 13 Sep 2022 20:00:43 +0200 Subject: [PATCH 13/20] Add namecoin image --- assets/images/namecoin.png | Bin 0 -> 359452 bytes lib/utilities/assets.dart | 2 +- pubspec.yaml | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 assets/images/namecoin.png diff --git a/assets/images/namecoin.png b/assets/images/namecoin.png new file mode 100644 index 0000000000000000000000000000000000000000..45cf8abb78caeec2a1ecc9ef6b19102064c3c124 GIT binary patch literal 359452 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

>FUMoBWl_b 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�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

  • k}s&PJfEmSU(U;PDo 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ⅈSF70F6P$1Se#=<5m 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 ^U3|D69<_bHN)uux@_jK9Olpbocy#Kdt=I6uLET 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 "assets/images/bitcoin.png"; String get epicCash => "assets/images/epic-cash.png"; String get bitcoincash => "assets/images/bitcoincash.png"; - String get namecoin => "assets/images/bitcoincash.png"; + String get namecoin => "assets/images/namecoin.png"; String imageFor({required Coin coin}) { switch (coin) { diff --git a/pubspec.yaml b/pubspec.yaml index 463d3d28a..01429ea50 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -189,6 +189,7 @@ flutter: - assets/images/bitcoin.png - assets/images/epic-cash.png - assets/images/bitcoincash.png + - assets/images/namecoin.png - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg From c5ff6dfed5a8fa15aa5c0230d1fbc08460c2a8d1 Mon Sep 17 00:00:00 2001 From: Likho Date: Tue, 13 Sep 2022 20:18:45 +0200 Subject: [PATCH 14/20] Fix: Test sending and receiving --- lib/services/coins/namecoin/namecoin_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 7c6706aab..7ed8d6bfb 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -308,7 +308,7 @@ class NamecoinWallet extends CoinServiceAPI { throw ArgumentError('Invalid version or Network mismatch'); } else { try { - decodeBech32 = segwit.decode(address); + decodeBech32 = segwit.decode(address, namecoin.bech32!); } catch (err) { // Bech32 decode fail } From 6ea7ed195e07c27578ef7cd7051b6317b80026dc Mon Sep 17 00:00:00 2001 From: Likho Date: Wed, 14 Sep 2022 11:09:01 +0200 Subject: [PATCH 15/20] Add block explorer for NMC --- lib/utilities/block_explorers.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index c89f79432..78afe5f7f 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -25,6 +25,6 @@ Uri getBlockExplorerTransactionUrlFor({ case Coin.bitcoincash: return Uri.parse("https://blockchair.com/bitcoin-cash/transaction/$txid"); case Coin.namecoin: - return Uri.parse("uri"); + return Uri.parse("https://chainz.cryptoid.info/nmc/tx.dws?$txid.htm"); } } From 2f5650ce307aee8f74e1bdaae3b7b03800357794 Mon Sep 17 00:00:00 2001 From: Likho Date: Thu, 15 Sep 2022 21:41:12 +0200 Subject: [PATCH 16/20] WIP: BCH and NMC tests --- .../coins/bitcoincash/bitcoincash_wallet.dart | 8 +- .../coins/namecoin/namecoin_wallet.dart | 2 + .../bitcoincash_history_sample_data.dart | 120 + .../bitcoincash/bitcoincash_wallet_test.dart | 59 +- .../bitcoincash_wallet_test_parameters.dart | 13 + .../namecoin_history_sample_data.dart | 95 + .../namecoin_transaction_data_samples.dart | 355 ++ .../namecoin/namecoin_utxo_sample_data.dart | 58 + .../coins/namecoin/namecoin_wallet_test.dart | 4448 +++++++++++++++++ .../namecoin/namecoin_wallet_test.mocks.dart | 352 ++ .../namecoin_wallet_test_parameters.dart | 0 11 files changed, 5484 insertions(+), 26 deletions(-) create mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart create mode 100644 test/services/coins/namecoin/namecoin_history_sample_data.dart create mode 100644 test/services/coins/namecoin/namecoin_transaction_data_samples.dart create mode 100644 test/services/coins/namecoin/namecoin_utxo_sample_data.dart create mode 100644 test/services/coins/namecoin/namecoin_wallet_test.dart create mode 100644 test/services/coins/namecoin/namecoin_wallet_test.mocks.dart create mode 100644 test/services/coins/namecoin/namecoin_wallet_test_parameters.dart diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index e5da66fc8..89f44ad11 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1506,6 +1506,7 @@ class BitcoinCashWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); + print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1681,15 +1682,20 @@ class BitcoinCashWallet extends CoinServiceAPI { }) async { try { final Map> args = {}; + print("Address $addresses"); for (final entry in addresses.entries) { args[entry.key] = [_convertToScriptHash(entry.value, _network)]; } - final response = await electrumXClient.getBatchHistory(args: args); + 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; } + print("result ${jsonEncode(result)}"); return result; } catch (e, s) { Logging.instance.log( diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 7ed8d6bfb..379fda4e2 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1858,6 +1858,8 @@ class NamecoinWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = _convertToScriptHash(allAddresses[i], _network); + + print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); diff --git a/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart b/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart index e69de29bb..c34f68865 100644 --- a/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart +++ b/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart @@ -0,0 +1,120 @@ +final Map> historyBatchArgs0 = { + "k_0_0": ["4061323fc54ad0fd2fb6d3fd3af583068d7a733f562242a71e00ea7a82fb482b"], + "k_0_1": ["04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b"], + "k_0_2": ["a0345933dd4146905a279f9aa35c867599fec2c52993a8f5da3a477acd0ebcfc"], + "k_0_3": ["607bc74daf946bfd9d593606f4393e44555a3dd0b529ddd08a0422be7955912e"], + "k_0_4": ["449dfb82e6f09f7e190f21fe63aaad5ccb854ba1f44f0a6622f6d71fff19fc63"], + "k_0_5": ["3643e3fe26e0b08dcbc89c47efce3b3264f361160341e3c2a6c73681dde12d39"], + "k_0_6": ["6daca5039b35adcbe62441b68eaaa48e9b0a806ab5a34314bd394b9b5c9289e5"], + "k_0_7": ["113f3d214f202795fdc3dccc6942395812270e787abb88fe4ddfa14f33d62d6f"], + "k_0_8": ["5dea575b85959647509d2ab3c92cda3776a4deba444486a7925ae3b71306e7e3"], + "k_0_9": ["5e2e6d3b43dfa29fabf66879d9ba67e4bb2f9f7ed10cfbb75e0b445eb4b84287"], + "k_0_10": [ + "1bfe42869b6b1e5efa1e1b47f382615e3d27e3e66e9cc8ae46b71ece067b4d37" + ], + "k_0_11": ["e0b38e944c5343e67c807a334fcf4b6563a6311447c99a105a0cf2cc3594ad11"] +}; + +final Map> historyBatchArgs1 = { + "k_0_0": ["4061323fc54ad0fd2fb6d3fd3af583068d7a733f562242a71e00ea7a82fb482b"], + "k_0_1": ["04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b"], + "k_0_2": ["a0345933dd4146905a279f9aa35c867599fec2c52993a8f5da3a477acd0ebcfc"], + "k_0_3": ["607bc74daf946bfd9d593606f4393e44555a3dd0b529ddd08a0422be7955912e"], + "k_0_4": ["449dfb82e6f09f7e190f21fe63aaad5ccb854ba1f44f0a6622f6d71fff19fc63"], + "k_0_5": ["3643e3fe26e0b08dcbc89c47efce3b3264f361160341e3c2a6c73681dde12d39"], + "k_0_6": ["6daca5039b35adcbe62441b68eaaa48e9b0a806ab5a34314bd394b9b5c9289e5"], + "k_0_7": ["113f3d214f202795fdc3dccc6942395812270e787abb88fe4ddfa14f33d62d6f"], + "k_0_8": ["5dea575b85959647509d2ab3c92cda3776a4deba444486a7925ae3b71306e7e3"], + "k_0_9": ["5e2e6d3b43dfa29fabf66879d9ba67e4bb2f9f7ed10cfbb75e0b445eb4b84287"], + "k_0_10": [ + "1bfe42869b6b1e5efa1e1b47f382615e3d27e3e66e9cc8ae46b71ece067b4d37" + ], + "k_0_11": ["e0b38e944c5343e67c807a334fcf4b6563a6311447c99a105a0cf2cc3594ad11"] +}; + +final Map>> historyBatchResponse = { + "k_0_0": [], + "s_0_0": [{}, {}], + "w_0_0": [], + "k_0_1": [{}], + "s_0_1": [], + "w_0_1": [{}, {}, {}], + "k_0_2": [], + "s_0_2": [], + "w_0_2": [], + "k_0_3": [], + "s_0_3": [], + "w_0_3": [], + "k_0_4": [], + "s_0_4": [], + "w_0_4": [], + "k_0_5": [], + "s_0_5": [], + "w_0_5": [], + "k_0_6": [], + "s_0_6": [], + "w_0_6": [], + "k_0_7": [], + "s_0_7": [], + "w_0_7": [], + "k_0_8": [], + "s_0_8": [], + "w_0_8": [], + "k_0_9": [], + "s_0_9": [], + "w_0_9": [], + "k_0_10": [], + "s_0_10": [], + "w_0_10": [], + "k_0_11": [], + "s_0_11": [], + "w_0_11": [] +}; + +final Map>> emptyHistoryBatchResponse = { + "k_0_0": [], + "s_0_0": [], + "w_0_0": [], + "k_0_1": [], + "s_0_1": [], + "w_0_1": [], + "k_0_2": [], + "s_0_2": [], + "w_0_2": [], + "k_0_3": [], + "s_0_3": [], + "w_0_3": [], + "k_0_4": [], + "s_0_4": [], + "w_0_4": [], + "k_0_5": [], + "s_0_5": [], + "w_0_5": [], + "k_0_6": [], + "s_0_6": [], + "w_0_6": [], + "k_0_7": [], + "s_0_7": [], + "w_0_7": [], + "k_0_8": [], + "s_0_8": [], + "w_0_8": [], + "k_0_9": [], + "s_0_9": [], + "w_0_9": [], + "k_0_10": [], + "s_0_10": [], + "w_0_10": [], + "k_0_11": [], + "s_0_11": [], + "w_0_11": [] +}; + +final List activeScriptHashes = [ + "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75", + "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12", + "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63", + "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f", + "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c", + "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b", +]; diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index 0e39892b1..a9b402f60 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -16,6 +18,7 @@ import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/prefs.dart'; // import '../../../cached_electrumx_test.mocks.dart'; // import '../../../screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart'; @@ -845,15 +848,15 @@ void main() { await bch?.initializeExisting(); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincashtestnet), + await bch!.currentReceivingAddress, bitcoincash), true); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincashtestnet), + await bch!.currentReceivingAddress, bitcoincash), true); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincashtestnet), + await bch!.currentReceivingAddress, bitcoincash), true); verifyNever(client?.ping()).called(0); @@ -896,8 +899,7 @@ void main() { expect(addresses?.length, 2); for (int i = 0; i < 2; i++) { - expect( - Address.validateAddress(addresses![i], bitcoincashtestnet), true); + expect(Address.validateAddress(addresses![i], bitcoincash), true); } verifyNever(client?.ping()).called(0); @@ -1833,8 +1835,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); @@ -1902,7 +1904,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .called(1); @@ -1927,6 +1929,13 @@ void main() { "hash_function": "sha256", "services": [] }); + + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((thing) async { + // print(jsonEncode(thing.namedArguments.entries.first.value)); + // return {}; + // }); + when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => emptyHistoryBatchResponse); when(client?.getBatchHistory(args: historyBatchArgs1)) @@ -1942,7 +1951,7 @@ void main() { height: 4000); expect(await bch?.mnemonic, TEST_MNEMONIC.split(" ")); - + // verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); @@ -1993,7 +2002,7 @@ void main() { bch = BitcoinCashWallet( walletId: testWalletId, walletName: testWalletName, - coin: Coin.bitcoincashTestNet, + coin: Coin.bitcoincash, client: client!, cachedClient: cachedClient!, tracker: tracker!, @@ -2082,8 +2091,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); final wallet = await Hive.openBox(testWalletId); @@ -2101,7 +2110,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); expect(secureStore?.interactions, 6); expect(secureStore?.writes, 3); @@ -2127,8 +2136,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); @@ -2196,7 +2205,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .called(1); @@ -2224,8 +2233,8 @@ void main() { when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); @@ -2285,7 +2294,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .called(1); @@ -2779,8 +2788,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); final wallet = await Hive.openBox(testWalletId); @@ -2797,7 +2806,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); expect(secureStore?.interactions, 6); expect(secureStore?.writes, 3); @@ -2824,8 +2833,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); when(client?.getHistory(scripthash: anyNamed("scripthash"))) .thenThrow(Exception("some exception")); @@ -2842,7 +2851,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); verify(client?.getBlockHeadTip()).called(1); verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart new file mode 100644 index 000000000..c47053955 --- /dev/null +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart @@ -0,0 +1,13 @@ +const TEST_MNEMONIC = + "market smart dolphin hero liberty frog bubble tilt river electric ensure orient"; + +const ROOT_WIF = "KxpP3c45bRPbPzecYfistA5u3Z4SSGCCiCS4pjWrS5LF1g4mXC66"; +const NODE_WIF_44 = "KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o"; +const NODE_WIF_49 = "L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg"; +const NODE_WIF_84 = "cU1E2J1eHbQh36TCtsqneAsKaXRwRhSs74GEDPXWCGY7f1xpsymM"; + +const FILL_ADDRESSES_EXPECTED_CHANGE_MAINNET = + "{0: {p2pkh: {publicKey: 02a1a969c63ed41662a23afc640f76ee4a53c35fba4a13ff871931849abdc0670c, wif: Kws4oRj4Z3uT4WNejbuLsWvqTeso4dGciVu75jjd5fa36Tst48ER, address: 1JfZd6cSNr924p4ZwewG1CeRw7AqoYQ6uE}, p2sh: {publicKey: 020cebf05d41da01c00fbb7f7c04cf85741188da0e5ee22ae9fac6e7b5098b8cac, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3ANTVqufTH1tLAuoQHhng8jndRsA9hcNy7}, p2wpkh: {publicKey: 0301def02c073852fc8c28182749be431ac22a2699b51c896fbc48457107c10c65, wif: L3UwhkWiXSAyCWYVjaFCyMhGcqeDU8soCmqhuFYFadcVQepKKcjy, address: bc1qwt76574cgnhjv0nx4f52qylyla0t50d60znk5u}}, 1: {p2pkh: {publicKey: 02617b5f7868bd0402a3e3ff6fa224aede55f13218bbef9987467455d068d629cd, wif: KzDwZXPyNhPix2nV5wri4QaGj6Tge7znvf49D5tgL7EvAnYV6Pas, address: 1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U}, p2sh: {publicKey: 03b4c1e17b7257850fd2c0cf69acd397616835de29867cfe047bf1064b9803f773, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3LNBen8sNa6kXsWuMc6tynHBQr3vUHn3nL}, p2wpkh: {publicKey: 038b646c426ed75204bd92f55f0519fb89c41a503844aecf187a55eb00a5d8b5eb, wif: L1jQHTyFP4sNyqdKeooJzw3AUghPeSmGoW7UmZwDsS1Js4UxiGyh, address: bc1qn2x7h96kufgfjxtkhsnq03jqwqde8zasffqvd2}}, 2: {p2pkh: {publicKey: 031643235b0355ac366ade3ec125e466be42602a6167ba1cc68f39c38929f3c785, wif: KzqPH523RyAKwzRDg8PqjvNVDV8Fex2qrKxwwLGcvc9XZHfvaErb, address: 1EwEMkfeivF8nEkG3tWhBSqibnDjoREBoF}, p2sh: {publicKey: 02312b44be7de07c21bed9bd8b0c2f3d64be41c5f5120a463fcb85371b3f44cce1, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3MPyLcCkGPKyZULKSzkYDZQxxMmG8HNRPi}, p2wpkh: {publicKey: 031a10a60de8563b1be55e8274538d6ec6375d19764e81303999de2605634ea15a, wif: L2u4AYxEKkDamcXjAfXviuTPuRuWe1aN9zXEX1PSpzrmqGtSHQqe, address: bc1qpcn5wd2cx7syc28r0t4w4ym8yy6fck87nrxn4p}}, 3: {p2pkh: {publicKey: 02f552c7b15e90df9ff99f35e8b5bd84eff0bdef6cb6c75e13d1df81d334c6b786, wif: Kxi7qVxEqwaBhR3tuewpfi8EDqqR7fBzgQUDambVWGPEP3oG9JUM, address: 1MifgAa6CzqmTF4euVSD2DivD3xUGDbuA5}, p2sh: {publicKey: 02a7ba8279be4c182292b855cbf4349dba68f8a776b9b8e04e05585ac9f605e7f1, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 34pa7nXRZ3XahoFKhhnaLy4DqzfC9v5aSm}, p2wpkh: {publicKey: 03d712c240ccd578b8fb59acf88a8fbacf2ab14301a900e7907c739244e61029bd, wif: L3ezWHMUvdwprhChDVn7iVV8otpLUBrdkZB7X97GEbiVZbxJkDPc, address: bc1qemkha74vvmlz8yg56tesswpz77wg07m9wu23sc}}, 4: {p2pkh: {publicKey: 03d86c453c6b8ef6239db8fc270c76a9b08faf2c58f3de74cbe2cb0bc0cef7139a, wif: KzNpx8vgcNbYcqPZxHFwvKSRwPpyZNhmF4Eqa4k1wuWJZovwaXNC, address: 1BxYVw7u85QWVoVCHJAJkCCZZjWeXYW6EA}, p2sh: {publicKey: 020e9095a31b9c1e580b3773d34d1f752f9c4de42feb1c3480f5234a2392762fbc, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 33tciSXesiShRULW4bds5BE8358GsJc8CA}, p2wpkh: {publicKey: 02908673d298d4929a724ff9627c3fc710e27f0d727fbb0f2741c00dac1d792120, wif: L5QYEMxjY42c9JnpEQCDUbqCUfZ1vKoELe41rREuGzszcZgzVepx, address: bc1qpl7rh3ykw5h9823wv3uw0jrehlty6dfjp65ykt}}, 5: {p2pkh: {publicKey: 0368fda0552db99cfac045f57327cecd9b365d8813982dda051f1156ceefacd80b, wif: Ky8KYn2vMmWiMw6QEnE6JsM3uwQgAZBjiUHB938MVtnSgpiDjh7o, address: 14g637PwocgKr3S949WDitY7Bj3L1Bttcb}, p2sh: {publicKey: 03a1c4977ec9e9e02ba011cf56cee34c1617a7e05e85183a4207efd6425079a914, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 34Z5gAgPiUsfA9VHdAss2AD5h5Xk5TEp9K}, p2wpkh: {publicKey: 028c44701f2e918ba9853737aa1c6c7b5302a4c9198bf0eb22a75fb6fd3ef4dc9e, wif: Kx7wWb9xvNTaiWkzEmLqJ7gsQEaD6VPJTodr71rQwtpCU5dZDbEP, address: bc1q0ja83w4jwplrxjggcy8ltnrz70qrhnsmfwjv47}}, 6: {p2pkh: {publicKey: 03844bb44c430540dd2fd42630200a059f06480e024b7a06ea628b5094f48b944f, wif: Kwe6hMa7t9qcWW7fHBFTpBDDyzBdWzkHjhngiTLRKZzBjUqqppRJ, address: 1JNghjiaNWrWgvAJnmGyyFEbQMWV35QT1D}, p2sh: {publicKey: 03add39947c251a1f42f2fe4dbf41ea6dfed2bcf56bbe5ab6c44213235b68a1d0a, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3AnduCZuFcXFtBjnz7Y6vRJBwYvfeNKJtR}, p2wpkh: {publicKey: 02883642dad55a3f5003440ff7e066c97624539841643e900db80eaac7ebab7270, wif: L4sisqYU1xSzbnx25xuyLefifGL5BoGDXh5Vsex9UCBSDHhFYt8h, address: bc1qdjjssjd6vl6e3egqts7qxcj6dqrs6krgj4dv3q}}, 7: {p2pkh: {publicKey: 03487700edb11cf39b095b17da1f44f702398bd707dfdace4d88ee0227a48c8a0c, wif: L3UmMi9h8RQ8Y8jKATFjum77kc9ng6Jr71fq1H9rGxxdspKGXt12, address: 1Q1FMnBJxQ5uh2UDQbvwQ1y1KMpqVd3whq}, p2sh: {publicKey: 03a600d4880ce8a53287f25b6534f97a689ba7006f665ad1fe3e2bb8d3c9620ec5, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 3J9GqZ2b5raBNyGmuBg8ttyg5x8zaqSrMp}, p2wpkh: {publicKey: 035b91a2c439d6cc34a706449b8d9843721cc499aec4b38ebdd211978a52431fc0, wif: KzJZE96Hjwn2dtuYpBgMoR9s86TgQ3apRsNTxGZTpyW2RVA6HppV, address: bc1qs2df496wpp4339qjuypc2l2r6e0z7vndz70ac7}}, 8: {p2pkh: {publicKey: 0254ff6bad342ae9af2e04ee9d876682771ad86a76336a817eadb2e4e5bf68070a, wif: KxhFsZNv8XDUxLECR9y965eWFSGBbc2FrTkRvuyRPwPM9xxeCfV4, address: 1JD8uzAKQo9DgR2om6gS98S92cgqscZ3Ur}, p2sh: {publicKey: 02f574d6a16300e4d497647814fd2b0fa2eaba32af19fcf65821f70743bc566f9d, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3BqGdLjubq2vAGRDNRkxk5uMqd6FPoMg7N}, p2wpkh: {publicKey: 0227cb798c29365364027e5df67f6890985fd89ecbca571e51bb6260272cc66ec1, wif: Kxrpxu5kNAdy9jqz7etcCg716gsEk1K5weiMLKg1fhLPdxitvzsy, address: bc1qhpvcc08dxa7m5ztkxsfsplmj69yzsg6p3dna63}}, 9: {p2pkh: {publicKey: 02b8b1a447c71bb4ed82fd3719ec9e665df5c9c6e31c7a14beb56c65f355a08bc4, wif: Ky9kWxSwqBQP9ShkZNCfLwDqpzqnDEvQG3NjfYY3A3VFE4nPLqrs, address: 16EpYJQoM1K2ZKFUEMBAJubiNsMghkvBUx}, p2sh: {publicKey: 03f6cc36b17f382490d1a08bc9f7c28302c704c69cb6c153d2828710cd93a1c215, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3HBjEYyPH9H8JvuVbibNaZ2bJyD8564r1Y}, p2wpkh: {publicKey: 030f021f31d9eac4279f8ae12a512885769978dc93a74e0e4c91eba994a7d02cda, wif: L26bxcZZ8PLaa23X9myLgzh3hAYxjQ9MsSfiz49yiEdys7dzr66z, address: bc1q6hxsggrtcrvcdxpzu7e8qv2m9md3zaghjjsxut}}}"; + +const FILL_ADDRESSES_EXPECTED_RECEIVING_MAINNET = + "{0: {p2pkh: {publicKey: 0359bc5f4918d68ab3730886c4fdebec1cfdeba9db1170582da7072183d7afce99, wif: KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o, address: 1GDtrDP62zQETQESHt48Z82afXWcvX8qNv}, p2sh: {publicKey: 02addc76ef5845c0e74e5ada1d97568d0ee76856031e60a084f6f6a0e7be51d84b, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk}, p2wpkh: {publicKey: 02e5b585efaec9e6486f4b8afb3415ffd71a89566c1f84bb5331c4cddc905b7555, wif: KzCvDQSH6qqUFhPnbM42zbLK3qPeHXKVVpc6A5bAqADanHQFqypQ, address: bc1qz36w7sv4lnt09saurf94lwk5tc6qsyhky9yfgm}}, 1: {p2pkh: {publicKey: 036070efb466bfbf689efe1e7a27fa405bdbb16fa4c836f7b71feeb7ec9f5c5db4, wif: L3aA8PRqcj1iuZnQMJ3M5x68zhywgwgGbzPtDsQMBWRmqUaKyyBQ, address: 1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw}, p2sh: {publicKey: 02b8f46d7741d709bc1dc81386edaf4e6dad8b78b1fb9ec0c5b1f8e08e7d72f395, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3Ggn4xZmXjMCDUdQSTjxTi1PdVrtDGUy5Z}, p2wpkh: {publicKey: 03627f4374d7c992c40d743786028f94b1e1ca354b94ffa776b51ba50c80fba1d7, wif: L5gAb3ABSYTBTL85FhoRxHa7dJMtz9sVLHWkEY7kYKXT2cDg71Ym, address: bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0}}, 2: {p2pkh: {publicKey: 03f2171f073a8fbf5b844a20e5b6ae356a07e9e6dc5af24cd4b3e54a1599eb4137, wif: L27AJt6ZRU4D3sf7Ls1HLq2ruF8c6iYq9iJVvW8Xm2kMKrKcPwzZ, address: 1Mu5SqaUBHB1HkyvEtjHjrNQVaai2MoFFz}, p2sh: {publicKey: 024e6d7230df59dc28f94ab7bd45c5aed53ac0ea96e8a1ee46edcca5594fa3ec66, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3K8CQxXWp2kjbGcnub4sh935H5s1AfrFmp}, p2wpkh: {publicKey: 0365b55bdae2d0ada31bfc73371f7106e0e20563dff0763c48da8ce2ca70346bd8, wif: KxttkPfRdY9uwEqgHGPo9d7Bb3qTsBzHSgmv8EVTS2Gbh88GgJZQ, address: bc1qsegc09enu2ts4dg7lnxee6tv8fy78m89cfzdy9}}, 3: {p2pkh: {publicKey: 020d00dd73194f8f087a3740594f2eeaa545224fb634462e06d281ad5759e77cbd, wif: L5Mdvj7tkY4Sx5JTiB1WtQJPogqXGLunaFAubAQMvuAK3TjuXXxD, address: 16d7AeqhmspaMyeJKF3cDwxjkLUTFZQdTn}, p2sh: {publicKey: 038d2aec8c0da1bafc3e51fe7fdd3aa60f121983071d97ed754c05f24c54d7d09b, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 3QwDpsqwb5VfKQVUVLzaDwgC51pea1ymHr}, p2wpkh: {publicKey: 02f144cd8c1d6966db09da86bf662933480ad6e7f2862f7878780f21bc1448d563, wif: Ky9zeEcFB7td3dPYCiXxrMPCQkKyfm4feaWp5PAWxY7kBwCccUxz, address: bc1q3h5llpmhvr89el03dktd936jqsn7t6pytr2nlr}}, 4: {p2pkh: {publicKey: 038d6142ee0db16d4ff23c95d1c157428d48e11fae7752ddacaf6cccce6db61fc6, wif: Ky41iFhmo2Y5mYp1UALsYqRhhKajEy7AxLJcSDYRVdwW7iv3vmTK, address: 1EFyHH7G4Hk8aCjGWPer1Qc9e8iZsbfZC8}, p2sh: {publicKey: 03a1624a73009c17ceefe299ca998ee941b501692b2f3a032e037b50726415c78b, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 38YQK65m9A278nCbWKTx3EJtyK9Vega1Z9}, p2wpkh: {publicKey: 031e816e4a35c686ee30387e01c26341927806674986a57ee5755114f3ee59d560, wif: L5AiWbctHW9EyaD5vyzCrxKmKppdMwdRFTvB1TUKLWvibo5R3QXa, address: bc1qj69ku48uu6lqu6uhd62a37zvuwta2dlc28frxc}}, 5: {p2pkh: {publicKey: 024697dbd5ad644c285fb9ce25a38d0f8b48de9dfbda146eba8f5659184fb2779c, wif: L3yze1iu58vmF7GyoUHqnmYCm4qLsBoGiyRgBPbt68gB3kKnQfCv, address: 16ZbDHYV97o6xeXoQMWus1a8NMNyxqHZon}, p2sh: {publicKey: 021b442ef944676b70e6ef23ecb932c015b6934ff5d094db88820735be36f6d807, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 3LFx71JddSzs8qE51P4JHHjxsgiSTZdk4z}, p2wpkh: {publicKey: 0307ffe6b1c0dfca40b6126140691a4d907a053dcfd97b816cc7ff86775486e612, wif: L3F1KPjtaA4uyfcmMSH2sFWs2TW2aX6Lcn8vetrdqtwgKgvfvVGg, address: bc1ql66zvg82lk425g8j4jx5flt255n8a9up328z3l}}, 6: {p2pkh: {publicKey: 02a5087e407ad853720a7010c1870bbc35a9ee09d90777c617801c983aaccc8523, wif: L1fLMxyhSKSsLskB3iBcfbfCinVr6MRE9YYVKXfCbLjPLZLAUpb7, address: 1FzukoiU7vXb7invLNLCCLAFdfK2gmP45A}, p2sh: {publicKey: 02d4f8bc88178a215b5c95e079b7c1ff0b62ec2884d5ffa71122683557a16615f3, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3GpAXutnaxCYuV2MSgSiXQTSafE7Aouifs}, p2wpkh: {publicKey: 03f7eb3138cbaa7baabdef7b903ff1a3369a8d7937c15ae17dac7084aa4cfd8afe, wif: KyWW73wu11xVnmFKMzq9AuxQ3TXw2KjwCzxUa9WbYx2Ee5oh2Nk1, address: bc1q4ev5cas22cn8rna3a7je238p3plgqqdjv9xt0e}}, 7: {p2pkh: {publicKey: 0366efa2d1624fd9ff0d9feffa39ff47c10ef196fe209421dcebf5d07dcb8907ce, wif: KwxWpA4scQ7RVKDFKjXvKWn6PdVXpr6nzW9T6ju6VP3MmjBRb5eK, address: 13qhwGjBzshL724ZZNv2C2XmUNpFoDLx4Z}, p2sh: {publicKey: 03b08cabbcaa97c20bf30e0830c8c7a2361ba63d1ba987032ee7cd97d666b62692, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 39ZZhvQJGwohf2ndBGXYWW31t1E91j9wcB}, p2wpkh: {publicKey: 03a353139ce07e31b33f1c5b0ece6f08ac117339a1a56aa1b3af011216cf2cbf88, wif: KwL9LaYjNvwWiYgDiUBLnVW7W5j2FyWqmzrioTA2QRgwdz6bUTQH, address: bc1q96wmh0qc2lwauzxv7la206d03zysc9zfw3ajkd}}, 8: {p2pkh: {publicKey: 032234e3903ebcd72c081119fe4b4c2d98e3fd3940b5b6cd7737f5f4d3240dcb7d, wif: L5kwyvViXwBcFqzJL5cffcJR7S1vEHxHuKHAQrAq6FtknvJG4Cx3, address: 1LPGD9sExsBMTLGxnTTEDYBHrtRSZCKNrN}, p2sh: {publicKey: 02c1ca19626e4784b2ce19b68d2887e4c9c733176db189e536b71a79c07178b4af, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3FXRAzFAgVMDcqbv6WE9Ki7yBACrRAh2Wn}, p2wpkh: {publicKey: 020fd4e2cd43f98366dd60fcc8902564c532c9396c35ebb840b63c965deed16330, wif: L4EgRsvLsju7uVXsTEchYnGdwmFrxhbE9SkMuvnaMDSMxmopNg8C, address: bc1q354ttmgwjzjf7jmym2aw9p5geeq9lhk8vcffap}}, 9: {p2pkh: {publicKey: 03dd5e2f3b828bdc2653ce7a7ce34ebfa8118077b647bdf4922c5d15f4546dc419, wif: Ky4f8LEYkXMkLiYX4t2PR6V2vJigyCoYTYATu4B5pnjDDywUD2U7, address: 13GXUQDr91Z5prRxAQQFHUvKNqs2hEWrkJ}, p2sh: {publicKey: 034a4cf3a57083ade346ecaab24dc931e2a991107c7c42cf170d1c6e3b84560e68, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3FstRy5yCE7a3wCNS8SgFKi4SF3zWK8vB6}, p2wpkh: {publicKey: 020cb048fc44a2db09be875ccd938634c557b19e2062fbc55372783be1116737e7, wif: KyDq3LHf9NkUWChPxVEAV3dQkX89GJWUkSw8cSVLooXronEwKouy, address: bc1qn99xewnyhqcleyj03f2y0crdhqmxhm5cmextw8}}}"; diff --git a/test/services/coins/namecoin/namecoin_history_sample_data.dart b/test/services/coins/namecoin/namecoin_history_sample_data.dart new file mode 100644 index 000000000..baa0535ec --- /dev/null +++ b/test/services/coins/namecoin/namecoin_history_sample_data.dart @@ -0,0 +1,95 @@ +final Map> historyBatchArgs0 = { + "k_0_0": ["bd84ae7e09414b0ccf5dcbf70a1f89f2fd42119a98af35dd4ecc80210fed0487"], + "k_0_1": ["3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6"] +}; +final Map> historyBatchArgs1 = { + "k_0_0": ["dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7"], + "k_0_1": ["71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d"] +}; +final Map> historyBatchArgs2 = { + "k_0_0": ["c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9"] +}; + +final Map>> historyBatchResponse = { + "k_0_0": [], + "s_0_0": [{}, {}], + "w_0_0": [], + "k_0_1": [{}], + "s_0_1": [], + "w_0_1": [{}, {}, {}], + "k_0_2": [], + "s_0_2": [], + "w_0_2": [], + "k_0_3": [], + "s_0_3": [], + "w_0_3": [], + "k_0_4": [], + "s_0_4": [], + "w_0_4": [], + "k_0_5": [], + "s_0_5": [], + "w_0_5": [], + "k_0_6": [], + "s_0_6": [], + "w_0_6": [], + "k_0_7": [], + "s_0_7": [], + "w_0_7": [], + "k_0_8": [], + "s_0_8": [], + "w_0_8": [], + "k_0_9": [], + "s_0_9": [], + "w_0_9": [], + "k_0_10": [], + "s_0_10": [], + "w_0_10": [], + "k_0_11": [], + "s_0_11": [], + "w_0_11": [] +}; + +final Map>> emptyHistoryBatchResponse = { + "k_0_0": [], + "s_0_0": [], + "w_0_0": [], + "k_0_1": [], + "s_0_1": [], + "w_0_1": [], + "k_0_2": [], + "s_0_2": [], + "w_0_2": [], + "k_0_3": [], + "s_0_3": [], + "w_0_3": [], + "k_0_4": [], + "s_0_4": [], + "w_0_4": [], + "k_0_5": [], + "s_0_5": [], + "w_0_5": [], + "k_0_6": [], + "s_0_6": [], + "w_0_6": [], + "k_0_7": [], + "s_0_7": [], + "w_0_7": [], + "k_0_8": [], + "s_0_8": [], + "w_0_8": [], + "k_0_9": [], + "s_0_9": [], + "w_0_9": [], + "k_0_10": [], + "s_0_10": [], + "w_0_10": [], + "k_0_11": [], + "s_0_11": [], + "w_0_11": [] +}; + +final List activeScriptHashes = [ + "83b744ccb88827d544081c1a03ea782a7d00d6224ff9fddb7d0fbad399e1cae7", + "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874", + "5baba32b1899d5e740838559ef39b7d8e9ba302bd24b732eeedd4c0e6ec65b51", +]; diff --git a/test/services/coins/namecoin/namecoin_transaction_data_samples.dart b/test/services/coins/namecoin/namecoin_transaction_data_samples.dart new file mode 100644 index 000000000..2199cfcc3 --- /dev/null +++ b/test/services/coins/namecoin/namecoin_transaction_data_samples.dart @@ -0,0 +1,355 @@ +import 'package:stackwallet/models/paymint/transactions_model.dart'; + +final transactionData = TransactionData.fromMap({ + "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6": tx1, + "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7": tx2, + "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d": tx3, + "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9": tx4, +}); + +final tx1 = Transaction( + txid: "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6", + confirmedStatus: true, + confirmations: 212, + txType: "Received", + amount: 1000000, + fees: 23896, + height: 629633, + address: "nc1qwfda4s9qmdqpnykgpjf85n09ath983srtuxcqx", + timestamp: 1663093275, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 2, + outputSize: 2, + inputs: [ + Input( + txid: "290904699ccbebd0921c4acc4f7a10f41141ee6a07bc64ebca5674c1e5ee8dfa", + vout: 1, + ), + Input( + txid: "bd84ae7e09414b0ccf5dcbf70a1f89f2fd42119a98af35dd4ecc80210fed0487", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "nc1qwfda4s9qmdqpnykgpjf85n09ath983srtuxcqx", + value: 1000000, + ), + Output( + scriptpubkeyAddress: "nc1qp7h7fxcnkqcpul202z6nh8yjy8jpt39jcpeapj", + value: 29853562, + ) + ], +); + +final tx2 = Transaction( + txid: "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + confirmedStatus: true, + confirmations: 150, + txType: "Sent", + amount: 988567, + fees: 11433, + height: 629695, + address: "nc1qraffwaq3cxngwp609e03ynwsx8ykgjnjve9f3y", + timestamp: 1663142110, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 1, + outputSize: 1, + inputs: [ + Input( + txid: "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "nc1qraffwaq3cxngwp609e03ynwsx8ykgjnjve9f3y", + value: 988567, + ), + ], +); + +final tx3 = Transaction( + txid: "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + confirmedStatus: true, + confirmations: 147, + txType: "Received", + amount: 988567, + fees: 11433, + height: 629699, + address: "nc1qw4srwqq2semrxje4x6zcrg53g07q0pr3yqv5kr", + timestamp: 1663145287, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 2, + outputSize: 1, + inputs: [ + Input( + txid: "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + vout: 0, + ), + Input( + txid: "80f8c6de5be2243013348219bbb7043a6d8d00ddc716baf6a69eab517f9a6fc1", + vout: 1, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "nc1qw4srwqq2semrxje4x6zcrg53g07q0pr3yqv5kr", + value: 1000000, + ), + Output( + scriptpubkeyAddress: "nc1qsgr7u4hd22rc64r9vlef69en9wzlvmjt8dzyrm", + value: 28805770, + ), + ], +); + +final tx4 = Transaction( + txid: "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", + confirmedStatus: true, + confirmations: 130, + txType: "Sent", + amount: 988567, + fees: 11433, + height: 629717, + address: "nc1qmdt0fxhpwx7x5ymmm9gvh229adu0kmtukfcsjk", + timestamp: 1663155739, + worthNow: "0.00", + worthAtBlockTimestamp: "0.00", + inputSize: 1, + outputSize: 1, + inputs: [ + Input( + txid: "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + vout: 0, + ), + ], + outputs: [ + Output( + scriptpubkeyAddress: "nc1qmdt0fxhpwx7x5ymmm9gvh229adu0kmtukfcsjk", + value: 988567, + ), + ], +); + +final tx1Raw = { + "txid": "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6", + "hash": "40c8dd876cf111dc00d3aa2fedc93a77c18b391931939d4f99a760226cbff675", + "version": 2, + "size": 394, + "vsize": 232, + "weight": 925, + "locktime": 0, + "vin": [ + { + "txid": + "290904699ccbebd0921c4acc4f7a10f41141ee6a07bc64ebca5674c1e5ee8dfa", + "vout": 1, + "scriptSig": { + "asm": "001466d2173325f3d379c6beb0a4949e937308edb152", + "hex": "16001466d2173325f3d379c6beb0a4949e937308edb152" + }, + "txinwitness": [ + "3044022062d0f32dc051ed1e91889a96070121c77d895f69d2ed5a307d8b320e0352186702206a0c2613e708e5ef8a935aba61b8fa14ddd6ca4e9a80a8b4ded126a879217dd101", + "0303cd92ed121ef22398826af055f3006769210e019f8fb43bd2f5556282d84997" + ], + "sequence": 4294967295 + }, + { + "txid": + "bd84ae7e09414b0ccf5dcbf70a1f89f2fd42119a98af35dd4ecc80210fed0487", + "vout": 0, + "scriptSig": {"asm": "", "hex": ""}, + "txinwitness": [ + "3045022100e8814706766a2d7588908c51209c3b7095241bbc681febdd6b317b7e9b6ea97502205c33c63e4d8a675c19122bfe0057afce2159e6bd86f2c9aced214de77099dc8b01", + "03c35212e3a4c0734735eccae9219987dc78d9cf6245ab247942d430d0a01d61be" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.01, + "n": 0, + "scriptPubKey": { + "asm": "0 725bdac0a0db401992c80c927a4de5eaee53c603", + "hex": "0014725bdac0a0db401992c80c927a4de5eaee53c603", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qwfda4s9qmdqpnykgpjf85n09ath983srtuxcqx"] + } + }, + { + "value": 0.29853562, + "n": 1, + "scriptPubKey": { + "asm": "0 0fafe49b13b0301e7d4f50b53b9c9221e415c4b2", + "hex": "00140fafe49b13b0301e7d4f50b53b9c9221e415c4b2", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qp7h7fxcnkqcpul202z6nh8yjy8jpt39jcpeapj"] + } + } + ], + "hex": + "02000000000102fa8deee5c17456caeb64bc076aee4111f4107a4fcc4a1c92d0ebcb9c69040929010000001716001466d2173325f3d379c6beb0a4949e937308edb152ffffffff8704ed0f2180cc4edd35af989a1142fdf2891f0af7cb5dcf0c4b41097eae84bd0000000000ffffffff0240420f0000000000160014725bdac0a0db401992c80c927a4de5eaee53c6037a87c701000000001600140fafe49b13b0301e7d4f50b53b9c9221e415c4b202473044022062d0f32dc051ed1e91889a96070121c77d895f69d2ed5a307d8b320e0352186702206a0c2613e708e5ef8a935aba61b8fa14ddd6ca4e9a80a8b4ded126a879217dd101210303cd92ed121ef22398826af055f3006769210e019f8fb43bd2f5556282d8499702483045022100e8814706766a2d7588908c51209c3b7095241bbc681febdd6b317b7e9b6ea97502205c33c63e4d8a675c19122bfe0057afce2159e6bd86f2c9aced214de77099dc8b012103c35212e3a4c0734735eccae9219987dc78d9cf6245ab247942d430d0a01d61be00000000", + "blockhash": + "c9f53cc7cbf654cbcc400e17b33e03a32706d6e6647ad7085c688540f980a378", + "confirmations": 212, + "time": 1663093275, + "blocktime": 1663093275 +}; + +final tx2Raw = { + "txid": "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + "hash": "32dbc0d21327e0cb94ec6069a8d235affd99689ffc5f68959bfb720bafc04bcf", + "version": 2, + "size": 192, + "vsize": 110, + "weight": 438, + "locktime": 0, + "vin": [ + { + "txid": + "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6", + "vout": 0, + "scriptSig": {"asm": "", "hex": ""}, + "txinwitness": [ + "30450221009d58ebfaab8eae297910bca93a7fd48f94ce52a1731cf27fb4c043368fa10e8d02207e88f5d868113d9567999793be0a5b752ad704d04224046839763cefe46463a501", + "02f6ca5274b59dfb014f6a0d690671964290dac7f97fe825f723204e6cb8daf086" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00988567, + "n": 0, + "scriptPubKey": { + "asm": "0 1f52977411c1a687074f2e5f124dd031c9644a72", + "hex": "00141f52977411c1a687074f2e5f124dd031c9644a72", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qraffwaq3cxngwp609e03ynwsx8ykgjnjve9f3y"] + } + } + ], + "hex": + "02000000000101c6ccf4ddc2a21434ed634636378923d01014b2d3b2f124999f3e7c88d043f53e0000000000ffffffff0197150f00000000001600141f52977411c1a687074f2e5f124dd031c9644a72024830450221009d58ebfaab8eae297910bca93a7fd48f94ce52a1731cf27fb4c043368fa10e8d02207e88f5d868113d9567999793be0a5b752ad704d04224046839763cefe46463a5012102f6ca5274b59dfb014f6a0d690671964290dac7f97fe825f723204e6cb8daf08600000000", + "blockhash": + "ae1129ee834853c45b9edbb7228497c7fa423d7d1bdec8fd155f9e3c429c84d3", + "confirmations": 150, + "time": 1663142110, + "blocktime": 1663142110 +}; + +final tx3Raw = { + "txid": "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + "hash": "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e", + "version": 2, + "size": 370, + "vsize": 208, + "weight": 832, + "locktime": 0, + "vin": [ + { + "txid": + "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + "vout": 0, + "scriptSig": {"asm": "", "hex": ""}, + "txinwitness": [ + "304402203535cf570aca7c1acfa6e8d2f43e0b188b76d0b7a75ffca448e6af953ffe8b6302202ea52b312aaaf6d615d722bd92535d1e8b25fa9584a8dbe34dfa1ea9c18105ca01", + "038b68078a95f73f8710e8464dec52c61f9e21675ddf69d4f61b93cc417cf73d74" + ], + "sequence": 4294967295 + }, + { + "txid": + "80f8c6de5be2243013348219bbb7043a6d8d00ddc716baf6a69eab517f9a6fc1", + "vout": 1, + "scriptSig": {"asm": "", "hex": ""}, + "txinwitness": [ + "3044022045268613674326251c46caeaf435081ca753e4ee2018d79480c4930ad7d5e19f022050090a9add82e7272b8206b9d369675e7e9a5f1396fc93490143f0053666102901", + "028e2ede901e69887cb80603c8e207839f61a477d59beff17705162a2045dd974e" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.01, + "n": 0, + "scriptPubKey": { + "asm": "0 756037000a8676334b35368581a29143fc078471", + "hex": "0014756037000a8676334b35368581a29143fc078471", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qw4srwqq2semrxje4x6zcrg53g07q0pr3yqv5kr"] + } + }, + { + "value": 0.2880577, + "n": 1, + "scriptPubKey": { + "asm": "0 8207ee56ed52878d546567f29d17332b85f66e4b", + "hex": "00148207ee56ed52878d546567f29d17332b85f66e4b", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qsgr7u4hd22rc64r9vlef69en9wzlvmjt8dzyrm"] + } + } + ], + "hex": + "02000000000102d7609f2ebf00afdc6b8cda9a5e92b4b9a0b8aaafadf890fbf99721854395fadf0000000000ffffffffc16f9a7f51ab9ea6f6ba16c7dd008d6d3a04b7bb198234133024e25bdec6f8800100000000ffffffff0240420f0000000000160014756037000a8676334b35368581a29143fc0784718a8ab701000000001600148207ee56ed52878d546567f29d17332b85f66e4b0247304402203535cf570aca7c1acfa6e8d2f43e0b188b76d0b7a75ffca448e6af953ffe8b6302202ea52b312aaaf6d615d722bd92535d1e8b25fa9584a8dbe34dfa1ea9c18105ca0121038b68078a95f73f8710e8464dec52c61f9e21675ddf69d4f61b93cc417cf73d7402473044022045268613674326251c46caeaf435081ca753e4ee2018d79480c4930ad7d5e19f022050090a9add82e7272b8206b9d369675e7e9a5f1396fc93490143f005366610290121028e2ede901e69887cb80603c8e207839f61a477d59beff17705162a2045dd974e00000000", + "blockhash": + "98f388ba99e3b6fc421c23edf3c699ada082b01e5a5d130af7550b7fa6184f2f", + "confirmations": 147, + "time": 1663145287, + "blocktime": 1663145287 +}; + +final tx4Raw = { + "txid": "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", + "hash": "c6b544ddd7d901fcc7218208a6cfc8e1819c403a22cc8a1f1a7029aafa427925", + "version": 2, + "size": 192, + "vsize": 110, + "weight": 438, + "locktime": 0, + "vin": [ + { + "txid": + "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + "vout": 0, + "scriptSig": {"asm": "", "hex": ""}, + "txinwitness": [ + "3045022100c664c6ad206999e019954c5206a26c2eca1ae2572288c0f78074c279a4a210ce022017456fdf85f744d694fa2e4638acee782d809268ea4808c04d91da3ac4fe7fd401", + "035456b63e86c0a6235cb3debfb9654966a4c2362ec678ae3b9beec53d31a25eba" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00988567, + "n": 0, + "scriptPubKey": { + "asm": "0 db56f49ae171bc6a137bd950cba945eb78fb6d7c", + "hex": "0014db56f49ae171bc6a137bd950cba945eb78fb6d7c", + "reqSigs": 1, + "type": "witness_v0_keyhash", + "addresses": ["nc1qmdt0fxhpwx7x5ymmm9gvh229adu0kmtukfcsjk"] + } + } + ], + "hex": + "020000000001014da0dde1ee465c062356dd3e2f9d04430753148b0f0dc3d81b32e7e93265b5710000000000ffffffff0197150f0000000000160014db56f49ae171bc6a137bd950cba945eb78fb6d7c02483045022100c664c6ad206999e019954c5206a26c2eca1ae2572288c0f78074c279a4a210ce022017456fdf85f744d694fa2e4638acee782d809268ea4808c04d91da3ac4fe7fd40121035456b63e86c0a6235cb3debfb9654966a4c2362ec678ae3b9beec53d31a25eba00000000", + "blockhash": + "6f60029ff3a32ca2d7e7e23c02b9cb35f61e7f9481992f9c3ded2c60c7b1de9b", + "confirmations": 130, + "time": 1663155739, + "blocktime": 1663155739 +}; diff --git a/test/services/coins/namecoin/namecoin_utxo_sample_data.dart b/test/services/coins/namecoin/namecoin_utxo_sample_data.dart new file mode 100644 index 000000000..d54f00f24 --- /dev/null +++ b/test/services/coins/namecoin/namecoin_utxo_sample_data.dart @@ -0,0 +1,58 @@ +import 'package:stackwallet/models/paymint/utxo_model.dart'; + +final Map>> batchGetUTXOResponse0 = { + "some id 0": [ + { + "tx_pos": 0, + "value": 988567, + "tx_hash": + "32dbc0d21327e0cb94ec6069a8d235affd99689ffc5f68959bfb720bafc04bcf", + "height": 629695 + }, + { + "tx_pos": 0, + "value": 1000000, + "tx_hash": + "40c8dd876cf111dc00d3aa2fedc93a77c18b391931939d4f99a760226cbff675", + "height": 629633 + }, + ], + "some id 1": [], +}; + +final utxoList = [ + UtxoObject( + txid: "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + vout: 0, + status: Status( + confirmed: true, + confirmations: 150, + blockHeight: 629695, + blockTime: 1663142110, + blockHash: + "32dbc0d21327e0cb94ec6069a8d235affd99689ffc5f68959bfb720bafc04bcf", + ), + value: 988567, + fiatWorth: "\$0", + txName: "nc1qraffwaq3cxngwp609e03ynwsx8ykgjnjve9f3y", + blocked: false, + isCoinbase: false, + ), + UtxoObject( + txid: "3ef543d0887c3e9f9924f1b2d3b21410d0238937364663ed3414a2c2ddf4ccc6", + vout: 0, + status: Status( + confirmed: true, + confirmations: 212, + blockHeight: 629633, + blockTime: 1663093275, + blockHash: + "40c8dd876cf111dc00d3aa2fedc93a77c18b391931939d4f99a760226cbff675", + ), + value: 1000000, + fiatWorth: "\$0", + txName: "nc1qwfda4s9qmdqpnykgpjf85n09ath983srtuxcqx", + blocked: false, + isCoinbase: false, + ), +]; diff --git a/test/services/coins/namecoin/namecoin_wallet_test.dart b/test/services/coins/namecoin/namecoin_wallet_test.dart new file mode 100644 index 000000000..82238310b --- /dev/null +++ b/test/services/coins/namecoin/namecoin_wallet_test.dart @@ -0,0 +1,4448 @@ +import 'package:bitcoindart/bitcoindart.dart'; +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.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; +import 'package:stackwallet/services/price.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:tuple/tuple.dart'; + +import 'namecoin_history_sample_data.dart'; +import 'namecoin_transaction_data_samples.dart'; +import 'namecoin_utxo_sample_data.dart'; +import 'namecoin_wallet_test.mocks.dart'; +import 'namecoin_wallet_test_parameters.dart'; + +@GenerateMocks( + [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +void main() { + group("namecoin constants", () { + test("namecoin minimum confirmations", () async { + expect(MINIMUM_CONFIRMATIONS, 2); + }); + test("namecoin dust limit", () async { + expect(DUST_LIMIT, 294); + }); + test("namecoin mainnet genesis block hash", () async { + expect(GENESIS_HASH_MAINNET, + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + }); + }); + + test("namecoin DerivePathType enum", () { + expect(DerivePathType.values.length, 3); + expect(DerivePathType.values.toString(), + "[DerivePathType.bip44, DerivePathType.bip49, DerivePathType.bip84]"); + }); + + group("bip32 node/root", () { + // test("getBip32Root", () { + // final root = getBip32Root(TEST_MNEMONIC, namecoin); + // expect(root.toWIF(), ROOT_WIF); + // }); + + // test("getBip32NodeFromRoot", () { + // final root = getBip32Root(TEST_MNEMONIC, namecoin); + // // two mainnet + // final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44); + // expect(node44.toWIF(), NODE_WIF_44); + // final node49 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip49); + // expect(node49.toWIF(), NODE_WIF_49); + // // and one on testnet + // final node84 = getBip32NodeFromRoot( + // 0, 0, getBip32Root(TEST_MNEMONIC, testnet), DerivePathType.bip84); + // expect(node84.toWIF(), NODE_WIF_84); + // // a bad derive path + // bool didThrow = false; + // try { + // getBip32NodeFromRoot(0, 0, root, null); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // // finally an invalid network + // didThrow = false; + // final invalidNetwork = NetworkType( + // messagePrefix: '\x18hello world\n', + // bech32: 'gg', + // bip32: Bip32Type(public: 0x055521e, private: 0x055555), + // pubKeyHash: 0x55, + // scriptHash: 0x55, + // wif: 0x00); + // try { + // getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork), + // DerivePathType.bip44); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // }); + + // test("basic getBip32Node", () { + // final node = + // getBip32Node(0, 0, TEST_MNEMONIC, testnet, DerivePathType.bip84); + // expect(node.toWIF(), NODE_WIF_84); + // }); + }); + + // group("validate testnet namecoin addresses", () { + // MockElectrumX? client; + // MockCachedElectrumX? cachedClient; + // MockPriceAPI? priceAPI; + // FakeSecureStorage? secureStore; + // MockTransactionNotificationTracker? tracker; + // + // NamecoinWallet? testnetWallet; + // + // setUp(() { + // client = MockElectrumX(); + // cachedClient = MockCachedElectrumX(); + // priceAPI = MockPriceAPI(); + // secureStore = FakeSecureStorage(); + // tracker = MockTransactionNotificationTracker(); + // + // testnetWallet = NamecoinWallet( + // walletId: "validateAddressTestNet", + // walletName: "validateAddressTestNet", + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // }); + // + // test("valid testnet namecoin legacy/p2pkh address", () { + // expect( + // testnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("valid testnet namecoin p2sh-p2wpkh address", () { + // expect( + // testnetWallet?.validateAddress("2Mugf9hpSYdQPPLNtWiU2utCi6cM9v5Pnro"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("valid testnet namecoin p2wpkh address", () { + // expect( + // testnetWallet + // ?.validateAddress("tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid testnet namecoin legacy/p2pkh address", () { + // expect( + // testnetWallet?.validateAddress("16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid testnet namecoin p2sh-p2wpkh address", () { + // expect( + // testnetWallet?.validateAddress("3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid testnet namecoin p2wpkh address", () { + // expect( + // testnetWallet + // ?.validateAddress("bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // }); + + group("validate mainnet namecoin addresses", () { + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + NamecoinWallet? mainnetWallet; + + setUp(() { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + mainnetWallet = NamecoinWallet( + walletId: "validateAddressMainNet", + walletName: "validateAddressMainNet", + coin: Coin.namecoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + // test("valid mainnet legacy/p2pkh address type", () { + // expect( + // mainnetWallet?.addressType( + // address: "nc1qmdt0fxhpwx7x5ymmm9gvh229adu0kmtukfcsjk"), + // DerivePathType.bip44); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("valid mainnet p2sh-p2wpkh address type", () { + // expect( + // mainnetWallet?.addressType( + // address: "3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), + // DerivePathType.bip49); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + + test("valid mainnet bech32 p2wpkh address type", () { + expect( + mainnetWallet?.addressType( + address: "bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), + DerivePathType.bip84); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + // test("invalid base58 address type", () { + // expect( + // () => mainnetWallet?.addressType( + // address: "mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + // throwsArgumentError); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + + test("invalid bech32 address type", () { + expect( + () => mainnetWallet?.addressType( + address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + test("address has no matching script", () { + expect( + () => mainnetWallet?.addressType( + address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), + throwsArgumentError); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(tracker); + verifyNoMoreInteractions(priceAPI); + }); + + // test("valid mainnet namecoin legacy/p2pkh address", () { + // expect( + // mainnetWallet?.validateAddress("16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("valid mainnet namecoin p2sh-p2wpkh address", () { + // expect( + // mainnetWallet?.validateAddress("3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("valid mainnet namecoin p2wpkh address", () { + // expect( + // mainnetWallet + // ?.validateAddress("bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), + // true); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid mainnet namecoin legacy/p2pkh address", () { + // expect( + // mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid mainnet namecoin p2sh-p2wpkh address", () { + // expect( + // mainnetWallet?.validateAddress("2Mugf9hpSYdQPPLNtWiU2utCi6cM9v5Pnro"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("invalid mainnet namecoin p2wpkh address", () { + // expect( + // mainnetWallet + // ?.validateAddress("tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), + // false); + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // verifyNoMoreInteractions(priceAPI); + // }); + }); + + group("testNetworkConnection", () { + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + NamecoinWallet? btc; + + setUp(() { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + btc = NamecoinWallet( + walletId: "testNetworkConnection", + walletName: "testNetworkConnection", + coin: Coin.namecoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("attempted connection fails due to server error", () async { + when(client?.ping()).thenAnswer((_) async => false); + final bool? result = await btc?.testNetworkConnection(); + expect(result, false); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("attempted connection fails due to exception", () async { + when(client?.ping()).thenThrow(Exception); + final bool? result = await btc?.testNetworkConnection(); + expect(result, false); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("attempted connection test success", () async { + when(client?.ping()).thenAnswer((_) async => true); + final bool? result = await btc?.testNetworkConnection(); + expect(result, true); + expect(secureStore?.interactions, 0); + verify(client?.ping()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + group("basic getters, setters, and functions", () { + final testWalletId = "BTCtestWalletID"; + final testWalletName = "BTCWallet"; + + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + NamecoinWallet? btc; + + setUp(() async { + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + btc = NamecoinWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: Coin.namecoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + test("get networkType main", () async { + expect(Coin.namecoin, Coin.namecoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get networkType test", () async { + btc = NamecoinWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: Coin.bitcoinTestNet, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + expect(Coin.bitcoinTestNet, Coin.bitcoinTestNet); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get cryptoCurrency", () async { + expect(Coin.namecoin, Coin.namecoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get coinName", () async { + expect(Coin.namecoin, Coin.namecoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get coinTicker", () async { + expect(Coin.namecoin, Coin.namecoin); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("get and set walletName", () async { + expect(Coin.namecoin, Coin.namecoin); + btc?.walletName = "new name"; + expect(btc?.walletName, "new name"); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("estimateTxFee", () async { + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 1), 356); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 900), 356); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 999), 356); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 1000), 356); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 1001), 712); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); + expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + 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_TESTNET, + "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 btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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 btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + // 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 btc?.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); + // verifyNoMoreInteractions(priceAPI); + // }); + }); + + group("Bitcoin service class functions that depend on shared storage", () { + final testWalletId = "BTCtestWalletID"; + final testWalletName = "BTCWallet"; + + bool hiveAdaptersRegistered = false; + + MockElectrumX? client; + MockCachedElectrumX? cachedClient; + MockPriceAPI? priceAPI; + FakeSecureStorage? secureStore; + MockTransactionNotificationTracker? tracker; + + NamecoinWallet? btc; + + setUp(() async { + await setUpTestHive(); + if (!hiveAdaptersRegistered) { + hiveAdaptersRegistered = true; + + // Registering Transaction Model Adapters + Hive.registerAdapter(TransactionDataAdapter()); + Hive.registerAdapter(TransactionChunkAdapter()); + Hive.registerAdapter(TransactionAdapter()); + Hive.registerAdapter(InputAdapter()); + Hive.registerAdapter(OutputAdapter()); + + // Registering Utxo Model Adapters + Hive.registerAdapter(UtxoDataAdapter()); + Hive.registerAdapter(UtxoObjectAdapter()); + Hive.registerAdapter(StatusAdapter()); + + final wallets = await Hive.openBox('wallets'); + await wallets.put('currentWalletName', testWalletName); + } + + client = MockElectrumX(); + cachedClient = MockCachedElectrumX(); + priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); + tracker = MockTransactionNotificationTracker(); + + btc = NamecoinWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: Coin.namecoin, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + }); + + // test("initializeWallet no network", () async { + // when(client?.ping()).thenAnswer((_) async => false); + // expect(await btc?.initializeWallet(), false); + // expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("initializeWallet no network exception", () async { + // when(client?.ping()).thenThrow(Exception("Network connection failed")); + // final wallets = await Hive.openBox(testWalletId); + // expect(await btc?.initializeExisting(), false); + // expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + 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 btc?.initializeNew(); + final wallets = await Hive.openBox(testWalletId); + + expectLater(() => btc?.initializeExisting(), throwsA(isA())) + .then((_) { + expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + 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"); + + final wallets = await Hive.openBox(testWalletId); + expectLater(() => btc?.initializeExisting(), throwsA(isA())) + .then((_) { + expect(secureStore?.interactions, 1); + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + }); + + // test("initializeWallet testnet 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": [] + // }); + // + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // + // expectLater(() => btc?.initializeWallet(), throwsA(isA())) + // .then((_) { + // expect(secureStore?.interactions, 0); + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // }); + + // test("getCurrentNode", () async { + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // 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": [] + // }); + // expect(await btc?.initializeWallet(), true); + // + // bool didThrow = false; + // try { + // await btc?.getCurrentNode(); + // } catch (_) { + // didThrow = true; + // } + // // expect no nodes on a fresh wallet unless set in db externally + // expect(didThrow, true); + // + // // 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"); + // + // // try fetching again + // final node = await btc?.getCurrentNode(); + // expect(node.toString(), + // "ElectrumXNode: {address: some address, port: 9000, name: default, useSSL: true}"); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("initializeWallet new main net wallet", () async { + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // 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": [] + // }); + // expect(await btc?.initializeWallet(), true); + // + // final wallet = await Hive.openBox(testWalletId); + // + // expect(await wallet.get("addressBookEntries"), {}); + // expect(await wallet.get('notes'), null); + // expect(await wallet.get("id"), testWalletId); + // expect(await wallet.get("preferredFiatCurrency"), null); + // expect(await wallet.get("blocked_tx_hashes"), ["0xdefault"]); + // + // final changeAddressesP2PKH = await wallet.get("changeAddressesP2PKH"); + // expect(changeAddressesP2PKH, isA>()); + // expect(changeAddressesP2PKH.length, 1); + // expect(await wallet.get("changeIndexP2PKH"), 0); + // final changeAddressesP2SH = await wallet.get("changeAddressesP2SH"); + // expect(changeAddressesP2SH, isA>()); + // expect(changeAddressesP2SH.length, 1); + // expect(await wallet.get("changeIndexP2SH"), 0); + // final changeAddressesP2WPKH = await wallet.get("changeAddressesP2WPKH"); + // expect(changeAddressesP2WPKH, isA>()); + // expect(changeAddressesP2WPKH.length, 1); + // expect(await wallet.get("changeIndexP2WPKH"), 0); + // + // final receivingAddressesP2PKH = + // await wallet.get("receivingAddressesP2PKH"); + // expect(receivingAddressesP2PKH, isA>()); + // expect(receivingAddressesP2PKH.length, 1); + // expect(await wallet.get("receivingIndexP2PKH"), 0); + // final receivingAddressesP2SH = await wallet.get("receivingAddressesP2SH"); + // expect(receivingAddressesP2SH, isA>()); + // expect(receivingAddressesP2SH.length, 1); + // expect(await wallet.get("receivingIndexP2SH"), 0); + // final receivingAddressesP2WPKH = + // await wallet.get("receivingAddressesP2WPKH"); + // expect(receivingAddressesP2WPKH, isA>()); + // expect(receivingAddressesP2WPKH.length, 1); + // expect(await wallet.get("receivingIndexP2WPKH"), 0); + // + // final p2pkhReceiveDerivations = jsonDecode(await secureStore?.read( + // key: "${testWalletId}_receiveDerivationsP2PKH")); + // expect(p2pkhReceiveDerivations.length, 1); + // final p2shReceiveDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2SH")); + // expect(p2shReceiveDerivations.length, 1); + // final p2wpkhReceiveDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH")); + // expect(p2wpkhReceiveDerivations.length, 1); + // + // final p2pkhChangeDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2PKH")); + // expect(p2pkhChangeDerivations.length, 1); + // final p2shChangeDerivations = jsonDecode( + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH")); + // expect(p2shChangeDerivations.length, 1); + // final p2wpkhChangeDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH")); + // expect(p2wpkhChangeDerivations.length, 1); + // + // expect(secureStore?.interactions, 26); // 20 in reality + 6 in this test + // expect(secureStore?.reads, 19); // 13 in reality + 6 in this test + // expect(secureStore?.writes, 7); + // expect(secureStore?.deletes, 0); + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("initializeWallet existing main net wallet", () async { + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((_) 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": [] + // }); + // // init new wallet + // expect(await btc?.initializeWallet(), true); + // + // // fetch data to compare later + // final newWallet = await Hive.openBox(testWalletId); + // + // final addressBookEntries = await newWallet.get("addressBookEntries"); + // final notes = await newWallet.get('notes'); + // final wID = await newWallet.get("id"); + // final currency = await newWallet.get("preferredFiatCurrency"); + // final blockedHashes = await newWallet.get("blocked_tx_hashes"); + // + // final changeAddressesP2PKH = await newWallet.get("changeAddressesP2PKH"); + // final changeIndexP2PKH = await newWallet.get("changeIndexP2PKH"); + // final changeAddressesP2SH = await newWallet.get("changeAddressesP2SH"); + // final changeIndexP2SH = await newWallet.get("changeIndexP2SH"); + // final changeAddressesP2WPKH = + // await newWallet.get("changeAddressesP2WPKH"); + // final changeIndexP2WPKH = await newWallet.get("changeIndexP2WPKH"); + // + // final receivingAddressesP2PKH = + // await newWallet.get("receivingAddressesP2PKH"); + // final receivingIndexP2PKH = await newWallet.get("receivingIndexP2PKH"); + // final receivingAddressesP2SH = + // await newWallet.get("receivingAddressesP2SH"); + // final receivingIndexP2SH = await newWallet.get("receivingIndexP2SH"); + // final receivingAddressesP2WPKH = + // await newWallet.get("receivingAddressesP2WPKH"); + // final receivingIndexP2WPKH = await newWallet.get("receivingIndexP2WPKH"); + // + // final p2pkhReceiveDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH")); + // final p2shReceiveDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2SH")); + // final p2wpkhReceiveDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH")); + // + // final p2pkhChangeDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2PKH")); + // final p2shChangeDerivations = jsonDecode( + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH")); + // final p2wpkhChangeDerivations = jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH")); + // + // // exit new wallet + // await btc?.exit(); + // + // // open existing/created wallet + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.namecoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // + // // init existing + // expect(await btc?.initializeWallet(), true); + // + // // compare data to ensure state matches state of previously closed wallet + // final wallet = await Hive.openBox(testWalletId); + // + // expect(await wallet.get("addressBookEntries"), addressBookEntries); + // expect(await wallet.get('notes'), notes); + // expect(await wallet.get("id"), wID); + // expect(await wallet.get("preferredFiatCurrency"), currency); + // expect(await wallet.get("blocked_tx_hashes"), blockedHashes); + // + // expect(await wallet.get("changeAddressesP2PKH"), changeAddressesP2PKH); + // expect(await wallet.get("changeIndexP2PKH"), changeIndexP2PKH); + // expect(await wallet.get("changeAddressesP2SH"), changeAddressesP2SH); + // expect(await wallet.get("changeIndexP2SH"), changeIndexP2SH); + // expect(await wallet.get("changeAddressesP2WPKH"), changeAddressesP2WPKH); + // expect(await wallet.get("changeIndexP2WPKH"), changeIndexP2WPKH); + // + // expect( + // await wallet.get("receivingAddressesP2PKH"), receivingAddressesP2PKH); + // expect(await wallet.get("receivingIndexP2PKH"), receivingIndexP2PKH); + // expect( + // await wallet.get("receivingAddressesP2SH"), receivingAddressesP2SH); + // expect(await wallet.get("receivingIndexP2SH"), receivingIndexP2SH); + // expect(await wallet.get("receivingAddressesP2WPKH"), + // receivingAddressesP2WPKH); + // expect(await wallet.get("receivingIndexP2WPKH"), receivingIndexP2WPKH); + // + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH")), + // p2pkhReceiveDerivations); + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2SH")), + // p2shReceiveDerivations); + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH")), + // p2wpkhReceiveDerivations); + // + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2PKH")), + // p2pkhChangeDerivations); + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2SH")), + // p2shChangeDerivations); + // expect( + // jsonDecode(await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH")), + // p2wpkhChangeDerivations); + // + // expect(secureStore?.interactions, 32); // 20 in reality + 12 in this test + // expect(secureStore?.reads, 25); // 13 in reality + 12 in this test + // expect(secureStore?.writes, 7); + // expect(secureStore?.deletes, 0); + // verify(client?.ping()).called(2); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // // test("get fiatPrice", () async { + // // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // // await Hive.openBox(testWalletId); + // // expect(await btc.basePrice, Decimal.fromInt(10)); + // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + // + // test("get current receiving addresses", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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": [] + // }); + // await btc?.initializeWallet(); + // expect( + // Address.validateAddress(await btc!.currentReceivingAddress, testnet), + // true); + // expect( + // Address.validateAddress( + // await btc!.currentReceivingAddressP2SH, testnet), + // true); + // expect( + // Address.validateAddress( + // await btc!.currentLegacyReceivingAddress, testnet), + // true); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("get allOwnAddresses", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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": [] + // }); + // await btc?.initializeWallet(); + // final addresses = await btc?.allOwnAddresses; + // expect(addresses, isA>()); + // expect(addresses?.length, 6); + // + // for (int i = 0; i < 6; i++) { + // expect(Address.validateAddress(addresses[i], testnet), true); + // } + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("get utxos and balances", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => batchGetUTXOResponse0); + // + // when(client?.estimateFee(blocks: 10)) + // .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); + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // + // when(cachedClient?.getTransaction( + // txHash: tx1.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // txHash: tx2.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // txHash: tx3.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: tx4.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx4Raw); + // + // await btc?.initializeNew(); + // await btc?.initializeExisting(); + // final utxoData = await btc?.utxoData; + // expect(utxoData, isA()); + // expect(utxoData.toString(), + // r"{totalUserCurrency: $0.0076497, satoshiBalance: 76497, bitcoinBalance: 0.00076497, unspentOutputArray: [{txid: 88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c, vout: 0, value: 17000, fiat: $0.0017, blocked: false, status: {confirmed: true, blockHash: 00000000000000198ca8300deab26c5c1ec1df0da5afd30c9faabd340d8fc194, blockHeight: 437146, blockTime: 1652994245, confirmations: 100}}, {txid: b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528, vout: 0, value: 36037, fiat: $0.0036037, blocked: false, status: {confirmed: false, blockHash: 000000000000003db63ad679a539f2088dcc97a149c99ca790ce0c5f7b5acff0, blockHeight: 441696, blockTime: 1652923129, confirmations: 0}}, {txid: dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3, vout: 1, value: 14714, fiat: $0.0014714, blocked: false, status: {confirmed: false, blockHash: 0000000000000030bec9bc58a3ab4857de1cc63cfed74204a6be57f125fb2fa7, blockHeight: 437146, blockTime: 1652888705, confirmations: 0}}, {txid: b39bac02b65af46a49e2985278fe24ca00dd5d627395d88f53e35568a04e10fa, vout: 0, value: 8746, fiat: $0.0008746, blocked: false, status: {confirmed: true, blockHash: 0000000039b80e9a10b7bcaf0f193b51cb870a4febe9b427c1f41a3f42eaa80b, blockHeight: 441696, blockTime: 1652993683, confirmations: 22861}}]}"); + // + // final outputs = await btc?.unspentOutputs; + // expect(outputs, isA>()); + // expect(outputs?.length, 4); + // + // final availableBalance = await btc?.availableBalance; + // expect(availableBalance, Decimal.parse("0.00025746")); + // + // final totalBalance = await btc?.totalBalance; + // expect(totalBalance, Decimal.parse("0.00076497")); + // + // final pendingBalance = await btc?.pendingBalance; + // expect(pendingBalance, Decimal.parse("0.00050751")); + // + // final balanceMinusMaxFee = await btc?.balanceMinusMaxFee; + // expect(balanceMinusMaxFee, Decimal.parse("-9.99974254")); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.estimateFee(blocks: 1)).called(1); + // verify(client?.estimateFee(blocks: 5)).called(1); + // verify(client?.estimateFee(blocks: 10)).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx1.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx2.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx3.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // txHash: tx4.txid, + // coin: Coin.bitcoinTestNet, + // callOutSideMainIsolate: false)) + // .called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("get utxos - multiple batches", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => {}); + // + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); + // + // await btc?.initializeNew(); + // await btc?.initializeExisting(); + // + // // add some extra addresses to make sure we have more than the single batch size of 10 + // final wallet = await Hive.openBox(DB); + // final addresses = await wallet.get("receivingAddressesP2WPKH"); + // addresses.add("tb1qpfl2uz3jvazy9wr4vqhwluyhgtd29rsmghpqxp"); + // addresses.add("tb1qznt3psdpcyz8lwj7xxl6q78hjw2mj095nd4gxu"); + // addresses.add("tb1q7yjjyh9h4uy7j0wdtcmptw3g083kxrqlvgjz86"); + // addresses.add("tb1qt05shktwcq7kgxccva20cfwt47kav9s6n8yr9p"); + // addresses.add("tb1q4nk5wdylywl4dg2a45naae7u08vtgyujqfrv58"); + // addresses.add("tb1qxwccgfq9tmd6lx823cuejuea9wdzpaml9wkapm"); + // addresses.add("tb1qk88negkdqusr8tpj0hpvs98lq6ka4vyw6kfnqf"); + // addresses.add("tb1qw0jzneqwp0t4ah9w3za4k9d8d4tz8y3zxqmtgx"); + // addresses.add("tb1qccqjlpndx46sv7t6uurlyyjre5vwjfdzzlf2vd"); + // addresses.add("tb1q3hfpe69rrhr5348xd04rfz9g3h22yk64pwur8v"); + // addresses.add("tb1q4rp373202aur96a28lp0pmts6kp456nka45e7d"); + // await wallet.put("receivingAddressesP2WPKH", addresses); + // + // final utxoData = await btc?.utxoData; + // expect(utxoData, isA()); + // + // final outputs = await btc?.unspentOutputs; + // expect(outputs, isA>()); + // expect(outputs?.length, 0); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(2); + // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("get utxos fails", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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?.getBatchUTXOs(args: anyNamed("args"))) + // .thenThrow(Exception("some exception")); + // + // await btc?.initializeWallet(); + // final utxoData = await btc?.utxoData; + // expect(utxoData, isA()); + // expect(utxoData.toString(), + // r"{totalUserCurrency: $0.00, satoshiBalance: 0, bitcoinBalance: 0, unspentOutputArray: []}"); + // + // final outputs = await btc?.unspentOutputs; + // expect(outputs, isA>()); + // expect(outputs?.length, 0); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("chain height fetch, update, and get", () async { + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // 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": [] + // }); + // await btc?.initializeWallet(); + // + // // get stored + // expect(await btc?.storedChainHeight, 0); + // + // // fetch fails + // when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); + // expect(await btc?.chainHeight, -1); + // + // // fetch succeeds + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { + // "height": 100, + // "hex": "some block hex", + // }); + // expect(await btc?.chainHeight, 100); + // + // // update + // await btc?.updateStoredChainHeight(newHeight: 1000); + // + // // fetch updated + // expect(await btc?.storedChainHeight, 1000); + // + // verify(client?.ping()).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBlockHeadTip()).called(2); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("fetch and update useBiometrics", () async { + // // get + // expect(await btc?.useBiometrics, false); + // + // // then update + // await btc?.updateBiometricsUsage(true); + // + // // finally check updated + // expect(await btc?.useBiometrics, true); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("getTxCount succeeds", () async { + // when(client?.getHistory( + // scripthash: + // "4e94cc7b4a85791445260ae4403233b6a4784185f9716d73f136c6642615fce9")) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 200004, + // "tx_hash": + // "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" + // }, + // { + // "height": 215008, + // "tx_hash": + // "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" + // } + // ]); + // + // final count = + // await btc?.getTxCount(address: "3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"); + // + // expect(count, 2); + // + // verify(client?.getHistory( + // scripthash: + // "4e94cc7b4a85791445260ae4403233b6a4784185f9716d73f136c6642615fce9")) + // .called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("getTxCount fails", () async { + // when(client?.getHistory( + // scripthash: + // "4e94cc7b4a85791445260ae4403233b6a4784185f9716d73f136c6642615fce9")) + // .thenThrow(Exception("some exception")); + // + // bool didThrow = false; + // try { + // await btc?.getTxCount(address: "3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory( + // scripthash: + // "4e94cc7b4a85791445260ae4403233b6a4784185f9716d73f136c6642615fce9")) + // .called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("_checkCurrentReceivingAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 200004, + // "tx_hash": + // "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" + // }, + // { + // "height": 215008, + // "tx_hash": + // "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" + // } + // ]); + // + // await btc?.initializeWallet(); + // + // bool didThrow = false; + // try { + // await btc?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(3); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.ping()).called(1); + // + // expect(secureStore?.interactions, 29); + // expect(secureStore?.reads, 19); + // expect(secureStore?.writes, 10); + // expect(secureStore?.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("_checkCurrentReceivingAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // final wallet = await Hive.openBox(testWalletId); + // + // await btc?.initializeNew(); + // await btc?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await btc?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.ping()).called(1); + // + // expect(secureStore?.interactions, 20); + // expect(secureStore?.reads, 13); + // expect(secureStore?.writes, 7); + // expect(secureStore?.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("_checkCurrentChangeAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 200004, + // "tx_hash": + // "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" + // }, + // { + // "height": 215008, + // "tx_hash": + // "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" + // } + // ]); + // + // await btc?.initializeWallet(); + // + // bool didThrow = false; + // try { + // await btc?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(3); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.ping()).called(1); + // + // expect(secureStore?.interactions, 29); + // expect(secureStore?.reads, 19); + // expect(secureStore?.writes, 10); + // expect(secureStore?.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("_checkCurrentChangeAddressesForTransactions 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await btc?.initializeWallet(); + // + // bool didThrow = false; + // try { + // await btc?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.ping()).called(1); + // + // expect(secureStore?.interactions, 20); + // expect(secureStore?.reads, 13); + // expect(secureStore?.writes, 7); + // expect(secureStore?.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("getAllTxsToWatch", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // var notifications = {"show": 0}; + // const MethodChannel('dexterous.com/flutter/local_notifications') + // .setMockMethodCallHandler((call) async { + // notifications[call.method]++; + // }); + // + // btc?.pastUnconfirmedTxs = { + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // "b39bac02b65af46a49e2985278fe24ca00dd5d627395d88f53e35568a04e10fa", + // }; + // + // await btc?.getAllTxsToWatch(transactionData); + // expect(notifications.length, 1); + // expect(notifications["show"], 3); + // + // expect(btc?.unconfirmedTxs, { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // 'dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3', + // }); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("refreshIfThereIsNewData true A", () async { + // when(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // )).thenAnswer((_) async => tx1Raw); + // + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // await wallet.put('receivingAddressesP2SH', [ + // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", + // ]); + // await wallet.put('receivingAddressesP2WPKH', [ + // "tb1q3ywehep0ykrkaqkt0hrgsqyns4mnz2ls8nxfzg", + // ]); + // + // await wallet.put('changeAddressesP2PKH', []); + // await wallet.put('changeAddressesP2SH', []); + // await wallet.put('changeAddressesP2WPKH', []); + // + // btc?.unconfirmedTxs = { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c" + // }; + // + // final result = await btc?.refreshIfThereIsNewData(); + // + // expect(result, true); + // + // verify(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).called(1); + // verify(client.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // )).called(1); + // + // expect(secureStore.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("refreshIfThereIsNewData true B", () async { + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((_) async => Decimal.fromInt(10)); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // final uuids = Map>.from( + // realInvocation.namedArguments.values.first) + // .keys + // .toList(growable: false); + // return { + // uuids[0]: [ + // { + // "tx_hash": + // "dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3", + // "height": 2226003 + // }, + // { + // "tx_hash": + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // "height": 2226102 + // } + // ], + // uuids[1]: [ + // { + // "tx_hash": + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // "height": 2226326 + // } + // ], + // }; + // }); + // + // when(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // )).thenAnswer((_) async => tx1Raw); + // + // when(cachedClient?.getTransaction( + // tx_hash: + // "dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "6261002b30122ab3b2ba8c481134e8a3ce08a3a1a429b8ebb3f28228b100ac1a", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx5Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "717080fc0054f655260b1591a0059bf377a589a98284173d20a1c8f3316c086e", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx6Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "1baec51e7630e3640ccf0e34f160c8ad3eb6021ecafe3618a1afae328f320f53", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx7Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "b39bac02b65af46a49e2985278fe24ca00dd5d627395d88f53e35568a04e10fa", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx4Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "46b1f19763ac68e39b8218429f4e29b150f850901562fe44a05fade9e0acd65f", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx8Raw); + // + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // await wallet.put('receivingAddressesP2SH', [ + // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", + // ]); + // await wallet.put('receivingAddressesP2WPKH', [ + // "tb1q3ywehep0ykrkaqkt0hrgsqyns4mnz2ls8nxfzg", + // ]); + // + // await wallet.put('changeAddressesP2PKH', []); + // await wallet.put('changeAddressesP2SH', []); + // await wallet.put('changeAddressesP2WPKH', []); + // + // btc.unconfirmedTxs = { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // }; + // + // final result = await btc?.refreshIfThereIsNewData(); + // + // expect(result, true); + // + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(2); + // verify(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: anyNamed("tx_hash"), + // verbose: true, + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .called(9); + // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("refreshIfThereIsNewData false A", () async { + // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) + // // .thenAnswer((_) async => Decimal.fromInt(10)); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // final uuids = Map>.from( + // realInvocation.namedArguments.values.first) + // .keys + // .toList(growable: false); + // return { + // uuids[0]: [ + // { + // "tx_hash": + // "dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3", + // "height": 2226003 + // }, + // { + // "tx_hash": + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // "height": 2226102 + // } + // ], + // uuids[1]: [ + // { + // "tx_hash": + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // "height": 2226326 + // } + // ], + // }; + // }); + // + // when(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).thenAnswer((_) async => tx2Raw); + // when(client?.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // )).thenAnswer((_) async => tx1Raw); + // + // when(cachedClient?.getTransaction( + // tx_hash: + // "dcca229760b44834478f0b266c9b3f5801e0139fdecacdc0820e447289a006d3", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "88b7b5077d940dde1bc63eba37a09dec8e7b9dad14c183a2e879a21b6ec0ac1c", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx1Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "6261002b30122ab3b2ba8c481134e8a3ce08a3a1a429b8ebb3f28228b100ac1a", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx5Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "717080fc0054f655260b1591a0059bf377a589a98284173d20a1c8f3316c086e", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx6Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "1baec51e7630e3640ccf0e34f160c8ad3eb6021ecafe3618a1afae328f320f53", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx7Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "b39bac02b65af46a49e2985278fe24ca00dd5d627395d88f53e35568a04e10fa", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx4Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "46b1f19763ac68e39b8218429f4e29b150f850901562fe44a05fade9e0acd65f", + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx8Raw); + // + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // await wallet.put('receivingAddressesP2SH', [ + // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", + // ]); + // await wallet.put('receivingAddressesP2WPKH', [ + // "tb1q3ywehep0ykrkaqkt0hrgsqyns4mnz2ls8nxfzg", + // ]); + // + // await wallet.put('changeAddressesP2PKH', []); + // await wallet.put('changeAddressesP2SH', []); + // await wallet.put('changeAddressesP2WPKH', []); + // + // btc?.unconfirmedTxs = { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // }; + // + // final result = await btc?.refreshIfThereIsNewData(); + // + // expect(result, false); + // + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(2); + // verify(client?.getTransaction( + // tx_hash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: anyNamed("tx_hash"), + // verbose: true, + // coinName: "tBitcoin", + // callOutSideMainIsolate: false)) + // .called(15); + // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + // test("refreshIfThereIsNewData false B", () async { + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenThrow(Exception("some exception")); + // + // when(client?.getTransaction( + // txHash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).thenAnswer((_) async => tx2Raw); + // + // btc = NamecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: Coin.bitcoinTestNet, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // priceAPI: priceAPI, + // secureStore: secureStore, + // ); + // final wallet = await Hive.openBox(testWalletId); + // await wallet.put('receivingAddressesP2PKH', []); + // await wallet.put('receivingAddressesP2SH', [ + // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", + // ]); + // await wallet.put('receivingAddressesP2WPKH', [ + // "tb1q3ywehep0ykrkaqkt0hrgsqyns4mnz2ls8nxfzg", + // ]); + // + // await wallet.put('changeAddressesP2PKH', []); + // await wallet.put('changeAddressesP2SH', []); + // await wallet.put('changeAddressesP2WPKH', []); + // + // btc?.txTracker = { + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // }; + // + // // btc?.unconfirmedTxs = { + // // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // // }; + // + // final result = await btc?.refreshIfThereIsNewData(); + // + // expect(result, false); + // + // verify(client?.getBatchHistory(args: anyNamed("args"))).called(1); + // verify(client?.getTransaction( + // txHash: + // "b2f75a017a7435f1b8c2e080a865275d8f80699bba68d8dce99a94606e7b3528", + // )).called(1); + // + // expect(secureStore?.interactions, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + 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 btc?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, true); + + verify(client?.getServerFeatures()).called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test( + "recoverFromMnemonic using empty seed on testnet fails due to bad genesis hash match", + () async { + btc = NamecoinWallet( + walletId: testWalletId, + walletName: testWalletName, + coin: Coin.bitcoinTestNet, + client: client!, + cachedClient: cachedClient!, + tracker: tracker!, + priceAPI: priceAPI, + secureStore: secureStore, + ); + 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": [] + }); + + bool hasThrown = false; + try { + await btc?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + } catch (_) { + hasThrown = true; + } + expect(hasThrown, true); + + verify(client?.getServerFeatures()).called(1); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + 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 btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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(); + final wallet = await Hive.openBox(testWalletId); + bool hasThrown = false; + try { + await btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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); + + final wallet = await Hive.openBox(testWalletId); + + await btc?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + expect(await btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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 btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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 {}); + + 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; + }); + + final wallet = await Hive.openBox(testWalletId); + + // restore so we have something to rescan + await btc?.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 btc?.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(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + .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?.writes, 25); + expect(secureStore?.reads, 32); + expect(secureStore?.deletes, 6); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + 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(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + .thenAnswer((realInvocation) async {}); + + 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; + }); + + final wallet = await Hive.openBox(testWalletId); + + // restore so we have something to rescan + await btc?.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 btc?.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(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + .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?.writes, 19); + expect(secureStore?.reads, 32); + expect(secureStore?.deletes, 12); + + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + // test("fetchBuildTxData 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(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // modify addresses to trigger all change code branches + // final chg44 = await secureStore?.read( + // key: testWalletId + "_changeDerivationsP2PKH"); + // await secureStore?.write( + // key: testWalletId + "_changeDerivationsP2PKH", + // value: chg44?.replaceFirst("1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U", + // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); + // final chg49 = + // await secureStore?.read(key: testWalletId + "_changeDerivationsP2SH"); + // await secureStore?.write( + // key: testWalletId + "_changeDerivationsP2SH", + // value: chg49?.replaceFirst("3ANTVqufTH1tLAuoQHhng8jndRsA9hcNy7", + // "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); + // final chg84 = await secureStore?.read( + // key: testWalletId + "_changeDerivationsP2WPKH"); + // await secureStore?.write( + // key: testWalletId + "_changeDerivationsP2WPKH", + // value: chg84?.replaceFirst( + // "bc1qn2x7h96kufgfjxtkhsnq03jqwqde8zasffqvd2", + // "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); + // + // final data = await btc?.fetchBuildTxData(utxoList); + // + // expect(data?.length, 3); + // expect( + // data?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // ?.length, + // 2); + // expect( + // data?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // .length, + // 3); + // expect( + // data?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // .length, + // 2); + // expect( + // data?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // ["output"], + // isA()); + // expect( + // data?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["output"], + // isA()); + // expect( + // data?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // ["output"], + // isA()); + // expect( + // data?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // ["keyPair"], + // isA()); + // expect( + // data?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["keyPair"], + // isA()); + // expect( + // data?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // ["keyPair"], + // isA()); + // expect( + // data?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["redeemScript"], + // isA()); + // + // // modify addresses to trigger all receiving code branches + // 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")); + // + // final data2 = await btc?.fetchBuildTxData(utxoList); + // + // expect(data2?.length, 3); + // expect( + // data2?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // .length, + // 2); + // expect( + // data2?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // .length, + // 3); + // expect( + // data2?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // .length, + // 2); + // expect( + // data2?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // ["output"], + // isA()); + // expect( + // data2?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["output"], + // isA()); + // expect( + // data2?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // ["output"], + // isA()); + // expect( + // data2?["2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703"] + // ["keyPair"], + // isA()); + // expect( + // data2?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["keyPair"], + // isA()); + // expect( + // data2?["3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4"] + // ["keyPair"], + // isA()); + // expect( + // data2?["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["redeemScript"], + // isA()); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(2); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(2); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 38); + // expect(secureStore?.writes, 13); + // expect(secureStore?.reads, 25); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("fetchBuildTxData throws", () 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(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenThrow(Exception("some exception")); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // bool didThrow = false; + // try { + // await btc?.fetchBuildTxData(utxoList); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 14); + // expect(secureStore?.writes, 7); + // expect(secureStore?.reads, 7); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("build transaction 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(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final data = await btc?.fetchBuildTxData(utxoList); + // + // final txData = await btc?.buildTransaction( + // utxosToUse: utxoList, + // utxoSigningData: data!, + // recipients: ["bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc"], + // satoshiAmounts: [13000]); + // + // expect(txData?.length, 2); + // expect(txData?["hex"], isA()); + // expect(txData?["vSize"], isA()); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("build transaction 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(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final data = await btc?.fetchBuildTxData(utxoList); + // + // // give bad data toi build tx + // data["ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7"] + // ["keyPair"] = null; + // + // bool didThrow = false; + // try { + // await btc?.buildTransaction( + // utxosToUse: utxoList, + // utxoSigningData: data!, + // recipients: ["bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc"], + // satoshiAmounts: [13000]); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("two output coinSelection 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => [ + // {"height": 1000, "tx_hash": "some tx hash"} + // ]); + // when(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 18000, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, isA>()); + // expect(result.length > 0, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // + // expect(secureStore?.interactions, 29); + // expect(secureStore?.writes, 11); + // expect(secureStore?.reads, 18); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("one output option A coinSelection", () 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => [ + // {"height": 1000, "tx_hash": "some tx hash"} + // ]); + // when(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 18500, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, isA>()); + // expect(result.length > 0, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("one output option B coinSelection", () 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => [ + // {"height": 1000, "tx_hash": "some tx hash"} + // ]); + // when(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 18651, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, isA>()); + // expect(result.length > 0, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("insufficient funds option A coinSelection", () 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); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 20000, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, 1); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 20); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 10); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("insufficient funds option B coinSelection", () 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); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 19000, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, 2); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // expect(secureStore?.interactions, 20); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 10); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("insufficient funds option C coinSelection", () 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(cachedClient.?getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // final result = await btc?.coinSelection( + // 18900, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, 2); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // + // expect(secureStore?.interactions, 26); + // expect(secureStore?.writes, 10); + // expect(secureStore?.reads, 16); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("check for more outputs coinSelection", () 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(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => [ + // {"height": 1000, "tx_hash": "some tx hash"} + // ]); + // + // final result = await btc?.coinSelection( + // 11900, 1000, "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // utxos: utxoList); + // + // expect(result, isA>()); + // expect(result.length > 0, true); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(2); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(2); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // + // expect(secureStore?.interactions, 33); + // expect(secureStore?.writes, 11); + // expect(secureStore?.reads, 22); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + // + // test("prepareSend and confirmSend succeed", () 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?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => [ + // {"height": 1000, "tx_hash": "some tx hash"} + // ]); + // when(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000); + // + // // 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")); + // + // btc?.outputsList = utxoList; + // + // final result = await btc?.prepareSend( + // toAddress: "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // amount: 15000); + // + // expect(result, isA>()); + // expect(result?.length! > 0, true); + // + // when(client?.broadcastTransaction( + // rawTx: result!["hex"], requestID: anyNamed("requestID"))) + // .thenAnswer((_) async => "some txHash"); + // + // final sentResult = await btc?.confirmSend(txData: result!); + // expect(sentResult, "some txHash"); + // + // verify(client?.getServerFeatures()).called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(cachedClient?.getTransaction( + // tx_hash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coinName: "Bitcoin", + // callOutSideMainIsolate: false)) + // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.broadcastTransaction( + // rawTx: result!["hex"], requestID: anyNamed("requestID"))) + // .called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // + // expect(secureStore?.interactions, 29); + // expect(secureStore?.writes, 11); + // expect(secureStore?.reads, 18); + // expect(secureStore?.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(priceAPI); + // }); + + 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: + "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + coin: Coin.namecoin)) + .thenAnswer((_) async => tx9Raw); + when(cachedClient?.getTransaction( + txHash: + "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + coin: Coin.namecoin)) + .thenAnswer((_) async => tx10Raw); + when(cachedClient?.getTransaction( + txHash: + "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + coin: Coin.namecoin, + )).thenAnswer((_) async => tx11Raw); + + // recover to fill data + await btc?.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")); + + btc?.outputsList = utxoList; + + bool didThrow = false; + try { + await btc?.prepareSend( + address: "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + 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); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend no hex", () async { + bool didThrow = false; + try { + await btc?.confirmSend(txData: {"some": "strange map"}); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend hex is not string", () async { + bool didThrow = false; + try { + await btc?.confirmSend(txData: {"hex": true}); + } catch (_) { + didThrow = true; + } + + expect(didThrow, true); + + expect(secureStore?.interactions, 0); + verifyNoMoreInteractions(client); + verifyNoMoreInteractions(cachedClient); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend hex is string but missing other data", () async { + bool didThrow = false; + try { + await btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + test("confirmSend fails due to vSize being greater than fee", () async { + bool didThrow = false; + try { + await btc + ?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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 btc + ?.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); + verifyNoMoreInteractions(priceAPI); + }); + // + // // 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 { + // // btc = NamecoinWallet( + // // walletId: testWalletId, + // // walletName: testWalletName, + // // networkType: BasicNetworkType.test, + // // client: client, + // // cachedClient: cachedClient, + // // priceAPI: priceAPI, + // // 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 = btc.cachedElectrumXClient; + // // + // // // return when refresh is called on node changed trigger + // // btc.longMutex = true; + // // + // // GlobalEventBus.instance + // // .fire(NodesChangedEvent(NodesChangedEventType.updatedCurrentNode)); + // // + // // // make sure event has processed before continuing + // // await Future.delayed(Duration(seconds: 5)); + // // + // // final b = btc.cachedElectrumXClient; + // // + // // expect(identical(a, b), false); + // // + // // await btc.exit(); + // // + // // expect(secureStore.interactions, 0); + // // verifyNoMoreInteractions(client); + // // verifyNoMoreInteractions(cachedClient); + // // verifyNoMoreInteractions(priceAPI); + // // }); + + 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 btc?.recoverFromMnemonic( + mnemonic: TEST_MNEMONIC, + maxUnusedAddressGap: 2, + maxNumberOfIndexesToCheck: 1000, + height: 4000); + + btc?.refreshMutex = true; + + await btc?.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); + verifyNoMoreInteractions(priceAPI); + }); + + 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); + + when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")) + .thenAnswer((_) async => {Coin.namecoin: Tuple2(Decimal.one, 0.3)}); + + 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 btc?.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 btc?.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); + verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2); + + 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); + verifyNoMoreInteractions(priceAPI); + }); + + tearDown(() async { + await tearDownTestHive(); + }); + }); +} diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart new file mode 100644 index 000000000..d86949a61 --- /dev/null +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -0,0 +1,352 @@ +// Mocks generated by Mockito 5.2.0 from annotations +// in stackwallet/test/services/coins/namecoin/namecoin_wallet_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i6; + +import 'package:decimal/decimal.dart' as _i2; +import 'package:http/http.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; +import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/services/transaction_notification_tracker.dart' + as _i11; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; +import 'package:stackwallet/utilities/prefs.dart' as _i3; +import 'package:tuple/tuple.dart' as _i10; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeDecimal_0 extends _i1.Fake implements _i2.Decimal {} + +class _FakePrefs_1 extends _i1.Fake implements _i3.Prefs {} + +class _FakeClient_2 extends _i1.Fake implements _i4.Client {} + +/// A class which mocks [ElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { + MockElectrumX() { + _i1.throwOnMissingStub(this); + } + + @override + set failovers(List<_i5.ElectrumXNode>? _failovers) => + super.noSuchMethod(Invocation.setter(#failovers, _failovers), + returnValueForMissingStub: null); + @override + int get currentFailoverIndex => + (super.noSuchMethod(Invocation.getter(#currentFailoverIndex), + returnValue: 0) as int); + @override + set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod( + Invocation.setter(#currentFailoverIndex, _currentFailoverIndex), + returnValueForMissingStub: null); + @override + String get host => + (super.noSuchMethod(Invocation.getter(#host), returnValue: '') as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i6.Future request( + {String? command, + List? args = const [], + Duration? connectionTimeout = const Duration(seconds: 60), + String? requestID, + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#request, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #requestID: requestID, + #retries: retries + }), + returnValue: Future.value()) as _i6.Future); + @override + _i6.Future>> batchRequest( + {String? command, + Map>? args, + Duration? connectionTimeout = const Duration(seconds: 60), + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#batchRequest, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #retries: retries + }), + returnValue: Future>>.value( + >[])) + as _i6.Future>>); + @override + _i6.Future ping({String? requestID, int? retryCount = 1}) => + (super.noSuchMethod( + Invocation.method( + #ping, [], {#requestID: requestID, #retryCount: retryCount}), + returnValue: Future.value(false)) as _i6.Future); + @override + _i6.Future> getBlockHeadTip({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBlockHeadTip, [], {#requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getServerFeatures({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getServerFeatures, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i6 + .Future>); + @override + _i6.Future broadcastTransaction({String? rawTx, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#broadcastTransaction, [], + {#rawTx: rawTx, #requestID: requestID}), + returnValue: Future.value('')) as _i6.Future); + @override + _i6.Future> getBalance( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBalance, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future>> getHistory( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getHistory, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) + as _i6.Future>>); + @override + _i6.Future>>> getBatchHistory( + {Map>? args}) => + (super.noSuchMethod( + Invocation.method(#getBatchHistory, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i6 + .Future>>>); + @override + _i6.Future>> getUTXOs( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getUTXOs, [], {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) as _i6 + .Future>>); + @override + _i6.Future>>> getBatchUTXOs( + {Map>? args}) => + (super.noSuchMethod(Invocation.method(#getBatchUTXOs, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i6 + .Future>>>); + @override + _i6.Future> getTransaction( + {String? txHash, bool? verbose = true, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #verbose: verbose, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getAnonymitySet( + {String? groupId = r'1', + String? blockhash = r'', + String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], { + #groupId: groupId, + #blockhash: blockhash, + #requestID: requestID + }), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future getMintData({dynamic mints, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getMintData, [], {#mints: mints, #requestID: requestID}), + returnValue: Future.value()) as _i6.Future); + @override + _i6.Future> getUsedCoinSerials( + {String? requestID, int? startNumber}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#requestID: requestID, #startNumber: startNumber}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + Invocation.method(#getLatestCoinId, [], {#requestID: requestID}), + returnValue: Future.value(0)) as _i6.Future); + @override + _i6.Future> getFeeRate({String? requestID}) => (super + .noSuchMethod(Invocation.method(#getFeeRate, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i6 + .Future>); + @override + _i6.Future<_i2.Decimal> estimateFee({String? requestID, int? blocks}) => + (super.noSuchMethod( + Invocation.method( + #estimateFee, [], {#requestID: requestID, #blocks: blocks}), + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); + @override + _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + Invocation.method(#relayFee, [], {#requestID: requestID}), + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); +} + +/// A class which mocks [CachedElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { + MockCachedElectrumX() { + _i1.throwOnMissingStub(this); + } + + @override + String get server => + (super.noSuchMethod(Invocation.getter(#server), returnValue: '') + as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i3.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), + returnValue: _FakePrefs_1()) as _i3.Prefs); + @override + List<_i5.ElectrumXNode> get failovers => + (super.noSuchMethod(Invocation.getter(#failovers), + returnValue: <_i5.ElectrumXNode>[]) as List<_i5.ElectrumXNode>); + @override + _i6.Future> getAnonymitySet( + {String? groupId, String? blockhash = r'', _i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], + {#groupId: groupId, #blockhash: blockhash, #coin: coin}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getTransaction( + {String? txHash, _i8.Coin? coin, bool? verbose = true}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #coin: coin, #verbose: verbose}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getUsedCoinSerials( + {_i8.Coin? coin, int? startNumber = 0}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#coin: coin, #startNumber: startNumber}), + returnValue: Future>.value([])) + as _i6.Future>); + @override + _i6.Future clearSharedTransactionCache({_i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#clearSharedTransactionCache, [], {#coin: coin}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); +} + +/// A class which mocks [PriceAPI]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { + MockPriceAPI() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Client get client => (super.noSuchMethod(Invocation.getter(#client), + returnValue: _FakeClient_2()) as _i4.Client); + @override + void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( + Invocation.method(#resetLastCalledToForceNextCallToUpdateCache, []), + returnValueForMissingStub: null); + @override + _i6.Future>> + getPricesAnd24hChange({String? baseCurrency}) => (super.noSuchMethod( + Invocation.method( + #getPricesAnd24hChange, [], {#baseCurrency: baseCurrency}), + returnValue: + Future>>.value( + <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{})) + as _i6.Future>>); +} + +/// A class which mocks [TransactionNotificationTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTransactionNotificationTracker extends _i1.Mock + implements _i11.TransactionNotificationTracker { + MockTransactionNotificationTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get walletId => + (super.noSuchMethod(Invocation.getter(#walletId), returnValue: '') + as String); + @override + List get pendings => + (super.noSuchMethod(Invocation.getter(#pendings), returnValue: []) + as List); + @override + List get confirmeds => (super + .noSuchMethod(Invocation.getter(#confirmeds), returnValue: []) + as List); + @override + bool wasNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedPending, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedPending, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + bool wasNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedConfirmed, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedConfirmed, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); +} diff --git a/test/services/coins/namecoin/namecoin_wallet_test_parameters.dart b/test/services/coins/namecoin/namecoin_wallet_test_parameters.dart new file mode 100644 index 000000000..e69de29bb From aba579f64eb18dfeb7bc115bebfa154427d4b5b5 Mon Sep 17 00:00:00 2001 From: Likho Date: Fri, 16 Sep 2022 13:13:30 +0200 Subject: [PATCH 17/20] WIP: Fix bch failing tests, add testnet --- .../coins/bitcoincash/bitcoincash_wallet.dart | 18 ++++ lib/services/coins/coin_service.dart | 10 +++ lib/utilities/address_utils.dart | 2 + lib/utilities/assets.dart | 4 + lib/utilities/cfcolors.dart | 1 + lib/utilities/constants.dart | 1 + lib/utilities/default_nodes.dart | 15 ++++ lib/utilities/enums/coin_enum.dart | 16 ++++ .../bitcoincash/bitcoincash_wallet_test.dart | 85 ++++++++----------- 9 files changed, 104 insertions(+), 48 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 89f44ad11..56f6d3aaf 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -81,6 +81,9 @@ bip32.BIP32 getBip32NodeFromRoot( case 0x80: // bch mainnet wif coinType = "145"; // bch mainnet break; + case 0xef: // bch testnet wif + coinType = "1"; // bch testnet + break; default: throw Exception("Invalid Bitcoincash network type used!"); } @@ -137,6 +140,8 @@ class BitcoinCashWallet extends CoinServiceAPI { switch (coin) { case Coin.bitcoincash: return bitcoincash; + case Coin.bitcoincashTestnet: + return bitcoincashtestnet; default: throw Exception("Bitcoincash network type not set!"); } @@ -1228,6 +1233,11 @@ class BitcoinCashWallet extends CoinServiceAPI { throw Exception("genesis hash does not match main net!"); } break; + case Coin.bitcoincashTestnet: + if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + throw Exception("genesis hash does not match test net!"); + } + break; default: throw Exception( "Attempted to generate a BitcoinWallet using a non bitcoin coin type: ${coin.name}"); @@ -3086,3 +3096,11 @@ final bitcoincash = NetworkType( pubKeyHash: 0x00, scriptHash: 0x05, wif: 0x80); + +final bitcoincashtestnet = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: Bip32Type(public: 0x043587cf, private: 0x04358394), + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef); diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 5db6bd8bc..89231f200 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -109,6 +109,16 @@ abstract class CoinServiceAPI { tracker: tracker, ); + case Coin.bitcoincashTestnet: + return BitcoinCashWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + client: client, + cachedClient: cachedClient, + tracker: tracker, + ); + case Coin.dogecoin: return DogecoinWallet( walletId: walletId, diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 75a1f51f5..dcdee319b 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -57,6 +57,8 @@ class AddressUtils { return Address.validateAddress(address, namecoin); case Coin.bitcoinTestNet: return Address.validateAddress(address, testnet); + case Coin.bitcoincashTestnet: + return Address.validateAddress(address, bitcoincashtestnet); case Coin.firoTestNet: return Address.validateAddress(address, firoTestNetwork); case Coin.dogecoinTestNet: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index daaa76e01..87e3b44ed 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -115,6 +115,7 @@ class _SVG { // TODO provide proper assets String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; + String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg"; String get firoTestnet => "assets/svg/coin_icons/Firo.svg"; String get dogecoinTestnet => "assets/svg/coin_icons/Dogecoin.svg"; @@ -136,6 +137,8 @@ class _SVG { return namecoin; case Coin.bitcoinTestNet: return bitcoinTestnet; + case Coin.bitcoincashTestnet: + return bitcoinTestnet; case Coin.firoTestNet: return firoTestnet; case Coin.dogecoinTestNet: @@ -164,6 +167,7 @@ class _PNG { case Coin.bitcoinTestNet: return bitcoin; case Coin.bitcoincash: + case Coin.bitcoincashTestnet: return bitcoincash; case Coin.dogecoin: case Coin.dogecoinTestNet: diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index 93d1f2f03..fefc9522a 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -19,6 +19,7 @@ class _CoinThemeColor { case Coin.bitcoinTestNet: return bitcoin; case Coin.bitcoincash: + case Coin.bitcoincashTestnet: return bitcoincash; case Coin.dogecoin: case Coin.dogecoinTestNet: diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 843194ab9..0025c8cf8 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -65,6 +65,7 @@ abstract class Constants { return 600; case Coin.bitcoincash: + case Coin.bitcoincashTestnet: return 600; case Coin.dogecoin: diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 8f0a0f905..666a16032 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -143,6 +143,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get bitcoincashTestnet => NodeModel( + host: "testnet.hsmiths.com", + port: 53012, + name: defaultName, + id: _nodeId(Coin.bitcoincash), + useSSL: true, + enabled: true, + coinName: Coin.bitcoincash.name, + isFailover: true, + isDown: false, + ); + static NodeModel getNodeFor(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -169,6 +181,9 @@ abstract class DefaultNodes { case Coin.bitcoinTestNet: return bitcoinTestnet; + case Coin.bitcoincashTestnet: + return bitcoincashTestnet; + case Coin.firoTestNet: return firoTestnet; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index 1b642603e..c39761f54 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -24,6 +24,7 @@ enum Coin { /// bitcoinTestNet, + bitcoincashTestnet, dogecoinTestNet, firoTestNet, } @@ -50,6 +51,8 @@ extension CoinExt on Coin { return "Namecoin"; case Coin.bitcoinTestNet: return "tBitcoin"; + case Coin.bitcoincashTestnet: + return "tBitcoincash"; case Coin.firoTestNet: return "tFiro"; case Coin.dogecoinTestNet: @@ -75,6 +78,8 @@ extension CoinExt on Coin { return "NMC"; case Coin.bitcoinTestNet: return "tBTC"; + case Coin.bitcoincashTestnet: + return "tBCH"; case Coin.firoTestNet: return "tFIRO"; case Coin.dogecoinTestNet: @@ -101,6 +106,8 @@ extension CoinExt on Coin { return "namecoin"; case Coin.bitcoinTestNet: return "bitcoin"; + case Coin.bitcoincashTestnet: + return "bitcoincash"; case Coin.firoTestNet: return "firo"; case Coin.dogecoinTestNet: @@ -116,6 +123,7 @@ extension CoinExt on Coin { case Coin.firo: case Coin.namecoin: case Coin.bitcoinTestNet: + case Coin.bitcoincashTestnet: case Coin.firoTestNet: case Coin.dogecoinTestNet: return true; @@ -133,6 +141,7 @@ extension CoinExt on Coin { return btc.MINIMUM_CONFIRMATIONS; case Coin.bitcoincash: + case Coin.bitcoincashTestnet: return bch.MINIMUM_CONFIRMATIONS; case Coin.firo: @@ -182,6 +191,11 @@ Coin coinFromPrettyName(String name) { case "tBitcoin": case "bitcoinTestNet": return Coin.bitcoinTestNet; + + case "Bitcoincash Testnet": + case "tBitcoincash": + case "Bitcoin Cash Testnet": + return Coin.bitcoincashTestnet; case "Firo Testnet": case "tFiro": case "firoTestNet": @@ -214,6 +228,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.namecoin; case "tbtc": return Coin.bitcoinTestNet; + case "tbch": + return Coin.bitcoincashTestnet; case "tfiro": return Coin.firoTestNet; case "tdoge": diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index a9b402f60..cb79b905b 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -18,11 +18,6 @@ import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/prefs.dart'; - -// import '../../../cached_electrumx_test.mocks.dart'; -// import '../../../screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart'; -// import '../firo/firo_wallet_test.mocks.dart'; import 'bitcoincash_history_sample_data.dart'; import 'bitcoincash_transaction_data_samples.dart'; @@ -44,6 +39,11 @@ void main() { expect(GENESIS_HASH_MAINNET, "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); }); + + test("bitcoincash testnet genesis block hash", () async { + expect(GENESIS_HASH_TESTNET, + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"); + }); }); test("bitcoincash DerivePathType enum", () { @@ -95,7 +95,7 @@ void main() { test("valid mainnet legacy/p2pkh address type", () { expect( mainnetWallet?.addressType( - address: "DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), + address: "1DP3PUePwMa5CoZwzjznVKhzdLsZftjcAT"), DerivePathType.bip44); expect(secureStore?.interactions, 0); verifyNoMoreInteractions(client); @@ -140,21 +140,10 @@ void main() { verifyNoMoreInteractions(priceAPI); }); - test("valid mainnet bitcoincash legacy/p2pkh address", () { - expect( - mainnetWallet?.validateAddress("DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), - true); - expect(secureStore?.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - test("invalid mainnet bitcoincash legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), - false); + true); expect(secureStore?.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); @@ -230,9 +219,8 @@ void main() { group("basic getters, setters, and functions", () { final bchcoin = Coin.bitcoincash; - // final dtestcoin = Coin.bitcoincashTestNet; - final testWalletId = "DOGEtestWalletID"; - final testWalletName = "DOGEWallet"; + final testWalletId = "BCHtestWalletID"; + final testWalletName = "BCHWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; @@ -449,11 +437,11 @@ void main() { }); }); - group("DogeWallet service class functions that depend on shared storage", () { + group("BCHWallet service class functions that depend on shared storage", () { final bchcoin = Coin.bitcoincash; - // final dtestcoin = Coin.bitcoincashTestNet; - final testWalletId = "DOGEtestWalletID"; - final testWalletName = "DOGEWallet"; + final bchtestcoin = Coin.bitcoincashTestnet; + final testWalletId = "BCHtestWalletID"; + final testWalletName = "BCHWallet"; bool hiveAdaptersRegistered = false; @@ -589,7 +577,7 @@ void main() { "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, + "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", "services": [] }); @@ -822,7 +810,7 @@ void main() { bch = BitcoinCashWallet( walletId: testWalletId, walletName: testWalletName, - coin: bchcoin, + coin: bchtestcoin, client: client!, cachedClient: cachedClient!, tracker: tracker!, @@ -848,15 +836,15 @@ void main() { await bch?.initializeExisting(); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincash), + await bch!.currentReceivingAddress, bitcoincashtestnet), true); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincash), + await bch!.currentReceivingAddress, bitcoincashtestnet), true); expect( Address.validateAddress( - await bch!.currentReceivingAddress, bitcoincash), + await bch!.currentReceivingAddress, bitcoincashtestnet), true); verifyNever(client?.ping()).called(0); @@ -870,7 +858,7 @@ void main() { bch = BitcoinCashWallet( walletId: testWalletId, walletName: testWalletName, - coin: bchcoin, + coin: bchtestcoin, client: client!, cachedClient: cachedClient!, tracker: tracker!, @@ -899,7 +887,8 @@ void main() { expect(addresses?.length, 2); for (int i = 0; i < 2; i++) { - expect(Address.validateAddress(addresses![i], bitcoincash), true); + expect( + Address.validateAddress(addresses![i], bitcoincashtestnet), true); } verifyNever(client?.ping()).called(0); @@ -1081,7 +1070,7 @@ void main() { bch = BitcoinCashWallet( walletId: testWalletId, walletName: testWalletName, - coin: bchcoin, + coin: bchtestcoin, client: client!, cachedClient: cachedClient!, tracker: tracker!, @@ -1131,7 +1120,7 @@ void main() { bch = BitcoinCashWallet( walletId: testWalletId, walletName: testWalletName, - coin: bchcoin, + coin: bchtestcoin, client: client!, cachedClient: cachedClient!, tracker: tracker!, @@ -1188,28 +1177,28 @@ void main() { test("getTxCount succeeds", () async { when(client?.getHistory( scripthash: - "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + "1df1cab6d109d506aa424b00b6a013c5e1947dc13b78d62b4d0e9f518b3035d1")) .thenAnswer((realInvocation) async => [ { - "height": 4270352, + "height": 757727, "tx_hash": - "7b34e60cc37306f866667deb67b14096f4ea2add941fd6e2238a639000642b82" + "aaac451c49c2e3bcbccb8a9fded22257eeb94c1702b456171aa79250bc1b20e0" }, { - "height": 4274457, + "height": 0, "tx_hash": - "9cd994199f9ee58c823a03bab24da87c25e0157cb42c226e191aadadbb96e452" + "9ac29f35b72ca596bc45362d1f9556b0555e1fb633ca5ac9147a7fd467700afe" } ]); final count = - await bch?.getTxCount(address: "D6biRASajCy7GcJ8R6ZP4RE94fNRerJLCC"); + await bch?.getTxCount(address: "1MMi672ueYFXLLdtZqPe4FsrS46gNDyRq1"); expect(count, 2); verify(client?.getHistory( scripthash: - "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) + "1df1cab6d109d506aa424b00b6a013c5e1947dc13b78d62b4d0e9f518b3035d1")) .called(1); expect(secureStore?.interactions, 0); @@ -1218,7 +1207,7 @@ void main() { verifyNoMoreInteractions(tracker); verifyNoMoreInteractions(priceAPI); }); - + //TODO - Needs refactoring test("getTxCount fails", () async { when(client?.getHistory( scripthash: @@ -1233,10 +1222,10 @@ void main() { } expect(didThrow, true); - verify(client?.getHistory( + verifyNever(client?.getHistory( scripthash: "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) - .called(1); + .called(0); expect(secureStore?.interactions, 0); verifyNoMoreInteractions(client); @@ -1835,8 +1824,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); + when(client?.getBatchHistory(args: historyBatchArgs1)) + .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); @@ -2136,8 +2125,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); + when(client?.getBatchHistory(args: historyBatchArgs1)) + .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); From f9c58597561c3f5e4b28dc5c4df22ae311e49d83 Mon Sep 17 00:00:00 2001 From: Likho Date: Fri, 16 Sep 2022 15:44:27 +0200 Subject: [PATCH 18/20] WIP: TEsting bch --- .../coins/bitcoincash/bitcoincash_wallet.dart | 5 + .../bitcoincash/bitcoincash_wallet_test.dart | 14 +- .../bitcoincash_wallet_test.mocks.dart | 444 +++++++++--------- 3 files changed, 232 insertions(+), 231 deletions(-) diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 56f6d3aaf..aa83ea4a9 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -309,6 +309,11 @@ class BitcoinCashWallet extends CoinServiceAPI { throw Exception("genesis hash does not match main net!"); } break; + case Coin.bitcoincashTestnet: + if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + throw Exception("genesis hash does not match test net!"); + } + break; default: throw Exception( "Attempted to generate a BitcoinCashWallet using a non bch coin type: ${coin.name}"); diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index cb79b905b..5628c81f3 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -25,8 +25,8 @@ import 'bitcoincash_utxo_sample_data.dart'; import 'bitcoincash_wallet_test.mocks.dart'; import 'bitcoincash_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +@GenerateMocks([CachedElectrumX, PriceAPI, TransactionNotificationTracker], + customMocks: [MockSpec(returnNullOnMissingStub: true)]) void main() { group("bitcoincash constants", () { test("bitcoincash minimum confirmations", () async { @@ -2080,8 +2080,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); + when(client?.getBatchHistory(args: historyBatchArgs1)) + .thenAnswer((_) async => historyBatchResponse); final wallet = await Hive.openBox(testWalletId); @@ -2099,7 +2099,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); expect(secureStore?.interactions, 6); expect(secureStore?.writes, 3); @@ -2125,8 +2125,8 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index f24f8be4c..fa38e1f9e 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -2,18 +2,18 @@ // in stackwallet/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart. // Do not manually edit this file. -import 'dart:async' as _i6; +import 'dart:async' as _i7; -import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; +import 'package:decimal/decimal.dart' as _i4; +import 'package:http/http.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i5; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i6; import 'package:stackwallet/services/price.dart' as _i9; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i11; import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; -import 'package:stackwallet/utilities/prefs.dart' as _i3; +import 'package:stackwallet/utilities/prefs.dart' as _i2; import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint @@ -26,208 +26,16 @@ import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeDecimal_0 extends _i1.Fake implements _i2.Decimal {} +class _FakePrefs_0 extends _i1.Fake implements _i2.Prefs {} -class _FakePrefs_1 extends _i1.Fake implements _i3.Prefs {} +class _FakeClient_1 extends _i1.Fake implements _i3.Client {} -class _FakeClient_2 extends _i1.Fake implements _i4.Client {} - -/// A class which mocks [ElectrumX]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { - MockElectrumX() { - _i1.throwOnMissingStub(this); - } - - @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => - super.noSuchMethod(Invocation.setter(#failovers, _failovers), - returnValueForMissingStub: null); - @override - int get currentFailoverIndex => - (super.noSuchMethod(Invocation.getter(#currentFailoverIndex), - returnValue: 0) as int); - @override - set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod( - Invocation.setter(#currentFailoverIndex, _currentFailoverIndex), - returnValueForMissingStub: null); - @override - String get host => - (super.noSuchMethod(Invocation.getter(#host), returnValue: '') as String); - @override - int get port => - (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); - @override - bool get useSSL => - (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) - as bool); - @override - _i6.Future request( - {String? command, - List? args = const [], - Duration? connectionTimeout = const Duration(seconds: 60), - String? requestID, - int? retries = 2}) => - (super.noSuchMethod( - Invocation.method(#request, [], { - #command: command, - #args: args, - #connectionTimeout: connectionTimeout, - #requestID: requestID, - #retries: retries - }), - returnValue: Future.value()) as _i6.Future); - @override - _i6.Future>> batchRequest( - {String? command, - Map>? args, - Duration? connectionTimeout = const Duration(seconds: 60), - int? retries = 2}) => - (super.noSuchMethod( - Invocation.method(#batchRequest, [], { - #command: command, - #args: args, - #connectionTimeout: connectionTimeout, - #retries: retries - }), - returnValue: Future>>.value( - >[])) - as _i6.Future>>); - @override - _i6.Future ping({String? requestID, int? retryCount = 1}) => - (super.noSuchMethod( - Invocation.method( - #ping, [], {#requestID: requestID, #retryCount: retryCount}), - returnValue: Future.value(false)) as _i6.Future); - @override - _i6.Future> getBlockHeadTip({String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getBlockHeadTip, [], {#requestID: requestID}), - returnValue: - Future>.value({})) - as _i6.Future>); - @override - _i6.Future> getServerFeatures({String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getServerFeatures, [], {#requestID: requestID}), - returnValue: - Future>.value({})) as _i6 - .Future>); - @override - _i6.Future broadcastTransaction({String? rawTx, String? requestID}) => - (super.noSuchMethod( - Invocation.method(#broadcastTransaction, [], - {#rawTx: rawTx, #requestID: requestID}), - returnValue: Future.value('')) as _i6.Future); - @override - _i6.Future> getBalance( - {String? scripthash, String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getBalance, [], - {#scripthash: scripthash, #requestID: requestID}), - returnValue: - Future>.value({})) - as _i6.Future>); - @override - _i6.Future>> getHistory( - {String? scripthash, String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getHistory, [], - {#scripthash: scripthash, #requestID: requestID}), - returnValue: Future>>.value( - >[])) - as _i6.Future>>); - @override - _i6.Future>>> getBatchHistory( - {Map>? args}) => - (super.noSuchMethod( - Invocation.method(#getBatchHistory, [], {#args: args}), - returnValue: Future>>>.value( - >>{})) as _i6 - .Future>>>); - @override - _i6.Future>> getUTXOs( - {String? scripthash, String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getUTXOs, [], {#scripthash: scripthash, #requestID: requestID}), - returnValue: Future>>.value( - >[])) as _i6 - .Future>>); - @override - _i6.Future>>> getBatchUTXOs( - {Map>? args}) => - (super.noSuchMethod(Invocation.method(#getBatchUTXOs, [], {#args: args}), - returnValue: Future>>>.value( - >>{})) as _i6 - .Future>>>); - @override - _i6.Future> getTransaction( - {String? txHash, bool? verbose = true, String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getTransaction, [], - {#txHash: txHash, #verbose: verbose, #requestID: requestID}), - returnValue: - Future>.value({})) - as _i6.Future>); - @override - _i6.Future> getAnonymitySet( - {String? groupId = r'1', - String? blockhash = r'', - String? requestID}) => - (super.noSuchMethod( - Invocation.method(#getAnonymitySet, [], { - #groupId: groupId, - #blockhash: blockhash, - #requestID: requestID - }), - returnValue: - Future>.value({})) - as _i6.Future>); - @override - _i6.Future getMintData({dynamic mints, String? requestID}) => - (super.noSuchMethod( - Invocation.method( - #getMintData, [], {#mints: mints, #requestID: requestID}), - returnValue: Future.value()) as _i6.Future); - @override - _i6.Future> getUsedCoinSerials( - {String? requestID, int? startNumber}) => - (super.noSuchMethod( - Invocation.method(#getUsedCoinSerials, [], - {#requestID: requestID, #startNumber: startNumber}), - returnValue: - Future>.value({})) - as _i6.Future>); - @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( - Invocation.method(#getLatestCoinId, [], {#requestID: requestID}), - returnValue: Future.value(0)) as _i6.Future); - @override - _i6.Future> getFeeRate({String? requestID}) => (super - .noSuchMethod(Invocation.method(#getFeeRate, [], {#requestID: requestID}), - returnValue: - Future>.value({})) as _i6 - .Future>); - @override - _i6.Future<_i2.Decimal> estimateFee({String? requestID, int? blocks}) => - (super.noSuchMethod( - Invocation.method( - #estimateFee, [], {#requestID: requestID, #blocks: blocks}), - returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) - as _i6.Future<_i2.Decimal>); - @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( - Invocation.method(#relayFee, [], {#requestID: requestID}), - returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) - as _i6.Future<_i2.Decimal>); -} +class _FakeDecimal_2 extends _i1.Fake implements _i4.Decimal {} /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i5.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -244,44 +52,44 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) as bool); @override - _i3.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), - returnValue: _FakePrefs_1()) as _i3.Prefs); + _i2.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), + returnValue: _FakePrefs_0()) as _i2.Prefs); @override - List<_i5.ElectrumXNode> get failovers => + List<_i6.ElectrumXNode> get failovers => (super.noSuchMethod(Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[]) as List<_i5.ElectrumXNode>); + returnValue: <_i6.ElectrumXNode>[]) as List<_i6.ElectrumXNode>); @override - _i6.Future> getAnonymitySet( + _i7.Future> getAnonymitySet( {String? groupId, String? blockhash = r'', _i8.Coin? coin}) => (super.noSuchMethod( Invocation.method(#getAnonymitySet, [], {#groupId: groupId, #blockhash: blockhash, #coin: coin}), returnValue: Future>.value({})) - as _i6.Future>); + as _i7.Future>); @override - _i6.Future> getTransaction( + _i7.Future> getTransaction( {String? txHash, _i8.Coin? coin, bool? verbose = true}) => (super.noSuchMethod( Invocation.method(#getTransaction, [], {#txHash: txHash, #coin: coin, #verbose: verbose}), returnValue: Future>.value({})) - as _i6.Future>); + as _i7.Future>); @override - _i6.Future> getUsedCoinSerials( + _i7.Future> getUsedCoinSerials( {_i8.Coin? coin, int? startNumber = 0}) => (super.noSuchMethod( Invocation.method(#getUsedCoinSerials, [], {#coin: coin, #startNumber: startNumber}), returnValue: Future>.value([])) - as _i6.Future>); + as _i7.Future>); @override - _i6.Future clearSharedTransactionCache({_i8.Coin? coin}) => + _i7.Future clearSharedTransactionCache({_i8.Coin? coin}) => (super.noSuchMethod( Invocation.method(#clearSharedTransactionCache, [], {#coin: coin}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); } /// A class which mocks [PriceAPI]. @@ -293,21 +101,21 @@ class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { } @override - _i4.Client get client => (super.noSuchMethod(Invocation.getter(#client), - returnValue: _FakeClient_2()) as _i4.Client); + _i3.Client get client => (super.noSuchMethod(Invocation.getter(#client), + returnValue: _FakeClient_1()) as _i3.Client); @override void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( Invocation.method(#resetLastCalledToForceNextCallToUpdateCache, []), returnValueForMissingStub: null); @override - _i6.Future>> + _i7.Future>> getPricesAnd24hChange({String? baseCurrency}) => (super.noSuchMethod( Invocation.method( #getPricesAnd24hChange, [], {#baseCurrency: baseCurrency}), returnValue: - Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{})) - as _i6.Future>>); + Future>>.value( + <_i8.Coin, _i10.Tuple2<_i4.Decimal, double>>{})) + as _i7.Future>>); } /// A class which mocks [TransactionNotificationTracker]. @@ -336,17 +144,205 @@ class MockTransactionNotificationTracker extends _i1.Mock (super.noSuchMethod(Invocation.method(#wasNotifiedPending, [txid]), returnValue: false) as bool); @override - _i6.Future addNotifiedPending(String? txid) => + _i7.Future addNotifiedPending(String? txid) => (super.noSuchMethod(Invocation.method(#addNotifiedPending, [txid]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod(Invocation.method(#wasNotifiedConfirmed, [txid]), returnValue: false) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => + _i7.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod(Invocation.method(#addNotifiedConfirmed, [txid]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); +} + +/// A class which mocks [ElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { + @override + set failovers(List<_i6.ElectrumXNode>? _failovers) => + super.noSuchMethod(Invocation.setter(#failovers, _failovers), + returnValueForMissingStub: null); + @override + int get currentFailoverIndex => + (super.noSuchMethod(Invocation.getter(#currentFailoverIndex), + returnValue: 0) as int); + @override + set currentFailoverIndex(int? _currentFailoverIndex) => super.noSuchMethod( + Invocation.setter(#currentFailoverIndex, _currentFailoverIndex), + returnValueForMissingStub: null); + @override + String get host => + (super.noSuchMethod(Invocation.getter(#host), returnValue: '') as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i7.Future request( + {String? command, + List? args = const [], + Duration? connectionTimeout = const Duration(seconds: 60), + String? requestID, + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#request, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #requestID: requestID, + #retries: retries + }), + returnValue: Future.value()) as _i7.Future); + @override + _i7.Future>> batchRequest( + {String? command, + Map>? args, + Duration? connectionTimeout = const Duration(seconds: 60), + int? retries = 2}) => + (super.noSuchMethod( + Invocation.method(#batchRequest, [], { + #command: command, + #args: args, + #connectionTimeout: connectionTimeout, + #retries: retries + }), + returnValue: Future>>.value( + >[])) + as _i7.Future>>); + @override + _i7.Future ping({String? requestID, int? retryCount = 1}) => + (super.noSuchMethod( + Invocation.method( + #ping, [], {#requestID: requestID, #retryCount: retryCount}), + returnValue: Future.value(false)) as _i7.Future); + @override + _i7.Future> getBlockHeadTip({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBlockHeadTip, [], {#requestID: requestID}), + returnValue: + Future>.value({})) + as _i7.Future>); + @override + _i7.Future> getServerFeatures({String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getServerFeatures, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i7 + .Future>); + @override + _i7.Future broadcastTransaction({String? rawTx, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#broadcastTransaction, [], + {#rawTx: rawTx, #requestID: requestID}), + returnValue: Future.value('')) as _i7.Future); + @override + _i7.Future> getBalance( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getBalance, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i7.Future>); + @override + _i7.Future>> getHistory( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getHistory, [], + {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) + as _i7.Future>>); + @override + _i7.Future>>> getBatchHistory( + {Map>? args}) => + (super.noSuchMethod( + Invocation.method(#getBatchHistory, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i7 + .Future>>>); + @override + _i7.Future>> getUTXOs( + {String? scripthash, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getUTXOs, [], {#scripthash: scripthash, #requestID: requestID}), + returnValue: Future>>.value( + >[])) as _i7 + .Future>>); + @override + _i7.Future>>> getBatchUTXOs( + {Map>? args}) => + (super.noSuchMethod(Invocation.method(#getBatchUTXOs, [], {#args: args}), + returnValue: Future>>>.value( + >>{})) as _i7 + .Future>>>); + @override + _i7.Future> getTransaction( + {String? txHash, bool? verbose = true, String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #verbose: verbose, #requestID: requestID}), + returnValue: + Future>.value({})) + as _i7.Future>); + @override + _i7.Future> getAnonymitySet( + {String? groupId = r'1', + String? blockhash = r'', + String? requestID}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], { + #groupId: groupId, + #blockhash: blockhash, + #requestID: requestID + }), + returnValue: + Future>.value({})) + as _i7.Future>); + @override + _i7.Future getMintData({dynamic mints, String? requestID}) => + (super.noSuchMethod( + Invocation.method( + #getMintData, [], {#mints: mints, #requestID: requestID}), + returnValue: Future.value()) as _i7.Future); + @override + _i7.Future> getUsedCoinSerials( + {String? requestID, int? startNumber}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#requestID: requestID, #startNumber: startNumber}), + returnValue: + Future>.value({})) + as _i7.Future>); + @override + _i7.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + Invocation.method(#getLatestCoinId, [], {#requestID: requestID}), + returnValue: Future.value(0)) as _i7.Future); + @override + _i7.Future> getFeeRate({String? requestID}) => (super + .noSuchMethod(Invocation.method(#getFeeRate, [], {#requestID: requestID}), + returnValue: + Future>.value({})) as _i7 + .Future>); + @override + _i7.Future<_i4.Decimal> estimateFee({String? requestID, int? blocks}) => + (super.noSuchMethod( + Invocation.method( + #estimateFee, [], {#requestID: requestID, #blocks: blocks}), + returnValue: Future<_i4.Decimal>.value(_FakeDecimal_2())) + as _i7.Future<_i4.Decimal>); + @override + _i7.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + Invocation.method(#relayFee, [], {#requestID: requestID}), + returnValue: Future<_i4.Decimal>.value(_FakeDecimal_2())) + as _i7.Future<_i4.Decimal>); } From 76a4a7acd86418bd0bfb5ee1a0f0b1d39a5cb922 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 16 Sep 2022 07:55:40 -0600 Subject: [PATCH 19/20] update gitignore --- .gitignore | 2 ++ .../bitcoincash_wallet_test_parameters.dart | 13 ------------- .../namecoin/namecoin_wallet_test_parameters.dart | 0 3 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart delete mode 100644 test/services/coins/namecoin/namecoin_wallet_test_parameters.dart diff --git a/.gitignore b/.gitignore index cf2d96802..ffe41fd34 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,8 @@ lib/generated_plugin_registrant.dart test/services/coins/bitcoin/bitcoin_wallet_test_parameters.dart test/services/coins/firo/firo_wallet_test_parameters.dart test/services/coins/dogecoin/dogecoin_wallet_test_parameters.dart +test/services/coins/namecoin/namecoin_wallet_test_parameters.dart +test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart /integration_test/private.dart # Exceptions to above rules. diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart deleted file mode 100644 index c47053955..000000000 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart +++ /dev/null @@ -1,13 +0,0 @@ -const TEST_MNEMONIC = - "market smart dolphin hero liberty frog bubble tilt river electric ensure orient"; - -const ROOT_WIF = "KxpP3c45bRPbPzecYfistA5u3Z4SSGCCiCS4pjWrS5LF1g4mXC66"; -const NODE_WIF_44 = "KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o"; -const NODE_WIF_49 = "L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg"; -const NODE_WIF_84 = "cU1E2J1eHbQh36TCtsqneAsKaXRwRhSs74GEDPXWCGY7f1xpsymM"; - -const FILL_ADDRESSES_EXPECTED_CHANGE_MAINNET = - "{0: {p2pkh: {publicKey: 02a1a969c63ed41662a23afc640f76ee4a53c35fba4a13ff871931849abdc0670c, wif: Kws4oRj4Z3uT4WNejbuLsWvqTeso4dGciVu75jjd5fa36Tst48ER, address: 1JfZd6cSNr924p4ZwewG1CeRw7AqoYQ6uE}, p2sh: {publicKey: 020cebf05d41da01c00fbb7f7c04cf85741188da0e5ee22ae9fac6e7b5098b8cac, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3ANTVqufTH1tLAuoQHhng8jndRsA9hcNy7}, p2wpkh: {publicKey: 0301def02c073852fc8c28182749be431ac22a2699b51c896fbc48457107c10c65, wif: L3UwhkWiXSAyCWYVjaFCyMhGcqeDU8soCmqhuFYFadcVQepKKcjy, address: bc1qwt76574cgnhjv0nx4f52qylyla0t50d60znk5u}}, 1: {p2pkh: {publicKey: 02617b5f7868bd0402a3e3ff6fa224aede55f13218bbef9987467455d068d629cd, wif: KzDwZXPyNhPix2nV5wri4QaGj6Tge7znvf49D5tgL7EvAnYV6Pas, address: 1vFHF5q21GccoBwrB4zEUAs9i3Bfx797U}, p2sh: {publicKey: 03b4c1e17b7257850fd2c0cf69acd397616835de29867cfe047bf1064b9803f773, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3LNBen8sNa6kXsWuMc6tynHBQr3vUHn3nL}, p2wpkh: {publicKey: 038b646c426ed75204bd92f55f0519fb89c41a503844aecf187a55eb00a5d8b5eb, wif: L1jQHTyFP4sNyqdKeooJzw3AUghPeSmGoW7UmZwDsS1Js4UxiGyh, address: bc1qn2x7h96kufgfjxtkhsnq03jqwqde8zasffqvd2}}, 2: {p2pkh: {publicKey: 031643235b0355ac366ade3ec125e466be42602a6167ba1cc68f39c38929f3c785, wif: KzqPH523RyAKwzRDg8PqjvNVDV8Fex2qrKxwwLGcvc9XZHfvaErb, address: 1EwEMkfeivF8nEkG3tWhBSqibnDjoREBoF}, p2sh: {publicKey: 02312b44be7de07c21bed9bd8b0c2f3d64be41c5f5120a463fcb85371b3f44cce1, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3MPyLcCkGPKyZULKSzkYDZQxxMmG8HNRPi}, p2wpkh: {publicKey: 031a10a60de8563b1be55e8274538d6ec6375d19764e81303999de2605634ea15a, wif: L2u4AYxEKkDamcXjAfXviuTPuRuWe1aN9zXEX1PSpzrmqGtSHQqe, address: bc1qpcn5wd2cx7syc28r0t4w4ym8yy6fck87nrxn4p}}, 3: {p2pkh: {publicKey: 02f552c7b15e90df9ff99f35e8b5bd84eff0bdef6cb6c75e13d1df81d334c6b786, wif: Kxi7qVxEqwaBhR3tuewpfi8EDqqR7fBzgQUDambVWGPEP3oG9JUM, address: 1MifgAa6CzqmTF4euVSD2DivD3xUGDbuA5}, p2sh: {publicKey: 02a7ba8279be4c182292b855cbf4349dba68f8a776b9b8e04e05585ac9f605e7f1, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 34pa7nXRZ3XahoFKhhnaLy4DqzfC9v5aSm}, p2wpkh: {publicKey: 03d712c240ccd578b8fb59acf88a8fbacf2ab14301a900e7907c739244e61029bd, wif: L3ezWHMUvdwprhChDVn7iVV8otpLUBrdkZB7X97GEbiVZbxJkDPc, address: bc1qemkha74vvmlz8yg56tesswpz77wg07m9wu23sc}}, 4: {p2pkh: {publicKey: 03d86c453c6b8ef6239db8fc270c76a9b08faf2c58f3de74cbe2cb0bc0cef7139a, wif: KzNpx8vgcNbYcqPZxHFwvKSRwPpyZNhmF4Eqa4k1wuWJZovwaXNC, address: 1BxYVw7u85QWVoVCHJAJkCCZZjWeXYW6EA}, p2sh: {publicKey: 020e9095a31b9c1e580b3773d34d1f752f9c4de42feb1c3480f5234a2392762fbc, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 33tciSXesiShRULW4bds5BE8358GsJc8CA}, p2wpkh: {publicKey: 02908673d298d4929a724ff9627c3fc710e27f0d727fbb0f2741c00dac1d792120, wif: L5QYEMxjY42c9JnpEQCDUbqCUfZ1vKoELe41rREuGzszcZgzVepx, address: bc1qpl7rh3ykw5h9823wv3uw0jrehlty6dfjp65ykt}}, 5: {p2pkh: {publicKey: 0368fda0552db99cfac045f57327cecd9b365d8813982dda051f1156ceefacd80b, wif: Ky8KYn2vMmWiMw6QEnE6JsM3uwQgAZBjiUHB938MVtnSgpiDjh7o, address: 14g637PwocgKr3S949WDitY7Bj3L1Bttcb}, p2sh: {publicKey: 03a1c4977ec9e9e02ba011cf56cee34c1617a7e05e85183a4207efd6425079a914, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 34Z5gAgPiUsfA9VHdAss2AD5h5Xk5TEp9K}, p2wpkh: {publicKey: 028c44701f2e918ba9853737aa1c6c7b5302a4c9198bf0eb22a75fb6fd3ef4dc9e, wif: Kx7wWb9xvNTaiWkzEmLqJ7gsQEaD6VPJTodr71rQwtpCU5dZDbEP, address: bc1q0ja83w4jwplrxjggcy8ltnrz70qrhnsmfwjv47}}, 6: {p2pkh: {publicKey: 03844bb44c430540dd2fd42630200a059f06480e024b7a06ea628b5094f48b944f, wif: Kwe6hMa7t9qcWW7fHBFTpBDDyzBdWzkHjhngiTLRKZzBjUqqppRJ, address: 1JNghjiaNWrWgvAJnmGyyFEbQMWV35QT1D}, p2sh: {publicKey: 03add39947c251a1f42f2fe4dbf41ea6dfed2bcf56bbe5ab6c44213235b68a1d0a, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3AnduCZuFcXFtBjnz7Y6vRJBwYvfeNKJtR}, p2wpkh: {publicKey: 02883642dad55a3f5003440ff7e066c97624539841643e900db80eaac7ebab7270, wif: L4sisqYU1xSzbnx25xuyLefifGL5BoGDXh5Vsex9UCBSDHhFYt8h, address: bc1qdjjssjd6vl6e3egqts7qxcj6dqrs6krgj4dv3q}}, 7: {p2pkh: {publicKey: 03487700edb11cf39b095b17da1f44f702398bd707dfdace4d88ee0227a48c8a0c, wif: L3UmMi9h8RQ8Y8jKATFjum77kc9ng6Jr71fq1H9rGxxdspKGXt12, address: 1Q1FMnBJxQ5uh2UDQbvwQ1y1KMpqVd3whq}, p2sh: {publicKey: 03a600d4880ce8a53287f25b6534f97a689ba7006f665ad1fe3e2bb8d3c9620ec5, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 3J9GqZ2b5raBNyGmuBg8ttyg5x8zaqSrMp}, p2wpkh: {publicKey: 035b91a2c439d6cc34a706449b8d9843721cc499aec4b38ebdd211978a52431fc0, wif: KzJZE96Hjwn2dtuYpBgMoR9s86TgQ3apRsNTxGZTpyW2RVA6HppV, address: bc1qs2df496wpp4339qjuypc2l2r6e0z7vndz70ac7}}, 8: {p2pkh: {publicKey: 0254ff6bad342ae9af2e04ee9d876682771ad86a76336a817eadb2e4e5bf68070a, wif: KxhFsZNv8XDUxLECR9y965eWFSGBbc2FrTkRvuyRPwPM9xxeCfV4, address: 1JD8uzAKQo9DgR2om6gS98S92cgqscZ3Ur}, p2sh: {publicKey: 02f574d6a16300e4d497647814fd2b0fa2eaba32af19fcf65821f70743bc566f9d, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3BqGdLjubq2vAGRDNRkxk5uMqd6FPoMg7N}, p2wpkh: {publicKey: 0227cb798c29365364027e5df67f6890985fd89ecbca571e51bb6260272cc66ec1, wif: Kxrpxu5kNAdy9jqz7etcCg716gsEk1K5weiMLKg1fhLPdxitvzsy, address: bc1qhpvcc08dxa7m5ztkxsfsplmj69yzsg6p3dna63}}, 9: {p2pkh: {publicKey: 02b8b1a447c71bb4ed82fd3719ec9e665df5c9c6e31c7a14beb56c65f355a08bc4, wif: Ky9kWxSwqBQP9ShkZNCfLwDqpzqnDEvQG3NjfYY3A3VFE4nPLqrs, address: 16EpYJQoM1K2ZKFUEMBAJubiNsMghkvBUx}, p2sh: {publicKey: 03f6cc36b17f382490d1a08bc9f7c28302c704c69cb6c153d2828710cd93a1c215, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3HBjEYyPH9H8JvuVbibNaZ2bJyD8564r1Y}, p2wpkh: {publicKey: 030f021f31d9eac4279f8ae12a512885769978dc93a74e0e4c91eba994a7d02cda, wif: L26bxcZZ8PLaa23X9myLgzh3hAYxjQ9MsSfiz49yiEdys7dzr66z, address: bc1q6hxsggrtcrvcdxpzu7e8qv2m9md3zaghjjsxut}}}"; - -const FILL_ADDRESSES_EXPECTED_RECEIVING_MAINNET = - "{0: {p2pkh: {publicKey: 0359bc5f4918d68ab3730886c4fdebec1cfdeba9db1170582da7072183d7afce99, wif: KyaVkL3X4jUdq16CBrTR72VyQkLuczNyi9v6ztwZKdTm3dt7x57o, address: 1GDtrDP62zQETQESHt48Z82afXWcvX8qNv}, p2sh: {publicKey: 02addc76ef5845c0e74e5ada1d97568d0ee76856031e60a084f6f6a0e7be51d84b, wif: L3jFGqrCRMENcCofASf8m6vMNfYutxdqLe733NwEyXiKTBkkgqyg, address: 3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk}, p2wpkh: {publicKey: 02e5b585efaec9e6486f4b8afb3415ffd71a89566c1f84bb5331c4cddc905b7555, wif: KzCvDQSH6qqUFhPnbM42zbLK3qPeHXKVVpc6A5bAqADanHQFqypQ, address: bc1qz36w7sv4lnt09saurf94lwk5tc6qsyhky9yfgm}}, 1: {p2pkh: {publicKey: 036070efb466bfbf689efe1e7a27fa405bdbb16fa4c836f7b71feeb7ec9f5c5db4, wif: L3aA8PRqcj1iuZnQMJ3M5x68zhywgwgGbzPtDsQMBWRmqUaKyyBQ, address: 1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw}, p2sh: {publicKey: 02b8f46d7741d709bc1dc81386edaf4e6dad8b78b1fb9ec0c5b1f8e08e7d72f395, wif: L1v4hFFe8EvUX7DBVxnfh7Y22A8nNwh7py42vfr5TU1yAAa9Eadb, address: 3Ggn4xZmXjMCDUdQSTjxTi1PdVrtDGUy5Z}, p2wpkh: {publicKey: 03627f4374d7c992c40d743786028f94b1e1ca354b94ffa776b51ba50c80fba1d7, wif: L5gAb3ABSYTBTL85FhoRxHa7dJMtz9sVLHWkEY7kYKXT2cDg71Ym, address: bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0}}, 2: {p2pkh: {publicKey: 03f2171f073a8fbf5b844a20e5b6ae356a07e9e6dc5af24cd4b3e54a1599eb4137, wif: L27AJt6ZRU4D3sf7Ls1HLq2ruF8c6iYq9iJVvW8Xm2kMKrKcPwzZ, address: 1Mu5SqaUBHB1HkyvEtjHjrNQVaai2MoFFz}, p2sh: {publicKey: 024e6d7230df59dc28f94ab7bd45c5aed53ac0ea96e8a1ee46edcca5594fa3ec66, wif: KxxRzHcF6QkApcLtbyxeyQrHgnstCqCPPfLiLfJhee9PVinisekA, address: 3K8CQxXWp2kjbGcnub4sh935H5s1AfrFmp}, p2wpkh: {publicKey: 0365b55bdae2d0ada31bfc73371f7106e0e20563dff0763c48da8ce2ca70346bd8, wif: KxttkPfRdY9uwEqgHGPo9d7Bb3qTsBzHSgmv8EVTS2Gbh88GgJZQ, address: bc1qsegc09enu2ts4dg7lnxee6tv8fy78m89cfzdy9}}, 3: {p2pkh: {publicKey: 020d00dd73194f8f087a3740594f2eeaa545224fb634462e06d281ad5759e77cbd, wif: L5Mdvj7tkY4Sx5JTiB1WtQJPogqXGLunaFAubAQMvuAK3TjuXXxD, address: 16d7AeqhmspaMyeJKF3cDwxjkLUTFZQdTn}, p2sh: {publicKey: 038d2aec8c0da1bafc3e51fe7fdd3aa60f121983071d97ed754c05f24c54d7d09b, wif: L3qZiaviwh76RvKj5i69vu75upS3E62qmEk6BSJRgSVjw7FmJNDW, address: 3QwDpsqwb5VfKQVUVLzaDwgC51pea1ymHr}, p2wpkh: {publicKey: 02f144cd8c1d6966db09da86bf662933480ad6e7f2862f7878780f21bc1448d563, wif: Ky9zeEcFB7td3dPYCiXxrMPCQkKyfm4feaWp5PAWxY7kBwCccUxz, address: bc1q3h5llpmhvr89el03dktd936jqsn7t6pytr2nlr}}, 4: {p2pkh: {publicKey: 038d6142ee0db16d4ff23c95d1c157428d48e11fae7752ddacaf6cccce6db61fc6, wif: Ky41iFhmo2Y5mYp1UALsYqRhhKajEy7AxLJcSDYRVdwW7iv3vmTK, address: 1EFyHH7G4Hk8aCjGWPer1Qc9e8iZsbfZC8}, p2sh: {publicKey: 03a1624a73009c17ceefe299ca998ee941b501692b2f3a032e037b50726415c78b, wif: Ky6NQbHkhBLS3LDLmCuu3TFBVU1HNah7TYUiLhmc3u8HdZgpKYSs, address: 38YQK65m9A278nCbWKTx3EJtyK9Vega1Z9}, p2wpkh: {publicKey: 031e816e4a35c686ee30387e01c26341927806674986a57ee5755114f3ee59d560, wif: L5AiWbctHW9EyaD5vyzCrxKmKppdMwdRFTvB1TUKLWvibo5R3QXa, address: bc1qj69ku48uu6lqu6uhd62a37zvuwta2dlc28frxc}}, 5: {p2pkh: {publicKey: 024697dbd5ad644c285fb9ce25a38d0f8b48de9dfbda146eba8f5659184fb2779c, wif: L3yze1iu58vmF7GyoUHqnmYCm4qLsBoGiyRgBPbt68gB3kKnQfCv, address: 16ZbDHYV97o6xeXoQMWus1a8NMNyxqHZon}, p2sh: {publicKey: 021b442ef944676b70e6ef23ecb932c015b6934ff5d094db88820735be36f6d807, wif: KzDtpdJFLQWLSRdiEzhrqE8rzDaRi43tkxJ6K9ANeGcExQMNWYLz, address: 3LFx71JddSzs8qE51P4JHHjxsgiSTZdk4z}, p2wpkh: {publicKey: 0307ffe6b1c0dfca40b6126140691a4d907a053dcfd97b816cc7ff86775486e612, wif: L3F1KPjtaA4uyfcmMSH2sFWs2TW2aX6Lcn8vetrdqtwgKgvfvVGg, address: bc1ql66zvg82lk425g8j4jx5flt255n8a9up328z3l}}, 6: {p2pkh: {publicKey: 02a5087e407ad853720a7010c1870bbc35a9ee09d90777c617801c983aaccc8523, wif: L1fLMxyhSKSsLskB3iBcfbfCinVr6MRE9YYVKXfCbLjPLZLAUpb7, address: 1FzukoiU7vXb7invLNLCCLAFdfK2gmP45A}, p2sh: {publicKey: 02d4f8bc88178a215b5c95e079b7c1ff0b62ec2884d5ffa71122683557a16615f3, wif: L27TRwwmSB98trgkBduvx1yKvmYbu77ffcF6BQKADfD94vNwvW4W, address: 3GpAXutnaxCYuV2MSgSiXQTSafE7Aouifs}, p2wpkh: {publicKey: 03f7eb3138cbaa7baabdef7b903ff1a3369a8d7937c15ae17dac7084aa4cfd8afe, wif: KyWW73wu11xVnmFKMzq9AuxQ3TXw2KjwCzxUa9WbYx2Ee5oh2Nk1, address: bc1q4ev5cas22cn8rna3a7je238p3plgqqdjv9xt0e}}, 7: {p2pkh: {publicKey: 0366efa2d1624fd9ff0d9feffa39ff47c10ef196fe209421dcebf5d07dcb8907ce, wif: KwxWpA4scQ7RVKDFKjXvKWn6PdVXpr6nzW9T6ju6VP3MmjBRb5eK, address: 13qhwGjBzshL724ZZNv2C2XmUNpFoDLx4Z}, p2sh: {publicKey: 03b08cabbcaa97c20bf30e0830c8c7a2361ba63d1ba987032ee7cd97d666b62692, wif: KwDwKEifSPJeQKTFonmyBTvCNuApuT9y3s7PqmnFkZBFkxSaQ6BQ, address: 39ZZhvQJGwohf2ndBGXYWW31t1E91j9wcB}, p2wpkh: {publicKey: 03a353139ce07e31b33f1c5b0ece6f08ac117339a1a56aa1b3af011216cf2cbf88, wif: KwL9LaYjNvwWiYgDiUBLnVW7W5j2FyWqmzrioTA2QRgwdz6bUTQH, address: bc1q96wmh0qc2lwauzxv7la206d03zysc9zfw3ajkd}}, 8: {p2pkh: {publicKey: 032234e3903ebcd72c081119fe4b4c2d98e3fd3940b5b6cd7737f5f4d3240dcb7d, wif: L5kwyvViXwBcFqzJL5cffcJR7S1vEHxHuKHAQrAq6FtknvJG4Cx3, address: 1LPGD9sExsBMTLGxnTTEDYBHrtRSZCKNrN}, p2sh: {publicKey: 02c1ca19626e4784b2ce19b68d2887e4c9c733176db189e536b71a79c07178b4af, wif: KypeLKRPCoN9eZQ8awgBca47JyD6scST44Pg4woLpDiyT3hy9wZA, address: 3FXRAzFAgVMDcqbv6WE9Ki7yBACrRAh2Wn}, p2wpkh: {publicKey: 020fd4e2cd43f98366dd60fcc8902564c532c9396c35ebb840b63c965deed16330, wif: L4EgRsvLsju7uVXsTEchYnGdwmFrxhbE9SkMuvnaMDSMxmopNg8C, address: bc1q354ttmgwjzjf7jmym2aw9p5geeq9lhk8vcffap}}, 9: {p2pkh: {publicKey: 03dd5e2f3b828bdc2653ce7a7ce34ebfa8118077b647bdf4922c5d15f4546dc419, wif: Ky4f8LEYkXMkLiYX4t2PR6V2vJigyCoYTYATu4B5pnjDDywUD2U7, address: 13GXUQDr91Z5prRxAQQFHUvKNqs2hEWrkJ}, p2sh: {publicKey: 034a4cf3a57083ade346ecaab24dc931e2a991107c7c42cf170d1c6e3b84560e68, wif: L1zutoVai5EqS2LoAaEq16R2rcooiP6HAdA5zETwbvuYBky3neKw, address: 3FstRy5yCE7a3wCNS8SgFKi4SF3zWK8vB6}, p2wpkh: {publicKey: 020cb048fc44a2db09be875ccd938634c557b19e2062fbc55372783be1116737e7, wif: KyDq3LHf9NkUWChPxVEAV3dQkX89GJWUkSw8cSVLooXronEwKouy, address: bc1qn99xewnyhqcleyj03f2y0crdhqmxhm5cmextw8}}}"; diff --git a/test/services/coins/namecoin/namecoin_wallet_test_parameters.dart b/test/services/coins/namecoin/namecoin_wallet_test_parameters.dart deleted file mode 100644 index e69de29bb..000000000 From 5267d9198b56bacc75afe3d889c5b16c0754feb5 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 16 Sep 2022 08:36:30 -0600 Subject: [PATCH 20/20] update fullRescan succeeds test and sample data --- .../bitcoincash_history_sample_data.dart | 24 ++-- .../bitcoincash/bitcoincash_wallet_test.dart | 107 +----------------- 2 files changed, 16 insertions(+), 115 deletions(-) diff --git a/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart b/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart index c34f68865..42312585e 100644 --- a/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart +++ b/test/services/coins/bitcoincash/bitcoincash_history_sample_data.dart @@ -16,20 +16,20 @@ final Map> historyBatchArgs0 = { }; final Map> historyBatchArgs1 = { - "k_0_0": ["4061323fc54ad0fd2fb6d3fd3af583068d7a733f562242a71e00ea7a82fb482b"], - "k_0_1": ["04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b"], - "k_0_2": ["a0345933dd4146905a279f9aa35c867599fec2c52993a8f5da3a477acd0ebcfc"], - "k_0_3": ["607bc74daf946bfd9d593606f4393e44555a3dd0b529ddd08a0422be7955912e"], - "k_0_4": ["449dfb82e6f09f7e190f21fe63aaad5ccb854ba1f44f0a6622f6d71fff19fc63"], - "k_0_5": ["3643e3fe26e0b08dcbc89c47efce3b3264f361160341e3c2a6c73681dde12d39"], - "k_0_6": ["6daca5039b35adcbe62441b68eaaa48e9b0a806ab5a34314bd394b9b5c9289e5"], - "k_0_7": ["113f3d214f202795fdc3dccc6942395812270e787abb88fe4ddfa14f33d62d6f"], - "k_0_8": ["5dea575b85959647509d2ab3c92cda3776a4deba444486a7925ae3b71306e7e3"], - "k_0_9": ["5e2e6d3b43dfa29fabf66879d9ba67e4bb2f9f7ed10cfbb75e0b445eb4b84287"], + "k_0_0": ["50550ac9d45b7484b41e32751326127f3e121354e3bceead3e5fd020c94c4fe1"], + "k_0_1": ["f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34"], + "k_0_2": ["f729a8b3d47b265bf78ee78216174f3f5ef44aedfebf2d3224f1afadcfd6b52b"], + "k_0_3": ["82f5da8c4d26af2898dbb947c6afb83b5ad92e609345f1b89819293dd7714c75"], + "k_0_4": ["b4d6bf5639a8cd368772c26da95173940510618023e8952eb8db70aeb1d59cd2"], + "k_0_5": ["12e0f3cb2bf44b80f3c34cfd3fadc2a39de2f4776bc2be5b7100126db1238983"], + "k_0_6": ["ed5351a1e390d6635fa1ccf594998eb82fa627caf93541f3d5f1021b90e75ec7"], + "k_0_7": ["97917c094ec3afcd1b41338e7c06774b2f76c7a430e486c0080a86a141f39723"], + "k_0_8": ["58f96c6274cd3b74d362a30778497cef65f0c657ce94bb8b274b802e47876e3c"], + "k_0_9": ["99fb86f164906c621a42ee2b224972b3ea8ce10dbc1bccecbbdb1a7582e2954a"], "k_0_10": [ - "1bfe42869b6b1e5efa1e1b47f382615e3d27e3e66e9cc8ae46b71ece067b4d37" + "555b8d6a03d2b93c381d2cda19fac11034bf5128ccbcbe5ff46b87f17969b4cb" ], - "k_0_11": ["e0b38e944c5343e67c807a334fcf4b6563a6311447c99a105a0cf2cc3594ad11"] + "k_0_11": ["9d0163f011c1259568c188c4770606b25c823f8b76bbd262c1c7f3095ed24620"] }; final Map>> historyBatchResponse = { diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index 5628c81f3..c625dd4b7 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -20,8 +18,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'bitcoincash_history_sample_data.dart'; -import 'bitcoincash_transaction_data_samples.dart'; -import 'bitcoincash_utxo_sample_data.dart'; import 'bitcoincash_wallet_test.mocks.dart'; import 'bitcoincash_wallet_test_parameters.dart'; @@ -1811,102 +1807,6 @@ void main() { // // verifyNoMoreInteractions(priceAPI); // // }); - 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(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .thenAnswer((realInvocation) async {}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('changeIndexP2PKH', 123); - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - - bool hasThrown = false; - try { - await bch?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .called(1); - - expect(secureStore?.writes, 9); - expect(secureStore?.reads, 12); - expect(secureStore?.deletes, 2); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - test("get mnemonic list", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { "hosts": {}, @@ -2125,8 +2025,9 @@ void main() { }); when(client?.getBatchHistory(args: historyBatchArgs0)) .thenAnswer((_) async => historyBatchResponse); - // when(client?.getBatchHistory(args: historyBatchArgs1)) - // .thenAnswer((_) async => historyBatchResponse); + when(client?.getBatchHistory(args: historyBatchArgs1)) + .thenAnswer((_) async => historyBatchResponse); + when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .thenAnswer((realInvocation) async {}); @@ -2194,7 +2095,7 @@ void main() { verify(client?.getServerFeatures()).called(1); verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) .called(1);