Merge branch 'cypherstack:staging' into staging

This commit is contained in:
dethe 2024-09-01 14:49:46 +03:00 committed by GitHub
commit 56dcc0ba4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 155 additions and 109 deletions

@ -1 +1 @@
Subproject commit db7585d8cd493b143e0a0652c618904d1f636d1d Subproject commit c0b8994009d29bc7fd2f0f2edbb004c39bf93951

View file

@ -522,9 +522,11 @@ class _ConfirmTransactionViewState
SelectableText( SelectableText(
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange, amountWithoutChange,
ethContract: ref ethContract: widget.isTokenTx
.watch(pCurrentTokenWallet) ? ref
?.tokenContract, .watch(pCurrentTokenWallet)!
.tokenContract
: null,
), ),
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right, textAlign: TextAlign.right,
@ -745,9 +747,11 @@ class _ConfirmTransactionViewState
SelectableText( SelectableText(
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
amountWithoutChange, amountWithoutChange,
ethContract: ref ethContract: widget.isTokenTx
.read(pCurrentTokenWallet) ? ref
?.tokenContract, .watch(pCurrentTokenWallet)!
.tokenContract
: null,
), ),
style: STextStyles style: STextStyles
.desktopTextExtraExtraSmall( .desktopTextExtraExtraSmall(

View file

@ -881,6 +881,48 @@ class _SendViewState extends ConsumerState<SendView> {
} }
} }
String _getSendAllTitle(bool showCoinControl, Set<UTXO> selectedUTXOs) {
if (showCoinControl && selectedUTXOs.isNotEmpty) {
return "Send all selected";
}
return "Send all ${coin.ticker}";
}
Amount _selectedUtxosAmount(Set<UTXO> utxos) => Amount(
rawValue:
utxos.map((e) => BigInt.from(e.value)).reduce((v, e) => v += e),
fractionDigits: ref.read(pWalletCoin(walletId)).fractionDigits,
);
Future<void> _sendAllTapped(bool showCoinControl) async {
final Amount amount;
if (showCoinControl && selectedUTXOs.isNotEmpty) {
amount = _selectedUtxosAmount(selectedUTXOs);
} else if (isFiro) {
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
case FiroType.public:
amount = ref.read(pWalletBalance(walletId)).spendable;
break;
case FiroType.lelantus:
amount = ref.read(pWalletBalanceSecondary(walletId)).spendable;
break;
case FiroType.spark:
amount = ref.read(pWalletBalanceTertiary(walletId)).spendable;
break;
}
} else {
amount = ref.read(pWalletBalance(walletId)).spendable;
}
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
amount,
withUnitName: false,
);
_cryptoAmountChanged();
}
bool get isPaynymSend => widget.accountLite != null; bool get isPaynymSend => widget.accountLite != null;
bool isCustomFee = false; bool isCustomFee = false;
@ -1772,59 +1814,9 @@ class _SendViewState extends ConsumerState<SendView> {
), ),
if (coin is! Ethereum && coin is! Tezos) if (coin is! Ethereum && coin is! Tezos)
CustomTextButton( CustomTextButton(
text: "Send all ${coin.ticker}", text: _getSendAllTitle(
onTap: () async { showCoinControl, selectedUTXOs),
if (isFiro) { onTap: () => _sendAllTapped(showCoinControl),
final Amount amount;
switch (ref
.read(
publicPrivateBalanceStateProvider
.state,
)
.state) {
case FiroType.public:
amount = ref
.read(pWalletBalance(walletId))
.spendable;
break;
case FiroType.lelantus:
amount = ref
.read(
pWalletBalanceSecondary(
walletId,
),
)
.spendable;
break;
case FiroType.spark:
amount = ref
.read(
pWalletBalanceTertiary(
walletId,
),
)
.spendable;
break;
}
cryptoAmountController.text = ref
.read(pAmountFormatter(coin))
.format(
amount,
withUnitName: false,
);
} else {
cryptoAmountController.text = ref
.read(pAmountFormatter(coin))
.format(
ref
.read(pWalletBalance(walletId))
.spendable,
withUnitName: false,
);
}
_cryptoAmountChanged();
},
), ),
], ],
), ),

View file

