From d270ea38a2f497bd1eae9bed9c35fc19374bb75d Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 08:37:27 -0600 Subject: [PATCH 01/21] clean up tx count function --- lib/services/coins/firo/firo_wallet.dart | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 23e367ca4..bceb3fbcc 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3215,17 +3215,18 @@ class FiroWallet extends CoinServiceAPI ); } - //TODO call get transaction and check each tx to see if it is a "received" tx? - Future _getReceivedTxCount({required String address}) async { + Future _getTxCount({required String address}) async { try { - final scripthash = AddressUtils.convertToScriptHash(address, _network); - final transactions = - await electrumXClient.getHistory(scripthash: scripthash); + 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); + "Exception rethrown in _getReceivedTxCount(address: $address): $e", + level: LogLevel.Error, + ); rethrow; } } @@ -3234,8 +3235,7 @@ class FiroWallet extends CoinServiceAPI try { final currentReceiving = await _currentReceivingAddress; - final int txCount = - await _getReceivedTxCount(address: currentReceiving.value); + final int txCount = await _getTxCount(address: currentReceiving.value); Logging.instance.log( 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); @@ -3281,8 +3281,7 @@ class FiroWallet extends CoinServiceAPI Future checkChangeAddressForTransactions() async { try { final currentChange = await _currentChangeAddress; - final int txCount = - await _getReceivedTxCount(address: currentChange.value); + final int txCount = await _getTxCount(address: currentChange.value); Logging.instance.log( 'Number of txs for current change address: $currentChange: $txCount', level: LogLevel.Info); From e40637479667787a916519a37a283f138b8dc3b9 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 17 May 2023 11:34:40 -0600 Subject: [PATCH 02/21] clean up and optimizations --- lib/services/coins/firo/firo_wallet.dart | 125 ++++++----------------- 1 file changed, 29 insertions(+), 96 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index bceb3fbcc..812e84b8b 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -1930,12 +1930,13 @@ class FiroWallet extends CoinServiceAPI if ((await db .getTransactions(walletId) .filter() - .txidMatches(txid) - .findFirst()) == - null) { + .txidEqualTo(txid) + .count()) == + 0) { Logging.instance.log( - " txid not found in address history already ${transaction['tx_hash']}", - level: LogLevel.Info); + " txid not found in address history already ${transaction['tx_hash']}", + level: LogLevel.Info, + ); needsRefresh = true; break; } @@ -1958,79 +1959,27 @@ class FiroWallet extends CoinServiceAPI final currentChainHeight = await chainHeight; - final txTxns = await db - .getTransactions(walletId) - .filter() - .isLelantusIsNull() - .or() - .isLelantusEqualTo(false) - .findAll(); - final ltxTxns = await db - .getTransactions(walletId) - .filter() - .isLelantusEqualTo(true) - .findAll(); + final txCount = await db.getTransactions(walletId).count(); - for (isar_models.Transaction tx in txTxns) { - isar_models.Transaction? lTx; - try { - lTx = ltxTxns.firstWhere((e) => e.txid == tx.txid); - } catch (_) { - lTx = null; - } + const paginateLimit = 50; - if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - if (txTracker.wasNotifiedPending(tx.txid) && - !txTracker.wasNotifiedConfirmed(tx.txid)) { + 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 - unconfirmedTxnsToNotifyConfirmed.add(tx); - } - if (lTx != null && - (lTx.inputs.isEmpty || lTx.inputs.first.txid.isEmpty) && - lTx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == - false && - tx.type == isar_models.TransactionType.incoming) { - // If this is a received that is past 1 or more confirmations and has not been minted, - if (!txTracker.wasNotifiedPending(tx.txid)) { - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } else { - if (!txTracker.wasNotifiedPending(tx.txid)) { - // get all transactions that were not notified as pending yet - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } - - for (isar_models.Transaction tx in txTxns) { - if (!tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && - tx.inputs.first.txid.isNotEmpty) { - // Get all normal txs that are at 0 confirmations - unconfirmedTxnsToNotifyPending - .removeWhere((e) => e.txid == tx.inputs.first.txid); - Logging.instance.log("removed tx: ${tx.txid}", level: LogLevel.Info); - } - } - - for (isar_models.Transaction lTX in ltxTxns) { - isar_models.Transaction? tx; - try { - tx = ltxTxns.firstWhere((e) => e.txid == lTX.txid); - } catch (_) { - tx = null; - } - - if (tx == null) { - // if this is a ltx transaction that is unconfirmed and not represented in the normal transaction set. - if (!lTX.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - if (!txTracker.wasNotifiedPending(lTX.txid)) { - unconfirmedTxnsToNotifyPending.add(lTX); + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); } } else { - if (txTracker.wasNotifiedPending(lTX.txid) && - !txTracker.wasNotifiedConfirmed(lTX.txid)) { - unconfirmedTxnsToNotifyConfirmed.add(lTX); + // get all transactions that were not notified as pending yet + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); } } } @@ -3329,27 +3278,13 @@ class FiroWallet extends CoinServiceAPI .getAddresses(walletId) .filter() .not() - .typeEqualTo(isar_models.AddressType.nonWallet) - .and() - .group((q) => q - .subTypeEqualTo(isar_models.AddressSubType.receiving) - .or() - .subTypeEqualTo(isar_models.AddressSubType.change)) + .group( + (q) => q + .typeEqualTo(isar_models.AddressType.nonWallet) + .or() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet), + ) .findAll(); - // final List allAddresses = []; - // final receivingAddresses = - // DB.instance.get(boxName: walletId, key: 'receivingAddresses') - // as List; - // final changeAddresses = - // DB.instance.get(boxName: walletId, key: 'changeAddresses') - // as List; - // - // for (var i = 0; i < receivingAddresses.length; i++) { - // allAddresses.add(receivingAddresses[i] as String); - // } - // for (var i = 0; i < changeAddresses.length; i++) { - // allAddresses.add(changeAddresses[i] as String); - // } return allAddresses; } @@ -3647,7 +3582,7 @@ class FiroWallet extends CoinServiceAPI fractionDigits: Coin.firo.decimals, ).toJsonString(), fee: fees, - height: txObject["height"] as int? ?? 0, + height: txObject["height"] as int?, isCancelled: false, isLelantus: false, slateId: null, @@ -4569,8 +4504,6 @@ class FiroWallet extends CoinServiceAPI final response = await cachedElectrumXClient.getUsedCoinSerials( coin: coin, ); - print("getUsedCoinSerials"); - print(response); return response; } catch (e, s) { Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s", From efd274218448ac5e69feb4d87fe65236e9e2fea7 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 09:36:55 -0600 Subject: [PATCH 03/21] dev feat: pretty print json helper util --- lib/utilities/util.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index 6a31fae04..ecd9a7cca 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; @@ -50,4 +52,15 @@ abstract class Util { } return MaterialColor(color.value, swatch); } + + static void printJson(dynamic json) { + if (json is Map || json is List) { + final spaces = ' ' * 4; + final encoder = JsonEncoder.withIndent(spaces); + final pretty = encoder.convert(json); + log(pretty); + } else { + log(dynamic.toString()); + } + } } From 304675351af8528e948fdbd09b1c4d86afa3aff5 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 09:46:34 -0600 Subject: [PATCH 04/21] WIP: firo tx parsing clean up and fixes --- lib/services/coins/firo/firo_wallet.dart | 595 +++++++++++++++-------- 1 file changed, 397 insertions(+), 198 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 0929258a2..4e0edcb7f 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3347,6 +3347,15 @@ class FiroWallet extends CoinServiceAPI final List allAddresses = await _fetchAllOwnAddresses(); + Set receivingAddresses = allAddresses + .where((e) => e.subType == isar_models.AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + Set changeAddresses = allAddresses + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) => e.value) + .toSet(); + final List> allTxHashes = await _fetchHistory(allAddresses.map((e) => e.value).toList()); @@ -3384,215 +3393,405 @@ class FiroWallet extends CoinServiceAPI final List> txnsData = []; - Set changeAddresses = allAddresses - .where((e) => e.subType == isar_models.AddressSubType.change) - .map((e) => e.value) - .toSet(); - for (final txObject in allTransactions) { - // Logging.instance.log(txObject); - List sendersArray = []; - List recipientsArray = []; + final inputList = txObject["vin"] as List; + final outputList = txObject["vout"] as List; - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; + bool isMint = false; + bool isJMint = false; - 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 address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; - if (address != null) { - recipientsArray.add(address); - } - } - // Logging.instance.log("recipientsArray: $recipientsArray"); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element.value)); - // 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"] as String?; - final value = input["valueSat"] as int?; - if (address != null && value != null) { - if (allAddresses.where((e) => e.value == address).isNotEmpty) { - inputAmtSentFromWallet += value; - } - } - - if (value != null) { - inAmount += value; - } - } - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? - output["scriptPubKey"]?["address"] as String?; - final value = output["value"]; - - if (value != null) { - outAmount += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - - if (address != null) { - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } else { - outAddress = address; - } - } - } - } - - 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"] ?? 0; - // Logging.instance.log(address + value.toString()); - - if (allAddresses.where((e) => e.value == address).isNotEmpty) { - outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - outAddress = address; + // check if tx is Mint or jMint + for (final output in outputList) { + if (output["scriptPubKey"]?["type"] == "lelantusmint") { + final asm = output["scriptPubKey"]?["asm"] as String?; + if (asm != null) { + if (asm.startsWith("OP_LELANTUSJMINT")) { + isJMint = true; + break; + } else if (asm.startsWith("OP_LELANTUSMINT")) { + isMint = true; + break; + } else { + Logging.instance.log( + "Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}", + level: LogLevel.Error, + ); } + } else { + Logging.instance.log( + "ASM for lelantusmint tx: ${txObject["txid"]} is null!", + level: LogLevel.Error, + ); } } } - isar_models.TransactionType type; - isar_models.TransactionSubType subType = - isar_models.TransactionSubType.none; - int amount; - if (foundInSenders) { - type = isar_models.TransactionType.outgoing; - amount = inputAmtSentFromWallet; + Set inputAddresses = {}; + Set outputAddresses = {}; - if (txObject["vout"][0]["scriptPubKey"]["type"] == "lelantusmint") { - subType = isar_models.TransactionSubType.mint; - } - } else { - type = isar_models.TransactionType.incoming; - amount = outputAmtAddressedToWallet; - } - - final transactionAddress = - allAddresses.firstWhere((e) => e.value == outAddress, - orElse: () => isar_models.Address( - walletId: walletId, - value: outAddress, - derivationIndex: -1, - derivationPath: null, - type: isar_models.AddressType.nonWallet, - subType: isar_models.AddressSubType.nonWallet, - publicKey: [], - )); - - List outs = []; - List ins = []; - - for (final json in txObject["vin"] as List) { - bool isCoinBase = json['coinbase'] != null; - final input = isar_models.Input( - txid: json['txid'] as String? ?? "", - vout: json['vout'] as int? ?? -1, - scriptSig: json['scriptSig']?['hex'] as String?, - scriptSigAsm: json['scriptSig']?['asm'] as String?, - isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, - sequence: json['sequence'] as int?, - innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, - ); - ins.add(input); - } - - for (final json in txObject["vout"] as List) { - final output = isar_models.Output( - scriptPubKey: json['scriptPubKey']?['hex'] as String?, - scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, - scriptPubKeyType: json['scriptPubKey']?['type'] as String?, - scriptPubKeyAddress: - json["scriptPubKey"]?["addresses"]?[0] as String? ?? - json['scriptPubKey']['type'] as String, - value: Amount.fromDecimal( - Decimal.parse(json["value"].toString()), - fractionDigits: coin.decimals, - ).raw.toInt(), - ); - outs.add(output); - } - - final tx = isar_models.Transaction( - walletId: walletId, - txid: txObject["txid"] as String, - timestamp: txObject["blocktime"] as int? ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000), - type: type, - subType: subType, - amount: amount, - amountString: Amount( - rawValue: BigInt.from(amount), - fractionDigits: Coin.firo.decimals, - ).toJsonString(), - fee: fees, - height: txObject["height"] as int?, - isCancelled: false, - isLelantus: false, - slateId: null, - otherData: null, - nonce: null, - inputs: ins, - outputs: outs, + Amount totalInputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, ); - txnsData.add(Tuple2(tx, transactionAddress)); + Amount amountSentFromWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + // Parse mint transaction ================================================ + // We should be able to assume this belongs to this wallet + if (isMint) { + List ins = []; + + // Parse inputs + for (final input in inputList) { + // Both value and address should not be null for a mint + final address = input["address"] as String?; + final value = input["valueSat"] as int?; + + // We should not need to check whether the mint belongs to this + // wallet as any tx we look up will be looked up by one of this + // wallet's addresses + if (address != null && value != null) { + totalInputValue += value.toAmountAsRaw( + fractionDigits: coin.decimals, + ); + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String? ?? "", + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + // Parse outputs + for (final output in outputList) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + } + + final fee = totalInputValue - totalOutputValue; + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: isar_models.TransactionType.sentToSelf, + subType: isar_models.TransactionSubType.mint, + amount: totalOutputValue.raw.toInt(), + amountString: totalOutputValue.toJsonString(), + fee: fee.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: [], + ); + + txnsData.add(Tuple2(tx, null)); + + // Otherwise parse JMint transaction =================================== + } else if (isJMint) { + Amount jMintFees = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + // Parse inputs + List ins = []; + for (final input in inputList) { + // JMint fee + final nFee = Decimal.tryParse(input["nFees"].toString()); + if (nFee != null) { + final fees = Amount.fromDecimal( + nFee, + fractionDigits: coin.decimals, + ); + + jMintFees += fees; + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String? ?? "", + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + bool nonWalletAddressFoundInOutputs = false; + + // Parse outputs + List outs = []; + for (final output in outputList) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output['scriptPubKey']?['address'] as String?; + + if (address != null) { + outputAddresses.add(address); + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountReceivedInWallet += value; + } else { + nonWalletAddressFoundInOutputs = true; + } + } + + outs.add( + isar_models.Output( + scriptPubKey: output['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: output['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: address ?? "jmint", + value: value.raw.toInt(), + ), + ); + } + + const subType = isar_models.TransactionSubType.join; + final type = nonWalletAddressFoundInOutputs + ? isar_models.TransactionType.outgoing + : isar_models.TransactionType.incoming; + + final amount = nonWalletAddressFoundInOutputs + ? totalOutputValue + : amountReceivedInWallet; + + final possibleNonWalletAddresses = + receivingAddresses.difference(outputAddresses); + final possibleReceivingAddresses = + receivingAddresses.intersection(outputAddresses); + + final transactionAddress = nonWalletAddressFoundInOutputs + ? isar_models.Address( + walletId: walletId, + value: possibleNonWalletAddresses.first, + derivationIndex: -1, + derivationPath: null, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.nonWallet, + publicKey: [], + ) + : allAddresses.firstWhere( + (e) => e.value == possibleReceivingAddresses.first, + ); + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: subType, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: jMintFees.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + txnsData.add(Tuple2(tx, transactionAddress)); + + // Assume non lelantus transaction ===================================== + } else { + // parse inputs + List ins = []; + for (final input in inputList) { + final valueSat = input["valueSat"] as int?; + final address = input["address"] as String? ?? + input["scriptPubKey"]?["address"] as String? ?? + input["scriptPubKey"]?["addresses"]?[0] as String?; + + if (address != null && valueSat != null) { + final value = valueSat.toAmountAsRaw( + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue += value; + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet += value; + } + } + + ins.add( + isar_models.Input( + txid: input['txid'] as String, + vout: input['vout'] as int? ?? -1, + scriptSig: input['scriptSig']?['hex'] as String?, + scriptSigAsm: input['scriptSig']?['asm'] as String?, + isCoinbase: input['is_coinbase'] as bool?, + sequence: input['sequence'] as int?, + innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?, + ), + ); + } + + // parse outputs + List outs = []; + for (final output in outputList) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + // get output address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + if (address != null) { + outputAddresses.add(address); + + // if output was to my wallet, add value to amount received + if (receivingAddresses.contains(address)) { + amountReceivedInWallet += value; + } else if (changeAddresses.contains(address)) { + changeAmount += value; + } + } + + outs.add( + isar_models.Output( + scriptPubKey: output['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: output['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: address ?? "", + value: value.raw.toInt(), + ), + ); + } + + final mySentFromAddresses = [ + ...receivingAddresses.intersection(inputAddresses), + ...changeAddresses.intersection(inputAddresses) + ]; + final myReceivedOnAddresses = + receivingAddresses.intersection(outputAddresses); + final myChangeReceivedOnAddresses = + changeAddresses.intersection(outputAddresses); + + final fee = totalInputValue - totalOutputValue; + + // this is the address initially used to fetch the txid + isar_models.Address transactionAddress = + txObject["address"] as isar_models.Address; + + isar_models.TransactionType type; + Amount amount; + if (mySentFromAddresses.isNotEmpty && + myReceivedOnAddresses.isNotEmpty) { + // tx is sent to self + type = isar_models.TransactionType.sentToSelf; + + // should be 0 + amount = amountSentFromWallet - + amountReceivedInWallet - + fee - + changeAmount; + } else if (mySentFromAddresses.isNotEmpty) { + // outgoing tx + type = isar_models.TransactionType.outgoing; + amount = amountSentFromWallet - changeAmount - fee; + + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = isar_models.Address( + walletId: walletId, + value: possible, + derivationIndex: -1, + derivationPath: null, + subType: isar_models.AddressSubType.nonWallet, + type: isar_models.AddressType.nonWallet, + publicKey: [], + ); + } + } else { + // incoming tx + type = isar_models.TransactionType.incoming; + amount = amountReceivedInWallet; + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + // amount may overflow. Deprecated. Use amountString + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txObject["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + txnsData.add(Tuple2(tx, transactionAddress)); + } } await db.addNewTransactionData(txnsData, walletId); From 73d877654b13b484a45d9d1254a057c614e76c30 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 09:52:48 -0600 Subject: [PATCH 05/21] add tx subtype to tx details view in debug mode --- .../transaction_details_view.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 440356166..b85b676ea 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1129,6 +1129,46 @@ class _TransactionDetailsViewState ], ), ), + if (kDebugMode) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (kDebugMode) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Tx sub type", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + SelectableText( + _transaction.subType.toString(), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), isDesktop ? const _Divider() : const SizedBox( From a77c8c02fda1d8ac202b26388c9cdcce8d6307f5 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 10:11:44 -0600 Subject: [PATCH 06/21] ensure skipping tx if it is a jmint --- lib/services/coins/firo/firo_wallet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 4e0edcb7f..666dcef94 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -405,7 +405,7 @@ Future> staticProcessRestore( tx = null; } - if (tx == null) { + if (tx == null || tx.subType == isar_models.TransactionSubType.join) { // This is a jmint. return; } From 40dfced949a39e5e212468ee7f753d40fa6ee93f Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 10:43:08 -0600 Subject: [PATCH 07/21] fix: default to 0 mint index if none saved --- lib/services/coins/firo/firo_wallet.dart | 6 +++--- lib/services/mixins/firo_hive.dart | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 666dcef94..88af91067 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -2555,7 +2555,7 @@ class FiroWallet extends CoinServiceAPI var tmpTotal = total; var index = 1; var mints = >[]; - final nextFreeMintIndex = firoGetMintIndex()!; + final nextFreeMintIndex = firoGetMintIndex(); while (tmpTotal > 0) { final mintValue = min(tmpTotal, MINT_LIMIT); final mint = await _getMintHex( @@ -2720,7 +2720,7 @@ class FiroWallet extends CoinServiceAPI amount += utxosToUse[i].value; } - final index = firoGetMintIndex()!; + final index = firoGetMintIndex(); Logging.instance.log("index of mint $index", level: LogLevel.Info); for (var mintsElement in mintsMap) { @@ -2987,7 +2987,7 @@ class FiroWallet extends CoinServiceAPI // if a jmint was made add it to the unspent coin index LelantusCoin jmint = LelantusCoin( - index!, + index, transactionInfo['jmintValue'] as int? ?? 0, transactionInfo['publicCoin'] as String, transactionInfo['txid'] as String, diff --git a/lib/services/mixins/firo_hive.dart b/lib/services/mixins/firo_hive.dart index 321724ad1..180c9f278 100644 --- a/lib/services/mixins/firo_hive.dart +++ b/lib/services/mixins/firo_hive.dart @@ -35,9 +35,10 @@ mixin FiroHive { } // mintIndex - int? firoGetMintIndex() { + int firoGetMintIndex() { return DB.instance.get(boxName: _walletId, key: "mintIndex") - as int?; + as int? ?? + 0; } Future firoUpdateMintIndex(int mintIndex) async { From ee128810b53286a49da92ff7aa83a3cf040f15e8 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 10:53:06 -0600 Subject: [PATCH 08/21] fix debug address details view on desktop --- .../transaction_details_view.dart | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index b85b676ea..5a0068c93 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -619,16 +619,37 @@ class _TransactionDetailsViewState CustomTextButton( text: "Info", onTap: () { - Navigator.of(context) - .pushNamed( - AddressDetailsView - .routeName, - arguments: Tuple2( - _transaction.address - .value!.id, - widget.walletId, - ), - ); + if (isDesktop) { + showDialog( + context: context, + builder: (_) => + DesktopDialog( + maxHeight: + double.infinity, + child: + AddressDetailsView( + addressId: + _transaction + .address + .value! + .id, + walletId: widget + .walletId, + ), + ), + ); + } else { + Navigator.of(context) + .pushNamed( + AddressDetailsView + .routeName, + arguments: Tuple2( + _transaction.address + .value!.id, + widget.walletId, + ), + ); + } }, ) ], From 6065c29e0dbcdba167729f0c61a3316f36f8ecef Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 15:13:56 -0600 Subject: [PATCH 09/21] add update theme button --- .../sub_widgets/stack_theme_card.dart | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart index 7b36c873a..0193621bd 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -39,6 +39,7 @@ class _StackThemeCardState extends ConsumerState { late final StreamSubscription _subscription; late bool _hasTheme; + bool _needsUpdate = false; String? _cachedSize; Future _downloadAndInstall() async { @@ -84,6 +85,7 @@ class _StackThemeCardState extends ConsumerState { title: message, onOkPressed: (_) { setState(() { + _needsUpdate = !result; _hasTheme = result; }); }, @@ -141,16 +143,18 @@ class _StackThemeCardState extends ConsumerState { } } + StackTheme? getInstalled() => ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .findFirstSync(); + @override void initState() { - _hasTheme = ref - .read(mainDBProvider) - .isar - .stackThemes - .where() - .themeIdEqualTo(widget.data.id) - .countSync() > - 0; + final installedTheme = getInstalled(); + _hasTheme = installedTheme != null; _subscription = ref .read(mainDBProvider) @@ -158,18 +162,15 @@ class _StackThemeCardState extends ConsumerState { .stackThemes .watchLazy() .listen((event) async { - final hasTheme = (await ref - .read(mainDBProvider) - .isar - .stackThemes - .where() - .themeIdEqualTo(widget.data.id) - .count()) > - 0; + final installedTheme = getInstalled(); + final hasTheme = installedTheme != null; if (_hasTheme != hasTheme && mounted) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { setState(() { _hasTheme = hasTheme; + if (hasTheme) { + _needsUpdate = widget.data.version > installedTheme.version!; + } }); }); } @@ -272,6 +273,16 @@ class _StackThemeCardState extends ConsumerState { } }, ), + if (_hasTheme && _needsUpdate) + const SizedBox( + height: 12, + ), + if (_hasTheme && _needsUpdate) + PrimaryButton( + label: "Update", + buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l, + onPressed: _downloadPressed, + ), const SizedBox( height: 12, ), From 492ad6b353069289e185ec28a3ab75f7639cc0be Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 16:22:32 -0600 Subject: [PATCH 10/21] update monero ref --- crypto_plugins/flutter_libmonero | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 81659ce57..73d257ed2 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 81659ce57952c5ab54ffe6bacfbf43da159fff3e +Subproject commit 73d257ed2fe5b204cf3589822e226301b187b86d From 206a460cfa7a3044f9b1d376887a2a49e6dba3bd Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 17:19:16 -0600 Subject: [PATCH 11/21] fix: update theme button --- .../appearance_settings/sub_widgets/stack_theme_card.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart index 0193621bd..2f6f03091 100644 --- a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -155,6 +155,9 @@ class _StackThemeCardState extends ConsumerState { void initState() { final installedTheme = getInstalled(); _hasTheme = installedTheme != null; + if (_hasTheme) { + _needsUpdate = widget.data.version > (installedTheme?.version ?? 0); + } _subscription = ref .read(mainDBProvider) @@ -169,7 +172,8 @@ class _StackThemeCardState extends ConsumerState { setState(() { _hasTheme = hasTheme; if (hasTheme) { - _needsUpdate = widget.data.version > installedTheme.version!; + _needsUpdate = + widget.data.version > (installedTheme.version ?? 0); } }); }); From e7db6739ec8b8b8fd77550bd0089a738c3259bfc Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 23 May 2023 17:47:52 -0600 Subject: [PATCH 12/21] disable wownero on linux --- lib/main.dart | 6 ++++-- .../add_wallet_views/add_wallet_view/add_wallet_view.dart | 2 ++ .../settings/settings_menu/nodes_settings.dart | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 71604381a..8fa870214 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -70,7 +70,7 @@ final openedFromSWBFileStringStateProvider = // runs the MyApp widget and checks for new users, caching the value in the // miscellaneous box for later use void main() async { - WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + WidgetsFlutterBinding.ensureInitialized(); GoogleFonts.config.allowRuntimeFetching = false; if (Platform.isIOS) { Util.libraryPath = await getLibraryDirectory(); @@ -179,7 +179,9 @@ void main() async { } monero.onStartup(); - wownero.onStartup(); + if (!Platform.isLinux && !Platform.isWindows) { + wownero.onStartup(); + } // SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, // overlays: [SystemUiOverlay.bottom]); diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index c8cd467e8..d2dd8e30f 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -120,6 +120,8 @@ class _AddWalletViewState extends ConsumerState { if (Platform.isWindows) { _coins.remove(Coin.monero); _coins.remove(Coin.wownero); + } else if (Platform.isLinux) { + _coins.remove(Coin.wownero); } coinEntities.addAll(_coins.map((e) => CoinEntity(e))); diff --git a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart index 7c39b6c91..1bab6ab58 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart @@ -58,6 +58,8 @@ class _NodesSettings extends ConsumerState { if (Platform.isWindows) { _coins.remove(Coin.monero); _coins.remove(Coin.wownero); + } else if (Platform.isLinux) { + _coins.remove(Coin.wownero); } searchNodeController = TextEditingController(); From d18ba61c223da621fdd2fac24186862c99380ba4 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Tue, 23 May 2023 18:04:16 -0600 Subject: [PATCH 13/21] Bumped version (v1.7.10, build 174) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index eeba42567..3533282bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.9+173 +version: 1.7.10+174 environment: sdk: ">=2.17.0 <3.0.0" From 1a8aefa45cec44488847ee4cc55a152e6da4bb04 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 May 2023 10:25:13 -0600 Subject: [PATCH 14/21] fix: quick hack to show incoming joinsplit txns --- lib/services/coins/firo/firo_wallet.dart | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 88af91067..16b382ce2 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3361,33 +3361,33 @@ class FiroWallet extends CoinServiceAPI List> allTransactions = []; - final currentHeight = await chainHeight; + // final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final storedTx = await db - .getTransactions(walletId) - .filter() - .txidEqualTo(txHash["tx_hash"] as String) - .findFirst(); + // final storedTx = await db + // .getTransactions(walletId) + // .filter() + // .txidEqualTo(txHash["tx_hash"] as String) + // .findFirst(); - if (storedTx == null || - !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + // 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 db - .getAddresses(walletId) - .filter() - .valueEqualTo(txHash["address"] as String) - .findFirst(); - tx["height"] = txHash["height"]; - allTransactions.add(tx); - } + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); } + // } } final List> txnsData = From da522128de2a9c0ed1a184ecd76a8a08f777431f Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 May 2023 10:51:57 -0600 Subject: [PATCH 15/21] fix: eCash display name --- lib/utilities/enums/coin_enum.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index f1109a626..2f06c7ba9 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -62,7 +62,7 @@ extension CoinExt on Coin { case Coin.epicCash: return "Epic Cash"; case Coin.eCash: - return "E-Cash"; + return "eCash"; case Coin.ethereum: return "Ethereum"; case Coin.firo: From 0933546806f03dfe922a74c980542b6ed80e0d5b Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 May 2023 11:00:54 -0600 Subject: [PATCH 16/21] fix: various eCash tweaks + 0 conf --- .../generate_receiving_uri_qr_code_view.dart | 2 ++ .../manage_nodes_views/add_edit_node_view.dart | 1 + .../transaction_views/transaction_details_view.dart | 1 + lib/services/coins/ecash/ecash_wallet.dart | 10 +++++----- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index bc1281156..46867c7e1 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -150,6 +150,7 @@ class _GenerateUriQrCodeViewState extends State { String receivingAddress = widget.receivingAddress; if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) && receivingAddress.contains(":")) { // remove cash addr prefix @@ -246,6 +247,7 @@ class _GenerateUriQrCodeViewState extends State { String receivingAddress = widget.receivingAddress; if ((widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) && receivingAddress.contains(":")) { // remove cash addr prefix diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 1ff1a1359..aa5461ab2 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -150,6 +150,7 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.bitcoincash: case Coin.litecoin: case Coin.dogecoin: + case Coin.eCash: case Coin.firo: case Coin.namecoin: case Coin.particl: diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 5a0068c93..d3f8467cd 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1033,6 +1033,7 @@ class _TransactionDetailsViewState final String height; if (widget.coin == Coin.bitcoincash || + widget.coin == Coin.eCash || widget.coin == Coin.bitcoincashTestnet) { height = "${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}"; diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart index f45c23a73..ce04befe4 100644 --- a/lib/services/coins/ecash/ecash_wallet.dart +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -47,7 +47,7 @@ import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; -const int MINIMUM_CONFIRMATIONS = 1; +const int MINIMUM_CONFIRMATIONS = 0; const String GENESIS_HASH_MAINNET = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; @@ -361,9 +361,9 @@ class ECashWallet extends CoinServiceAPI print("format $format"); } - if (_coin == Coin.bitcoincashTestnet) { - return true; - } + // if (_coin == Coin.bitcoincashTestnet) { + // return true; + // } if (format == bitbox.Address.formatCashAddr) { return validateCashAddr(address); @@ -1899,7 +1899,7 @@ class ECashWallet extends CoinServiceAPI required List satoshiAmounts, }) async { final builder = bitbox.Bitbox.transactionBuilder( - testnet: coin == Coin.bitcoincashTestnet, + testnet: false, //coin == Coin.bitcoincashTestnet, ); // retrieve address' utxos from the rest api From d7aa8134d35023e78690b112e6f90302adefbda9 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 May 2023 13:02:36 -0600 Subject: [PATCH 17/21] block possible ordinal containing utxos --- .../coins/litecoin/litecoin_wallet.dart | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 274520f1d..b43deb631 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1793,6 +1793,27 @@ class LitecoinWallet extends CoinServiceAPI coin: coin, ); + final storedTx = await db.getTransaction( + walletId, + jsonUTXO["tx_hash"] as String, + ); + + bool shouldBlock = false; + String? blockReason; + String? label; + + if (storedTx?.amountString != null) { + final amount = Amount.fromSerializedJsonString( + storedTx!.amountString!, + ); + + if (amount.raw <= BigInt.from(10000)) { + shouldBlock = true; + blockReason = "May contain ordinal"; + label = "Possible ordinal"; + } + } + final vout = jsonUTXO["tx_pos"] as int; final outputs = txn["vout"] as List; @@ -1812,9 +1833,9 @@ class LitecoinWallet extends CoinServiceAPI txid: txn["txid"] as String, vout: vout, value: jsonUTXO["value"] as int, - name: "", - isBlocked: false, - blockedReason: null, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, isCoinbase: txn["is_coinbase"] as bool? ?? false, blockHash: txn["blockhash"] as String?, blockHeight: jsonUTXO["height"] as int?, From 35b631f32915b85be37a4c8856d0484b92d19bba Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 24 May 2023 13:07:44 -0600 Subject: [PATCH 18/21] fix: block possible ordinal containing utxos --- .../coins/litecoin/litecoin_wallet.dart | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index b43deb631..5f61ea96c 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1793,25 +1793,16 @@ class LitecoinWallet extends CoinServiceAPI coin: coin, ); - final storedTx = await db.getTransaction( - walletId, - jsonUTXO["tx_hash"] as String, - ); - bool shouldBlock = false; String? blockReason; String? label; - if (storedTx?.amountString != null) { - final amount = Amount.fromSerializedJsonString( - storedTx!.amountString!, - ); + final utxoAmount = jsonUTXO["value"] as int; - if (amount.raw <= BigInt.from(10000)) { - shouldBlock = true; - blockReason = "May contain ordinal"; - label = "Possible ordinal"; - } + if (utxoAmount <= 10000) { + shouldBlock = true; + blockReason = "May contain ordinal"; + label = "Possible ordinal"; } final vout = jsonUTXO["tx_pos"] as int; @@ -1832,7 +1823,7 @@ class LitecoinWallet extends CoinServiceAPI walletId: walletId, txid: txn["txid"] as String, vout: vout, - value: jsonUTXO["value"] as int, + value: utxoAmount, name: label ?? "", isBlocked: shouldBlock, blockedReason: blockReason, @@ -1847,16 +1838,20 @@ class LitecoinWallet extends CoinServiceAPI } } - Logging.instance - .log('Outputs fetched: $outputArray', level: LogLevel.Info); + Logging.instance.log( + 'Outputs fetched: $outputArray', + level: LogLevel.Info, + ); await db.updateUTXOs(walletId, outputArray); // finally update balance await _updateBalance(); } catch (e, s) { - Logging.instance - .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); + Logging.instance.log( + "Output fetch unsuccessful: $e\n$s", + level: LogLevel.Error, + ); } } From 74d3175d879555d92054f03151a856fc3fe38a3d Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 25 May 2023 09:24:07 -0600 Subject: [PATCH 19/21] fix: Handle sent to self transactions when sent to a change address --- lib/services/mixins/electrum_x_parsing.dart | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/services/mixins/electrum_x_parsing.dart b/lib/services/mixins/electrum_x_parsing.dart index c313a91eb..92cf86305 100644 --- a/lib/services/mixins/electrum_x_parsing.dart +++ b/lib/services/mixins/electrum_x_parsing.dart @@ -148,19 +148,31 @@ mixin ElectrumXParsing { type = TransactionType.outgoing; amount = amountSentFromWallet - changeAmount - fee; - final possible = - outputAddresses.difference(myChangeReceivedOnAddresses).first; + // non wallet addresses found in tx outputs + final nonWalletOutAddresses = outputAddresses.difference( + myChangeReceivedOnAddresses, + ); - if (transactionAddress.value != possible) { - transactionAddress = Address( - walletId: walletId, - value: possible, - derivationIndex: -1, - derivationPath: null, - subType: AddressSubType.nonWallet, - type: AddressType.nonWallet, - publicKey: [], - ); + if (nonWalletOutAddresses.isNotEmpty) { + final possible = nonWalletOutAddresses.first; + + if (transactionAddress.value != possible) { + transactionAddress = Address( + walletId: walletId, + value: possible, + derivationIndex: -1, + derivationPath: null, + subType: AddressSubType.nonWallet, + type: AddressType.nonWallet, + publicKey: [], + ); + } + } else { + // some other type of tx where the receiving address is + // one of my change addresses + + type = TransactionType.sentToSelf; + amount = changeAmount; } } else { // incoming tx From 3189203419632b2f712a608708343f432a9cc952 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 25 May 2023 10:05:20 -0600 Subject: [PATCH 20/21] fix: Firo total displayed on favourite card --- .../sub_widgets/favorite_card.dart | 111 +++++++++++------- .../sub_widgets/favorite_wallets.dart | 2 - .../desktop_favorite_wallets.dart | 1 - 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 87ffbccd1..8daf05a5d 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -6,7 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/themes/coin_icon_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; @@ -24,13 +24,11 @@ class FavoriteCard extends ConsumerStatefulWidget { required this.walletId, required this.width, required this.height, - required this.managerProvider, }) : super(key: key); final String walletId; final double width; final double height; - final ChangeNotifierProvider managerProvider; @override ConsumerState createState() => _FavoriteCardState(); @@ -38,15 +36,10 @@ class FavoriteCard extends ConsumerStatefulWidget { class _FavoriteCardState extends ConsumerState { late final String walletId; - late final ChangeNotifierProvider managerProvider; - - Amount _cachedBalance = Amount.zero; - Amount _cachedFiatValue = Amount.zero; @override void initState() { walletId = widget.walletId; - managerProvider = widget.managerProvider; super.initState(); } @@ -55,9 +48,13 @@ class _FavoriteCardState extends ConsumerState { @override Widget build(BuildContext context) { - final coin = ref.watch(managerProvider.select((value) => value.coin)); + final coin = ref.watch( + walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).coin), + ); final externalCalls = ref.watch( - prefsChangeNotifierProvider.select((value) => value.externalCalls)); + prefsChangeNotifierProvider.select((value) => value.externalCalls), + ); return ConditionalParent( condition: Util.isDesktop, @@ -109,7 +106,10 @@ class _FavoriteCardState extends ConsumerState { child: GestureDetector( onTap: () async { if (coin == Coin.monero || coin == Coin.wownero) { - await ref.read(managerProvider).initializeExisting(); + await ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .initializeExisting(); } if (mounted) { if (Util.isDesktop) { @@ -122,7 +122,9 @@ class _FavoriteCardState extends ConsumerState { WalletView.routeName, arguments: Tuple2( walletId, - managerProvider, + ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(walletId), ), ); } @@ -205,8 +207,12 @@ class _FavoriteCardState extends ConsumerState { children: [ Expanded( child: Text( - ref.watch(managerProvider - .select((value) => value.walletName)), + ref.watch( + walletsChangeNotifierProvider.select( + (value) => + value.getManager(walletId).walletName, + ), + ), style: STextStyles.itemSubtitle12(context).copyWith( color: Theme.of(context) .extension()! @@ -225,41 +231,54 @@ class _FavoriteCardState extends ConsumerState { ], ), ), - FutureBuilder( - future: Future(() => ref.watch(managerProvider - .select((value) => value.balance.total))), - builder: (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - if (snapshot.data != null) { - _cachedBalance = snapshot.data!; - if (externalCalls && _cachedBalance > Amount.zero) { - _cachedFiatValue = (_cachedBalance.decimal * - ref - .watch( - priceAnd24hChangeNotifierProvider - .select( - (value) => value.getPrice(coin), - ), - ) - .item1) - .toAmount(fractionDigits: 2); - } - } + Builder( + builder: (context) { + final balance = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).balance, + ), + ); + + Amount total = balance.total; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final balancePrivate = ref.watch( + walletsChangeNotifierProvider.select( + (value) => (value.getManager(walletId).wallet + as FiroWallet) + .balancePrivate, + ), + ); + + total += balancePrivate.total; } + + Amount fiatTotal = Amount.zero; + + if (externalCalls && total > Amount.zero) { + fiatTotal = (total.decimal * + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ) + .item1) + .toAmount(fractionDigits: 2); + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ FittedBox( fit: BoxFit.scaleDown, child: Text( - "${_cachedBalance.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), - decimalPlaces: ref.watch(managerProvider - .select((value) => value.coin.decimals)), + decimalPlaces: coin.decimals, )} ${coin.ticker}", style: STextStyles.titleBold12(context).copyWith( fontSize: 16, @@ -275,15 +294,17 @@ class _FavoriteCardState extends ConsumerState { ), if (externalCalls) Text( - "${_cachedFiatValue.localizedStringAsFixed( + "${fiatTotal.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), decimalPlaces: 2, )} ${ref.watch( - prefsChangeNotifierProvider - .select((value) => value.currency), + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), )}", style: STextStyles.itemSubtitle12(context).copyWith( diff --git a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart index afbea4562..665c147e1 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart @@ -211,7 +211,6 @@ class _FavoriteWalletsState extends ConsumerState { child: FavoriteCard( key: Key("favCard_$walletId"), walletId: walletId!, - managerProvider: managerProvider!, width: cardWidth, height: cardHeight, ), @@ -219,7 +218,6 @@ class _FavoriteWalletsState extends ConsumerState { : FavoriteCard( key: Key("favCard_$walletId"), walletId: walletId!, - managerProvider: managerProvider!, width: cardWidth, height: cardHeight, ) diff --git a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart index b9ce38eb4..03d0df2ca 100644 --- a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart +++ b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart @@ -74,7 +74,6 @@ class DesktopFavoriteWallets extends ConsumerWidget { key: Key(walletName), width: cardWidth, height: cardHeight, - managerProvider: managerProvider, ); }) ], From 6a92657f504405a7eb653f615fdb315e0b7ed1b2 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 25 May 2023 10:10:07 -0600 Subject: [PATCH 21/21] fix: Firo total displayed on managed favourite list card --- lib/widgets/managed_favorite.dart | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/widgets/managed_favorite.dart b/lib/widgets/managed_favorite.dart index 6383c8501..cd03e7f59 100644 --- a/lib/widgets/managed_favorite.dart +++ b/lib/widgets/managed_favorite.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/themes/coin_icon_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -34,6 +36,28 @@ class _ManagedFavoriteCardState extends ConsumerState { final isDesktop = Util.isDesktop; + final balance = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).balance, + ), + ); + + Amount total = balance.total; + if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) { + final balancePrivate = ref.watch( + walletsChangeNotifierProvider.select( + (value) => (value + .getManager( + widget.walletId, + ) + .wallet as FiroWallet) + .balancePrivate, + ), + ); + + total += balancePrivate.total; + } + return RoundedWhiteContainer( padding: EdgeInsets.all(isDesktop ? 0 : 4.0), child: RawMaterialButton( @@ -107,7 +131,7 @@ class _ManagedFavoriteCardState extends ConsumerState { ), Expanded( child: Text( - "${manager.balance.total.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider.select( (value) => value.locale, @@ -150,7 +174,7 @@ class _ManagedFavoriteCardState extends ConsumerState { height: 2, ), Text( - "${manager.balance.total.localizedStringAsFixed( + "${total.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider.select( (value) => value.locale,