CW-1200-Dust-amount-error-when-sending-All (#2495)

* Add isSendAll parameter to exchange provider trade creation

* Improve send-all transaction handling and fee calculation

change.
This commit is contained in:
Serhii
2025-09-05 05:26:53 +03:00
committed by GitHub
parent 4b7a89d397
commit 2d52d4585f
5 changed files with 104 additions and 1 deletions
+100
View File
@@ -851,6 +851,39 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionNoDustException();
}
// If there is only one output, and the amount to send is more than the max spendable amount
// then it is actually a send all transaction
if (outputs.length == 1) {
final maxSpendable = await _maxSpendableNoChangeAmount(
initialOutput: outputs.first,
feeRate: feeRate,
memo: memo,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
if (credentialsAmount > maxSpendable) {
throw BitcoinTransactionWrongBalanceException();
}
if (credentialsAmount >= maxSpendable) {
final estimateOutput = [
BitcoinOutput(
address: outputs.first.address,
value: BigInt.zero,
isSilentPayment: outputs.first.isSilentPayment,
)
];
return estimateSendAllTx(
estimateOutput,
feeRate,
memo: memo,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
}
final utxoDetails = _createUTXOS(
sendAll: false,
credentialsAmount: credentialsAmount,
@@ -952,7 +985,38 @@ abstract class ElectrumWalletBase
updatedOutputs.removeLast();
outputs.removeLast();
// If the computed change is negative or below dust:
// - negative: try a no-change tx (recalculate fee without change)
// - non-negative but dust: drop change and add remainder to fee
if (amountLeftForChange < 0) {
final tempNoChange = outputs.map((o) => o).toList();
final feeNoChange = await calcFee(
utxos: utxoDetails.utxos,
outputs: tempNoChange,
network: network,
memo: memo,
feeRate: feeRate,
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
vinOutpoints: utxoDetails.vinOutpoints,
);
final leftover = utxoDetails.allInputsAmount - credentialsAmount - feeNoChange;
if (leftover >= 0) {
final finalFee = feeNoChange + leftover; // absorb tiny remainder
return EstimatedTxResult(
utxos: utxoDetails.utxos,
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
publicKeys: utxoDetails.publicKeys,
fee: finalFee,
amount: amount,
hasChange: false,
isSendAll: spendingAllCoins,
memo: memo,
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
spendsSilentPayment: utxoDetails.spendsSilentPayment,
);
}
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
@@ -1016,6 +1080,42 @@ abstract class ElectrumWalletBase
}
}
Future<int> _maxSpendableNoChangeAmount({
required BitcoinOutput initialOutput,
required int feeRate,
String? memo,
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
final utxoDetailsAll = _createUTXOS(
sendAll: true,
paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
final output = [
BitcoinOutput(
address: initialOutput.address,
value: BigInt.zero,
isSilentPayment: initialOutput.isSilentPayment,
)
];
final feeNoChange = await calcFee(
utxos: utxoDetailsAll.utxos,
outputs: output,
network: network,
memo: memo,
feeRate: feeRate,
inputPrivKeyInfos: utxoDetailsAll.inputPrivKeyInfos,
vinOutpoints: utxoDetailsAll.vinOutpoints,
);
final maxSpendable = utxoDetailsAll.allInputsAmount - feeNoChange;
return maxSpendable > 0 ? maxSpendable : 0;
}
Future<int> calcFee({
required List<UtxoWithAddress> utxos,
required List<BitcoinBaseOutput> outputs,
@@ -213,6 +213,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e) {
log(e.toString());
@@ -200,6 +200,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e) {
log(e.toString());
@@ -260,6 +260,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e) {
printV(e.toString());
+1 -1
View File
@@ -119,7 +119,7 @@ class Trade extends HiveObject {
bool? isRefund;
@HiveField(21)
bool? isSendAll;
bool? isSendAll; /// Must be set on createTrade;
@HiveField(22)
String? router;