// /* // * This file is part of Stack Wallet. // * // * Copyright (c) 2023 Cypher Stack // * All Rights Reserved. // * The code is distributed under GPLv3 license, see LICENSE file for details. // * Generated by Cypher Stack on 2023-05-26 // * // */ // // import 'dart:async'; // import 'dart:convert'; // import 'dart:io'; // import 'dart:isolate'; // import 'dart:math'; // // import 'package:bip32/bip32.dart' as bip32; // import 'package:bip39/bip39.dart' as bip39; // import 'package:bitcoindart/bitcoindart.dart'; // import 'package:decimal/decimal.dart'; // import 'package:flutter/foundation.dart'; // import 'package:isar/isar.dart'; // import 'package:lelantus/lelantus.dart'; // import 'package:stackwallet/db/hive/db.dart'; // import 'package:stackwallet/db/isar/main_db.dart'; // import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart'; // import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; // import 'package:stackwallet/models/balance.dart'; // import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; // import 'package:stackwallet/models/lelantus_fee_data.dart'; // import 'package:stackwallet/models/paymint/fee_object_model.dart'; // import 'package:stackwallet/models/signing_data.dart'; // import 'package:stackwallet/services/coins/coin_service.dart'; // import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; // import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; // import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; // import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; // import 'package:stackwallet/services/event_bus/global_event_bus.dart'; // import 'package:stackwallet/services/mixins/wallet_cache.dart'; // import 'package:stackwallet/services/mixins/wallet_db.dart'; // import 'package:stackwallet/services/mixins/xpubable.dart'; // import 'package:stackwallet/services/node_service.dart'; // import 'package:stackwallet/services/transaction_notification_tracker.dart'; // import 'package:stackwallet/utilities/address_utils.dart'; // import 'package:stackwallet/utilities/amount/amount.dart'; // import 'package:stackwallet/utilities/bip32_utils.dart'; // import 'package:stackwallet/utilities/constants.dart'; // import 'package:stackwallet/utilities/default_nodes.dart'; // import 'package:stackwallet/utilities/enums/coin_enum.dart'; // import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; // import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; // import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; // import 'package:stackwallet/utilities/format.dart'; // import 'package:stackwallet/utilities/logger.dart'; // import 'package:stackwallet/utilities/prefs.dart'; // import 'package:stackwallet/widgets/crypto_notifications.dart'; // import 'package:tuple/tuple.dart'; // import 'package:uuid/uuid.dart'; // // // const DUST_LIMIT = 1000; // // const MINIMUM_CONFIRMATIONS = 1; // // const MINT_LIMIT = 5001 * 100000000; // // const MINT_LIMIT_TESTNET = 1001 * 100000000; // // // // const JMINT_INDEX = 5; // // const MINT_INDEX = 2; // // const TRANSACTION_LELANTUS = 8; // // const ANONYMITY_SET_EMPTY_ID = 0; // // // const String GENESIS_HASH_MAINNET = // // "4381deb85b1b2c9843c222944b616d997516dcbd6a964e1eaf0def0830695233"; // // const String GENESIS_HASH_TESTNET = // // "aa22adcc12becaf436027ffe62a8fb21b234c58c23865291e5dc52cf53f64fca"; // // // // final firoNetwork = NetworkType( // // messagePrefix: '\x18Zcoin Signed Message:\n', // // bech32: 'bc', // // bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), // // pubKeyHash: 0x52, // // scriptHash: 0x07, // // wif: 0xd2); // // // // final firoTestNetwork = NetworkType( // // messagePrefix: '\x18Zcoin Signed Message:\n', // // bech32: 'bc', // // bip32: Bip32Type(public: 0x043587cf, private: 0x04358394), // // pubKeyHash: 0x41, // // scriptHash: 0xb2, // // wif: 0xb9); // // // isolate // // Map isolates = {}; // // Future getIsolate(Map arguments) async { // ReceivePort receivePort = // ReceivePort(); //port for isolate to receive messages. // arguments['sendPort'] = receivePort.sendPort; // Logging.instance // .log("starting isolate ${arguments['function']}", level: LogLevel.Info); // Isolate isolate = await Isolate.spawn(executeNative, arguments); // Logging.instance.log("isolate spawned!", level: LogLevel.Info); // isolates[receivePort] = isolate; // return receivePort; // } // // Future executeNative(Map arguments) async { // await Logging.instance.initInIsolate(); // final sendPort = arguments['sendPort'] as SendPort; // final function = arguments['function'] as String; // try { // if (function == "createJoinSplit") { // final spendAmount = arguments['spendAmount'] as int; // final address = arguments['address'] as String; // final subtractFeeFromAmount = arguments['subtractFeeFromAmount'] as bool; // final mnemonic = arguments['mnemonic'] as String; // final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; // final index = arguments['index'] as int; // final lelantusEntries = // arguments['lelantusEntries'] as List; // final coin = arguments['coin'] as Coin; // final network = arguments['network'] as NetworkType?; // final locktime = arguments['locktime'] as int; // final anonymitySets = arguments['_anonymity_sets'] as List?; // if (!(network == null || anonymitySets == null)) { // var joinSplit = await isolateCreateJoinSplitTransaction( // spendAmount, // address, // subtractFeeFromAmount, // mnemonic, // mnemonicPassphrase, // index, // lelantusEntries, // locktime, // coin, // network, // anonymitySets, // ); // sendPort.send(joinSplit); // return; // } // } else if (function == "estimateJoinSplit") { // final spendAmount = arguments['spendAmount'] as int; // final subtractFeeFromAmount = arguments['subtractFeeFromAmount'] as bool?; // final lelantusEntries = // arguments['lelantusEntries'] as List; // final coin = arguments['coin'] as Coin; // // if (!(subtractFeeFromAmount == null)) { // var feeData = await isolateEstimateJoinSplitFee( // spendAmount, subtractFeeFromAmount, lelantusEntries, coin); // sendPort.send(feeData); // return; // } // } else if (function == "restore") { // final latestSetId = arguments['latestSetId'] as int; // final setDataMap = arguments['setDataMap'] as Map; // final usedSerialNumbers = arguments['usedSerialNumbers'] as List; // final mnemonic = arguments['mnemonic'] as String; // final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; // final coin = arguments['coin'] as Coin; // final network = arguments['network'] as NetworkType; // final walletId = arguments['walletId'] as String; // // final restoreData = await isolateRestore( // mnemonic, // mnemonicPassphrase, // coin, // latestSetId, // setDataMap, // usedSerialNumbers, // network, // walletId, // ); // sendPort.send(restoreData); // return; // } // // Logging.instance.log( // "Error Arguments for $function not formatted correctly", // level: LogLevel.Fatal); // sendPort.send("Error"); // } catch (e, s) { // Logging.instance.log( // "An error was thrown in this isolate $function: $e\n$s", // level: LogLevel.Error); // sendPort.send("Error"); // } finally { // await Logging.instance.isar?.close(); // } // } // // void stop(ReceivePort port) { // Isolate? isolate = isolates.remove(port); // if (isolate != null) { // Logging.instance.log('Stopping Isolate...', level: LogLevel.Info); // isolate.kill(priority: Isolate.immediate); // isolate = null; // } // } // // // Future> isolateRestore( // // String mnemonic, // // String mnemonicPassphrase, // // Coin coin, // // int _latestSetId, // // Map _setDataMap, // // List _usedSerialNumbers, // // NetworkType network, // // String walletId, // // ) async { // // List jindexes = []; // // List lelantusCoins = []; // // // // final List spendTxIds = []; // // int lastFoundIndex = 0; // // int currentIndex = 0; // // // // try { // // Set usedSerialNumbersSet = _usedSerialNumbers.toSet(); // // // // final root = await Bip32Utils.getBip32Root( // // mnemonic, // // mnemonicPassphrase, // // network, // // ); // // while (currentIndex < lastFoundIndex + 50) { // // final _derivePath = constructDerivePath( // // networkWIF: network.wif, // // chain: MINT_INDEX, // // index: currentIndex, // // ); // // final bip32.BIP32 mintKeyPair = await Bip32Utils.getBip32NodeFromRoot( // // root, // // _derivePath, // // ); // // final String mintTag = CreateTag( // // Format.uint8listToString(mintKeyPair.privateKey!), // // currentIndex, // // Format.uint8listToString(mintKeyPair.identifier), // // isTestnet: coin == Coin.firoTestNet, // // ); // // // // for (int setId = 1; setId <= _latestSetId; setId++) { // // final setData = _setDataMap[setId] as Map; // // final foundCoin = (setData["coins"] as List).firstWhere( // // (e) => e[1] == mintTag, // // orElse: () => [], // // ); // // // // if (foundCoin.length == 4) { // // lastFoundIndex = currentIndex; // // // // final String publicCoin = foundCoin[0] as String; // // final String txId = foundCoin[3] as String; // // // // // this value will either be an int or a String // // final dynamic thirdValue = foundCoin[2]; // // // // if (thirdValue is int) { // // final int amount = thirdValue; // // final String serialNumber = GetSerialNumber( // // amount, // // Format.uint8listToString(mintKeyPair.privateKey!), // // currentIndex, // // isTestnet: coin == Coin.firoTestNet, // // ); // // final bool isUsed = usedSerialNumbersSet.contains(serialNumber); // // // // lelantusCoins.removeWhere((e) => // // e.txid == txId && // // e.mintIndex == currentIndex && // // e.anonymitySetId != setId); // // // // lelantusCoins.add( // // isar_models.LelantusCoin( // // walletId: walletId, // // mintIndex: currentIndex, // // value: amount.toString(), // // txid: txId, // // anonymitySetId: setId, // // isUsed: isUsed, // // isJMint: false, // // otherData: // // publicCoin, // not really needed but saved just in case // // ), // // ); // // Logging.instance.log( // // "amount $amount used $isUsed", // // level: LogLevel.Info, // // ); // // } else if (thirdValue is String) { // // final int keyPath = GetAesKeyPath(publicCoin); // // final String derivePath = constructDerivePath( // // networkWIF: network.wif, // // chain: JMINT_INDEX, // // index: keyPath, // // ); // // final aesKeyPair = await Bip32Utils.getBip32NodeFromRoot( // // root, // // derivePath, // // ); // // // // if (aesKeyPair.privateKey != null) { // // final String aesPrivateKey = Format.uint8listToString( // // aesKeyPair.privateKey!, // // ); // // final int amount = decryptMintAmount( // // aesPrivateKey, // // thirdValue, // // ); // // // // final String serialNumber = GetSerialNumber( // // amount, // // Format.uint8listToString(mintKeyPair.privateKey!), // // currentIndex, // // isTestnet: coin == Coin.firoTestNet, // // ); // // bool isUsed = usedSerialNumbersSet.contains(serialNumber); // // lelantusCoins.removeWhere((e) => // // e.txid == txId && // // e.mintIndex == currentIndex && // // e.anonymitySetId != setId); // // // // lelantusCoins.add( // // isar_models.LelantusCoin( // // walletId: walletId, // // mintIndex: currentIndex, // // value: amount.toString(), // // txid: txId, // // anonymitySetId: setId, // // isUsed: isUsed, // // isJMint: true, // // otherData: // // publicCoin, // not really needed but saved just in case // // ), // // ); // // jindexes.add(currentIndex); // // // // spendTxIds.add(txId); // // } else { // // Logging.instance.log( // // "AES keypair derivation issue for derive path: $derivePath", // // level: LogLevel.Warning, // // ); // // } // // } else { // // Logging.instance.log( // // "Unexpected coin found: $foundCoin", // // level: LogLevel.Warning, // // ); // // } // // } // // } // // // // currentIndex++; // // } // // } catch (e, s) { // // Logging.instance.log("Exception rethrown from isolateRestore(): $e\n$s", // // level: LogLevel.Info); // // rethrow; // // } // // // // Map result = {}; // // // Logging.instance.log("mints $lelantusCoins", addToDebugMessagesDB: false); // // // Logging.instance.log("jmints $spendTxIds", addToDebugMessagesDB: false); // // // // result['_lelantus_coins'] = lelantusCoins; // // result['spendTxIds'] = spendTxIds; // // // // return result; // // } // // // Future> staticProcessRestore( // // List txns, // // Map result, // // int currentHeight, // // ) async { // // List lelantusCoins = // // result['_lelantus_coins'] as List; // // // // // Edit the receive transactions with the mint fees. // // List editedTransactions = []; // // // // for (final coin in lelantusCoins) { // // String txid = coin.txid; // // isar_models.Transaction? tx; // // try { // // tx = txns.firstWhere((e) => e.txid == txid); // // } catch (_) { // // tx = null; // // } // // // // if (tx == null || tx.subType == isar_models.TransactionSubType.join) { // // // This is a jmint. // // continue; // // } // // // // List inputTxns = []; // // for (final input in tx.inputs) { // // isar_models.Transaction? inputTx; // // try { // // inputTx = txns.firstWhere((e) => e.txid == input.txid); // // } catch (_) { // // inputTx = null; // // } // // if (inputTx != null) { // // inputTxns.add(inputTx); // // } // // } // // if (inputTxns.isEmpty) { // // //some error. // // Logging.instance.log( // // "cryptic \"//some error\" occurred in staticProcessRestore on lelantus coin: $coin", // // level: LogLevel.Error, // // ); // // continue; // // } // // // // int mintFee = tx.fee; // // int sharedFee = mintFee ~/ inputTxns.length; // // for (final inputTx in inputTxns) { // // final edited = isar_models.Transaction( // // walletId: inputTx.walletId, // // txid: inputTx.txid, // // timestamp: inputTx.timestamp, // // type: inputTx.type, // // subType: isar_models.TransactionSubType.mint, // // amount: inputTx.amount, // // amountString: Amount( // // rawValue: BigInt.from(inputTx.amount), // // fractionDigits: Coin.firo.decimals, // // ).toJsonString(), // // fee: sharedFee, // // height: inputTx.height, // // isCancelled: false, // // isLelantus: true, // // slateId: null, // // otherData: txid, // // nonce: null, // // inputs: inputTx.inputs, // // outputs: inputTx.outputs, // // numberOfMessages: null, // // )..address.value = inputTx.address.value; // // editedTransactions.add(edited); // // } // // } // // // Logging.instance.log(editedTransactions, addToDebugMessagesDB: false); // // // // Map transactionMap = {}; // // for (final e in txns) { // // transactionMap[e.txid] = e; // // } // // // Logging.instance.log(transactionMap, addToDebugMessagesDB: false); // // // // // update with edited transactions // // for (final tx in editedTransactions) { // // transactionMap[tx.txid] = tx; // // } // // // // transactionMap.removeWhere((key, value) => // // lelantusCoins.any((element) => element.txid == key) || // // ((value.height == -1 || value.height == null) && // // !value.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS))); // // // // result['newTxMap'] = transactionMap; // // return result; // // } // // // Future isolateEstimateJoinSplitFee( // // int spendAmount, // // bool subtractFeeFromAmount, // // List lelantusEntries, // // Coin coin) async { // // Logging.instance.log("estimateJoinsplit fee", level: LogLevel.Info); // // // for (int i = 0; i < lelantusEntries.length; i++) { // // // Logging.instance.log(lelantusEntries[i], addToDebugMessagesDB: false); // // // } // // Logging.instance // // .log("$spendAmount $subtractFeeFromAmount", level: LogLevel.Info); // // // // List changeToMint = List.empty(growable: true); // // List spendCoinIndexes = List.empty(growable: true); // // // Logging.instance.log(lelantusEntries, addToDebugMessagesDB: false); // // final fee = estimateFee( // // spendAmount, // // subtractFeeFromAmount, // // lelantusEntries, // // changeToMint, // // spendCoinIndexes, // // isTestnet: coin == Coin.firoTestNet, // // ); // // // // final estimateFeeData = // // LelantusFeeData(changeToMint[0], fee, spendCoinIndexes); // // Logging.instance.log( // // "estimateFeeData ${estimateFeeData.changeToMint} ${estimateFeeData.fee} ${estimateFeeData.spendCoinIndexes}", // // level: LogLevel.Info); // // return estimateFeeData; // // } // // // Future isolateCreateJoinSplitTransaction( // // int spendAmount, // // String address, // // bool subtractFeeFromAmount, // // String mnemonic, // // String mnemonicPassphrase, // // int index, // // List lelantusEntries, // // int locktime, // // Coin coin, // // NetworkType _network, // // List> anonymitySetsArg, // // ) async { // // final estimateJoinSplitFee = await isolateEstimateJoinSplitFee( // // spendAmount, subtractFeeFromAmount, lelantusEntries, coin); // // var changeToMint = estimateJoinSplitFee.changeToMint; // // var fee = estimateJoinSplitFee.fee; // // var spendCoinIndexes = estimateJoinSplitFee.spendCoinIndexes; // // Logging.instance // // .log("$changeToMint $fee $spendCoinIndexes", level: LogLevel.Info); // // if (spendCoinIndexes.isEmpty) { // // Logging.instance.log("Error, Not enough funds.", level: LogLevel.Error); // // return 1; // // } // // // // final tx = TransactionBuilder(network: _network); // // tx.setLockTime(locktime); // // // // tx.setVersion(3 | (TRANSACTION_LELANTUS << 16)); // // // // tx.addInput( // // '0000000000000000000000000000000000000000000000000000000000000000', // // 4294967295, // // 4294967295, // // Uint8List(0), // // ); // // final derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: MINT_INDEX, // // index: index, // // ); // // final jmintKeyPair = await Bip32Utils.getBip32Node( // // mnemonic, // // mnemonicPassphrase, // // _network, // // derivePath, // // ); // // // // final String jmintprivatekey = // // Format.uint8listToString(jmintKeyPair.privateKey!); // // // // final keyPath = getMintKeyPath(changeToMint, jmintprivatekey, index, // // isTestnet: coin == Coin.firoTestNet); // // // // final _derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: JMINT_INDEX, // // index: keyPath, // // ); // // final aesKeyPair = await Bip32Utils.getBip32Node( // // mnemonic, // // mnemonicPassphrase, // // _network, // // _derivePath, // // ); // // final aesPrivateKey = Format.uint8listToString(aesKeyPair.privateKey!); // // // // final jmintData = createJMintScript( // // changeToMint, // // Format.uint8listToString(jmintKeyPair.privateKey!), // // index, // // Format.uint8listToString(jmintKeyPair.identifier), // // aesPrivateKey, // // isTestnet: coin == Coin.firoTestNet, // // ); // // // // tx.addOutput( // // Format.stringToUint8List(jmintData), // // 0, // // ); // // // // int amount = spendAmount; // // if (subtractFeeFromAmount) { // // amount -= fee; // // } // // tx.addOutput( // // address, // // amount, // // ); // // // // final extractedTx = tx.buildIncomplete(); // // extractedTx.setPayload(Uint8List(0)); // // final txHash = extractedTx.getId(); // // // // final List setIds = []; // // final List> anonymitySets = []; // // final List anonymitySetHashes = []; // // final List groupBlockHashes = []; // // for (var i = 0; i < lelantusEntries.length; i++) { // // final anonymitySetId = lelantusEntries[i].anonymitySetId; // // if (!setIds.contains(anonymitySetId)) { // // setIds.add(anonymitySetId); // // final anonymitySet = anonymitySetsArg.firstWhere( // // (element) => element["setId"] == anonymitySetId, // // orElse: () => {}); // // if (anonymitySet.isNotEmpty) { // // anonymitySetHashes.add(anonymitySet['setHash'] as String); // // groupBlockHashes.add(anonymitySet['blockHash'] as String); // // List list = []; // // for (int i = 0; i < (anonymitySet['coins'] as List).length; i++) { // // list.add(anonymitySet['coins'][i][0] as String); // // } // // anonymitySets.add(list); // // } // // } // // } // // // // final String spendScript = createJoinSplitScript( // // txHash, // // spendAmount, // // subtractFeeFromAmount, // // Format.uint8listToString(jmintKeyPair.privateKey!), // // index, // // lelantusEntries, // // setIds, // // anonymitySets, // // anonymitySetHashes, // // groupBlockHashes, // // isTestnet: coin == Coin.firoTestNet); // // // // final finalTx = TransactionBuilder(network: _network); // // finalTx.setLockTime(locktime); // // // // finalTx.setVersion(3 | (TRANSACTION_LELANTUS << 16)); // // // // finalTx.addOutput( // // Format.stringToUint8List(jmintData), // // 0, // // ); // // // // finalTx.addOutput( // // address, // // amount, // // ); // // // // final extTx = finalTx.buildIncomplete(); // // extTx.addInput( // // Format.stringToUint8List( // // '0000000000000000000000000000000000000000000000000000000000000000'), // // 4294967295, // // 4294967295, // // Format.stringToUint8List("c9"), // // ); // // debugPrint("spendscript: $spendScript"); // // extTx.setPayload(Format.stringToUint8List(spendScript)); // // // // final txHex = extTx.toHex(); // // final txId = extTx.getId(); // // Logging.instance.log("txid $txId", level: LogLevel.Info); // // Logging.instance.log("txHex: $txHex", level: LogLevel.Info); // // // // final amountAmount = Amount( // // rawValue: BigInt.from(amount), // // fractionDigits: coin.decimals, // // ); // // // // return { // // "txid": txId, // // "txHex": txHex, // // "value": amount, // // "fees": Amount( // // rawValue: BigInt.from(fee), // // fractionDigits: coin.decimals, // // ).decimal.toDouble(), // // "fee": fee, // // "vSize": extTx.virtualSize(), // // "jmintValue": changeToMint, // // "spendCoinIndexes": spendCoinIndexes, // // "height": locktime, // // "txType": "Sent", // // "confirmed_status": false, // // "amount": amountAmount.decimal.toDouble(), // // "recipientAmt": amountAmount, // // "address": address, // // "timestamp": DateTime.now().millisecondsSinceEpoch ~/ 1000, // // "subType": "join", // // }; // // } // // Future getBlockHead(ElectrumXClient client) async { // try { // final tip = await client.getBlockHeadTip(); // return tip["height"] as int; // } catch (e) { // Logging.instance // .log("Exception rethrown in getBlockHead(): $e", level: LogLevel.Error); // rethrow; // } // } // // end of isolates // // // String constructDerivePath({ // // // required DerivePathType derivePathType, // // required int networkWIF, // // int account = 0, // // required int chain, // // required int index, // // }) { // // String coinType; // // switch (networkWIF) { // // case 0xd2: // firo mainnet wif // // coinType = "136"; // firo mainnet // // break; // // case 0xb9: // firo testnet wif // // coinType = "1"; // firo testnet // // break; // // default: // // throw Exception("Invalid Firo network wif used!"); // // } // // // // int purpose; // // // switch (derivePathType) { // // // case DerivePathType.bip44: // // purpose = 44; // // // break; // // // default: // // // throw Exception("DerivePathType $derivePathType not supported"); // // // } // // // // return "m/$purpose'/$coinType'/$account'/$chain/$index"; // // } // // // Future _getMintScriptWrapper( // // Tuple5 data) async { // // String mintHex = getMintScript(data.item1, data.item2, data.item3, data.item4, // // isTestnet: data.item5); // // return mintHex; // // } // // Future _setTestnetWrapper(bool isTestnet) async { // // setTestnet(isTestnet); // } // // /// Handles a single instance of a firo wallet // class FiroWallet extends CoinServiceAPI // with WalletCache, WalletDB // implements XPubAble { // // Constructor // FiroWallet({ // required String walletId, // required String walletName, // required Coin coin, // required ElectrumXClient client, // required CachedElectrumXClient cachedClient, // required TransactionNotificationTracker tracker, // required SecureStorageInterface secureStore, // MainDB? mockableOverride, // }) { // txTracker = tracker; // _walletId = walletId; // _walletName = walletName; // _coin = coin; // _electrumXClient = client; // _cachedElectrumXClient = cachedClient; // _secureStore = secureStore; // initCache(walletId, coin); // initWalletDB(mockableOverride: mockableOverride); // // Logging.instance.log("$walletName isolates length: ${isolates.length}", // level: LogLevel.Info); // // investigate possible issues killing shared isolates between multiple firo instances // for (final isolate in isolates.values) { // isolate.kill(priority: Isolate.immediate); // } // isolates.clear(); // } // // // // static const integrationTestFlag = // // bool.fromEnvironment("IS_INTEGRATION_TEST"); // // // // final _prefs = Prefs.instance; // // // // Timer? timer; // // late final Coin _coin; // // // // 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(); // // } // // } // // } // // // // NetworkType get _network { // // switch (coin) { // // case Coin.firo: // // return firoNetwork; // // case Coin.firoTestNet: // // return firoTestNetwork; // // default: // // throw Exception("Invalid network type!"); // // } // // } // // // // @override // // set isFavorite(bool markFavorite) { // // _isFavorite = markFavorite; // // updateCachedIsFavorite(markFavorite); // // } // // // // @override // // bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); // // // // bool? _isFavorite; // // // // @override // // Coin get coin => _coin; // // // // @override // // Future> get mnemonic => _getMnemonicList(); // // // // @override // // Future get mnemonicString => // // _secureStore.read(key: '${_walletId}_mnemonic'); // // // // @override // // Future get mnemonicPassphrase => _secureStore.read( // // key: '${_walletId}_mnemonicPassphrase', // // ); // // // // @override // // bool validateAddress(String address) { // // return Address.validateAddress(address, _network); // // } // // /// Holds wallet transaction data // Future> get _txnData => db // .getTransactions(walletId) // .filter() // .isLelantusIsNull() // .or() // .isLelantusEqualTo(false) // .findAll(); // // // _transactionData ??= _refreshTransactions(); // // // models.TransactionData? cachedTxData; // // // hack to add tx to txData before refresh completes // // required based on current app architecture where we don't properly store // // transactions locally in a good way // @override // Future updateSentCachedTxData(Map txData) async { // final transaction = isar_models.Transaction( // walletId: walletId, // txid: txData["txid"] as String, // timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, // type: isar_models.TransactionType.outgoing, // subType: isar_models.TransactionSubType.none, // // precision may be lost here hence the following amountString // amount: (txData["recipientAmt"] as Amount).raw.toInt(), // amountString: (txData["recipientAmt"] as Amount).toJsonString(), // fee: txData["fee"] as int, // height: null, // isCancelled: false, // isLelantus: false, // otherData: null, // slateId: null, // nonce: null, // inputs: [], // outputs: [], // numberOfMessages: null, // ); // // final address = txData["address"] is String // ? await db.getAddress(walletId, txData["address"] as String) // : null; // // await db.addNewTransactionData( // [ // Tuple2(transaction, address), // ], // walletId, // ); // } // // // // /// Holds the max fee that can be sent // // Future? _maxFee; // // // // @override // // Future get maxFee => _maxFee ??= _fetchMaxFee(); // // // // Future? _feeObject; // // // // @override // // Future get fees => _feeObject ??= _getFees(); // // // @override // // Future get currentReceivingAddress async => // // (await _currentReceivingAddress).value; // // // // Future get _currentReceivingAddress async => // // (await db // // .getAddresses(walletId) // // .filter() // // .typeEqualTo(isar_models.AddressType.p2pkh) // // .subTypeEqualTo(isar_models.AddressSubType.receiving) // // .sortByDerivationIndexDesc() // // .findFirst()) ?? // // await _generateAddressForChain(0, 0); // // // // Future get currentChangeAddress async => // // (await _currentChangeAddress).value; // // // // Future get _currentChangeAddress async => // // (await db // // .getAddresses(walletId) // // .filter() // // .typeEqualTo(isar_models.AddressType.p2pkh) // // .subTypeEqualTo(isar_models.AddressSubType.change) // // .sortByDerivationIndexDesc() // // .findFirst()) ?? // // await _generateAddressForChain(1, 0); // // // late String _walletName; // // // // @override // // String get walletName => _walletName; // // // // // setter for updating on rename // // @override // // set walletName(String newName) => _walletName = newName; // // // // /// unique wallet id // // late final String _walletId; // // // // @override // // String get walletId => _walletId; // // // // @override // // Future testNetworkConnection() async { // // try { // // final result = await _electrumXClient.ping(); // // return result; // // } catch (_) { // // return false; // // } // // } // // // // Timer? _networkAliveTimer; // // // // void startNetworkAlivePinging() { // // // call once on start right away // // _periodicPingCheck(); // // // // // then periodically check // // _networkAliveTimer = Timer.periodic( // // Constants.networkAliveTimerDuration, // // (_) async { // // _periodicPingCheck(); // // }, // // ); // // } // // // // void _periodicPingCheck() async { // // bool hasNetwork = await testNetworkConnection(); // // // // if (_isConnected != hasNetwork) { // // NodeConnectionStatus status = hasNetwork // // ? NodeConnectionStatus.connected // // : NodeConnectionStatus.disconnected; // // GlobalEventBus.instance // // .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); // // // // _isConnected = hasNetwork; // // if (hasNetwork) { // // unawaited(refresh()); // // } // // } // // } // // // // void stopNetworkAlivePinging() { // // _networkAliveTimer?.cancel(); // // _networkAliveTimer = null; // // } // // // // bool _isConnected = false; // // // // @override // // bool get isConnected => _isConnected; // // // Future> prepareSendPublic({ // // required String address, // // required Amount amount, // // Map? args, // // }) async { // // try { // // final feeRateType = args?["feeRate"]; // // final customSatsPerVByte = args?["satsPerVByte"] as int?; // // final feeRateAmount = args?["feeRateAmount"]; // // // // if (customSatsPerVByte != null) { // // // check for send all // // bool isSendAll = false; // // if (amount == balance.spendable) { // // isSendAll = true; // // } // // // // final result = await coinSelection( // // amount.raw.toInt(), // // -1, // // address, // // isSendAll, // // satsPerVByte: customSatsPerVByte, // // ); // // // // Logging.instance // // .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); // // if (result is int) { // // switch (result) { // // case 1: // // throw Exception("Insufficient balance!"); // // case 2: // // throw Exception("Insufficient funds to pay for transaction fee!"); // // default: // // throw Exception("Transaction failed with error code $result"); // // } // // } else { // // final hex = result["hex"]; // // if (hex is String) { // // final fee = result["fee"] as int; // // final vSize = result["vSize"] as int; // // // // Logging.instance.log("txHex: $hex", level: LogLevel.Info); // // Logging.instance.log("fee: $fee", level: LogLevel.Info); // // Logging.instance.log("vsize: $vSize", level: LogLevel.Info); // // // fee should never be less than vSize sanity check // // if (fee < vSize) { // // throw Exception( // // "Error in fee calculation: Transaction fee cannot be less than vSize"); // // } // // return result as Map; // // } else { // // throw Exception("sent hex is not a String!!!"); // // } // // } // // } else if (feeRateType is FeeRateType || feeRateAmount is int) { // // late final int rate; // // if (feeRateType is FeeRateType) { // // int fee = 0; // // final feeObject = await fees; // // switch (feeRateType) { // // case FeeRateType.fast: // // fee = feeObject.fast; // // break; // // case FeeRateType.average: // // fee = feeObject.medium; // // break; // // case FeeRateType.slow: // // fee = feeObject.slow; // // break; // // default: // // throw ArgumentError("Invalid use of custom fee"); // // } // // rate = fee; // // } else { // // rate = feeRateAmount as int; // // } // // // // // check for send all // // bool isSendAll = false; // // final balance = availablePublicBalance(); // // if (amount == balance) { // // isSendAll = true; // // } // // // // final txData = await coinSelection( // // amount.raw.toInt(), // // rate, // // address, // // isSendAll, // // ); // // // // Logging.instance.log("prepare send: $txData", level: LogLevel.Info); // // try { // // if (txData is int) { // // switch (txData) { // // case 1: // // throw Exception("Insufficient balance!"); // // case 2: // // throw Exception( // // "Insufficient funds to pay for transaction fee!"); // // default: // // throw Exception("Transaction failed with error code $txData"); // // } // // } else { // // final hex = txData["hex"]; // // // // if (hex is String) { // // final fee = txData["fee"] as int; // // final vSize = txData["vSize"] as int; // // // // Logging.instance // // .log("prepared txHex: $hex", level: LogLevel.Info); // // Logging.instance.log("prepared fee: $fee", level: LogLevel.Info); // // Logging.instance // // .log("prepared vSize: $vSize", level: LogLevel.Info); // // // // // fee should never be less than vSize sanity check // // if (fee < vSize) { // // throw Exception( // // "Error in fee calculation: Transaction fee cannot be less than vSize"); // // } // // // // return txData as Map; // // } else { // // throw Exception("prepared hex is not a String!!!"); // // } // // } // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from prepareSendPublic(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } else { // // throw ArgumentError("Invalid fee rate argument provided!"); // // } // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from prepareSendPublic(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // Future confirmSendPublic({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); // // txData["txid"] = txHash; // // // dirty ui update hack // // await updateSentCachedTxData(txData as Map); // // return txHash; // // } catch (e, s) { // // Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // @override // // Future> prepareSend({ // // required String address, // // required Amount amount, // // Map? args, // // }) async { // // if (amount.raw > BigInt.from(MINT_LIMIT)) { // // throw Exception( // // "Lelantus sends of more than 5001 are currently disabled"); // // } // // // // try { // // // check for send all // // bool isSendAll = false; // // final balance = availablePrivateBalance(); // // if (amount == balance) { // // // print("is send all"); // // isSendAll = true; // // } // // dynamic txHexOrError = await _createJoinSplitTransaction( // // amount.raw.toInt(), // // address, // // isSendAll, // // ); // // Logging.instance.log("txHexOrError $txHexOrError", level: LogLevel.Error); // // if (txHexOrError is int) { // // // Here, we assume that transaction crafting returned an error // // switch (txHexOrError) { // // case 1: // // throw Exception("Insufficient balance!"); // // default: // // throw Exception("Error Creating Transaction!"); // // } // // } else { // // final fee = txHexOrError["fee"] as int; // // final vSize = txHexOrError["vSize"] as int; // // // // 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 txHexOrError as Map; // // } // // } catch (e, s) { // // Logging.instance.log("Exception rethrown in firo prepareSend(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // @override // // Future confirmSend({required Map txData}) async { // // if (await _submitLelantusToNetwork(txData)) { // // try { // // final txid = txData["txid"] as String; // // // // return txid; // // } catch (e, s) { // // //todo: come back to this // // debugPrint("$e $s"); // // return txData["txid"] as String; // // // don't throw anything here or it will tell the user that th tx // // // failed even though it was successfully broadcast to network // // // throw Exception("Transaction failed."); // // } // // } else { // // //TODO provide more info // // throw Exception("Transaction failed."); // // } // // } // // // // Future> _getMnemonicList() async { // // final _mnemonicString = await mnemonicString; // // if (_mnemonicString == null) { // // return []; // // } // // final List data = _mnemonicString.split(' '); // // return data; // // } // // // // late ElectrumXClient _electrumXClient; // // // // ElectrumXClient get electrumXClient => _electrumXClient; // // // // late CachedElectrumXClient _cachedElectrumXClient; // // // // CachedElectrumXClient get cachedElectrumXClient => _cachedElectrumXClient; // // // // late SecureStorageInterface _secureStore; // // // // late TransactionNotificationTracker txTracker; // // // // 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? satsPerVByte, // // int additionalOutputs = 0, // // List? utxos, // // }) async { // // Logging.instance // // .log("Starting coinSelection ----------", level: LogLevel.Info); // // final List availableOutputs = utxos ?? await this.utxos; // // final currentChainHeight = await chainHeight; // // final List spendableOutputs = []; // // int spendableSatoshiValue = 0; // // // // // Build list of spendable outputs and totaling their satoshi amount // // for (var i = 0; i < availableOutputs.length; i++) { // // if (availableOutputs[i].isBlocked == false && // // availableOutputs[i] // // .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == // // true) { // // spendableOutputs.add(availableOutputs[i]); // // spendableSatoshiValue += availableOutputs[i].value; // // } // // } // // // // // sort spendable by age (oldest first) // // spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); // // // // Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", // // level: LogLevel.Info); // // Logging.instance // // .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); // // Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", // // level: LogLevel.Info); // // Logging.instance // // .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); // // // If the amount the user is trying to send is smaller than the amount that they have spendable, // // // then return 1, which indicates that they have an insufficient balance. // // if (spendableSatoshiValue < satoshiAmountToSend) { // // return 1; // // // If the amount the user wants to send is exactly equal to the amount they can spend, then return // // // 2, which indicates that they are not leaving enough over to pay the transaction fee // // } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { // // return 2; // // } // // // If neither of these statements pass, we assume that the user has a spendable balance greater // // // than the amount they're attempting to send. Note that this value still does not account for // // // the added transaction fee, which may require an extra input and will need to be checked for // // // later on. // // // // // Possible situation right here // // int satoshisBeingUsed = 0; // // int inputsBeingConsumed = 0; // // List utxoObjectsToUse = []; // // // // 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); // // // // // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray // // List recipientsArray = [_recipientAddress]; // // List recipientsAmtArray = [satoshiAmountToSend]; // // // // // gather required signing data // // final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); // // // // if (isSendAll) { // // Logging.instance // // .log("Attempting to send all $coin", level: LogLevel.Info); // // // // final int vSizeForOneOutput = (await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: [_recipientAddress], // // satoshiAmounts: [satoshisBeingUsed - 1], // // ))["vSize"] as int; // // int feeForOneOutput = satsPerVByte != null // // ? (satsPerVByte * vSizeForOneOutput) // // : estimateTxFee( // // vSize: vSizeForOneOutput, // // feeRatePerKB: selectedTxFeeRate, // // ); // // // // int amount = satoshiAmountToSend - feeForOneOutput; // // dynamic txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: [amount], // // ); // // // // int count = 0; // // int fee = feeForOneOutput; // // int vsize = txn["vSize"] as int; // // // // while (fee < vsize && count < 10) { // // // 10 being some reasonable max // // count++; // // fee += count; // // amount = satoshiAmountToSend - fee; // // // // txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: [amount], // // ); // // // // vsize = txn["vSize"] as int; // // } // // // // Map transactionObject = { // // "hex": txn["hex"], // // "recipient": recipientsArray[0], // // "recipientAmt": Amount( // // rawValue: BigInt.from(amount), // // fractionDigits: coin.decimals, // // ), // // "fee": feeForOneOutput, // // "vSize": txn["vSize"], // // }; // // return transactionObject; // // } // // // // final int vSizeForOneOutput = (await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: [_recipientAddress], // // satoshiAmounts: [satoshisBeingUsed - 1], // // ))["vSize"] as int; // // final int vSizeForTwoOutPuts = (await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: [ // // _recipientAddress, // // await _getCurrentAddressForChain(1), // // ], // // satoshiAmounts: [ // // satoshiAmountToSend, // // satoshisBeingUsed - satoshiAmountToSend - 1, // // ], // dust limit is the minimum amount a change output should be // // ))["vSize"] as int; // // // // // Assume 1 output, only for recipient and no change // // var feeForOneOutput = satsPerVByte != null // // ? (satsPerVByte * vSizeForOneOutput) // // : estimateTxFee( // // vSize: vSizeForOneOutput, // // feeRatePerKB: selectedTxFeeRate, // // ); // // // Assume 2 outputs, one for recipient and one for change // // var feeForTwoOutputs = satsPerVByte != null // // ? (satsPerVByte * vSizeForTwoOutPuts) // // : estimateTxFee( // // vSize: vSizeForTwoOutPuts, // // feeRatePerKB: selectedTxFeeRate, // // ); // // // // Logging.instance // // .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); // // Logging.instance // // .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); // // if (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 > 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(); // // final String newChangeAddress = await _getCurrentAddressForChain(1); // // // // int feeBeingPaid = // // satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; // // // // recipientsArray.add(newChangeAddress); // // recipientsAmtArray.add(changeOutputSize); // // // At this point, we have the outputs we're going to use, the amounts to send along with which addresses // // // we intend to send these amounts to. We have enough to send instructions to build the transaction. // // Logging.instance.log('2 outputs in tx', level: LogLevel.Info); // // Logging.instance // // .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); // // Logging.instance.log('Recipient output size: $satoshiAmountToSend', // // level: LogLevel.Info); // // Logging.instance.log('Change Output Size: $changeOutputSize', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Difference (fee being paid): $feeBeingPaid sats', // // level: LogLevel.Info); // // Logging.instance // // .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); // // dynamic txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: recipientsAmtArray, // // ); // // // // // make sure minimum fee is accurate if that is being used // // if (txn["vSize"] - feeBeingPaid == 1) { // // int changeOutputSize = // // satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int); // // feeBeingPaid = // // satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; // // recipientsAmtArray.removeLast(); // // recipientsAmtArray.add(changeOutputSize); // // Logging.instance.log('Adjusted Input size: $satoshisBeingUsed', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Adjusted Recipient output size: $satoshiAmountToSend', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Adjusted Change Output Size: $changeOutputSize', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Adjusted Difference (fee being paid): $feeBeingPaid sats', // // level: LogLevel.Info); // // Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', // // level: LogLevel.Info); // // txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: recipientsAmtArray, // // ); // // } // // // // Map transactionObject = { // // "hex": txn["hex"], // // "recipient": recipientsArray[0], // // "recipientAmt": Amount( // // rawValue: BigInt.from(recipientsAmtArray[0]), // // fractionDigits: coin.decimals, // // ), // // "fee": feeBeingPaid, // // "vSize": txn["vSize"], // // }; // // return transactionObject; // // } else { // // // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize // // // is smaller than or equal to [DUST_LIMIT]. Revert to single output transaction. // // Logging.instance.log('1 output in tx', level: LogLevel.Info); // // Logging.instance // // .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); // // Logging.instance.log('Recipient output size: $satoshiAmountToSend', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', // // level: LogLevel.Info); // // Logging.instance // // .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); // // dynamic txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: recipientsAmtArray, // // ); // // Map transactionObject = { // // "hex": txn["hex"], // // "recipient": recipientsArray[0], // // "recipientAmt": Amount( // // rawValue: BigInt.from(recipientsAmtArray[0]), // // fractionDigits: coin.decimals, // // ), // // "fee": satoshisBeingUsed - satoshiAmountToSend, // // "vSize": txn["vSize"], // // }; // // 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( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: recipientsAmtArray, // // ); // // Map transactionObject = { // // "hex": txn["hex"], // // "recipient": recipientsArray[0], // // "recipientAmt": Amount( // // rawValue: BigInt.from(recipientsAmtArray[0]), // // fractionDigits: coin.decimals, // // ), // // "fee": satoshisBeingUsed - satoshiAmountToSend, // // "vSize": txn["vSize"], // // }; // // return transactionObject; // // } // // } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) { // // // In this scenario, no additional change output is needed since inputs - outputs equal exactly // // // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin // // // crafting the transaction that the user requested. // // Logging.instance.log('1 output in tx', level: LogLevel.Info); // // Logging.instance // // .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); // // Logging.instance.log('Recipient output size: $satoshiAmountToSend', // // level: LogLevel.Info); // // Logging.instance.log( // // 'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats', // // level: LogLevel.Info); // // Logging.instance // // .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); // // dynamic txn = await buildTransaction( // // utxoSigningData: utxoSigningData, // // recipients: recipientsArray, // // satoshiAmounts: recipientsAmtArray, // // ); // // Map transactionObject = { // // "hex": txn["hex"], // // "recipient": recipientsArray[0], // // "recipientAmt": Amount( // // rawValue: BigInt.from(recipientsAmtArray[0]), // // fractionDigits: coin.decimals, // // ), // // "fee": feeForOneOutput, // // "vSize": txn["vSize"], // // }; // // 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, // // satsPerVByte: satsPerVByte, // // utxos: utxos, // // ); // // } // // return 2; // // } // // } // // // Future> fetchBuildTxData( // // List utxosToUse, // // ) async { // // // return data // // List signingData = []; // // // // try { // // // Populating the addresses to check // // for (var i = 0; i < utxosToUse.length; i++) { // // if (utxosToUse[i].address == null) { // // final txid = utxosToUse[i].txid; // // final tx = await _cachedElectrumXClient.getTransaction( // // txHash: txid, // // coin: coin, // // ); // // for (final output in tx["vout"] as List) { // // final n = output["n"]; // // if (n != null && n == utxosToUse[i].vout) { // // utxosToUse[i] = utxosToUse[i].copyWith( // // address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? // // output["scriptPubKey"]["address"] as String, // // ); // // } // // } // // } // // // // signingData.add( // // SigningData( // // derivePathType: DerivePathType.bip44, // // utxo: utxosToUse[i], // // ), // // ); // // } // // // // Map> receiveDerivations = {}; // // Map> changeDerivations = {}; // // // // for (final sd in signingData) { // // String? pubKey; // // String? wif; // // // // final address = await db.getAddress(walletId, sd.utxo.address!); // // if (address?.derivationPath != null) { // // final node = await Bip32Utils.getBip32Node( // // (await mnemonicString)!, // // (await mnemonicPassphrase)!, // // _network, // // address!.derivationPath!.value, // // ); // // // // wif = node.toWIF(); // // pubKey = Format.uint8listToString(node.publicKey); // // } // // if (wif == null || pubKey == null) { // // // fetch receiving derivations if null // // receiveDerivations[sd.derivePathType] ??= Map.from( // // jsonDecode((await _secureStore.read( // // key: "${walletId}_receiveDerivations", // // )) ?? // // "{}") as Map, // // ); // // // // dynamic receiveDerivation; // // for (int j = 0; // // j < receiveDerivations[sd.derivePathType]!.length && // // receiveDerivation == null; // // j++) { // // if (receiveDerivations[sd.derivePathType]!["$j"]["address"] == // // sd.utxo.address!) { // // receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"]; // // } // // } // // // // if (receiveDerivation != null) { // // pubKey = receiveDerivation["publicKey"] as String; // // wif = receiveDerivation["wif"] as String; // // } else { // // // fetch change derivations if null // // changeDerivations[sd.derivePathType] ??= Map.from( // // jsonDecode((await _secureStore.read( // // key: "${walletId}_changeDerivations", // // )) ?? // // "{}") as Map, // // ); // // // // dynamic changeDerivation; // // for (int j = 0; // // j < changeDerivations[sd.derivePathType]!.length && // // changeDerivation == null; // // j++) { // // if (changeDerivations[sd.derivePathType]!["$j"]["address"] == // // sd.utxo.address!) { // // changeDerivation = changeDerivations[sd.derivePathType]!["$j"]; // // } // // } // // // // if (changeDerivation != null) { // // pubKey = changeDerivation["publicKey"] as String; // // wif = changeDerivation["wif"] as String; // // } // // } // // } // // // // if (wif != null && pubKey != null) { // // final PaymentData data; // // final Uint8List? redeemScript; // // // // switch (sd.derivePathType) { // // case DerivePathType.bip44: // // data = P2PKH( // // data: PaymentData( // // pubkey: Format.stringToUint8List(pubKey), // // ), // // network: _network, // // ).data; // // redeemScript = null; // // break; // // // // default: // // throw Exception("DerivePathType unsupported"); // // } // // // // final keyPair = ECPair.fromWIF( // // wif, // // network: _network, // // ); // // // // sd.redeemScript = redeemScript; // // sd.output = data.output; // // sd.keyPair = keyPair; // // } else { // // throw Exception("key or wif not found for ${sd.utxo}"); // // } // // } // // // // return signingData; // // } catch (e, s) { // // Logging.instance // // .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); // // rethrow; // // } // // } // // // // /// Builds and signs a transaction // // Future> buildTransaction({ // // required List utxoSigningData, // // required List recipients, // // required List satoshiAmounts, // // }) async { // // Logging.instance // // .log("Starting buildTransaction ----------", level: LogLevel.Info); // // // // final txb = TransactionBuilder(network: _network); // // txb.setVersion(1); // // // // // Add transaction inputs // // for (var i = 0; i < utxoSigningData.length; i++) { // // final txid = utxoSigningData[i].utxo.txid; // // txb.addInput( // // txid, // // utxoSigningData[i].utxo.vout, // // null, // // utxoSigningData[i].output!, // // ); // // } // // // // // 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 < utxoSigningData.length; i++) { // // txb.sign( // // vin: i, // // keyPair: utxoSigningData[i].keyPair!, // // witnessValue: utxoSigningData[i].utxo.value, // // redeemScript: utxoSigningData[i].redeemScript, // // ); // // } // // } 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 updateNode(bool shouldRefresh) async { // // final failovers = NodeService(secureStorageInterface: _secureStore) // // .failoverNodesFor(coin: coin) // // .map( // // (e) => ElectrumXNode( // // address: e.host, // // port: e.port, // // name: e.name, // // id: e.id, // // useSSL: e.useSSL, // // ), // // ) // // .toList(); // // final newNode = await _getCurrentNode(); // // _electrumXClient = ElectrumXClient.from( // // node: newNode, // // prefs: _prefs, // // failovers: failovers, // // ); // // _cachedElectrumXClient = CachedElectrumXClient.from( // // electrumXClient: _electrumXClient, // // ); // // // // if (shouldRefresh) { // // unawaited(refresh()); // // } // // } // // // @override // // Future initializeNew( // // ({String mnemonicPassphrase, int wordCount})? data, // // ) async { // // Logging.instance // // .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); // // // // if (getCachedId() != null) { // // throw Exception( // // "Attempted to initialize a new wallet using an existing wallet ID!"); // // } // // // // await _prefs.init(); // // try { // // await _generateNewWallet(data); // // } catch (e, s) { // // Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", // // level: LogLevel.Fatal); // // rethrow; // // } // // // // await Future.wait([ // // updateCachedId(walletId), // // updateCachedIsFavorite(false), // // setLelantusCoinIsarRescanRequiredDone(), // // ]); // // } // // // static const String _lelantusCoinIsarRescanRequired = // // "lelantusCoinIsarRescanRequired"; // // // // Future setLelantusCoinIsarRescanRequiredDone() async { // // await DB.instance.put( // // boxName: walletId, // // key: _lelantusCoinIsarRescanRequired, // // value: false, // // ); // // } // // // // bool get lelantusCoinIsarRescanRequired => // // DB.instance.get( // // boxName: walletId, // // key: _lelantusCoinIsarRescanRequired, // // ) as bool? ?? // // true; // // // // Future firoRescanRecovery() async { // // try { // // await fullRescan(50, 1000); // // await setLelantusCoinIsarRescanRequiredDone(); // // return true; // // } catch (_) { // // return false; // // } // // } // // // @override // // Future initializeExisting() async { // // Logging.instance.log( // // "initializeExisting() $_walletId ${coin.prettyName} wallet.", // // level: LogLevel.Info, // // ); // // // // if (getCachedId() == null) { // // throw Exception( // // "Attempted to initialize an existing wallet using an unknown wallet ID!"); // // } // // await _prefs.init(); // // // await checkChangeAddressForTransactions(); // // // await checkReceivingAddressForTransactions(); // // } // // Future refreshIfThereIsNewData() async { // if (longMutex) return false; // if (_hasCalledExit) return false; // Logging.instance // .log("$walletName refreshIfThereIsNewData", level: LogLevel.Info); // // try { // bool needsRefresh = false; // Set txnsToCheck = {}; // // for (final String txid in txTracker.pendings) { // if (!txTracker.wasNotifiedConfirmed(txid)) { // txnsToCheck.add(txid); // } // } // // for (String txid in txnsToCheck) { // final txn = await electrumXClient.getTransaction(txHash: txid); // int confirmations = txn["confirmations"] as int? ?? 0; // bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; // if (!isUnconfirmed) { // needsRefresh = true; // break; // } // } // if (!needsRefresh) { // final allOwnAddresses = await _fetchAllOwnAddresses(); // List> allTxs = await _fetchHistory( // allOwnAddresses.map((e) => e.value).toList(growable: false)); // for (Map transaction in allTxs) { // final txid = transaction['tx_hash'] as String; // if ((await db // .getTransactions(walletId) // .filter() // .txidEqualTo(txid) // .count()) == // 0) { // Logging.instance.log( // " txid not found in address history already ${transaction['tx_hash']}", // level: LogLevel.Info, // ); // needsRefresh = true; // break; // } // } // } // return needsRefresh; // } catch (e, s) { // Logging.instance.log( // "Exception caught in refreshIfThereIsNewData: $e\n$s", // level: LogLevel.Error); // rethrow; // } // } // // Future getAllTxsToWatch() async { // if (_hasCalledExit) return; // Logging.instance.log("$walletName periodic", level: LogLevel.Info); // List unconfirmedTxnsToNotifyPending = []; // List unconfirmedTxnsToNotifyConfirmed = []; // // final currentChainHeight = await chainHeight; // // final txCount = await db.getTransactions(walletId).count(); // // const paginateLimit = 50; // // for (int i = 0; i < txCount; i += paginateLimit) { // final transactions = await db // .getTransactions(walletId) // .offset(i) // .limit(paginateLimit) // .findAll(); // for (final tx in transactions) { // if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // // get all transactions that were notified as pending but not as confirmed // if (txTracker.wasNotifiedPending(tx.txid) && // !txTracker.wasNotifiedConfirmed(tx.txid)) { // unconfirmedTxnsToNotifyConfirmed.add(tx); // } // } else { // // get all transactions that were not notified as pending yet // if (!txTracker.wasNotifiedPending(tx.txid)) { // unconfirmedTxnsToNotifyPending.add(tx); // } // } // } // } // // Logging.instance.log( // "unconfirmedTxnsToNotifyPending $unconfirmedTxnsToNotifyPending", // level: LogLevel.Info); // Logging.instance.log( // "unconfirmedTxnsToNotifyConfirmed $unconfirmedTxnsToNotifyConfirmed", // level: LogLevel.Info); // // for (final tx in unconfirmedTxnsToNotifyPending) { // final confirmations = tx.getConfirmations(currentChainHeight); // // switch (tx.type) { // case isar_models.TransactionType.incoming: // CryptoNotificationsEventBus.instance.fire( // CryptoNotificationEvent( // title: "Incoming transaction", // walletId: walletId, // date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), // shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, // txid: tx.txid, // confirmations: confirmations, // requiredConfirmations: MINIMUM_CONFIRMATIONS, // walletName: walletName, // coin: coin, // ), // ); // // await txTracker.addNotifiedPending(tx.txid); // break; // case isar_models.TransactionType.outgoing: // CryptoNotificationsEventBus.instance.fire( // CryptoNotificationEvent( // title: tx.subType == isar_models.TransactionSubType.mint // ? "Anonymizing" // : "Outgoing transaction", // walletId: walletId, // date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), // shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, // txid: tx.txid, // confirmations: confirmations, // requiredConfirmations: MINIMUM_CONFIRMATIONS, // walletName: walletName, // coin: coin, // ), // ); // // await txTracker.addNotifiedPending(tx.txid); // break; // default: // break; // } // } // // for (final tx in unconfirmedTxnsToNotifyConfirmed) { // if (tx.type == isar_models.TransactionType.incoming) { // CryptoNotificationsEventBus.instance.fire( // CryptoNotificationEvent( // title: "Incoming transaction confirmed", // walletId: walletId, // date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), // shouldWatchForUpdates: false, // txid: tx.txid, // requiredConfirmations: MINIMUM_CONFIRMATIONS, // walletName: walletName, // coin: coin, // ), // ); // // await txTracker.addNotifiedConfirmed(tx.txid); // } else if (tx.type == isar_models.TransactionType.outgoing && // tx.subType == isar_models.TransactionSubType.join) { // CryptoNotificationsEventBus.instance.fire( // CryptoNotificationEvent( // title: tx.subType == // isar_models.TransactionSubType.mint // redundant check? // ? "Anonymized" // : "Outgoing transaction confirmed", // walletId: walletId, // date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), // shouldWatchForUpdates: false, // txid: tx.txid, // requiredConfirmations: MINIMUM_CONFIRMATIONS, // walletName: walletName, // coin: coin, // ), // ); // await txTracker.addNotifiedConfirmed(tx.txid); // } // } // } // // // /// Generates initial wallet values such as mnemonic, chain (receive/change) arrays and indexes. // // Future _generateNewWallet( // // ({String mnemonicPassphrase, int wordCount})? data, // // ) async { // // Logging.instance // // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); // // if (!integrationTestFlag) { // // try { // // final features = await electrumXClient // // .getServerFeatures() // // .timeout(const Duration(seconds: 3)); // // Logging.instance.log("features: $features", level: LogLevel.Info); // // switch (coin) { // // case Coin.firo: // // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { // // throw Exception("genesis hash does not match main net!"); // // } // // break; // // case Coin.firoTestNet: // // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { // // throw Exception("genesis hash does not match test net!"); // // } // // break; // // default: // // throw Exception( // // "Attempted to generate a FiroWallet using a non firo coin type: ${coin.name}"); // // } // // } catch (e, s) { // // Logging.instance.log("$e/n$s", level: LogLevel.Info); // // } // // } // // // // // this should never fail as overwriting a mnemonic is big bad // // if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { // // longMutex = false; // // throw Exception("Attempted to overwrite mnemonic on initialize new!"); // // } // // final int strength; // // if (data == null || data.wordCount == 12) { // // strength = 128; // // } else if (data.wordCount == 24) { // // strength = 256; // // } else { // // throw Exception("Invalid word count"); // // } // // await _secureStore.write( // // key: '${_walletId}_mnemonic', // // value: bip39.generateMnemonic(strength: strength)); // // await _secureStore.write( // // key: '${_walletId}_mnemonicPassphrase', // // value: data?.mnemonicPassphrase ?? "", // // ); // // // // // Generate and add addresses to relevant arrays // // final initialReceivingAddress = await _generateAddressForChain(0, 0); // // final initialChangeAddress = await _generateAddressForChain(1, 0); // // // // await db.putAddresses([ // // initialReceivingAddress, // // initialChangeAddress, // // ]); // // } // // // // bool refreshMutex = false; // // // // @override // // bool get isRefreshing => refreshMutex; // // // // /// 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; // // } // // Logging.instance // // .log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info); // // try { // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.syncing, // // walletId, // // coin, // // ), // // ); // // // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); // // // // await checkReceivingAddressForTransactions(); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); // // // // await _refreshUTXOs(); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); // // // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.25, walletId)); // // // // await _refreshTransactions(); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId)); // // // // final feeObj = _getFees(); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); // // // // _feeObject = Future(() => feeObj); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); // // // // // final lelantusCoins = getLelantusCoinMap(); // // // Logging.instance.log("_lelantus_coins at refresh: $lelantusCoins", // // // level: LogLevel.Warning, printFullLength: true); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId)); // // // // await _refreshLelantusData(); // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); // // // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); // // // // await _refreshBalance(); // // // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId)); // // // // await getAllTxsToWatch(); // // // // GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); // // // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.synced, // // walletId, // // coin, // // ), // // ); // // refreshMutex = false; // // // // if (isActive || shouldAutoSync) { // // timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { // // bool shouldNotify = await refreshIfThereIsNewData(); // // if (shouldNotify) { // // await refresh(); // // GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( // // "New data found in $walletId $walletName in background!", // // walletId)); // // } // // }); // // } // // } catch (error, strace) { // // refreshMutex = false; // // GlobalEventBus.instance.fire( // // NodeConnectionStatusChangedEvent( // // NodeConnectionStatus.disconnected, // // walletId, // // coin, // // ), // // ); // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.unableToSync, // // walletId, // // coin, // // ), // // ); // // Logging.instance.log( // // "Caught exception in refreshWalletData(): $error\n$strace", // // level: LogLevel.Warning); // // } // // } // // // Future _fetchMaxFee() async { // // final balance = availablePrivateBalance(); // // int spendAmount = // // (balance.decimal * Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) // // .toBigInt() // // .toInt(); // // int fee = await estimateJoinSplitFee(spendAmount); // // return fee; // // } // // // Future> _getLelantusEntry() async { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // if (_mnemonicPassphrase == null) { // // Logging.instance.log( // // "Exception in _getLelantusEntry: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", // // level: LogLevel.Error); // // } // // // // final List lelantusCoins = // // await _getUnspentCoins(); // // // // final root = await Bip32Utils.getBip32Root( // // _mnemonic!, // // _mnemonicPassphrase!, // // _network, // // ); // // // // final waitLelantusEntries = lelantusCoins.map((coin) async { // // final derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: MINT_INDEX, // // index: coin.mintIndex, // // ); // // final keyPair = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); // // // // if (keyPair.privateKey == null) { // // Logging.instance.log("error bad key", level: LogLevel.Error); // // return DartLelantusEntry(1, 0, 0, 0, 0, ''); // // } // // final String privateKey = Format.uint8listToString(keyPair.privateKey!); // // return DartLelantusEntry(coin.isUsed ? 1 : 0, 0, coin.anonymitySetId, // // int.parse(coin.value), coin.mintIndex, privateKey); // // }).toList(); // // // // final lelantusEntries = await Future.wait(waitLelantusEntries); // // // // if (lelantusEntries.isNotEmpty) { // // // should be redundant as _getUnspentCoins() should // // // already remove all where value=0 // // lelantusEntries.removeWhere((element) => element.amount == 0); // // } // // // // return lelantusEntries; // // } // // // Future> _getUnspentCoins() async { // // final lelantusCoinsList = await db.isar.lelantusCoins // // .where() // // .walletIdEqualTo(walletId) // // .filter() // // .isUsedEqualTo(false) // // .not() // // .group((q) => q // // .valueEqualTo("0") // // .or() // // .anonymitySetIdEqualTo(ANONYMITY_SET_EMPTY_ID)) // // .findAll(); // // // // return lelantusCoinsList; // // } // // // // // index 0 and 1 for the funds available to spend. // // // index 2 and 3 for all the funds in the wallet (including the undependable ones) // // // Future> _refreshBalance() async { // // Future _refreshBalance() async { // // try { // // final utxosUpdateFuture = _refreshUTXOs(); // // final lelantusCoins = await db.isar.lelantusCoins // // .where() // // .walletIdEqualTo(walletId) // // .filter() // // .isUsedEqualTo(false) // // .not() // // .valueEqualTo(0.toString()) // // .findAll(); // // // // final currentChainHeight = await chainHeight; // // int intLelantusBalance = 0; // // int unconfirmedLelantusBalance = 0; // // // // for (final lelantusCoin in lelantusCoins) { // // isar_models.Transaction? txn = db.isar.transactions // // .where() // // .txidWalletIdEqualTo( // // lelantusCoin.txid, // // walletId, // // ) // // .findFirstSync(); // // // // if (txn == null) { // // Logging.instance.log( // // "Transaction not found in DB for lelantus coin: $lelantusCoin", // // level: LogLevel.Fatal, // // ); // // } else { // // if (txn.isLelantus != true) { // // Logging.instance.log( // // "Bad database state found in $walletName $walletId for _refreshBalance lelantus", // // level: LogLevel.Fatal, // // ); // // } // // // // if (txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // // // mint tx, add value to balance // // intLelantusBalance += int.parse(lelantusCoin.value); // // } else { // // unconfirmedLelantusBalance += int.parse(lelantusCoin.value); // // } // // } // // } // // // // _balancePrivate = Balance( // // total: Amount( // // rawValue: // // BigInt.from(intLelantusBalance + unconfirmedLelantusBalance), // // fractionDigits: coin.decimals, // // ), // // spendable: Amount( // // rawValue: BigInt.from(intLelantusBalance), // // fractionDigits: coin.decimals, // // ), // // blockedTotal: Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ), // // pendingSpendable: Amount( // // rawValue: BigInt.from(unconfirmedLelantusBalance), // // fractionDigits: coin.decimals, // // ), // // ); // // await updateCachedBalanceSecondary(_balancePrivate!); // // // // // wait for updated uxtos to get updated public balance // // await utxosUpdateFuture; // // } catch (e, s) { // // Logging.instance.log("Exception rethrown in getFullBalance(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // Future anonymizeAllPublicFunds() async { // // try { // // var mintResult = await _mintSelection(); // // if (mintResult.isEmpty) { // // Logging.instance.log("nothing to mint", level: LogLevel.Info); // // return; // // } // // await _submitLelantusToNetwork(mintResult); // // unawaited(refresh()); // // } catch (e, s) { // // Logging.instance.log( // // "Exception caught in anonymizeAllPublicFunds(): $e\n$s", // // level: LogLevel.Warning); // // rethrow; // // } // // } // // // /// Returns the mint transaction hex to mint all of the available funds. // // Future> _mintSelection() async { // // final currentChainHeight = await chainHeight; // // final List availableOutputs = await utxos; // // final List spendableOutputs = []; // // // // // Build list of spendable outputs and totaling their satoshi amount // // for (var i = 0; i < availableOutputs.length; i++) { // // if (availableOutputs[i].isBlocked == false && // // availableOutputs[i] // // .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == // // true && // // !(availableOutputs[i].isCoinbase && // // availableOutputs[i].getConfirmations(currentChainHeight) <= // // 101)) { // // spendableOutputs.add(availableOutputs[i]); // // } // // } // // // // final lelantusCoins = await db.isar.lelantusCoins // // .where() // // .walletIdEqualTo(walletId) // // .filter() // // .not() // // .valueEqualTo(0.toString()) // // .findAll(); // // // // final data = await _txnData; // // for (final value in data) { // // if (value.inputs.isNotEmpty) { // // for (var element in value.inputs) { // // if (lelantusCoins.any((e) => e.txid == value.txid) && // // spendableOutputs.firstWhere( // // (output) => output?.txid == element.txid, // // orElse: () => null) != // // null) { // // spendableOutputs // // .removeWhere((output) => output!.txid == element.txid); // // } // // } // // } // // } // // // // // If there is no Utxos to mint then stop the function. // // if (spendableOutputs.isEmpty) { // // Logging.instance.log("_mintSelection(): No spendable outputs found", // // level: LogLevel.Info); // // return {}; // // } // // // // int satoshisBeingUsed = 0; // // List utxoObjectsToUse = []; // // // // for (var i = 0; i < spendableOutputs.length; i++) { // // final spendable = spendableOutputs[i]; // // if (spendable != null) { // // utxoObjectsToUse.add(spendable); // // satoshisBeingUsed += spendable.value; // // } // // } // // // // var mintsWithoutFee = await createMintsFromAmount(satoshisBeingUsed); // // // // var tmpTx = await buildMintTransaction( // // utxoObjectsToUse, satoshisBeingUsed, mintsWithoutFee); // // // // int vSize = (tmpTx['transaction'] as Transaction).virtualSize(); // // final Decimal dvSize = Decimal.fromInt(vSize); // // // // final feesObject = await fees; // // // // final Decimal fastFee = Amount( // // rawValue: BigInt.from(feesObject.fast), // // fractionDigits: coin.decimals, // // ).decimal; // // int firoFee = // // (dvSize * fastFee * Decimal.fromInt(100000)).toDouble().ceil(); // // // int firoFee = (vSize * feesObject.fast * (1 / 1000.0) * 100000000).ceil(); // // // // if (firoFee < vSize) { // // firoFee = vSize + 1; // // } // // firoFee = firoFee + 10; // // int satoshiAmountToSend = satoshisBeingUsed - firoFee; // // // // var mintsWithFee = await createMintsFromAmount(satoshiAmountToSend); // // // // Map transaction = await buildMintTransaction( // // utxoObjectsToUse, satoshiAmountToSend, mintsWithFee); // // transaction['transaction'] = ""; // // Logging.instance.log(transaction.toString(), level: LogLevel.Info); // // Logging.instance.log(transaction['txHex'], level: LogLevel.Info); // // return transaction; // // } // // // Future>> createMintsFromAmount(int total) async { // // if (total > MINT_LIMIT) { // // throw Exception( // // "Lelantus mints of more than 5001 are currently disabled"); // // } // // // // int tmpTotal = total; // // int counter = 0; // // final lastUsedIndex = await db.getHighestUsedMintIndex(walletId: walletId); // // final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; // // // // final root = await Bip32Utils.getBip32Root( // // (await mnemonic).join(" "), // // (await mnemonicPassphrase)!, // // _network, // // ); // // // // final mints = >[]; // // while (tmpTotal > 0) { // // final index = nextFreeMintIndex + counter; // // // // final bip32.BIP32 mintKeyPair = await Bip32Utils.getBip32NodeFromRoot( // // root, // // constructDerivePath( // // networkWIF: _network.wif, // // chain: MINT_INDEX, // // index: index, // // ), // // ); // // // // final String mintTag = CreateTag( // // Format.uint8listToString(mintKeyPair.privateKey!), // // index, // // Format.uint8listToString(mintKeyPair.identifier), // // isTestnet: coin == Coin.firoTestNet, // // ); // // final List> anonymitySets; // // try { // // anonymitySets = await fetchAnonymitySets(); // // } catch (e, s) { // // Logging.instance.log( // // "Firo needs better internet to create mints: $e\n$s", // // level: LogLevel.Fatal, // // ); // // rethrow; // // } // // // // bool isUsedMintTag = false; // // // // // stupid dynamic maps // // for (final set in anonymitySets) { // // final setCoins = set["coins"] as List; // // for (final coin in setCoins) { // // if (coin[1] == mintTag) { // // isUsedMintTag = true; // // break; // // } // // } // // if (isUsedMintTag) { // // break; // // } // // } // // // // if (isUsedMintTag) { // // Logging.instance.log( // // "Found used index when minting", // // level: LogLevel.Warning, // // ); // // } // // // // if (!isUsedMintTag) { // // final mintValue = min(tmpTotal, // // (coin == Coin.firoTestNet ? MINT_LIMIT_TESTNET : MINT_LIMIT)); // // final mint = await _getMintHex( // // mintValue, // // index, // // ); // // // // mints.add({ // // "value": mintValue, // // "script": mint, // // "index": index, // // }); // // tmpTotal = tmpTotal - // // (coin == Coin.firoTestNet ? MINT_LIMIT_TESTNET : MINT_LIMIT); // // } // // // // counter++; // // } // // return mints; // // } // // /// returns a valid txid if successful // Future submitHexToNetwork(String hex) async { // try { // final txid = await electrumXClient.broadcastTransaction(rawTx: hex); // return txid; // } catch (e, s) { // Logging.instance.log( // "Caught exception in submitHexToNetwork(\"$hex\"): $e $s", // printFullLength: true, // level: LogLevel.Info); // // return an invalid tx // return "transaction submission failed"; // } // } // // // /// Builds and signs a transaction // // Future> buildMintTransaction( // // List utxosToUse, // // int satoshisPerRecipient, // // List> mintsMap, // // ) async { // // List addressStringsToGet = []; // // // // // Populating the addresses to derive // // for (var i = 0; i < utxosToUse.length; i++) { // // final txid = utxosToUse[i].txid; // // final outputIndex = utxosToUse[i].vout; // // // // // txid may not work for this as txid may not always be the same as tx_hash? // // final tx = await cachedElectrumXClient.getTransaction( // // txHash: txid, // // verbose: true, // // coin: coin, // // ); // // // // final vouts = tx["vout"] as List?; // // if (vouts != null && outputIndex < vouts.length) { // // final address = // // vouts[outputIndex]["scriptPubKey"]["addresses"][0] as String?; // // if (address != null) { // // addressStringsToGet.add(address); // // } // // } // // } // // // // final List addresses = []; // // for (final addressString in addressStringsToGet) { // // final address = await db.getAddress(walletId, addressString); // // if (address == null) { // // Logging.instance.log( // // "Failed to fetch the corresponding address object for $addressString", // // level: LogLevel.Fatal, // // ); // // } else { // // addresses.add(address); // // } // // } // // // // List ellipticCurvePairArray = []; // // List outputDataArray = []; // // // // Map? receiveDerivations; // // Map? changeDerivations; // // // // for (final addressString in addressStringsToGet) { // // String? pubKey; // // String? wif; // // // // final address = await db.getAddress(walletId, addressString); // // // // if (address?.derivationPath != null) { // // final node = await Bip32Utils.getBip32Node( // // (await mnemonicString)!, // // (await mnemonicPassphrase)!, // // _network, // // address!.derivationPath!.value, // // ); // // wif = node.toWIF(); // // pubKey = Format.uint8listToString(node.publicKey); // // } // // // // if (wif == null || pubKey == null) { // // receiveDerivations ??= Map.from( // // jsonDecode((await _secureStore.read( // // key: "${walletId}_receiveDerivations")) ?? // // "{}") as Map, // // ); // // for (var i = 0; i < receiveDerivations.length; i++) { // // final receive = receiveDerivations["$i"]; // // if (receive['address'] == addressString) { // // wif = receive['wif'] as String; // // pubKey = receive['publicKey'] as String; // // break; // // } // // } // // // // if (wif == null || pubKey == null) { // // changeDerivations ??= Map.from( // // jsonDecode((await _secureStore.read( // // key: "${walletId}_changeDerivations")) ?? // // "{}") as Map, // // ); // // // // for (var i = 0; i < changeDerivations.length; i++) { // // final change = changeDerivations["$i"]; // // if (change['address'] == addressString) { // // wif = change['wif'] as String; // // pubKey = change['publicKey'] as String; // // // // break; // // } // // } // // } // // } // // // // ellipticCurvePairArray.add( // // ECPair.fromWIF( // // wif!, // // network: _network, // // ), // // ); // // outputDataArray.add(P2PKH( // // network: _network, // // data: PaymentData( // // pubkey: Format.stringToUint8List( // // pubKey!, // // ), // // ), // // ).data.output!); // // } // // // // final txb = TransactionBuilder(network: _network); // // txb.setVersion(2); // // // // int height = await getBlockHead(electrumXClient); // // txb.setLockTime(height); // // int amount = 0; // // // Add transaction inputs // // for (var i = 0; i < utxosToUse.length; i++) { // // txb.addInput( // // utxosToUse[i].txid, utxosToUse[i].vout, null, outputDataArray[i]); // // amount += utxosToUse[i].value; // // } // // // // for (var mintsElement in mintsMap) { // // Logging.instance.log("using $mintsElement", level: LogLevel.Info); // // Uint8List mintu8 = // // Format.stringToUint8List(mintsElement['script'] as String); // // txb.addOutput(mintu8, mintsElement['value'] as int); // // } // // // // for (var i = 0; i < utxosToUse.length; i++) { // // txb.sign( // // vin: i, // // keyPair: ellipticCurvePairArray[i], // // witnessValue: utxosToUse[i].value, // // ); // // } // // var incomplete = txb.buildIncomplete(); // // var txId = incomplete.getId(); // // var txHex = incomplete.toHex(); // // int fee = amount - incomplete.outs[0].value!; // // // // var builtHex = txb.build(); // // // return builtHex; // // // final locale = // // // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; // // return { // // "transaction": builtHex, // // "txid": txId, // // "txHex": txHex, // // "value": amount - fee, // // "fees": Amount( // // rawValue: BigInt.from(fee), // // fractionDigits: coin.decimals, // // ).decimal.toDouble(), // // "height": height, // // "txType": "Sent", // // "confirmed_status": false, // // "amount": Amount( // // rawValue: BigInt.from(amount), // // fractionDigits: coin.decimals, // // ).decimal.toDouble(), // // "timestamp": DateTime.now().millisecondsSinceEpoch ~/ 1000, // // "subType": "mint", // // "mintsMap": mintsMap, // // }; // // } // // // // TODO: verify this function does what we think it does // // Future _refreshLelantusData() async { // // final lelantusCoins = await db.isar.lelantusCoins // // .where() // // .walletIdEqualTo(walletId) // // .filter() // // .isUsedEqualTo(false) // // .not() // // .valueEqualTo(0.toString()) // // .findAll(); // // // // final List updatedCoins = []; // // // // final usedSerialNumbersSet = (await getUsedCoinSerials()).toSet(); // // // // final root = await Bip32Utils.getBip32Root( // // (await mnemonic).join(" "), // // (await mnemonicPassphrase)!, // // _network, // // ); // // // // for (final coin in lelantusCoins) { // // final _derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: MINT_INDEX, // // index: coin.mintIndex, // // ); // // final bip32.BIP32 mintKeyPair = await Bip32Utils.getBip32NodeFromRoot( // // root, // // _derivePath, // // ); // // // // final String serialNumber = GetSerialNumber( // // int.parse(coin.value), // // Format.uint8listToString(mintKeyPair.privateKey!), // // coin.mintIndex, // // isTestnet: this.coin == Coin.firoTestNet, // // ); // // final bool isUsed = usedSerialNumbersSet.contains(serialNumber); // // // // if (isUsed) { // // updatedCoins.add(coin.copyWith(isUsed: isUsed)); // // } // // // // final tx = await db.getTransaction(walletId, coin.txid); // // if (tx == null) { // // print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); // // } // // } // // // // if (updatedCoins.isNotEmpty) { // // try { // // await db.isar.writeTxn(() async { // // for (final c in updatedCoins) { // // await db.isar.lelantusCoins.deleteByMintIndexWalletId( // // c.mintIndex, // // c.walletId, // // ); // // } // // await db.isar.lelantusCoins.putAll(updatedCoins); // // }); // // } catch (e, s) { // // Logging.instance.log( // // "$e\n$s", // // level: LogLevel.Fatal, // // ); // // rethrow; // // } // // } // // } // // // Future _getMintHex(int amount, int index) async { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // if (_mnemonicPassphrase == null) { // // Logging.instance.log( // // "Exception in _getMintHex: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", // // level: LogLevel.Error); // // } // // // // final derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: MINT_INDEX, // // index: index, // // ); // // final mintKeyPair = await Bip32Utils.getBip32Node( // // _mnemonic!, // // _mnemonicPassphrase!, // // _network, // // derivePath, // // ); // // // // String keydata = Format.uint8listToString(mintKeyPair.privateKey!); // // String seedID = Format.uint8listToString(mintKeyPair.identifier); // // // // String mintHex = await compute( // // _getMintScriptWrapper, // // Tuple5( // // amount, // // keydata, // // index, // // seedID, // // coin == Coin.firoTestNet, // // ), // // ); // // return mintHex; // // } // // // Future _submitLelantusToNetwork( // // Map transactionInfo) async { // // // final latestSetId = await getLatestSetId(); // // final txid = await submitHexToNetwork(transactionInfo['txHex'] as String); // // // success if txid matches the generated txid // // Logging.instance.log( // // "_submitLelantusToNetwork txid: ${transactionInfo['txid']}", // // level: LogLevel.Info); // // // // if (txid == transactionInfo['txid']) { // // final lastUsedIndex = // // await db.getHighestUsedMintIndex(walletId: walletId); // // final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; // // // // if (transactionInfo['spendCoinIndexes'] != null) { // // // This is a joinsplit // // // // final spentCoinIndexes = // // transactionInfo['spendCoinIndexes'] as List; // // final List updatedCoins = []; // // // // // Update all of the coins that have been spent. // // // // for (final index in spentCoinIndexes) { // // final possibleCoin = await db.isar.lelantusCoins // // .where() // // .mintIndexWalletIdEqualTo(index, walletId) // // .findFirst(); // // // // if (possibleCoin != null) { // // updatedCoins.add(possibleCoin.copyWith(isUsed: true)); // // } // // } // // // // // if a jmint was made add it to the unspent coin index // // final jmint = isar_models.LelantusCoin( // // walletId: walletId, // // mintIndex: nextFreeMintIndex, // // value: (transactionInfo['jmintValue'] as int? ?? 0).toString(), // // txid: transactionInfo['txid'] as String, // // anonymitySetId: latestSetId, // // isUsed: false, // // isJMint: true, // // otherData: null, // // ); // // // // try { // // await db.isar.writeTxn(() async { // // for (final c in updatedCoins) { // // await db.isar.lelantusCoins.deleteByMintIndexWalletId( // // c.mintIndex, // // c.walletId, // // ); // // } // // await db.isar.lelantusCoins.putAll(updatedCoins); // // // // await db.isar.lelantusCoins.put(jmint); // // }); // // } catch (e, s) { // // Logging.instance.log( // // "$e\n$s", // // level: LogLevel.Fatal, // // ); // // rethrow; // // } // // // // final amount = Amount.fromDecimal( // // Decimal.parse(transactionInfo["amount"].toString()), // // fractionDigits: coin.decimals, // // ); // // // // // add the send transaction // // final transaction = isar_models.Transaction( // // walletId: walletId, // // txid: transactionInfo['txid'] as String, // // timestamp: transactionInfo['timestamp'] as int? ?? // // (DateTime.now().millisecondsSinceEpoch ~/ 1000), // // type: isar_models.TransactionType.outgoing, // // subType: isar_models.TransactionSubType.join, // // amount: amount.raw.toInt(), // // amountString: amount.toJsonString(), // // fee: Amount.fromDecimal( // // Decimal.parse(transactionInfo["fees"].toString()), // // fractionDigits: coin.decimals, // // ).raw.toInt(), // // height: transactionInfo["height"] as int?, // // isCancelled: false, // // isLelantus: true, // // slateId: null, // // nonce: null, // // otherData: transactionInfo["otherData"] as String?, // // inputs: [], // // outputs: [], // // numberOfMessages: null, // // ); // // // // final transactionAddress = await db // // .getAddresses(walletId) // // .filter() // // .valueEqualTo(transactionInfo["address"] as String) // // .findFirst() ?? // // isar_models.Address( // // walletId: walletId, // // value: transactionInfo["address"] as String, // // derivationIndex: -1, // // derivationPath: null, // // type: isar_models.AddressType.nonWallet, // // subType: isar_models.AddressSubType.nonWallet, // // publicKey: [], // // ); // // // // final List> // // txnsData = []; // // // // txnsData.add(Tuple2(transaction, transactionAddress)); // // // // await db.addNewTransactionData(txnsData, walletId); // // } else { // // // This is a mint // // Logging.instance.log("this is a mint", level: LogLevel.Info); // // // // final List updatedCoins = []; // // // // for (final mintMap // // in transactionInfo['mintsMap'] as List>) { // // final index = mintMap['index'] as int; // // final mint = isar_models.LelantusCoin( // // walletId: walletId, // // mintIndex: index, // // value: (mintMap['value'] as int).toString(), // // txid: transactionInfo['txid'] as String, // // anonymitySetId: latestSetId, // // isUsed: false, // // isJMint: false, // // otherData: null, // // ); // // // // updatedCoins.add(mint); // // } // // // Logging.instance.log(coins); // // try { // // await db.isar.writeTxn(() async { // // await db.isar.lelantusCoins.putAll(updatedCoins); // // }); // // } catch (e, s) { // // Logging.instance.log( // // "$e\n$s", // // level: LogLevel.Fatal, // // ); // // rethrow; // // } // // } // // return true; // // } else { // // // Failed to send to network // // return false; // // } // // } // // // // Future _getFees() async { // // try { // // //TODO adjust numbers for different speeds? // // const int f = 1, m = 5, s = 20; // // // // final fast = await electrumXClient.estimateFee(blocks: f); // // final medium = await electrumXClient.estimateFee(blocks: m); // // final slow = await electrumXClient.estimateFee(blocks: s); // // // // final feeObject = FeeObject( // // numberOfBlocksFast: f, // // numberOfBlocksAverage: m, // // numberOfBlocksSlow: s, // // fast: Amount.fromDecimal( // // fast, // // fractionDigits: coin.decimals, // // ).raw.toInt(), // // medium: Amount.fromDecimal( // // medium, // // fractionDigits: coin.decimals, // // ).raw.toInt(), // // slow: Amount.fromDecimal( // // slow, // // fractionDigits: coin.decimals, // // ).raw.toInt(), // // ); // // // // Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); // // return feeObject; // // } catch (e) { // // Logging.instance // // .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); // // rethrow; // // } // // } // // // // Future _getCurrentNode() async { // // final node = NodeService(secureStorageInterface: _secureStore) // // .getPrimaryNodeFor(coin: coin) ?? // // DefaultNodes.getNodeFor(coin); // // // // return ElectrumXNode( // // address: node.host, // // port: node.port, // // name: node.name, // // useSSL: node.useSSL, // // id: node.id, // // ); // // } // // // // Future _getTxCount({required String address}) async { // // try { // // final scriptHash = AddressUtils.convertToScriptHash(address, _network); // // final transactions = await electrumXClient.getHistory( // // scripthash: scriptHash, // // ); // // return transactions.length; // // } catch (e) { // // Logging.instance.log( // // "Exception rethrown in _getReceivedTxCount(address: $address): $e", // // level: LogLevel.Error, // // ); // // rethrow; // // } // // } // // // // Future checkReceivingAddressForTransactions() async { // // try { // // final currentReceiving = await _currentReceivingAddress; // // // // final int txCount = await _getTxCount(address: currentReceiving.value); // // Logging.instance.log( // // 'Number of txs for current receiving address $currentReceiving: $txCount', // // level: LogLevel.Info); // // // // if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // // // First increment the receiving index // // final newReceivingIndex = currentReceiving.derivationIndex + 1; // // // // // Use new index to derive a new receiving address // // final newReceivingAddress = await _generateAddressForChain( // // 0, // // newReceivingIndex, // // ); // // // // final existing = await db // // .getAddresses(walletId) // // .filter() // // .valueEqualTo(newReceivingAddress.value) // // .findFirst(); // // if (existing == null) { // // // Add that new change address // // await db.putAddress(newReceivingAddress); // // } else { // // // we need to update the address // // await db.updateAddress(existing, newReceivingAddress); // // } // // // keep checking until address with no tx history is set as current // // await checkReceivingAddressForTransactions(); // // } // // } on SocketException catch (se, s) { // // Logging.instance.log( // // "SocketException caught in checkReceivingAddressForTransactions(): $se\n$s", // // level: LogLevel.Error); // // return; // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from checkReceivingAddressForTransactions(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // Future checkChangeAddressForTransactions() async { // // try { // // final currentChange = await _currentChangeAddress; // // final int txCount = await _getTxCount(address: currentChange.value); // // Logging.instance.log( // // 'Number of txs for current change address: $currentChange: $txCount', // // level: LogLevel.Info); // // // // if (txCount >= 1 || currentChange.derivationIndex < 0) { // // // First increment the change index // // final newChangeIndex = currentChange.derivationIndex + 1; // // // // // Use new index to derive a new change address // // final newChangeAddress = await _generateAddressForChain( // // 1, // // newChangeIndex, // // ); // // // // final existing = await db // // .getAddresses(walletId) // // .filter() // // .valueEqualTo(newChangeAddress.value) // // .findFirst(); // // if (existing == null) { // // // Add that new change address // // await db.putAddress(newChangeAddress); // // } else { // // // we need to update the address // // await db.updateAddress(existing, newChangeAddress); // // } // // // keep checking until address with no tx history is set as current // // await checkChangeAddressForTransactions(); // // } // // } on SocketException catch (se, s) { // // Logging.instance.log( // // "SocketException caught in checkChangeAddressForTransactions(): $se\n$s", // // level: LogLevel.Error); // // return; // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from checkChangeAddressForTransactions(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // Future> _fetchAllOwnAddresses() async { // // final allAddresses = await db // // .getAddresses(walletId) // // .filter() // // .not() // // .group( // // (q) => q // // .typeEqualTo(isar_models.AddressType.nonWallet) // // .or() // // .subTypeEqualTo(isar_models.AddressSubType.nonWallet), // // ) // // .findAll(); // // return allAddresses; // // } // // // Future>> _fetchHistory( // // List allAddresses) async { // // try { // // List> allTxHashes = []; // // // // final Map>> batches = {}; // // final Map requestIdToAddressMap = {}; // // const batchSizeMax = 100; // // int batchNumber = 0; // // for (int i = 0; i < allAddresses.length; i++) { // // if (batches[batchNumber] == null) { // // batches[batchNumber] = {}; // // } // // final scripthash = // // AddressUtils.convertToScriptHash(allAddresses[i], _network); // // final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); // // requestIdToAddressMap[id] = allAddresses[i]; // // batches[batchNumber]!.addAll({ // // id: [scripthash] // // }); // // if (i % batchSizeMax == batchSizeMax - 1) { // // batchNumber++; // // } // // } // // // // for (int i = 0; i < batches.length; i++) { // // final response = // // await _electrumXClient.getBatchHistory(args: batches[i]!); // // for (final entry in response.entries) { // // for (int j = 0; j < entry.value.length; j++) { // // entry.value[j]["address"] = requestIdToAddressMap[entry.key]; // // if (!allTxHashes.contains(entry.value[j])) { // // allTxHashes.add(entry.value[j]); // // } // // } // // } // // } // // // // return allTxHashes; // // } catch (e, s) { // // Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error); // // rethrow; // // } // // } // // // bool _duplicateTxCheck( // // List> allTransactions, String txid) { // // for (int i = 0; i < allTransactions.length; i++) { // // if (allTransactions[i]["txid"] == txid) { // // return true; // // } // // } // // return false; // // } // // // Future _refreshTransactions() async { // // // // } // // // Future _refreshUTXOs() async { // // final allAddresses = await _fetchAllOwnAddresses(); // // // // try { // // final fetchedUtxoList = >>[]; // // // // final Map>> batches = {}; // // const batchSizeMax = 100; // // int batchNumber = 0; // // for (int i = 0; i < allAddresses.length; i++) { // // if (batches[batchNumber] == null) { // // batches[batchNumber] = {}; // // } // // final scripthash = // // AddressUtils.convertToScriptHash(allAddresses[i].value, _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 currentChainHeight = await chainHeight; // // // // final List outputArray = []; // // Amount satoshiBalanceTotal = Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ); // // Amount satoshiBalancePending = Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ); // // Amount satoshiBalanceSpendable = Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ); // // Amount satoshiBalanceBlocked = Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ); // // // // for (int i = 0; i < fetchedUtxoList.length; i++) { // // for (int j = 0; j < fetchedUtxoList[i].length; j++) { // // final txn = await cachedElectrumXClient.getTransaction( // // txHash: fetchedUtxoList[i][j]["tx_hash"] as String, // // verbose: true, // // coin: coin, // // ); // // // // final utxo = isar_models.UTXO( // // walletId: walletId, // // txid: txn["txid"] as String, // // vout: fetchedUtxoList[i][j]["tx_pos"] as int, // // value: fetchedUtxoList[i][j]["value"] as int, // // name: "", // // isBlocked: false, // // blockedReason: null, // // isCoinbase: txn["is_coinbase"] as bool? ?? false, // // blockHash: txn["blockhash"] as String?, // // blockHeight: fetchedUtxoList[i][j]["height"] as int?, // // blockTime: txn["blocktime"] as int?, // // ); // // // // final utxoAmount = Amount( // // rawValue: BigInt.from(utxo.value), // // fractionDigits: coin.decimals, // // ); // // satoshiBalanceTotal = satoshiBalanceTotal + utxoAmount; // // // // if (utxo.isBlocked) { // // satoshiBalanceBlocked = satoshiBalanceBlocked + utxoAmount; // // } else { // // if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // // satoshiBalanceSpendable = satoshiBalanceSpendable + utxoAmount; // // } else { // // satoshiBalancePending = satoshiBalancePending + utxoAmount; // // } // // } // // // // outputArray.add(utxo); // // } // // } // // // // Logging.instance // // .log('Outputs fetched: $outputArray', level: LogLevel.Info); // // // // await db.isar.writeTxn(() async { // // await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll(); // // await db.isar.utxos.putAll(outputArray); // // }); // // // // // finally update public balance // // _balance = Balance( // // total: satoshiBalanceTotal, // // spendable: satoshiBalanceSpendable, // // blockedTotal: satoshiBalanceBlocked, // // pendingSpendable: satoshiBalancePending, // // ); // // await updateCachedBalance(_balance!); // // } catch (e, s) { // // Logging.instance // // .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); // // } // // } // // // // /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] // // /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! // // Future _getCurrentAddressForChain(int chain) async { // // final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 // // ? isar_models.AddressSubType.receiving // // : isar_models.AddressSubType.change; // // // // isar_models.Address? address = await db // // .getAddresses(walletId) // // .filter() // // .typeEqualTo(isar_models.AddressType.p2pkh) // // .subTypeEqualTo(subType) // // .sortByDerivationIndexDesc() // // .findFirst(); // // // // return address!.value; // // } // // // // /// Generates a new internal or external chain address for the wallet using a BIP84 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) async { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // if (_mnemonicPassphrase == null) { // // Logging.instance.log( // // "Exception in _generateAddressForChain: mnemonic passphrase null," // // " possible migration issue; if using internal builds, delete " // // "wallet and restore from seed, if using a release build, " // // "please file bug report", // // level: LogLevel.Error); // // } // // // // final derivePath = constructDerivePath( // // networkWIF: _network.wif, // // chain: chain, // // index: index, // // ); // // // // final node = await Bip32Utils.getBip32Node( // // _mnemonic!, // // _mnemonicPassphrase!, // // _network, // // derivePath, // // ); // // // // final address = P2PKH( // // network: _network, // // data: PaymentData( // // pubkey: node.publicKey, // // ), // // ).data.address!; // // // // return isar_models.Address( // // walletId: walletId, // // value: address, // // publicKey: node.publicKey, // // type: isar_models.AddressType.p2pkh, // // derivationIndex: index, // // derivationPath: isar_models.DerivationPath()..value = derivePath, // // subType: chain == 0 // // ? isar_models.AddressSubType.receiving // // : isar_models.AddressSubType.change, // // ); // // } // // // @override // // Future fullRescan( // // int maxUnusedAddressGap, // // int maxNumberOfIndexesToCheck, // // ) async { // // Logging.instance.log("Starting full rescan!", level: LogLevel.Info); // // // timer?.cancel(); // // // for (final isolate in isolates.values) { // // // isolate.kill(priority: Isolate.immediate); // // // } // // // isolates.clear(); // // longMutex = true; // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.syncing, // // walletId, // // coin, // // ), // // ); // // // // // clear cache // // await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // // // // // back up data // // // await _rescanBackup(); // // // // // clear blockchain info // // await db.deleteWalletBlockchainData(walletId); // // await _deleteDerivations(); // // // // try { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // if (_mnemonicPassphrase == null) { // // Logging.instance.log( // // "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", // // level: LogLevel.Error); // // } // // // // await _recoverWalletFromBIP32SeedPhrase( // // _mnemonic!, // // _mnemonicPassphrase!, // // maxUnusedAddressGap, // // maxNumberOfIndexesToCheck, // // true, // // ); // // // // longMutex = false; // // await refresh(); // // Logging.instance.log("Full rescan complete!", level: LogLevel.Info); // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.synced, // // walletId, // // coin, // // ), // // ); // // } catch (e, s) { // // GlobalEventBus.instance.fire( // // WalletSyncStatusChangedEvent( // // WalletSyncStatus.unableToSync, // // walletId, // // coin, // // ), // // ); // // // // // restore from backup // // // await _rescanRestore(); // // // // longMutex = false; // // Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // Future _deleteDerivations() async { // // // P2PKH derivations // // await _secureStore.delete(key: "${walletId}_receiveDerivations"); // // await _secureStore.delete(key: "${walletId}_changeDerivations"); // // } // // // // /// wrapper for _recoverWalletFromBIP32SeedPhrase() // // @override // // Future recoverFromMnemonic({ // // required String mnemonic, // // String? mnemonicPassphrase, // // required int maxUnusedAddressGap, // // required int maxNumberOfIndexesToCheck, // // required int height, // // }) async { // // try { // // await compute( // // _setTestnetWrapper, // // coin == Coin.firoTestNet, // // ); // // 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.firo: // // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { // // throw Exception("genesis hash does not match main net!"); // // } // // break; // // case Coin.firoTestNet: // // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { // // throw Exception("genesis hash does not match test net!"); // // } // // break; // // default: // // throw Exception( // // "Attempted to generate a FiroWallet using a non firo 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!"); // // // } // // // } // // } // // // this should never fail // // if ((await mnemonicString) != null || // // (await this.mnemonicPassphrase) != null) { // // longMutex = false; // // throw Exception("Attempted to overwrite mnemonic on restore!"); // // } // // await _secureStore.write( // // key: '${_walletId}_mnemonic', value: mnemonic.trim()); // // await _secureStore.write( // // key: '${_walletId}_mnemonicPassphrase', // // value: mnemonicPassphrase ?? "", // // ); // // await _recoverWalletFromBIP32SeedPhrase( // // mnemonic.trim(), // // mnemonicPassphrase ?? "", // // maxUnusedAddressGap, // // maxNumberOfIndexesToCheck, // // false, // // ); // // await setLelantusCoinIsarRescanRequiredDone(); // // // // await compute( // // _setTestnetWrapper, // // false, // // ); // // } catch (e, s) { // // await compute( // // _setTestnetWrapper, // // false, // // ); // // Logging.instance.log( // // "Exception rethrown from recoverFromMnemonic(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // bool longMutex = false; // // // Future> getSetDataMap(int latestSetId) async { // // final Map setDataMap = {}; // // final anonymitySets = await fetchAnonymitySets(); // // for (int setId = 1; setId <= latestSetId; setId++) { // // final setData = anonymitySets // // .firstWhere((element) => element["setId"] == setId, orElse: () => {}); // // // // if (setData.isNotEmpty) { // // setDataMap[setId] = setData; // // } // // } // // return setDataMap; // // } // // // Future> _getBatchTxCount({ // // required Map addresses, // // }) async { // // try { // // final Map> args = {}; // // for (final entry in addresses.entries) { // // args[entry.key] = [ // // AddressUtils.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, int>> _checkGaps( // // int maxNumberOfIndexesToCheck, // // int maxUnusedAddressGap, // // int txCountBatchSize, // // bip32.BIP32 root, // // int chain, // // ) async { // // List addressArray = []; // // int gapCounter = 0; // // int highestIndexWithHistory = 0; // // // // for (int index = 0; // // index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; // // index += txCountBatchSize) { // // List iterationsAddressArray = []; // // Logging.instance.log( // // "index: $index, \t GapCounter $chain: $gapCounter", // // level: LogLevel.Info, // // ); // // // // final _id = "k_$index"; // // Map txCountCallArgs = {}; // // // // for (int j = 0; j < txCountBatchSize; j++) { // // final derivePath = constructDerivePath( // // networkWIF: root.network.wif, // // chain: chain, // // index: index + j, // // ); // // final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); // // // // final data = PaymentData(pubkey: node.publicKey); // // final String addressString = P2PKH( // // data: data, // // network: _network, // // ).data.address!; // // const isar_models.AddressType addrType = isar_models.AddressType.p2pkh; // // // // final address = isar_models.Address( // // walletId: walletId, // // value: addressString, // // publicKey: node.publicKey, // // type: addrType, // // derivationIndex: index + j, // // derivationPath: isar_models.DerivationPath()..value = derivePath, // // subType: chain == 0 // // ? isar_models.AddressSubType.receiving // // : isar_models.AddressSubType.change, // // ); // // // // addressArray.add(address); // // // // txCountCallArgs.addAll({ // // "${_id}_$j": addressString, // // }); // // } // // // // // get address tx counts // // final counts = await _getBatchTxCount(addresses: txCountCallArgs); // // // // // check and add appropriate addresses // // for (int k = 0; k < txCountBatchSize; k++) { // // int count = counts["${_id}_$k"]!; // // if (count > 0) { // // iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!); // // // // // update highest // // highestIndexWithHistory = index + k; // // // // // reset counter // // gapCounter = 0; // // } // // // // // 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 Tuple2(addressArray, highestIndexWithHistory); // // } // // // 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 _recoverHistory( // // String suppliedMnemonic, // // String mnemonicPassphrase, // // int maxUnusedAddressGap, // // int maxNumberOfIndexesToCheck, // // bool isRescan, // // ) async { // // final root = await Bip32Utils.getBip32Root( // // suppliedMnemonic, // // mnemonicPassphrase, // // _network, // // ); // // // // final List, int>>> receiveFutures = // // []; // // final List, int>>> changeFutures = // // []; // // // // const receiveChain = 0; // // const changeChain = 1; // // const indexZero = 0; // // // // // 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, // // ); // // // // receiveFutures.add( // // _checkGaps( // // maxNumberOfIndexesToCheck, // // maxUnusedAddressGap, // // txCountBatchSize, // // root, // // receiveChain, // // ), // // ); // // // // // change addresses // // Logging.instance.log( // // "checking change addresses...", // // level: LogLevel.Info, // // ); // // changeFutures.add( // // _checkGaps( // // maxNumberOfIndexesToCheck, // // maxUnusedAddressGap, // // txCountBatchSize, // // root, // // changeChain, // // ), // // ); // // // // // io limitations may require running these linearly instead // // final futuresResult = await Future.wait([ // // Future.wait(receiveFutures), // // Future.wait(changeFutures), // // ]); // // // // final receiveResults = futuresResult[0]; // // final changeResults = futuresResult[1]; // // // // final List addressesToStore = []; // // // // int highestReceivingIndexWithHistory = 0; // // // 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 // // for (final tuple in receiveResults) { // // if (tuple.item1.isEmpty) { // // final address = await _generateAddressForChain( // // receiveChain, // // indexZero, // // ); // // addressesToStore.add(address); // // } else { // // highestReceivingIndexWithHistory = // // max(tuple.item2, highestReceivingIndexWithHistory); // // addressesToStore.addAll(tuple.item1); // // } // // } // // // // int highestChangeIndexWithHistory = 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. // // for (final tuple in changeResults) { // // if (tuple.item1.isEmpty) { // // final address = await _generateAddressForChain( // // changeChain, // // indexZero, // // ); // // addressesToStore.add(address); // // } else { // // highestChangeIndexWithHistory = // // max(tuple.item2, highestChangeIndexWithHistory); // // addressesToStore.addAll(tuple.item1); // // } // // } // // // // // remove extra addresses to help minimize risk of creating a large gap // // addressesToStore.removeWhere((e) => // // e.subType == isar_models.AddressSubType.change && // // e.derivationIndex > highestChangeIndexWithHistory); // // addressesToStore.removeWhere((e) => // // e.subType == isar_models.AddressSubType.receiving && // // e.derivationIndex > highestReceivingIndexWithHistory); // // // // if (isRescan) { // // await db.updateOrPutAddresses(addressesToStore); // // } else { // // await db.putAddresses(addressesToStore); // // } // // // // await Future.wait([ // // _refreshTransactions(), // // _refreshUTXOs(), // // ]); // // // // await Future.wait([ // // updateCachedId(walletId), // // updateCachedIsFavorite(false), // // ]); // // // // longMutex = false; // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", // // level: LogLevel.Error); // // // // longMutex = false; // // rethrow; // // } // // } // // // // /// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic. // // Future _recoverWalletFromBIP32SeedPhrase( // // String suppliedMnemonic, // // String mnemonicPassphrase, // // int maxUnusedAddressGap, // // int maxNumberOfIndexesToCheck, // // bool isRescan, // // ) async { // // longMutex = true; // // Logging.instance // // .log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info); // // try { // // final latestSetId = await getLatestSetId(); // // final setDataMap = getSetDataMap(latestSetId); // // // // final usedSerialNumbers = getUsedCoinSerials(); // // final generateAndCheckAddresses = _recoverHistory( // // suppliedMnemonic, // // mnemonicPassphrase, // // maxUnusedAddressGap, // // maxNumberOfIndexesToCheck, // // isRescan, // // ); // // // // await Future.wait([ // // updateCachedId(walletId), // // updateCachedIsFavorite(false), // // ]); // // // // await Future.wait([ // // usedSerialNumbers, // // setDataMap, // // generateAndCheckAddresses, // // ]); // // // // await _restore(latestSetId, await setDataMap, await usedSerialNumbers); // // longMutex = false; // // } catch (e, s) { // // longMutex = false; // // Logging.instance.log( // // "Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // // Future _restore( // // int latestSetId, // // Map setDataMap, // // List usedSerialNumbers, // // ) async { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // // // final dataFuture = _refreshTransactions(); // // // // ReceivePort receivePort = await getIsolate({ // // "function": "restore", // // "mnemonic": _mnemonic, // // "mnemonicPassphrase": _mnemonicPassphrase, // // "coin": coin, // // "latestSetId": latestSetId, // // "setDataMap": setDataMap, // // "usedSerialNumbers": usedSerialNumbers, // // "network": _network, // // "walletId": walletId, // // }); // // // // await Future.wait([dataFuture]); // // var result = await receivePort.first; // // if (result is String) { // // Logging.instance // // .log("restore() ->> this is a string", level: LogLevel.Error); // // stop(receivePort); // // throw Exception("isolate restore failed."); // // } // // stop(receivePort); // // // // final message = await staticProcessRestore( // // (await _txnData), // // result as Map, // // await chainHeight, // // ); // // // // final coins = message['_lelantus_coins'] as List; // // // // try { // // await db.isar.writeTxn(() async { // // await db.isar.lelantusCoins.putAll(coins); // // }); // // } catch (e, s) { // // Logging.instance.log( // // "$e\n$s", // // level: LogLevel.Fatal, // // ); // // // don't just rethrow since isar likes to strip stack traces for some reason // // throw Exception("e=$e & s=$s"); // // } // // // // final transactionMap = // // message["newTxMap"] as Map; // // Map> data = // // {}; // // // // for (final entry in transactionMap.entries) { // // data[entry.key] = Tuple2(entry.value.address.value, entry.value); // // } // // // // // Create the joinsplit transactions. // // final spendTxs = await getJMintTransactions( // // _cachedElectrumXClient, // // message["spendTxIds"] as List, // // coin, // // ); // // Logging.instance.log(spendTxs, level: LogLevel.Info); // // // // for (var element in spendTxs.entries) { // // final address = element.value.address.value ?? // // data[element.value.txid]?.item1 ?? // // element.key; // // // isar_models.Address( // // // walletId: walletId, // // // value: transactionInfo["address"] as String, // // // derivationIndex: -1, // // // type: isar_models.AddressType.nonWallet, // // // subType: isar_models.AddressSubType.nonWallet, // // // publicKey: [], // // // ); // // // // data[element.value.txid] = Tuple2(address, element.value); // // } // // // // final List> txnsData = // // []; // // // // for (final value in data.values) { // // final transactionAddress = value.item1!; // // final outs = // // value.item2.outputs.where((_) => true).toList(growable: false); // // final ins = value.item2.inputs.where((_) => true).toList(growable: false); // // // // txnsData.add(Tuple2( // // value.item2.copyWith(inputs: ins, outputs: outs).item1, // // transactionAddress)); // // } // // // // await db.addNewTransactionData(txnsData, walletId); // // } // // // Future>> fetchAnonymitySets() async { // // try { // // final latestSetId = await getLatestSetId(); // // // // final List> sets = []; // // List>> anonFutures = []; // // for (int i = 1; i <= latestSetId; i++) { // // final set = cachedElectrumXClient.getAnonymitySet( // // groupId: "$i", // // coin: coin, // // ); // // anonFutures.add(set); // // } // // await Future.wait(anonFutures); // // for (int i = 1; i <= latestSetId; i++) { // // Map set = (await anonFutures[i - 1]); // // set["setId"] = i; // // sets.add(set); // // } // // return sets; // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown from refreshAnonymitySets: $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // // Future _createJoinSplitTransaction( // // int spendAmount, String address, bool subtractFeeFromAmount) async { // // final _mnemonic = await mnemonicString; // // final _mnemonicPassphrase = await mnemonicPassphrase; // // final lastUsedIndex = await db.getHighestUsedMintIndex(walletId: walletId); // // final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; // // final lelantusEntry = await _getLelantusEntry(); // // final anonymitySets = await fetchAnonymitySets(); // // final locktime = await getBlockHead(electrumXClient); // // // final locale = // // // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; // // // // ReceivePort receivePort = await getIsolate({ // // "function": "createJoinSplit", // // "spendAmount": spendAmount, // // "address": address, // // "subtractFeeFromAmount": subtractFeeFromAmount, // // "mnemonic": _mnemonic, // // "mnemonicPassphrase": _mnemonicPassphrase, // // "index": nextFreeMintIndex, // // // "price": price, // // "lelantusEntries": lelantusEntry, // // "locktime": locktime, // // "coin": coin, // // "network": _network, // // "_anonymity_sets": anonymitySets, // // // "locale": locale, // // }); // // var message = await receivePort.first; // // if (message is String) { // // Logging.instance // // .log("Error in CreateJoinSplit: $message", level: LogLevel.Error); // // stop(receivePort); // // return 3; // // } // // if (message is int) { // // stop(receivePort); // // return message; // // } // // stop(receivePort); // // Logging.instance.log('Closing createJoinSplit!', level: LogLevel.Info); // // return message; // // } // // // Future getLatestSetId() async { // // try { // // final id = await electrumXClient.getLelantusLatestCoinId(); // // return id; // // } catch (e, s) { // // Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s", // // level: LogLevel.Error); // // rethrow; // // } // // } // // Future> getUsedCoinSerials() async { // try { // final response = await cachedElectrumXClient.getUsedCoinSerials( // coin: coin, // ); // return response; // } catch (e, s) { // Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s", // level: LogLevel.Error); // rethrow; // } // } // // // // @override // // Future exit() async { // // _hasCalledExit = true; // // timer?.cancel(); // // timer = null; // // stopNetworkAlivePinging(); // // for (final isolate in isolates.values) { // // isolate.kill(priority: Isolate.immediate); // // } // // isolates.clear(); // // Logging.instance // // .log("$walletName firo_wallet exit finished", level: LogLevel.Info); // // } // // // // bool _hasCalledExit = false; // // // // @override // // bool get hasCalledExit => _hasCalledExit; // // // // bool isActive = false; // // // // @override // // void Function(bool)? get onIsActiveWalletChanged => (isActive) async { // // timer?.cancel(); // // timer = null; // // if (isActive) { // // await compute( // // _setTestnetWrapper, // // coin == Coin.firoTestNet, // // ); // // } else { // // await compute( // // _setTestnetWrapper, // // false, // // ); // // } // // this.isActive = isActive; // // }; // // // Future estimateJoinSplitFee( // // int spendAmount, // // ) async { // // var lelantusEntry = await _getLelantusEntry(); // // final balance = availablePrivateBalance().decimal; // // int spendAmount = // // (balance * Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) // // .toBigInt() // // .toInt(); // // if (spendAmount == 0 || lelantusEntry.isEmpty) { // // return LelantusFeeData(0, 0, []).fee; // // } // // ReceivePort receivePort = await getIsolate({ // // "function": "estimateJoinSplit", // // "spendAmount": spendAmount, // // "subtractFeeFromAmount": true, // // "lelantusEntries": lelantusEntry, // // "coin": coin, // // }); // // // // final message = await receivePort.first; // // if (message is String) { // // Logging.instance.log("this is a string", level: LogLevel.Error); // // stop(receivePort); // // throw Exception("_fetchMaxFee isolate failed"); // // } // // stop(receivePort); // // Logging.instance.log('Closing estimateJoinSplit!', level: LogLevel.Info); // // return (message as LelantusFeeData).fee; // // } // // // // @override // // Future estimateFeeFor(Amount amount, int feeRate) async { // // int fee = await estimateJoinSplitFee(amount.raw.toInt()); // // return Amount(rawValue: BigInt.from(fee), fractionDigits: coin.decimals); // // } // // // // Future estimateFeeForPublic(Amount amount, int feeRate) async { // // final available = balance.spendable; // // // // if (available == amount) { // // return amount - (await sweepAllEstimate(feeRate)); // // } else if (amount <= Amount.zero || amount > available) { // // return roughFeeEstimate(1, 2, feeRate); // // } // // // // Amount runningBalance = Amount( // // rawValue: BigInt.zero, // // fractionDigits: coin.decimals, // // ); // // int inputCount = 0; // // for (final output in (await utxos)) { // // if (!output.isBlocked) { // // runningBalance = runningBalance + // // Amount( // // rawValue: BigInt.from(output.value), // // fractionDigits: coin.decimals, // // ); // // inputCount++; // // if (runningBalance > amount) { // // break; // // } // // } // // } // // // // final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); // // final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); // // // // final dustLimitAmount = Amount( // // rawValue: BigInt.from(DUST_LIMIT), // // fractionDigits: coin.decimals, // // ); // // // // if (runningBalance - amount > oneOutPutFee) { // // if (runningBalance - amount > oneOutPutFee + dustLimitAmount) { // // final change = runningBalance - amount - twoOutPutFee; // // if (change > dustLimitAmount && // // runningBalance - amount - change == twoOutPutFee) { // // return runningBalance - amount - change; // // } else { // // return runningBalance - amount; // // } // // } else { // // return runningBalance - amount; // // } // // } else if (runningBalance - amount == oneOutPutFee) { // // return oneOutPutFee; // // } else { // // return twoOutPutFee; // // } // // } // // // Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { // // return Amount( // // rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * // // (feeRatePerKB / 1000).ceil()), // // fractionDigits: coin.decimals, // // ); // // } // // // Future sweepAllEstimate(int feeRate) async { // // int available = 0; // // int inputCount = 0; // // for (final output in (await utxos)) { // // if (!output.isBlocked && // // output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { // // available += output.value; // // inputCount++; // // } // // } // // // // // transaction will only have 1 output minus the fee // // final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); // // // // return Amount( // // rawValue: BigInt.from(available), // // fractionDigits: coin.decimals, // // ) - // // estimatedFee; // // } // // // 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; // // // delete unused large parts // // tx.remove("hex"); // // tx.remove("lelantusData"); // // // // allTransactions.add(tx); // // } // // } // // } // // if (currentFutureCount != 0) { // // currentFutureCount = 0; // // await Future.wait(transactionFutures); // // for (final fTx in transactionFutures) { // // final tx = await fTx; // // // delete unused large parts // // tx.remove("hex"); // // tx.remove("lelantusData"); // // // // allTransactions.add(tx); // // } // // } // // return allTransactions; // // } // // // Future> // // getJMintTransactions( // // CachedElectrumXClient cachedClient, // // List transactions, // // // String currency, // // Coin coin, // // // Decimal currentPrice, // // // String locale, // // ) async { // // try { // // Map txs = {}; // // List> allTransactions = // // await fastFetch(transactions); // // // // for (int i = 0; i < allTransactions.length; i++) { // // try { // // final tx = allTransactions[i]; // // // // var sendIndex = 1; // // if (tx["vout"][0]["value"] != null && // // Decimal.parse(tx["vout"][0]["value"].toString()) > Decimal.zero) { // // sendIndex = 0; // // } // // tx["amount"] = tx["vout"][sendIndex]["value"]; // // tx["address"] = tx["vout"][sendIndex]["scriptPubKey"]["addresses"][0]; // // tx["fees"] = tx["vin"][0]["nFees"]; // // // // final Amount amount = Amount.fromDecimal( // // Decimal.parse(tx["amount"].toString()), // // fractionDigits: coin.decimals, // // ); // // // // final txn = isar_models.Transaction( // // walletId: walletId, // // txid: tx["txid"] as String, // // timestamp: tx["time"] as int? ?? // // (DateTime.now().millisecondsSinceEpoch ~/ 1000), // // type: isar_models.TransactionType.outgoing, // // subType: isar_models.TransactionSubType.join, // // amount: amount.raw.toInt(), // // amountString: amount.toJsonString(), // // fee: Amount.fromDecimal( // // Decimal.parse(tx["fees"].toString()), // // fractionDigits: coin.decimals, // // ).raw.toInt(), // // height: tx["height"] as int?, // // isCancelled: false, // // isLelantus: true, // // slateId: null, // // otherData: null, // // nonce: null, // // inputs: [], // // outputs: [], // // numberOfMessages: null, // // ); // // // // final address = await db // // .getAddresses(walletId) // // .filter() // // .valueEqualTo(tx["address"] as String) // // .findFirst() ?? // // isar_models.Address( // // walletId: walletId, // // value: tx["address"] as String, // // derivationIndex: -2, // // derivationPath: null, // // type: isar_models.AddressType.nonWallet, // // subType: isar_models.AddressSubType.unknown, // // publicKey: [], // // ); // // // // txs[address] = txn; // // } catch (e, s) { // // Logging.instance.log( // // "Exception caught in getJMintTransactions(): $e\n$s", // // level: LogLevel.Info); // // rethrow; // // } // // } // // return txs; // // } catch (e, s) { // // Logging.instance.log( // // "Exception rethrown in getJMintTransactions(): $e\n$s", // // level: LogLevel.Info); // // rethrow; // // } // // } // // @override // Future generateNewAddress() async { // try { // final currentReceiving = await _currentReceivingAddress; // // final newReceivingIndex = currentReceiving.derivationIndex + 1; // // // Use new index to derive a new receiving address // final newReceivingAddress = await _generateAddressForChain( // 0, // newReceivingIndex, // ); // // // Add that new receiving address // await db.putAddress(newReceivingAddress); // // return true; // } catch (e, s) { // Logging.instance.log( // "Exception rethrown from generateNewAddress(): $e\n$s", // level: LogLevel.Error); // return false; // } // } // // //getCachedBalanceSecondary // Amount availablePrivateBalance() { // return balancePrivate.spendable; // } // // Amount availablePublicBalance() { // return balance.spendable; // } // // // Future get chainHeight async { // // try { // // final result = await _electrumXClient.getBlockHeadTip(); // // final height = result["height"] as int; // // await updateCachedChainHeight(height); // // if (height > storedChainHeight) { // // GlobalEventBus.instance.fire( // // UpdatedInBackgroundEvent( // // "Updated current chain height in $walletId $walletName!", // // walletId, // // ), // // ); // // } // // return height; // // } catch (e, s) { // // Logging.instance.log("Exception caught in chainHeight: $e\n$s", // // level: LogLevel.Error); // // return storedChainHeight; // // } // // } // // // // @override // // int get storedChainHeight => getCachedChainHeight(); // // // // @override // // Balance get balance => _balance ??= getCachedBalance(); // // Balance? _balance; // // // // Balance get balancePrivate => _balancePrivate ??= getCachedBalanceSecondary(); // // Balance? _balancePrivate; // // // // @override // // Future> get utxos => db.getUTXOs(walletId).findAll(); // // // // @override // // Future> get transactions => // // db.getTransactions(walletId).findAll(); // // // // @override // // Future get xpub async { // // final node = await Bip32Utils.getBip32Root( // // (await mnemonic).join(" "), // // await mnemonicPassphrase ?? "", // // _network, // // ); // // // // return node.neutered().toBase58(); // // } // }