Electrum enhancements (#1794)

* Enhance the code for sending/sending-ALL for Electrum

* remove prints [skip ci]
This commit is contained in:
Omar Hatem 2024-11-08 12:50:21 +02:00 committed by GitHub
parent 0fcfd76afd
commit 389c334f10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 63 additions and 100 deletions

View file

@ -11,7 +11,6 @@ import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
@ -597,8 +596,8 @@ abstract class ElectrumWalletBase
UtxoDetails _createUTXOS({ UtxoDetails _createUTXOS({
required bool sendAll, required bool sendAll,
required int credentialsAmount,
required bool paysToSilentPayment, required bool paysToSilentPayment,
int credentialsAmount = 0,
int? inputsCount, int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) { }) {
@ -732,13 +731,11 @@ abstract class ElectrumWalletBase
List<BitcoinOutput> outputs, List<BitcoinOutput> outputs,
int feeRate, { int feeRate, {
String? memo, String? memo,
int credentialsAmount = 0,
bool hasSilentPayment = false, bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async { }) async {
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
sendAll: true, sendAll: true,
credentialsAmount: credentialsAmount,
paysToSilentPayment: hasSilentPayment, paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom, coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
@ -764,23 +761,11 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee);
} }
if (amount <= 0) {
throw BitcoinTransactionWrongBalanceException();
}
// Attempting to send less than the dust limit // Attempting to send less than the dust limit
if (_isBelowDust(amount)) { if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException(); throw BitcoinTransactionNoDustException();
} }
if (credentialsAmount > 0) {
final amountLeftForFee = amount - credentialsAmount;
if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) {
amount -= amountLeftForFee;
fee += amountLeftForFee;
}
}
if (outputs.length == 1) { if (outputs.length == 1) {
outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
} }
@ -810,6 +795,11 @@ abstract class ElectrumWalletBase
bool hasSilentPayment = false, bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async { }) async {
// Attempting to send less than the dust limit
if (_isBelowDust(credentialsAmount)) {
throw BitcoinTransactionNoDustException();
}
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
sendAll: false, sendAll: false,
credentialsAmount: credentialsAmount, credentialsAmount: credentialsAmount,
@ -894,7 +884,43 @@ abstract class ElectrumWalletBase
final lastOutput = updatedOutputs.last; final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee; final amountLeftForChange = amountLeftForChangeAndFee - fee;
if (!_isBelowDust(amountLeftForChange)) { if (_isBelowDust(amountLeftForChange)) {
// If has change that is lower than dust, will end up with tx rejected by network rules
// so remove the change amount
updatedOutputs.removeLast();
outputs.removeLast();
if (amountLeftForChange < 0) {
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
} else {
throw BitcoinTransactionWrongBalanceException();
}
}
return EstimatedTxResult(
utxos: utxoDetails.utxos,
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
publicKeys: utxoDetails.publicKeys,
fee: fee,
amount: amount,
hasChange: false,
isSendAll: spendingAllCoins,
memo: memo,
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
spendsSilentPayment: utxoDetails.spendsSilentPayment,
);
} else {
// Here, lastOutput already is change, return the amount left without the fee to the user's address. // Here, lastOutput already is change, return the amount left without the fee to the user's address.
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput( updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
address: lastOutput.address, address: lastOutput.address,
@ -908,75 +934,6 @@ abstract class ElectrumWalletBase
isSilentPayment: lastOutput.isSilentPayment, isSilentPayment: lastOutput.isSilentPayment,
isChange: true, isChange: true,
); );
} else {
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
updatedOutputs.removeLast();
outputs.removeLast();
// Still has inputs to spend before failing
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
hasSilentPayment: hasSilentPayment,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
final estimatedSendAll = await estimateSendAllTx(
updatedOutputs,
feeRate,
memo: memo,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
if (estimatedSendAll.amount == credentialsAmount) {
return estimatedSendAll;
}
// Estimate to user how much is needed to send to cover the fee
final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1;
throw BitcoinTransactionNoDustOnChangeException(
bitcoinAmountToString(amount: maxAmountWithReturningChange),
bitcoinAmountToString(amount: estimatedSendAll.amount),
);
}
// Attempting to send less than the dust limit
if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException();
}
final totalAmount = amount + fee;
if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
throw BitcoinTransactionWrongBalanceException();
}
if (totalAmount > utxoDetails.allInputsAmount) {
if (spendingAllCoins) {
throw BitcoinTransactionWrongBalanceException();
} else {
updatedOutputs.removeLast();
outputs.removeLast();
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
}
return EstimatedTxResult( return EstimatedTxResult(
utxos: utxoDetails.utxos, utxos: utxoDetails.utxos,
@ -985,12 +942,13 @@ abstract class ElectrumWalletBase
fee: fee, fee: fee,
amount: amount, amount: amount,
hasChange: true, hasChange: true,
isSendAll: false, isSendAll: spendingAllCoins,
memo: memo, memo: memo,
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
spendsSilentPayment: utxoDetails.spendsSilentPayment, spendsSilentPayment: utxoDetails.spendsSilentPayment,
); );
} }
}
Future<int> calcFee({ Future<int> calcFee({
required List<UtxoWithAddress> utxos, required List<UtxoWithAddress> utxos,
@ -1080,15 +1038,20 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!); : feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx; EstimatedTxResult estimatedTx;
final updatedOutputs = final updatedOutputs = outputs
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList(); .map((e) => BitcoinOutput(
address: e.address,
value: e.value,
isSilentPayment: e.isSilentPayment,
isChange: e.isChange,
))
.toList();
if (sendAll) { if (sendAll) {
estimatedTx = await estimateSendAllTx( estimatedTx = await estimateSendAllTx(
updatedOutputs, updatedOutputs,
feeRateInt, feeRateInt,
memo: memo, memo: memo,
credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom, coinTypeToSpendFrom: coinTypeToSpendFrom,
); );

View file

@ -568,10 +568,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: ledger_flutter_plus name: ledger_flutter_plus
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42 sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.5" version: "1.4.1"
ledger_litecoin: ledger_litecoin:
dependency: "direct main" dependency: "direct main"
description: description: