diff --git a/lib/models/isar/models/blockchain_data/utxo.dart b/lib/models/isar/models/blockchain_data/utxo.dart index dd492d9b8..87558c541 100644 --- a/lib/models/isar/models/blockchain_data/utxo.dart +++ b/lib/models/isar/models/blockchain_data/utxo.dart @@ -80,9 +80,14 @@ class UTXO { return max(0, currentChainHeight - (blockHeight! - 1)); } - bool isConfirmed(int currentChainHeight, int minimumConfirms) { + bool isConfirmed( + int currentChainHeight, + int minimumConfirms, + int minimumCoinbaseConfirms, + ) { final confirmations = getConfirmations(currentChainHeight); - return confirmations >= minimumConfirms; + return confirmations >= + (isCoinbase ? minimumCoinbaseConfirms : minimumConfirms); } @ignore diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index 4582ef47d..9d080a2a0 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -112,9 +112,14 @@ class TransactionV2 { return max(0, currentChainHeight - (height! - 1)); } - bool isConfirmed(int currentChainHeight, int minimumConfirms) { + bool isConfirmed( + int currentChainHeight, + int minimumConfirms, + int minimumCoinbaseConfirms, + ) { final confirmations = getConfirmations(currentChainHeight); - return confirmations >= minimumConfirms; + return confirmations >= + (isCoinbase() ? minimumCoinbaseConfirms : minimumConfirms); } Amount getFee({required int fractionDigits}) { @@ -225,6 +230,7 @@ class TransactionV2 { String statusLabel({ required int currentChainHeight, required int minConfirms, + required int minCoinbaseConfirms, }) { String prettyConfirms() => "(${getConfirmations(currentChainHeight)}/$minConfirms)"; @@ -233,7 +239,7 @@ class TransactionV2 { subType == TransactionSubType.mint || (subType == TransactionSubType.sparkMint && type == TransactionType.sentToSelf)) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Anonymized"; } else { return "Anonymizing ${prettyConfirms()}"; @@ -248,7 +254,7 @@ class TransactionV2 { if (isCancelled) { return "Cancelled"; } else if (type == TransactionType.incoming) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Received"; } else { if (numberOfMessages == 1) { @@ -260,7 +266,7 @@ class TransactionV2 { } } } else if (type == TransactionType.outgoing) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Sent (confirmed)"; } else { if (numberOfMessages == 1) { @@ -278,19 +284,19 @@ class TransactionV2 { // if (_transaction.isMinting) { // return "Minting"; // } else - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Received"; } else { return "Receiving ${prettyConfirms()}"; } } else if (type == TransactionType.outgoing) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Sent"; } else { return "Sending ${prettyConfirms()}"; } } else if (type == TransactionType.sentToSelf) { - if (isConfirmed(currentChainHeight, minConfirms)) { + if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) { return "Sent to self"; } else { return "Sent to self ${prettyConfirms()}"; @@ -308,6 +314,9 @@ class TransactionV2 { return map[key]; } + bool isCoinbase() => + type == TransactionType.incoming && inputs.any((e) => e.coinbase != null); + @override String toString() { return 'TransactionV2(\n' diff --git a/lib/pages/coin_control/coin_control_view.dart b/lib/pages/coin_control/coin_control_view.dart index 095046736..07703a149 100644 --- a/lib/pages/coin_control/coin_control_view.dart +++ b/lib/pages/coin_control/coin_control_view.dart @@ -350,6 +350,7 @@ class _CoinControlViewState extends ConsumerState { utxo.isConfirmed( currentHeight, minConfirms, + coin.minCoinbaseConfirms, )), initialSelectedState: isSelected, onSelectedChanged: (value) { @@ -414,6 +415,7 @@ class _CoinControlViewState extends ConsumerState { utxo.isConfirmed( currentHeight, minConfirms, + coin.minCoinbaseConfirms, )), initialSelectedState: isSelected, onSelectedChanged: (value) { @@ -558,6 +560,7 @@ class _CoinControlViewState extends ConsumerState { utxo.isConfirmed( currentHeight, minConfirms, + coin.minCoinbaseConfirms, )), initialSelectedState: isSelected, onSelectedChanged: (value) { diff --git a/lib/pages/coin_control/utxo_card.dart b/lib/pages/coin_control/utxo_card.dart index c0a8395aa..0881c7eb6 100644 --- a/lib/pages/coin_control/utxo_card.dart +++ b/lib/pages/coin_control/utxo_card.dart @@ -117,6 +117,11 @@ class _UtxoCardState extends ConsumerState { .getWallet(widget.walletId) .cryptoCurrency .minConfirms, + ref + .watch(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minCoinbaseConfirms, ) ? UTXOStatusIconStatus.confirmed : UTXOStatusIconStatus.unconfirmed, diff --git a/lib/pages/coin_control/utxo_details_view.dart b/lib/pages/coin_control/utxo_details_view.dart index d2b378f79..ba71f7a01 100644 --- a/lib/pages/coin_control/utxo_details_view.dart +++ b/lib/pages/coin_control/utxo_details_view.dart @@ -98,6 +98,11 @@ class _UtxoDetailsViewState extends ConsumerState { final confirmed = utxo!.isConfirmed( currentHeight, ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms, + ref + .watch(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); return ConditionalParent( diff --git a/lib/pages/wallet_view/sub_widgets/tx_icon.dart b/lib/pages/wallet_view/sub_widgets/tx_icon.dart index 94f9b033a..1ea611a80 100644 --- a/lib/pages/wallet_view/sub_widgets/tx_icon.dart +++ b/lib/pages/wallet_view/sub_widgets/tx_icon.dart @@ -13,6 +13,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; + import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../models/isar/models/isar_models.dart'; import '../../../models/isar/stack_theme.dart'; @@ -106,6 +107,11 @@ class TxIcon extends ConsumerWidget { !tx.isConfirmed( currentHeight, ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms, + ref + .watch(pWallets) + .getWallet(tx.walletId) + .cryptoCurrency + .minCoinbaseConfirms, ), tx.subType, tx.type, diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart index 24dd77005..ed6e007d2 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart @@ -15,14 +15,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; + import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../../models/isar/models/contact_entry.dart'; import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/transaction_filter.dart'; -import '../../sub_widgets/tx_icon.dart'; -import '../transaction_search_filter_view.dart'; -import 'transaction_v2_card.dart'; -import 'transaction_v2_details_view.dart'; import '../../../../providers/db/main_db_provider.dart'; import '../../../../providers/global/address_book_service_provider.dart'; import '../../../../providers/providers.dart'; @@ -49,6 +46,10 @@ import '../../../../widgets/loading_indicator.dart'; import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; +import '../../sub_widgets/tx_icon.dart'; +import '../transaction_search_filter_view.dart'; +import 'transaction_v2_card.dart'; +import 'transaction_v2_details_view.dart'; typedef _GroupedTransactions = ({ String label, @@ -866,6 +867,11 @@ class _DesktopTransactionCardRowState String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( currentChainHeight: height, minConfirms: minConfirms, + minCoinbaseConfirms: ref + .read(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); @override diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart index 0cce71e0f..34e5c662b 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart @@ -60,6 +60,11 @@ class _TransactionCardStateV2 extends ConsumerState { .getWallet(walletId) .cryptoCurrency .minConfirms, + minCoinbaseConfirms: ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); @override diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart index 666d8d041..f3ed94877 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -373,6 +373,11 @@ class _TransactionV2DetailsViewState String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( currentChainHeight: height, minConfirms: minConfirms, + minCoinbaseConfirms: ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minCoinbaseConfirms, ); Future fetchContactNameFor(String address) async { @@ -567,6 +572,7 @@ class _TransactionV2DetailsViewState final confirmedTxn = _transaction.isConfirmed( currentHeight, coin.minConfirms, + coin.minCoinbaseConfirms, ); return ConditionalParent( @@ -1367,6 +1373,7 @@ class _TransactionV2DetailsViewState ? _transaction.isConfirmed( currentHeight, minConfirms, + coin.minCoinbaseConfirms, ) ? ref .watch(pAmountFormatter(coin)) @@ -1484,9 +1491,9 @@ class _TransactionV2DetailsViewState height = "Unknown"; } else { final confirmed = _transaction.isConfirmed( - currentHeight, - minConfirms, - ); + currentHeight, + minConfirms, + coin.minCoinbaseConfirms); if (widget.coin is! Epiccash && confirmed) { height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; diff --git a/lib/pages_desktop_specific/coin_control/utxo_row.dart b/lib/pages_desktop_specific/coin_control/utxo_row.dart index ca33fe8f2..26204375c 100644 --- a/lib/pages_desktop_specific/coin_control/utxo_row.dart +++ b/lib/pages_desktop_specific/coin_control/utxo_row.dart @@ -11,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/isar.dart'; + import '../../db/isar/main_db.dart'; import '../../models/isar/models/isar_models.dart'; import '../../pages/coin_control/utxo_details_view.dart'; @@ -141,6 +142,11 @@ class _UtxoRowState extends ConsumerState { .getWallet(widget.walletId) .cryptoCurrency .minConfirms, + ref + .watch(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minCoinbaseConfirms, ) ? UTXOStatusIconStatus.confirmed : UTXOStatusIconStatus.unconfirmed, diff --git a/lib/wallets/crypto_currency/coins/firo.dart b/lib/wallets/crypto_currency/coins/firo.dart index 9e4a3bddf..d8a6381b2 100644 --- a/lib/wallets/crypto_currency/coins/firo.dart +++ b/lib/wallets/crypto_currency/coins/firo.dart @@ -54,6 +54,9 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface { @override int get minConfirms => 1; + @override + int get minCoinbaseConfirms => 100; + @override bool get torSupport => true; diff --git a/lib/wallets/crypto_currency/crypto_currency.dart b/lib/wallets/crypto_currency/crypto_currency.dart index 074536be6..066675d28 100644 --- a/lib/wallets/crypto_currency/crypto_currency.dart +++ b/lib/wallets/crypto_currency/crypto_currency.dart @@ -60,6 +60,7 @@ abstract class CryptoCurrency { bool get torSupport => false; int get minConfirms; + int get minCoinbaseConfirms => minConfirms; // TODO: [prio=low] could be handled differently as (at least) epiccash does not use this String get genesisHash; diff --git a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart index 462dacbde..9d4a4b739 100644 --- a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart @@ -129,6 +129,7 @@ class BitcoinFrostWallet extends Wallet (e) => !e.isConfirmed( currentHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ), ); if (utxos.isEmpty) { @@ -330,7 +331,11 @@ class BitcoinFrostWallet extends Wallet final height = await chainHeight; for (final output in (await mainDB.getUTXOs(walletId).findAll())) { if (!output.isBlocked && - output.isConfirmed(height, cryptoCurrency.minConfirms)) { + output.isConfirmed( + height, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )) { available += output.value; inputCount++; } @@ -448,7 +453,11 @@ class BitcoinFrostWallet extends Wallet .findFirst(); if (storedTx == null || - !storedTx.isConfirmed(currentHeight, cryptoCurrency.minConfirms)) { + !storedTx.isConfirmed( + currentHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )) { final tx = await electrumXCachedClient.getTransaction( txHash: txHash["tx_hash"] as String, verbose: true, @@ -1060,6 +1069,7 @@ class BitcoinFrostWallet extends Wallet if (utxo.isConfirmed( currentChainHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, )) { satoshiBalanceSpendable += utxoAmount; } else { diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index f68a83917..1ff9999fe 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -592,7 +592,11 @@ class FiroWallet extends Bip39HDWallet ); if (_unconfirmedTxids.contains(tx.txid)) { - if (tx.isConfirmed(await chainHeight, cryptoCurrency.minConfirms)) { + if (tx.isConfirmed( + await chainHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )) { txns.add(tx); _unconfirmedTxids.removeWhere((e) => e == tx.txid); } else { diff --git a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart index d2ca7b254..e270cb7c9 100644 --- a/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart +++ b/lib/wallets/wallet/intermediate/bip39_hd_wallet.dart @@ -309,6 +309,7 @@ abstract class Bip39HDWallet extends Bip39Wallet if (utxo.isConfirmed( currentChainHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, )) { satoshiBalanceSpendable += utxoAmount; } else { diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index d65fb7b62..b542f071c 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -141,6 +141,7 @@ mixin ElectrumXInterface e.isConfirmed( currentChainHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, )), ) .toList(); @@ -1920,6 +1921,7 @@ mixin ElectrumXInterface e.isConfirmed( info.cachedChainHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ), ) .toList(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart index 7116f6c7d..3d57f3c36 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart @@ -925,6 +925,7 @@ mixin LelantusInterface if (availableOutputs[i].isConfirmed( currentChainHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ) == true && !(availableOutputs[i].isCoinbase && diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart index f86e76d0d..b20638362 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart @@ -479,6 +479,7 @@ mixin PaynymInterface availableOutputs[i].isConfirmed( await fetchChainHeight(), cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ) == true) { spendableOutputs.add(availableOutputs[i]); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart index c71fa4c36..80bff1a7a 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart @@ -121,6 +121,7 @@ mixin RbfInterface (e) => !e.isConfirmed( height, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ), ); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index c0843246c..977a95124 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -1750,6 +1750,7 @@ mixin SparkInterface (e) => !e.isConfirmed( currentHeight, cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, ), ); @@ -1845,7 +1846,11 @@ mixin SparkInterface .where( (e) => canCPFP || - e.isConfirmed(currentHeight, cryptoCurrency.minConfirms), + e.isConfirmed( + currentHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + ), ) .toList();