add functionality for different number of required min confirms for coinbase transactions and apply to firo

This commit is contained in:
julian 2024-11-21 09:40:40 -06:00 committed by julian-CStack
parent 3566d75d58
commit 40b0f49f20
20 changed files with 107 additions and 21 deletions

View file

@ -80,9 +80,14 @@ class UTXO {
return max(0, currentChainHeight - (blockHeight! - 1)); return max(0, currentChainHeight - (blockHeight! - 1));
} }
bool isConfirmed(int currentChainHeight, int minimumConfirms) { bool isConfirmed(
int currentChainHeight,
int minimumConfirms,
int minimumCoinbaseConfirms,
) {
final confirmations = getConfirmations(currentChainHeight); final confirmations = getConfirmations(currentChainHeight);
return confirmations >= minimumConfirms; return confirmations >=
(isCoinbase ? minimumCoinbaseConfirms : minimumConfirms);
} }
@ignore @ignore

View file

@ -112,9 +112,14 @@ class TransactionV2 {
return max(0, currentChainHeight - (height! - 1)); return max(0, currentChainHeight - (height! - 1));
} }
bool isConfirmed(int currentChainHeight, int minimumConfirms) { bool isConfirmed(
int currentChainHeight,
int minimumConfirms,
int minimumCoinbaseConfirms,
) {
final confirmations = getConfirmations(currentChainHeight); final confirmations = getConfirmations(currentChainHeight);
return confirmations >= minimumConfirms; return confirmations >=
(isCoinbase() ? minimumCoinbaseConfirms : minimumConfirms);
} }
Amount getFee({required int fractionDigits}) { Amount getFee({required int fractionDigits}) {
@ -225,6 +230,7 @@ class TransactionV2 {
String statusLabel({ String statusLabel({
required int currentChainHeight, required int currentChainHeight,
required int minConfirms, required int minConfirms,
required int minCoinbaseConfirms,
}) { }) {
String prettyConfirms() => String prettyConfirms() =>
"(${getConfirmations(currentChainHeight)}/$minConfirms)"; "(${getConfirmations(currentChainHeight)}/$minConfirms)";
@ -233,7 +239,7 @@ class TransactionV2 {
subType == TransactionSubType.mint || subType == TransactionSubType.mint ||
(subType == TransactionSubType.sparkMint && (subType == TransactionSubType.sparkMint &&
type == TransactionType.sentToSelf)) { type == TransactionType.sentToSelf)) {
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Anonymized"; return "Anonymized";
} else { } else {
return "Anonymizing ${prettyConfirms()}"; return "Anonymizing ${prettyConfirms()}";
@ -248,7 +254,7 @@ class TransactionV2 {
if (isCancelled) { if (isCancelled) {
return "Cancelled"; return "Cancelled";
} else if (type == TransactionType.incoming) { } else if (type == TransactionType.incoming) {
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Received"; return "Received";
} else { } else {
if (numberOfMessages == 1) { if (numberOfMessages == 1) {
@ -260,7 +266,7 @@ class TransactionV2 {
} }
} }
} else if (type == TransactionType.outgoing) { } else if (type == TransactionType.outgoing) {
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Sent (confirmed)"; return "Sent (confirmed)";
} else { } else {
if (numberOfMessages == 1) { if (numberOfMessages == 1) {
@ -278,19 +284,19 @@ class TransactionV2 {
// if (_transaction.isMinting) { // if (_transaction.isMinting) {
// return "Minting"; // return "Minting";
// } else // } else
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Received"; return "Received";
} else { } else {
return "Receiving ${prettyConfirms()}"; return "Receiving ${prettyConfirms()}";
} }
} else if (type == TransactionType.outgoing) { } else if (type == TransactionType.outgoing) {
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Sent"; return "Sent";
} else { } else {
return "Sending ${prettyConfirms()}"; return "Sending ${prettyConfirms()}";
} }
} else if (type == TransactionType.sentToSelf) { } else if (type == TransactionType.sentToSelf) {
if (isConfirmed(currentChainHeight, minConfirms)) { if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
return "Sent to self"; return "Sent to self";
} else { } else {
return "Sent to self ${prettyConfirms()}"; return "Sent to self ${prettyConfirms()}";
@ -308,6 +314,9 @@ class TransactionV2 {
return map[key]; return map[key];
} }
bool isCoinbase() =>
type == TransactionType.incoming && inputs.any((e) => e.coinbase != null);
@override @override
String toString() { String toString() {
return 'TransactionV2(\n' return 'TransactionV2(\n'

View file

@ -350,6 +350,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
utxo.isConfirmed( utxo.isConfirmed(
currentHeight, currentHeight,
minConfirms, minConfirms,
coin.minCoinbaseConfirms,
)), )),
initialSelectedState: isSelected, initialSelectedState: isSelected,
onSelectedChanged: (value) { onSelectedChanged: (value) {
@ -414,6 +415,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
utxo.isConfirmed( utxo.isConfirmed(
currentHeight, currentHeight,
minConfirms, minConfirms,
coin.minCoinbaseConfirms,
)), )),
initialSelectedState: isSelected, initialSelectedState: isSelected,
onSelectedChanged: (value) { onSelectedChanged: (value) {
@ -558,6 +560,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
utxo.isConfirmed( utxo.isConfirmed(
currentHeight, currentHeight,
minConfirms, minConfirms,
coin.minCoinbaseConfirms,
)), )),
initialSelectedState: isSelected, initialSelectedState: isSelected,
onSelectedChanged: (value) { onSelectedChanged: (value) {

View file

@ -117,6 +117,11 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
.getWallet(widget.walletId) .getWallet(widget.walletId)
.cryptoCurrency .cryptoCurrency
.minConfirms, .minConfirms,
ref
.watch(pWallets)
.getWallet(widget.walletId)
.cryptoCurrency
.minCoinbaseConfirms,
) )
? UTXOStatusIconStatus.confirmed ? UTXOStatusIconStatus.confirmed
: UTXOStatusIconStatus.unconfirmed, : UTXOStatusIconStatus.unconfirmed,

View file

@ -98,6 +98,11 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
final confirmed = utxo!.isConfirmed( final confirmed = utxo!.isConfirmed(
currentHeight, currentHeight,
ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms, ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms,
ref
.watch(pWallets)
.getWallet(widget.walletId)
.cryptoCurrency
.minCoinbaseConfirms,
); );
return ConditionalParent( return ConditionalParent(

View file

@ -13,6 +13,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
import '../../../models/isar/models/isar_models.dart'; import '../../../models/isar/models/isar_models.dart';
import '../../../models/isar/stack_theme.dart'; import '../../../models/isar/stack_theme.dart';
@ -106,6 +107,11 @@ class TxIcon extends ConsumerWidget {
!tx.isConfirmed( !tx.isConfirmed(
currentHeight, currentHeight,
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms, ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
ref
.watch(pWallets)
.getWallet(tx.walletId)
.cryptoCurrency
.minCoinbaseConfirms,
), ),
tx.subType, tx.subType,
tx.type, tx.type,

View file

@ -15,14 +15,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
import '../../../../models/isar/models/contact_entry.dart'; import '../../../../models/isar/models/contact_entry.dart';
import '../../../../models/isar/models/isar_models.dart'; import '../../../../models/isar/models/isar_models.dart';
import '../../../../models/transaction_filter.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/db/main_db_provider.dart';
import '../../../../providers/global/address_book_service_provider.dart'; import '../../../../providers/global/address_book_service_provider.dart';
import '../../../../providers/providers.dart'; import '../../../../providers/providers.dart';
@ -49,6 +46,10 @@ import '../../../../widgets/loading_indicator.dart';
import '../../../../widgets/rounded_white_container.dart'; import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/stack_text_field.dart';
import '../../../../widgets/textfield_icon_button.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 = ({ typedef _GroupedTransactions = ({
String label, String label,
@ -866,6 +867,11 @@ class _DesktopTransactionCardRowState
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
currentChainHeight: height, currentChainHeight: height,
minConfirms: minConfirms, minConfirms: minConfirms,
minCoinbaseConfirms: ref
.read(pWallets)
.getWallet(widget.walletId)
.cryptoCurrency
.minCoinbaseConfirms,
); );
@override @override

View file

@ -60,6 +60,11 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
.getWallet(walletId) .getWallet(walletId)
.cryptoCurrency .cryptoCurrency
.minConfirms, .minConfirms,
minCoinbaseConfirms: ref
.read(pWallets)
.getWallet(walletId)
.cryptoCurrency
.minCoinbaseConfirms,
); );
@override @override

View file

@ -373,6 +373,11 @@ class _TransactionV2DetailsViewState
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
currentChainHeight: height, currentChainHeight: height,
minConfirms: minConfirms, minConfirms: minConfirms,
minCoinbaseConfirms: ref
.read(pWallets)
.getWallet(walletId)
.cryptoCurrency
.minCoinbaseConfirms,
); );
Future<String> fetchContactNameFor(String address) async { Future<String> fetchContactNameFor(String address) async {
@ -567,6 +572,7 @@ class _TransactionV2DetailsViewState
final confirmedTxn = _transaction.isConfirmed( final confirmedTxn = _transaction.isConfirmed(
currentHeight, currentHeight,
coin.minConfirms, coin.minConfirms,
coin.minCoinbaseConfirms,
); );
return ConditionalParent( return ConditionalParent(
@ -1367,6 +1373,7 @@ class _TransactionV2DetailsViewState
? _transaction.isConfirmed( ? _transaction.isConfirmed(
currentHeight, currentHeight,
minConfirms, minConfirms,
coin.minCoinbaseConfirms,
) )
? ref ? ref
.watch(pAmountFormatter(coin)) .watch(pAmountFormatter(coin))
@ -1484,9 +1491,9 @@ class _TransactionV2DetailsViewState
height = "Unknown"; height = "Unknown";
} else { } else {
final confirmed = _transaction.isConfirmed( final confirmed = _transaction.isConfirmed(
currentHeight, currentHeight,
minConfirms, minConfirms,
); coin.minCoinbaseConfirms);
if (widget.coin is! Epiccash && confirmed) { if (widget.coin is! Epiccash && confirmed) {
height = height =
"${_transaction.height == 0 ? "Unknown" : _transaction.height}"; "${_transaction.height == 0 ? "Unknown" : _transaction.height}";

View file

@ -11,6 +11,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import '../../db/isar/main_db.dart'; import '../../db/isar/main_db.dart';
import '../../models/isar/models/isar_models.dart'; import '../../models/isar/models/isar_models.dart';
import '../../pages/coin_control/utxo_details_view.dart'; import '../../pages/coin_control/utxo_details_view.dart';
@ -141,6 +142,11 @@ class _UtxoRowState extends ConsumerState<UtxoRow> {
.getWallet(widget.walletId) .getWallet(widget.walletId)
.cryptoCurrency .cryptoCurrency
.minConfirms, .minConfirms,
ref
.watch(pWallets)
.getWallet(widget.walletId)
.cryptoCurrency
.minCoinbaseConfirms,
) )
? UTXOStatusIconStatus.confirmed ? UTXOStatusIconStatus.confirmed
: UTXOStatusIconStatus.unconfirmed, : UTXOStatusIconStatus.unconfirmed,

View file

@ -54,6 +54,9 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
@override @override
int get minConfirms => 1; int get minConfirms => 1;
@override
int get minCoinbaseConfirms => 100;
@override @override
bool get torSupport => true; bool get torSupport => true;

View file

@ -60,6 +60,7 @@ abstract class CryptoCurrency {
bool get torSupport => false; bool get torSupport => false;
int get minConfirms; int get minConfirms;
int get minCoinbaseConfirms => minConfirms;
// TODO: [prio=low] could be handled differently as (at least) epiccash does not use this // TODO: [prio=low] could be handled differently as (at least) epiccash does not use this
String get genesisHash; String get genesisHash;

View file

@ -129,6 +129,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
(e) => !e.isConfirmed( (e) => !e.isConfirmed(
currentHeight, currentHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
), ),
); );
if (utxos.isEmpty) { if (utxos.isEmpty) {
@ -330,7 +331,11 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
final height = await chainHeight; final height = await chainHeight;
for (final output in (await mainDB.getUTXOs(walletId).findAll())) { for (final output in (await mainDB.getUTXOs(walletId).findAll())) {
if (!output.isBlocked && if (!output.isBlocked &&
output.isConfirmed(height, cryptoCurrency.minConfirms)) { output.isConfirmed(
height,
cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)) {
available += output.value; available += output.value;
inputCount++; inputCount++;
} }
@ -448,7 +453,11 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
.findFirst(); .findFirst();
if (storedTx == null || if (storedTx == null ||
!storedTx.isConfirmed(currentHeight, cryptoCurrency.minConfirms)) { !storedTx.isConfirmed(
currentHeight,
cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)) {
final tx = await electrumXCachedClient.getTransaction( final tx = await electrumXCachedClient.getTransaction(
txHash: txHash["tx_hash"] as String, txHash: txHash["tx_hash"] as String,
verbose: true, verbose: true,
@ -1060,6 +1069,7 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
if (utxo.isConfirmed( if (utxo.isConfirmed(
currentChainHeight, currentChainHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)) { )) {
satoshiBalanceSpendable += utxoAmount; satoshiBalanceSpendable += utxoAmount;
} else { } else {

View file

@ -592,7 +592,11 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
); );
if (_unconfirmedTxids.contains(tx.txid)) { if (_unconfirmedTxids.contains(tx.txid)) {
if (tx.isConfirmed(await chainHeight, cryptoCurrency.minConfirms)) { if (tx.isConfirmed(
await chainHeight,
cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)) {
txns.add(tx); txns.add(tx);
_unconfirmedTxids.removeWhere((e) => e == tx.txid); _unconfirmedTxids.removeWhere((e) => e == tx.txid);
} else { } else {

View file

@ -309,6 +309,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
if (utxo.isConfirmed( if (utxo.isConfirmed(
currentChainHeight, currentChainHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)) { )) {
satoshiBalanceSpendable += utxoAmount; satoshiBalanceSpendable += utxoAmount;
} else { } else {

View file

@ -141,6 +141,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
e.isConfirmed( e.isConfirmed(
currentChainHeight, currentChainHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
)), )),
) )
.toList(); .toList();
@ -1920,6 +1921,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
e.isConfirmed( e.isConfirmed(
info.cachedChainHeight, info.cachedChainHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
), ),
) )
.toList(); .toList();

View file

@ -925,6 +925,7 @@ mixin LelantusInterface<T extends ElectrumXCurrencyInterface>
if (availableOutputs[i].isConfirmed( if (availableOutputs[i].isConfirmed(
currentChainHeight, currentChainHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
) == ) ==
true && true &&
!(availableOutputs[i].isCoinbase && !(availableOutputs[i].isCoinbase &&

View file

@ -479,6 +479,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
availableOutputs[i].isConfirmed( availableOutputs[i].isConfirmed(
await fetchChainHeight(), await fetchChainHeight(),
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
) == ) ==
true) { true) {
spendableOutputs.add(availableOutputs[i]); spendableOutputs.add(availableOutputs[i]);

View file

@ -121,6 +121,7 @@ mixin RbfInterface<T extends ElectrumXCurrencyInterface>
(e) => !e.isConfirmed( (e) => !e.isConfirmed(
height, height,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
), ),
); );

View file

@ -1750,6 +1750,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
(e) => !e.isConfirmed( (e) => !e.isConfirmed(
currentHeight, currentHeight,
cryptoCurrency.minConfirms, cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
), ),
); );
@ -1845,7 +1846,11 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
.where( .where(
(e) => (e) =>
canCPFP || canCPFP ||
e.isConfirmed(currentHeight, cryptoCurrency.minConfirms), e.isConfirmed(
currentHeight,
cryptoCurrency.minConfirms,
cryptoCurrency.minCoinbaseConfirms,
),
) )
.toList(); .toList();