What changed, and why it matters
This commit changes how transaction fees are calculated for a specific type of Litecoin transaction called 'mwebPegIn' (Mimblewimble peg-in). Previously, the fee was estimated once and the transaction was built. Now, for mwebPegIn transactions, it loops: builds the transaction, checks the actual fee based on the final transaction size, and rebuilds if the fee is too low. This is a bug fix to ensure users pay the correct network fee and their transactions don't get stuck or fail. There is no clear security vulnerability here; it appears to be a correctness/reliability improvement.
No immediate security action required. This appears to be a routine bug fix for fee estimation accuracy. If reviewing for release, verify that the loop termination is guaranteed (fee increases monotonically and balance check prevents infinite loop) and that the `requiredFee` calculation correctly handles edge cases where `feeForFinalVSize` equals `vSize`.
Security signals we found
Transaction fee recalculation after signing to prevent underpayment
Loop bounded by fee convergence; throws exception if amount becomes negative
MWEB peg-in specific handling suggests prior fee estimation bug
No input validation changes, no cryptographic changes, no network trust changes
Evidence from the diff
The patch modifies electrumx_interface.dart in the transaction building path. It introduces a while (true) loop specifically for TxType.mwebPegIn transactions. The loop builds the transaction, then recalculates the required fee from the final virtual size (vSize) after signing, because signing can change the transaction size. If the initially estimated fee (feeForOneOutput) is insufficient compared to requiredFee, it updates feeForOneOutput and rebuilds. For non-mwebPegIn transactions, the original single-pass logic is preserved. The change addresses fee estimation accuracy for MWEB peg-in transactions, which may have had size/fee mismatches between estimation and final signed transaction.
Changed components
lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dartMWEB peg-in transaction constructionElectrumX-based wallet transaction fee estimationInspect captured patch +53 / −14
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
index 8c1c801..100aa9f 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart
@@ -569,23 +569,62 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
feeForOneOutput = overrideFeeAmount;
}
- final satoshiAmountToSend = satoshisBeingUsed - feeForOneOutput;
+ late TxData data;
+ if (txData.type == TxType.mwebPegIn) {
+ while (true) {
+ final satoshiAmountToSend = satoshisBeingUsed - feeForOneOutput;
+ if (satoshiAmountToSend.isNegative) {
+ throw Exception(
+ "Estimated fee ($feeForOneOutput sats) is greater than balance!",
+ );
+ }
- if (satoshiAmountToSend.isNegative) {
- throw Exception(
- "Estimated fee ($feeForOneOutput sats) is greater than balance!",
- );
- }
+ data = await buildTransaction(
+ txData: txData.copyWith(
+ recipients: await helperRecipientsConvert(
+ [recipientAddress],
+ [satoshiAmountToSend],
+ ),
+ ),
+ inputsWithKeys: inputsWithKeys,
+ );
- final data = await buildTransaction(
- txData: txData.copyWith(
- recipients: await helperRecipientsConvert(
- [recipientAddress],
- [satoshiAmountToSend],
+ if (overrideFeeAmount != null) {
+ break;
+ }
+
+ // Signing can change vSize, so calculate the fee from the final tx.
+ final vSize = BigInt.from(data.vSize!);
+ final feeForFinalVSize = BigInt.from(
+ satsPerVByte != null
+ ? satsPerVByte * data.vSize!
+ : estimateTxFee(vSize: data.vSize!, feeRatePerKB: feeRatePerKB),
+ );
+ final requiredFee = feeForFinalVSize > vSize ? feeForFinalVSize : vSize;
+ if (feeForOneOutput >= requiredFee) {
+ break;
+ }
+ feeForOneOutput = requiredFee;
+ }
+ } else {
+ final satoshiAmountToSend = satoshisBeingUsed - feeForOneOutput;
+
+ if (satoshiAmountToSend.isNegative) {
+ throw Exception(
+ "Estimated fee ($feeForOneOutput sats) is greater than balance!",
+ );
+ }
+
+ data = await buildTransaction(
+ txData: txData.copyWith(
+ recipients: await helperRecipientsConvert(
+ [recipientAddress],
+ [satoshiAmountToSend],
+ ),
),
- ),
- inputsWithKeys: inputsWithKeys,
- );
+ inputsWithKeys: inputsWithKeys,
+ );
+ }
return data.copyWith(
fee: Amount(
Why this scored 22/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.