From 3414c717414a39feffdcfac77c94b2b70770cf7a Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 13 Jan 2023 15:36:19 -0600 Subject: [PATCH] update other coin's parse transaction and db update --- .../coins/bitcoin/bitcoin_wallet.dart | 37 +- .../coins/bitcoincash/bitcoincash_wallet.dart | 71 ++-- .../coins/dogecoin/dogecoin_wallet.dart | 44 ++- lib/services/coins/firo/firo_wallet.dart | 341 +++-------------- .../coins/litecoin/litecoin_wallet.dart | 345 ++---------------- lib/services/coins/monero/monero_wallet.dart | 68 +--- .../coins/namecoin/namecoin_wallet.dart | 342 ++--------------- .../coins/particl/particl_wallet.dart | 149 ++------ .../coins/wownero/wownero_wallet.dart | 68 +--- lib/services/mixins/wallet_db.dart | 42 +++ 10 files changed, 267 insertions(+), 1240 deletions(-) diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index f0ece7dc4..0c7d421e0 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -2064,7 +2064,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB { final List< Tuple4, - List, isar_models.Address>> txnsData = []; + List, isar_models.Address?>> txnsData = []; for (final txObject in allTransactions) { final data = await parseTransaction( @@ -2077,40 +2077,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB { txnsData.add(data); } - await isar.writeTxn(() async { - for (final data in txnsData) { - final tx = data.item1; - - // save transaction - await isar.transactions.put(tx); - - // link and save outputs - if (data.item2.isNotEmpty) { - await isar.outputs.putAll(data.item2); - tx.outputs.addAll(data.item2); - await tx.outputs.save(); - } - - // link and save inputs - if (data.item3.isNotEmpty) { - await isar.inputs.putAll(data.item3); - tx.inputs.addAll(data.item3); - await tx.inputs.save(); - } - - // check if address exists in db and add if it does not - if (await isar.addresses - .where() - .valueEqualTo(data.item4.value) - .findFirst() == - null) { - await isar.addresses.put(data.item4); - } - // link and save address - tx.address.value = data.item4; - await tx.address.save(); - } - }); + await addNewTransactionData(txnsData); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index 4908efc37..0ed724632 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1940,21 +1940,31 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { List> allTransactions = []; - for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final currentHeight = await chainHeight; - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = await isar.addresses - .filter() - .valueEqualTo(txHash["address"] as String) - .findFirst(); - tx["height"] = txHash["height"]; - allTransactions.add(tx); + for (final txHash in allTxHashes) { + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await isar.addresses + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } // @@ -1964,7 +1974,9 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { // Logging.instance.log("allTransactions length: ${allTransactions.length}", // level: LogLevel.Info); - final List txns = []; + final List< + Tuple4, + List, isar_models.Address?>> txns = []; for (final txData in allTransactions) { Set inputAddresses = {}; @@ -2060,6 +2072,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { tx.timestamp = txData["blocktime"] as int? ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000); + // this is the address initially used to fetch the txid + isar_models.Address transactionAddress = + txData["address"] as isar_models.Address; + if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) { // tx is sent to self tx.type = isar_models.TransactionType.sentToSelf; @@ -2069,6 +2085,17 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { // outgoing tx tx.type = isar_models.TransactionType.outgoing; tx.amount = amountSentFromWallet - changeAmount - fee; + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = isar_models.Address() + ..value = possible + ..derivationIndex = -1 + ..subType = AddressSubType.nonWallet + ..type = AddressType.nonWallet + ..publicKey = []; + } } else { // incoming tx tx.type = isar_models.TransactionType.incoming; @@ -2079,7 +2106,9 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { tx.subType = isar_models.TransactionSubType.none; tx.fee = fee; - tx.address.value = txData["address"] as isar_models.Address; + + List inputs = []; + List outputs = []; for (final json in txData["vin"] as List) { bool isCoinBase = json['coinbase'] != null; @@ -2092,7 +2121,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { isCoinBase ? isCoinBase : json['is_coinbase'] as bool?; input.sequence = json['sequence'] as int?; input.innerRedeemScriptAsm = json['innerRedeemscriptAsm'] as String?; - tx.inputs.add(input); + inputs.add(input); } for (final json in txData["vout"] as List) { @@ -2107,7 +2136,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { Decimal.parse(json["value"].toString()), coin, ); - tx.outputs.add(output); + outputs.add(output); } tx.height = txData["height"] as int?; @@ -2116,12 +2145,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB { tx.slateId = null; tx.otherData = null; - txns.add(tx); + txns.add(Tuple4(tx, outputs, inputs, transactionAddress)); } - await isar.writeTxn(() async { - await isar.transactions.putAll(txns); - }); + await addNewTransactionData(txns); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 76f9e8bd8..f363d72d9 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1846,18 +1846,27 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { await fastFetch(hashes); List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = txHash["address"]; + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } @@ -1871,6 +1880,10 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { } await fastFetch(vHashes.toList()); + final List< + Tuple4, + List, isar_models.Address?>> txns = []; + for (final txObject in allTransactions) { final txn = await parseTransaction( txObject, @@ -1880,17 +1893,10 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { MINIMUM_CONFIRMATIONS, ); - // final tx = await isar.transactions - // .filter() - // .txidMatches(midSortedTx.txid) - // .findFirst(); - // // we don't need to check this but it saves a write tx instead of overwriting the transaction in Isar - // if (tx == null) { - await isar.writeTxn(() async { - await isar.transactions.put(txn.item1); - }); - // } + txns.add(txn); } + + await addNewTransactionData(txns); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 15aaa2a47..77409cb60 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3228,241 +3228,58 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive { } } + 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 { - // final changeAddresses = - // DB.instance.get(boxName: walletId, key: 'changeAddresses') - // as List; - // final List allAddresses = await _fetchAllOwnAddresses(); - // // Logging.instance.log("receiving addresses: $receivingAddresses"); - // // Logging.instance.log("change addresses: $changeAddresses"); - // - // List> allTxHashes = await _fetchHistory(allAddresses); - // - // final cachedTransactions = - // DB.instance.get(boxName: walletId, key: 'latest_tx_model') - // as models.TransactionData?; - // int latestTxnBlockHeight = - // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - // as int? ?? - // 0; - // - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - // - // List hashes = []; - // for (var element in allTxHashes) { - // hashes.add(element['tx_hash'] as String); - // } final List allAddresses = await _fetchAllOwnAddresses(); final List> allTxHashes = await _fetchHistory(allAddresses.map((e) => e.value).toList()); - List hashes = - allTxHashes.map((e) => e['tx_hash'] as String).toList(growable: false); + List> allTransactions = []; - List> allTransactions = await fastFetch(hashes); + final currentHeight = await chainHeight; - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); + for (final txHash in allTxHashes) { + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await isar.addresses + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } + } + } + + final List< + Tuple4, + List, isar_models.Address?>> txnsData = []; - // // sort thing stuff - // final currentPrice = await firoPrice; - // final List> midSortedArray = []; - // - // final locale = - // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - // - // Logging.instance.log("refresh the txs", level: LogLevel.Info); for (final txObject in allTransactions) { - // // Logging.instance.log(txObject); - // List sendersArray = []; - // List recipientsArray = []; - // - // // Usually only has value when txType = 'Send' - // int inputAmtSentFromWallet = 0; - // // Usually has value regardless of txType due to change addresses - // int outputAmtAddressedToWallet = 0; - // - // Map midSortedTx = {}; - // List aliens = []; - // - // for (final input in txObject["vin"] as List) { - // final address = input["address"] as String?; - // if (address != null) { - // sendersArray.add(address); - // } - // } - // - // // Logging.instance.log("sendersArray: $sendersArray"); - // - // for (final output in txObject["vout"] as List) { - // final addresses = output["scriptPubKey"]["addresses"] as List?; - // if (addresses != null && addresses.isNotEmpty) { - // recipientsArray.add(addresses[0] as String); - // } - // } - // // Logging.instance.log("recipientsArray: $recipientsArray"); - // - // final foundInSenders = - // allAddresses.any((element) => sendersArray.contains(element)); - // // Logging.instance.log("foundInSenders: $foundInSenders"); - // - // String outAddress = ""; - // - // int fees = 0; - // - // // If txType = Sent, then calculate inputAmtSentFromWallet, calculate who received how much in aliens array (check outputs) - // if (foundInSenders) { - // int outAmount = 0; - // int inAmount = 0; - // bool nFeesUsed = false; - // - // for (final input in txObject["vin"] as List) { - // final nFees = input["nFees"]; - // if (nFees != null) { - // nFeesUsed = true; - // fees = (Decimal.parse(nFees.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // final address = input["address"]; - // final value = input["valueSat"]; - // if (address != null && value != null) { - // if (allAddresses.contains(address)) { - // inputAmtSentFromWallet += value as int; - // } - // } - // - // if (value != null) { - // inAmount += value as int; - // } - // } - // - // for (final output in txObject["vout"] as List) { - // final addresses = output["scriptPubKey"]["addresses"] as List?; - // final value = output["value"]; - // if (addresses != null && addresses.isNotEmpty) { - // final address = addresses[0] as String; - // if (value != null) { - // if (changeAddresses.contains(address)) { - // inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } else { - // outAddress = address; - // } - // } - // } - // if (value != null) { - // outAmount += (Decimal.parse(value.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // - // fees = nFeesUsed ? fees : inAmount - outAmount; - // inputAmtSentFromWallet -= inAmount - outAmount; - // } else { - // for (final input in txObject["vin"] as List) { - // final nFees = input["nFees"]; - // if (nFees != null) { - // fees += (Decimal.parse(nFees.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // - // for (final output in txObject["vout"] as List) { - // final addresses = output["scriptPubKey"]["addresses"] as List?; - // if (addresses != null && addresses.isNotEmpty) { - // final address = addresses[0] as String; - // final value = output["value"]; - // // Logging.instance.log(address + value.toString()); - // - // if (allAddresses.contains(address)) { - // outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // outAddress = address; - // } - // } - // } - // } - // - // final int confirms = txObject["confirmations"] as int? ?? 0; - // - // // create final tx map - // midSortedTx["txid"] = txObject["txid"]; - // midSortedTx["confirmed_status"] = confirms >= MINIMUM_CONFIRMATIONS; - // midSortedTx["confirmations"] = confirms; - // midSortedTx["timestamp"] = txObject["blocktime"] ?? - // (DateTime.now().millisecondsSinceEpoch ~/ 1000); - // if (foundInSenders) { - // midSortedTx["txType"] = "Sent"; - // midSortedTx["amount"] = inputAmtSentFromWallet; - // final String worthNow = Format.localizedStringAsFixed( - // value: ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // midSortedTx["worthNow"] = worthNow; - // midSortedTx["worthAtBlockTimestamp"] = worthNow; - // if (txObject["vout"][0]["scriptPubKey"]["type"] == "lelantusmint") { - // midSortedTx["subType"] = "mint"; - // } - // } else { - // midSortedTx["txType"] = "Received"; - // midSortedTx["amount"] = outputAmtAddressedToWallet; - // final worthNow = Format.localizedStringAsFixed( - // value: - // ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2), - // decimalPlaces: 2, - // locale: locale!); - // midSortedTx["worthNow"] = worthNow; - // midSortedTx["worthAtBlockTimestamp"] = worthNow; - // } - // midSortedTx["aliens"] = aliens; - // midSortedTx["fees"] = fees; - // midSortedTx["address"] = outAddress; - // midSortedTx["inputSize"] = txObject["vin"].length; - // midSortedTx["outputSize"] = txObject["vout"].length; - // midSortedTx["inputs"] = txObject["vin"]; - // midSortedTx["outputs"] = txObject["vout"]; - // - // final int height = txObject["height"] as int? ?? 0; - // midSortedTx["height"] = height; - // - // if (height >= latestTxnBlockHeight) { - // latestTxnBlockHeight = height; - // } - // - // midSortedArray.add(midSortedTx); - - final txn = await parseTransaction( + final data = await parseTransaction( txObject, cachedElectrumXClient, allAddresses, @@ -3470,80 +3287,10 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive { MINIMUM_CONFIRMATIONS, ); - // final tx = await isar.transactions - // .filter() - // .txidMatches(midSortedTx.txid) - // .findFirst(); - // // we don't need to check this but it saves a write tx instead of overwriting the transaction in Isar - // if (tx == null) { - await isar.writeTxn(() async { - await isar.transactions.put(txn.item1); - }); - // } + txnsData.add(data); } - // - // // sort by date ---- //TODO not sure if needed - // // shouldn't be any issues with a null timestamp but I got one at some point? - // midSortedArray.sort((a, b) { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return (bT as int) - (aT as int); - // } - // }); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = - // models.extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (models.extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // transactionsMap - // .addAll(models.TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = models.TransactionData.fromMap(transactionsMap); - // - // await DB.instance.put( - // boxName: walletId, - // key: 'storedTxnDataHeight', - // value: latestTxnBlockHeight); - // await DB.instance.put( - // boxName: walletId, key: 'latest_tx_model', value: txModel); - // - // cachedTxData = txModel; - // return txModel; + + await addNewTransactionData(txnsData); } Future _refreshUTXOs() async { diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index c2ef099aa..6490a7f58 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -2094,83 +2094,42 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { final List allAddresses = await _fetchAllOwnAddresses(); - // final changeAddresses = DB.instance.get( - // boxName: walletId, key: 'changeAddressesP2WPKH') as List; - // final changeAddressesP2PKH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - // as List; - // final changeAddressesP2SH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - // as List; - // - // for (var i = 0; i < changeAddressesP2PKH.length; i++) { - // changeAddresses.add(changeAddressesP2PKH[i] as String); - // } - // for (var i = 0; i < changeAddressesP2SH.length; i++) { - // changeAddresses.add(changeAddressesP2SH[i] as String); - // } - final List> allTxHashes = await _fetchHistory(allAddresses.map((e) => e.value).toList()); - // final cachedTransactions = - // DB.instance.get(boxName: walletId, key: 'latest_tx_model') - // as TransactionData?; - // int latestTxnBlockHeight = - // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - // as int? ?? - // 0; - // - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - Set hashes = {}; for (var element in allTxHashes) { hashes.add(element['tx_hash'] as String); } await fastFetch(hashes.toList()); + List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = txHash["address"]; + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } - // Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - // Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); - // - // Logging.instance.log("allTransactions length: ${allTransactions.length}", - // level: LogLevel.Info); - // - // final priceData = - // await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - // Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - // final List> midSortedArray = []; - + // prefetch/cache Set vHashes = {}; for (final txObject in allTransactions) { for (int i = 0; i < (txObject["vin"] as List).length; i++) { @@ -2181,8 +2140,12 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { } await fastFetch(vHashes.toList()); + final List< + Tuple4, + List, isar_models.Address?>> txnsData = []; + for (final txObject in allTransactions) { - final txn = await parseTransaction( + final data = await parseTransaction( txObject, cachedElectrumXClient, allAddresses, @@ -2190,261 +2153,9 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { MINIMUM_CONFIRMATIONS, ); - // final tx = await isar.transactions - // .filter() - // .txidMatches(midSortedTx.txid) - // .findFirst(); - // // we don't need to check this but it saves a write tx instead of overwriting the transaction in Isar - // if (tx == null) { - await isar.writeTxn(() async { - await isar.transactions.put(txn.item1); - }); - // } - - // List sendersArray = []; - // List recipientsArray = []; - // - // // Usually only has value when txType = 'Send' - // int inputAmtSentFromWallet = 0; - // // Usually has value regardless of txType due to change addresses - // int outputAmtAddressedToWallet = 0; - // int fee = 0; - // - // Map midSortedTx = {}; - // - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"]![i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // final address = out["scriptPubKey"]["addresses"][0] as String?; - // if (address != null) { - // sendersArray.add(address); - // } - // } - // } - // } - // - // Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - // - // for (final output in txObject["vout"] as List) { - // final address = output["scriptPubKey"]["addresses"][0] as String?; - // if (address != null) { - // recipientsArray.add(address); - // } - // } - // - // Logging.instance - // .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - // - // final foundInSenders = - // allAddresses.any((element) => sendersArray.contains(element)); - // Logging.instance - // .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - // - // // If txType = Sent, then calculate inputAmtSentFromWallet - // if (foundInSenders) { - // int totalInput = 0; - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"]![i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // inputAmtSentFromWallet += - // (Decimal.parse(out["value"]!.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // } - // totalInput = inputAmtSentFromWallet; - // int totalOutput = 0; - // - // for (final output in txObject["vout"] as List) { - // final String address = - // output["scriptPubKey"]!["addresses"][0] as String; - // final value = output["value"]!; - // final _value = (Decimal.parse(value.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // totalOutput += _value; - // if (changeAddresses.contains(address)) { - // inputAmtSentFromWallet -= _value; - // } else { - // // change address from 'sent from' to the 'sent to' address - // txObject["address"] = address; - // } - // } - // // calculate transaction fee - // fee = totalInput - totalOutput; - // // subtract fee from sent to calculate correct value of sent tx - // inputAmtSentFromWallet -= fee; - // } else { - // // counters for fee calculation - // int totalOut = 0; - // int totalIn = 0; - // - // // add up received tx value - // for (final output in txObject["vout"] as List) { - // final address = output["scriptPubKey"]["addresses"][0]; - // if (address != null) { - // final value = (Decimal.parse(output["value"].toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // totalOut += value; - // if (allAddresses.contains(address)) { - // outputAmtAddressedToWallet += value; - // } - // } - // } - // - // // calculate fee for received tx - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"][i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // totalIn += (Decimal.parse(out["value"].toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // } - // fee = totalIn - totalOut; - // } - // - // // create final tx map - // midSortedTx["txid"] = txObject["txid"]; - // midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - // (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - // midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - // midSortedTx["timestamp"] = txObject["blocktime"] ?? - // (DateTime.now().millisecondsSinceEpoch ~/ 1000); - // - // if (foundInSenders) { - // midSortedTx["txType"] = "Sent"; - // midSortedTx["amount"] = inputAmtSentFromWallet; - // final String worthNow = - // ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2) - // .toStringAsFixed(2); - // midSortedTx["worthNow"] = worthNow; - // midSortedTx["worthAtBlockTimestamp"] = worthNow; - // } else { - // midSortedTx["txType"] = "Received"; - // midSortedTx["amount"] = outputAmtAddressedToWallet; - // final worthNow = - // ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2) - // .toStringAsFixed(2); - // midSortedTx["worthNow"] = worthNow; - // } - // midSortedTx["aliens"] = []; - // midSortedTx["fees"] = fee; - // midSortedTx["address"] = txObject["address"]; - // midSortedTx["inputSize"] = txObject["vin"].length; - // midSortedTx["outputSize"] = txObject["vout"].length; - // midSortedTx["inputs"] = txObject["vin"]; - // midSortedTx["outputs"] = txObject["vout"]; - // - // final int height = txObject["height"] as int; - // midSortedTx["height"] = height; - // - // if (height >= latestTxnBlockHeight) { - // latestTxnBlockHeight = height; - // } - // - // midSortedArray.add(midSortedTx); + txnsData.add(data); } - // - // // sort by date ---- //TODO not sure if needed - // // shouldn't be any issues with a null timestamp but I got one at some point? - // midSortedArray - // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // // { - // // final aT = a["timestamp"]; - // // final bT = b["timestamp"]; - // // - // // if (aT == null && bT == null) { - // // return 0; - // // } else if (aT == null) { - // // return -1; - // // } else if (bT == null) { - // // return 1; - // // } else { - // // return bT - aT; - // // } - // // }); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // transactionsMap - // .addAll(TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = TransactionData.fromMap(transactionsMap); - // - // await DB.instance.put( - // boxName: walletId, - // key: 'storedTxnDataHeight', - // value: latestTxnBlockHeight); - // await DB.instance.put( - // boxName: walletId, key: 'latest_tx_model', value: txModel); - // - // cachedTxData = txModel; - // return txModel; + await addNewTransactionData(txnsData); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 6e8a53283..c83ee60b7 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -46,6 +46,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; const int MINIMUM_CONFIRMATIONS = 10; @@ -848,7 +849,9 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { // // final Set cachedTxids = Set.from(txidsList); - final List txns = []; + final List< + Tuple4, + List, isar_models.Address?>> txnsData = []; if (transactions != null) { for (var tx in transactions.entries) { @@ -865,6 +868,7 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { txn.txid = tx.value.id; txn.timestamp = (tx.value.date.millisecondsSinceEpoch ~/ 1000); + isar_models.Address? address; if (tx.value.direction == TransactionDirection.incoming) { final addressInfo = tx.value.additionalInfo; @@ -874,7 +878,7 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { ); if (addressString != null) { - txn.address.value = await isar.addresses + address = await isar.addresses .filter() .valueEqualTo(addressString) .findFirst(); @@ -899,67 +903,11 @@ class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { txn.slateId = null; txn.otherData = null; - txns.add(txn); + txnsData.add(Tuple4(txn, [], [], address)); } } - await isar.writeTxn(() async { - await isar.transactions.putAll(txns); - }); - - // // sort by date ---- - // midSortedArray - // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // Logging.instance.log(midSortedArray, level: LogLevel.Info); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // final Map transactionsMap = {}; - // transactionsMap - // .addAll(TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = TransactionData.fromMap(transactionsMap); - // - // // await DB.instance.put( - // // boxName: walletId, - // // key: 'storedTxnDataHeight', - // // value: latestTxnBlockHeight); - // // await DB.instance.put( - // // boxName: walletId, key: 'latest_tx_model', value: txModel); - // // await DB.instance.put( - // // boxName: walletId, - // // key: 'cachedTxids', - // // value: cachedTxids.toList(growable: false)); - // - // return txModel; + await addNewTransactionData(txnsData); } Future _pathForWalletDir({ diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index d71d5460e..bb9e872ce 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -2076,69 +2076,37 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { final List allAddresses = await _fetchAllOwnAddresses(); - // final changeAddresses = DB.instance.get( - // boxName: walletId, key: 'changeAddressesP2WPKH') as List; - // final changeAddressesP2PKH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - // as List; - // final changeAddressesP2SH = - // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - // as List; - // - // for (var i = 0; i < changeAddressesP2PKH.length; i++) { - // changeAddresses.add(changeAddressesP2PKH[i] as String); - // } - // for (var i = 0; i < changeAddressesP2SH.length; i++) { - // changeAddresses.add(changeAddressesP2SH[i] as String); - // } - final List> allTxHashes = await _fetchHistory(allAddresses.map((e) => e.value).toList()); - // final cachedTransactions = - // DB.instance.get(boxName: walletId, key: 'latest_tx_model') - // as TransactionData?; - // int latestTxnBlockHeight = - // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - // as int? ?? - // 0; - // - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - Set hashes = {}; for (var element in allTxHashes) { hashes.add(element['tx_hash'] as String); } await fastFetch(hashes.toList()); List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = txHash["address"]; + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } @@ -2158,8 +2126,12 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { } await fastFetch(vHashes.toList()); + final List< + Tuple4, + List, isar_models.Address>> txnsData = []; + for (final txObject in allTransactions) { - final txn = await parseTransaction( + final data = await parseTransaction( txObject, cachedElectrumXClient, allAddresses, @@ -2167,271 +2139,9 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB { MINIMUM_CONFIRMATIONS, ); - // final tx = await isar.transactions - // .filter() - // .txidMatches(midSortedTx.txid) - // .findFirst(); - // // we don't need to check this but it saves a write tx instead of overwriting the transaction in Isar - // if (tx == null) { - await isar.writeTxn(() async { - await isar.transactions.put(txn.item1); - }); - // } - - // List sendersArray = []; - // List recipientsArray = []; - // - // // Usually only has value when txType = 'Send' - // int inputAmtSentFromWallet = 0; - // // Usually has value regardless of txType due to change addresses - // int outputAmtAddressedToWallet = 0; - // int fee = 0; - // - // Map midSortedTx = {}; - // - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"]![i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // String? address = out["scriptPubKey"]["address"] as String?; - // if (address == null && out["scriptPubKey"]["address"] != null) { - // address = out["scriptPubKey"]["address"] as String?; - // } - // - // if (address != null) { - // sendersArray.add(address); - // } - // } - // } - // } - // - // Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - // - // for (final output in txObject["vout"] as List) { - // String? address = output["scriptPubKey"]["address"] as String?; - // if (address == null && output["scriptPubKey"]["address"] != null) { - // address = output["scriptPubKey"]["address"] as String?; - // } - // if (address != null) { - // recipientsArray.add(address); - // } - // } - // - // Logging.instance - // .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - // - // final foundInSenders = - // allAddresses.any((element) => sendersArray.contains(element)); - // Logging.instance - // .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - // - // // If txType = Sent, then calculate inputAmtSentFromWallet - // if (foundInSenders) { - // int totalInput = 0; - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"]![i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // inputAmtSentFromWallet += - // (Decimal.parse(out["value"]!.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // } - // totalInput = inputAmtSentFromWallet; - // int totalOutput = 0; - // - // for (final output in txObject["vout"] as List) { - // Logging.instance.log(output, level: LogLevel.Info); - // final address = output["scriptPubKey"]["address"]; - // final value = output["value"]; - // final _value = (Decimal.parse(value.toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // totalOutput += _value; - // if (changeAddresses.contains(address)) { - // inputAmtSentFromWallet -= _value; - // } else { - // // change address from 'sent from' to the 'sent to' address - // txObject["address"] = address; - // } - // } - // // calculate transaction fee - // fee = totalInput - totalOutput; - // // subtract fee from sent to calculate correct value of sent tx - // inputAmtSentFromWallet -= fee; - // } else { - // // counters for fee calculation - // int totalOut = 0; - // int totalIn = 0; - // - // // add up received tx value - // for (final output in txObject["vout"] as List) { - // String? address = output["scriptPubKey"]["address"] as String?; - // if (address == null && output["scriptPubKey"]["address"] != null) { - // address = output["scriptPubKey"]["address"] as String?; - // } - // if (address != null) { - // final value = (Decimal.parse(output["value"].toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // totalOut += value; - // if (allAddresses.contains(address)) { - // outputAmtAddressedToWallet += value; - // } - // } - // } - // - // // calculate fee for received tx - // for (int i = 0; i < (txObject["vin"] as List).length; i++) { - // final input = txObject["vin"][i] as Map; - // final prevTxid = input["txid"] as String; - // final prevOut = input["vout"] as int; - // final tx = await _cachedElectrumXClient.getTransaction( - // txHash: prevTxid, - // coin: coin, - // ); - // - // for (final out in tx["vout"] as List) { - // if (prevOut == out["n"]) { - // totalIn += (Decimal.parse(out["value"].toString()) * - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toBigInt() - // .toInt(); - // } - // } - // } - // fee = totalIn - totalOut; - // } - // - // // create final tx map - // midSortedTx["txid"] = txObject["txid"]; - // midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - // (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - // midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - // midSortedTx["timestamp"] = txObject["blocktime"] ?? - // (DateTime.now().millisecondsSinceEpoch ~/ 1000); - // - // if (foundInSenders) { - // midSortedTx["txType"] = "Sent"; - // midSortedTx["amount"] = inputAmtSentFromWallet; - // final String worthNow = - // ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2) - // .toStringAsFixed(2); - // midSortedTx["worthNow"] = worthNow; - // midSortedTx["worthAtBlockTimestamp"] = worthNow; - // } else { - // midSortedTx["txType"] = "Received"; - // midSortedTx["amount"] = outputAmtAddressedToWallet; - // final worthNow = - // ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - // Decimal.fromInt(Constants.satsPerCoin(coin))) - // .toDecimal(scaleOnInfinitePrecision: 2) - // .toStringAsFixed(2); - // midSortedTx["worthNow"] = worthNow; - // } - // midSortedTx["aliens"] = []; - // midSortedTx["fees"] = fee; - // midSortedTx["address"] = txObject["address"]; - // midSortedTx["inputSize"] = txObject["vin"].length; - // midSortedTx["outputSize"] = txObject["vout"].length; - // midSortedTx["inputs"] = txObject["vin"]; - // midSortedTx["outputs"] = txObject["vout"]; - // - // final int height = txObject["height"] as int; - // midSortedTx["height"] = height; - // - // if (height >= latestTxnBlockHeight) { - // latestTxnBlockHeight = height; - // } - // - // midSortedArray.add(midSortedTx); + txnsData.add(data); } - // - // // sort by date ---- //TODO not sure if needed - // // shouldn't be any issues with a null timestamp but I got one at some point? - // midSortedArray - // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // // { - // // final aT = a["timestamp"]; - // // final bT = b["timestamp"]; - // // - // // if (aT == null && bT == null) { - // // return 0; - // // } else if (aT == null) { - // // return -1; - // // } else if (bT == null) { - // // return 1; - // // } else { - // // return bT - aT; - // // } - // // }); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // transactionsMap - // .addAll(TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = TransactionData.fromMap(transactionsMap); - // - // await DB.instance.put( - // boxName: walletId, - // key: 'storedTxnDataHeight', - // value: latestTxnBlockHeight); - // await DB.instance.put( - // boxName: walletId, key: 'latest_tx_model', value: txModel); - // - // cachedTxData = txModel; - // return txModel; + await addNewTransactionData(txnsData); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 24ee293f2..37035b93c 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1962,9 +1962,6 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { Future _refreshTransactions() async { final allAddresses = await _fetchAllOwnAddresses(); - // final changeAddresses = DB.instance.get( - // boxName: walletId, key: 'changeAddressesP2WPKH') as List; - List changeAddresses = allAddresses .where((e) => e.subType == isar_models.AddressSubType.change) .map((e) => e.value) @@ -1973,54 +1970,38 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { final List> allTxHashes = await _fetchHistory( allAddresses.map((e) => e.value).toList(growable: false)); - // final cachedTransactions = - // DB.instance.get(boxName: walletId, key: 'latest_tx_model') - // as TransactionData?; - // int latestTxnBlockHeight = - // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - // as int? ?? - // 0; - // - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - Set hashes = {}; for (var element in allTxHashes) { hashes.add(element['tx_hash'] as String); } await fastFetch(hashes.toList()); List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await isar.transactions + .where() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = await isar.addresses - .filter() - .valueEqualTo(txHash["address"] as String) - .findFirst(); - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + // TODO fix this for sent to self transactions? + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await isar.addresses + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } @@ -2044,7 +2025,9 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { } await fastFetch(vHashes.toList()); - final List txns = []; + final List< + Tuple4, + List, isar_models.Address?>> txns = []; for (final txObject in allTransactions) { List sendersArray = []; @@ -2269,7 +2252,11 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { tx.subType = isar_models.TransactionSubType.none; tx.fee = fee; - tx.address.value = midSortedTx["address"] as isar_models.Address?; + isar_models.Address? transactionAddress = + midSortedTx["address"] as isar_models.Address?; + + List inputs = []; + List outputs = []; for (final json in midSortedTx["vin"] as List) { bool isCoinBase = json['coinbase'] != null; @@ -2282,7 +2269,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { isCoinBase ? isCoinBase : json['is_coinbase'] as bool?; input.sequence = json['sequence'] as int?; input.innerRedeemScriptAsm = json['innerRedeemscriptAsm'] as String?; - tx.inputs.add(input); + inputs.add(input); } for (final json in midSortedTx["vout"] as List) { @@ -2298,7 +2285,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { Decimal.tryParse(json["value"].toString()) ?? Decimal.zero, coin, ); - tx.outputs.add(output); + outputs.add(output); } tx.height = height; @@ -2307,76 +2294,10 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB { tx.slateId = null; tx.otherData = null; - txns.add(tx); + txns.add(Tuple4(tx, outputs, inputs, transactionAddress)); } - await isar.writeTxn(() async { - await isar.transactions.putAll(txns); - }); - - // - // // sort by date ---- //TODO not sure if needed - // // shouldn't be any issues with a null timestamp but I got one at some point? - // midSortedArray - // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // // { - // // final aT = a["timestamp"]; - // // final bT = b["timestamp"]; - // // - // // if (aT == null && bT == null) { - // // return 0; - // // } else if (aT == null) { - // // return -1; - // // } else if (bT == null) { - // // return 1; - // // } else { - // // return bT - aT; - // // } - // // }); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // transactionsMap - // .addAll(TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = TransactionData.fromMap(transactionsMap); - // - // await DB.instance.put( - // boxName: walletId, - // key: 'storedTxnDataHeight', - // value: latestTxnBlockHeight); - // await DB.instance.put( - // boxName: walletId, key: 'latest_tx_model', value: txModel); - // - // return txModel; + await addNewTransactionData(txns); } int estimateTxFee({required int vSize, required int feeRatePerKB}) { diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 701ae0589..70f16d58d 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -48,6 +48,7 @@ import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; const int MINIMUM_CONFIRMATIONS = 10; @@ -875,7 +876,9 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { // } // } - final List txns = []; + final List< + Tuple4, + List, isar_models.Address?>> txnsData = []; if (transactions != null) { for (var tx in transactions.entries) { @@ -934,6 +937,7 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { txn.txid = tx.value.id; txn.timestamp = (tx.value.date.millisecondsSinceEpoch ~/ 1000); + isar_models.Address? address; if (tx.value.direction == TransactionDirection.incoming) { final addressInfo = tx.value.additionalInfo; @@ -943,7 +947,7 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { ); if (addressString != null) { - txn.address.value = await isar.addresses + address = await isar.addresses .filter() .valueEqualTo(addressString) .findFirst(); @@ -968,67 +972,11 @@ class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { txn.slateId = null; txn.otherData = null; - txns.add(txn); + txnsData.add(Tuple4(txn, [], [], address)); } } - await isar.writeTxn(() async { - await isar.transactions.putAll(txns); - }); - - // // sort by date ---- - // midSortedArray - // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // Logging.instance.log(midSortedArray, level: LogLevel.Info); - // - // // buildDateTimeChunks - // final Map result = {"dateTimeChunks": []}; - // final dateArray = []; - // - // for (int i = 0; i < midSortedArray.length; i++) { - // final txObject = midSortedArray[i]; - // final date = extractDateFromTimestamp(txObject["timestamp"] as int); - // final txTimeArray = [txObject["timestamp"], date]; - // - // if (dateArray.contains(txTimeArray[1])) { - // result["dateTimeChunks"].forEach((dynamic chunk) { - // if (extractDateFromTimestamp(chunk["timestamp"] as int) == - // txTimeArray[1]) { - // if (chunk["transactions"] == null) { - // chunk["transactions"] = >[]; - // } - // chunk["transactions"].add(txObject); - // } - // }); - // } else { - // dateArray.add(txTimeArray[1]); - // final chunk = { - // "timestamp": txTimeArray[0], - // "transactions": [txObject], - // }; - // result["dateTimeChunks"].add(chunk); - // } - // } - // - // // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - // final Map transactionsMap = {}; - // transactionsMap - // .addAll(TransactionData.fromJson(result).getAllTransactions()); - // - // final txModel = TransactionData.fromMap(transactionsMap); - // // - // // await DB.instance.put( - // // boxName: walletId, - // // key: 'storedTxnDataHeight', - // // value: latestTxnBlockHeight); - // // await DB.instance.put( - // // boxName: walletId, key: 'latest_tx_model', value: txModel); - // // await DB.instance.put( - // // boxName: walletId, - // // key: 'cachedTxids', - // // value: cachedTxids.toList(growable: false)); - // - // return txModel; + await addNewTransactionData(txnsData); } Future _pathForWalletDir({ diff --git a/lib/services/mixins/wallet_db.dart b/lib/services/mixins/wallet_db.dart index 365b6155a..5ea3a236a 100644 --- a/lib/services/mixins/wallet_db.dart +++ b/lib/services/mixins/wallet_db.dart @@ -1,6 +1,7 @@ import 'package:isar/isar.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; mixin WalletDB { Isar? _isar; @@ -29,4 +30,45 @@ mixin WalletDB { } Future isarClose() async => await _isar?.close() ?? false; + + Future addNewTransactionData( + List, List, Address?>> + transactionsData) async { + await isar.writeTxn(() async { + for (final data in transactionsData) { + final tx = data.item1; + + // save transaction + await isar.transactions.put(tx); + + // link and save outputs + if (data.item2.isNotEmpty) { + await isar.outputs.putAll(data.item2); + tx.outputs.addAll(data.item2); + await tx.outputs.save(); + } + + // link and save inputs + if (data.item3.isNotEmpty) { + await isar.inputs.putAll(data.item3); + tx.inputs.addAll(data.item3); + await tx.inputs.save(); + } + + if (data.item4 != null) { + // check if address exists in db and add if it does not + if (await isar.addresses + .where() + .valueEqualTo(data.item4!.value) + .findFirst() == + null) { + await isar.addresses.put(data.item4!); + } + // link and save address + tx.address.value = data.item4; + await tx.address.save(); + } + } + }); + } }