@ -18,6 +18,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import '../../../../models/isar/models/blockchain_data/utxo.dart';
import '../../../../models/isar/models/contact_entry.dart'; import '../../../../models/isar/models/contact_entry.dart';
import '../../../../models/paynym/paynym_account_lite.dart'; import '../../../../models/paynym/paynym_account_lite.dart';
import '../../../../models/send_view_auto_fill_data.dart'; import '../../../../models/send_view_auto_fill_data.dart';
@ -932,30 +933,45 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
ref.read(pSendAmount.notifier).state = amount; ref.read(pSendAmount.notifier).state = amount;
} }
Future<void> sendAllTapped() async { String _getSendAllTitle(bool showCoinControl, Set<UTXO> selectedUTXOs) {
final info = ref.read(pWalletInfo(walletId)); if (showCoinControl && selectedUTXOs.isNotEmpty) {
return "Send all selected";
}
if (coin is Firo) { return "Send all ${coin.ticker}";
}
Amount _selectedUtxosAmount(Set<UTXO> utxos) => Amount(
rawValue:
utxos.map((e) => BigInt.from(e.value)).reduce((v, e) => v += e),
fractionDigits: ref.read(pWalletCoin(walletId)).fractionDigits,
);
Future<void> _sendAllTapped(bool showCoinControl) async {
final Amount amount;
if (showCoinControl && ref.read(desktopUseUTXOs).isNotEmpty) {
amount = _selectedUtxosAmount(ref.read(desktopUseUTXOs));
} else if (coin is Firo) {
switch (ref.read(publicPrivateBalanceStateProvider.state).state) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
case FiroType.public: case FiroType.public:
cryptoAmountController.text = info.cachedBalance.spendable.decimal amount = ref.read(pWalletBalance(walletId)).spendable;
.toStringAsFixed(coin.fractionDigits);
break; break;
case FiroType.lelantus: case FiroType.lelantus:
cryptoAmountController.text = info amount = ref.read(pWalletBalanceSecondary(walletId)).spendable;
.cachedBalanceSecondary.spendable.decimal
.toStringAsFixed(coin.fractionDigits);
break; break;
case FiroType.spark: case FiroType.spark:
cryptoAmountController.text = info amount = ref.read(pWalletBalanceTertiary(walletId)).spendable;
.cachedBalanceTertiary.spendable.decimal
.toStringAsFixed(coin.fractionDigits);
break; break;
} }
} else { } else {
cryptoAmountController.text = info.cachedBalance.spendable.decimal amount = ref.read(pWalletBalance(walletId)).spendable;
.toStringAsFixed(coin.fractionDigits);
} }
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
amount,
withUnitName: false,
);
} }
void _showDesktopCoinControl() async { void _showDesktopCoinControl() async {
@ -1280,8 +1296,11 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
if (coin is! Ethereum && coin is! Tezos) if (coin is! Ethereum && coin is! Tezos)
CustomTextButton( CustomTextButton(
text: "Send all ${coin.ticker}", text: _getSendAllTitle(
onTap: sendAllTapped, showCoinControl,
ref.watch(desktopUseUTXOs),
),
onTap: () => _sendAllTapped(showCoinControl),
), ),
], ],
), ),

View file

@ -105,6 +105,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
required TxData txData, required TxData txData,
required bool coinControl, required bool coinControl,
required bool isSendAll, required bool isSendAll,
required bool isSendAllCoinControlUtxos,
int additionalOutputs = 0, int additionalOutputs = 0,
List<UTXO>? utxos, List<UTXO>? utxos,
}) async { }) async {
@ -144,7 +145,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
if (spendableSatoshiValue < satoshiAmountToSend) { if (spendableSatoshiValue < satoshiAmountToSend) {
throw Exception("Insufficient balance"); throw Exception("Insufficient balance");
} else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { } else if (spendableSatoshiValue == satoshiAmountToSend &&
!isSendAll &&
!isSendAllCoinControlUtxos) {
throw Exception("Insufficient balance to pay transaction fee"); throw Exception("Insufficient balance to pay transaction fee");
} }
@ -220,7 +223,13 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
// gather required signing data // gather required signing data
final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse);
if (isSendAll) { if (isSendAll || isSendAllCoinControlUtxos) {
if (satoshiAmountToSend != satoshisBeingUsed) {
throw Exception(
"Something happened that should never actually happen. "
"Please report this error to the developers.",
);
}
return await _sendAllBuilder( return await _sendAllBuilder(
txData: txData, txData: txData,
recipientAddress: recipientAddress, recipientAddress: recipientAddress,
@ -357,6 +366,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
additionalOutputs: additionalOutputs + 1, additionalOutputs: additionalOutputs + 1,
utxos: utxos, utxos: utxos,
coinControl: coinControl, coinControl: coinControl,
isSendAllCoinControlUtxos: isSendAllCoinControlUtxos,
); );
} }
throw Exception("Insufficient balance to pay transaction fee"); throw Exception("Insufficient balance to pay transaction fee");
@ -1681,11 +1691,23 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
@override @override
Future<TxData> prepareSend({required TxData txData}) async { Future<TxData> prepareSend({required TxData txData}) async {
try { try {
if (txData.amount == null) {
throw Exception("No recipients in attempted transaction!");
}
final feeRateType = txData.feeRateType; final feeRateType = txData.feeRateType;
final customSatsPerVByte = txData.satsPerVByte; final customSatsPerVByte = txData.satsPerVByte;
final feeRateAmount = txData.feeRateAmount; final feeRateAmount = txData.feeRateAmount;
final utxos = txData.utxos; final utxos = txData.utxos;
final bool coinControl = utxos != null;
final isSendAllCoinControlUtxos = coinControl &&
txData.amount!.raw ==
utxos
.map((e) => e.value)
.fold(BigInt.zero, (p, e) => p + BigInt.from(e));
if (customSatsPerVByte != null) { if (customSatsPerVByte != null) {
// check for send all // check for send all
bool isSendAll = false; bool isSendAll = false;
@ -1694,8 +1716,6 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
isSendAll = true; isSendAll = true;
} }
final bool coinControl = utxos != null;
if (coinControl && if (coinControl &&
this is CpfpInterface && this is CpfpInterface &&
txData.amount == txData.amount ==
@ -1709,6 +1729,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
isSendAll: isSendAll, isSendAll: isSendAll,
utxos: utxos?.toList(), utxos: utxos?.toList(),
coinControl: coinControl, coinControl: coinControl,
isSendAllCoinControlUtxos: isSendAllCoinControlUtxos,
); );
Logging.instance Logging.instance
@ -1750,8 +1771,6 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
isSendAll = true; isSendAll = true;
} }
final bool coinControl = utxos != null;
final result = await coinSelection( final result = await coinSelection(
txData: txData.copyWith( txData: txData.copyWith(
feeRateAmount: rate, feeRateAmount: rate,
@ -1759,6 +1778,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
isSendAll: isSendAll, isSendAll: isSendAll,
utxos: utxos?.toList(), utxos: utxos?.toList(),
coinControl: coinControl, coinControl: coinControl,
isSendAllCoinControlUtxos: isSendAllCoinControlUtxos,
); );
Logging.instance.log("prepare send: $result", level: LogLevel.Info); Logging.instance.log("prepare send: $result", level: LogLevel.Info);

View file

@ -23,6 +23,7 @@ import '../../isar/models/spark_coin.dart';
import '../../isar/models/wallet_info.dart'; import '../../isar/models/wallet_info.dart';
import '../../models/tx_data.dart'; import '../../models/tx_data.dart';
import '../intermediate/bip39_hd_wallet.dart'; import '../intermediate/bip39_hd_wallet.dart';
import 'cpfp_interface.dart';
import 'electrumx_interface.dart'; import 'electrumx_interface.dart';
const kDefaultSparkIndex = 1; const kDefaultSparkIndex = 1;
@ -1809,36 +1810,44 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
throw Exception("Attempted send of zero amount"); throw Exception("Attempted send of zero amount");
} }
final utxos = txData.utxos;
final bool coinControl = utxos != null;
final utxosTotal = coinControl
? utxos
.map((e) => e.value)
.fold(BigInt.zero, (p, e) => p + BigInt.from(e))
: null;
if (coinControl && utxosTotal! < total) {
throw Exception("Insufficient selected UTXOs!");
}
final isSendAllCoinControlUtxos = coinControl && total == utxosTotal;
final currentHeight = await chainHeight; final currentHeight = await chainHeight;
// coin control not enabled for firo currently so we can ignore this final availableOutputs = utxos?.toList() ??
// final utxosToUse = txData.utxos?.toList() ?? await mainDB.isar.utxos await mainDB.isar.utxos
// .where() .where()
// .walletIdEqualTo(walletId) .walletIdEqualTo(walletId)
// .filter() .filter()
// .isBlockedEqualTo(false) .isBlockedEqualTo(false)
// .and() .and()
// .group((q) => q.usedEqualTo(false).or().usedIsNull()) .group((q) => q.usedEqualTo(false).or().usedIsNull())
// .and() .and()
// .valueGreaterThan(0) .valueGreaterThan(0)
// .findAll(); .findAll();
final spendableUtxos = await mainDB.isar.utxos
.where()
.walletIdEqualTo(walletId)
.filter()
.isBlockedEqualTo(false)
.and()
.group((q) => q.usedEqualTo(false).or().usedIsNull())
.and()
.valueGreaterThan(0)
.findAll();
spendableUtxos.removeWhere( final canCPFP = this is CpfpInterface && coinControl;
(e) => !e.isConfirmed(
currentHeight, final spendableUtxos = availableOutputs
cryptoCurrency.minConfirms, .where(
), (e) =>
); canCPFP ||
e.isConfirmed(currentHeight, cryptoCurrency.minConfirms),
)
.toList();
if (spendableUtxos.isEmpty) { if (spendableUtxos.isEmpty) {
throw Exception("No available UTXOs found to anonymize"); throw Exception("No available UTXOs found to anonymize");
@ -1849,7 +1858,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
.reduce((value, element) => value += element); .reduce((value, element) => value += element);
final bool subtractFeeFromAmount; final bool subtractFeeFromAmount;
if (available < total) { if (isSendAllCoinControlUtxos) {
subtractFeeFromAmount = true;
} else if (available < total) {
throw Exception("Insufficient balance"); throw Exception("Insufficient balance");
} else if (available == total) { } else if (available == total) {
subtractFeeFromAmount = true; subtractFeeFromAmount = true;