Electrum enhancements (#1794)

* Enhance the code for sending/sending-ALL for Electrum * remove prints [skip ci]

Omar Hatem committed Nov 8, 2024 at 12:50 UTC 389c334f1076779a134cd037b2af5b1b1c11df2b
2 files changed +63 -100
cw_bitcoin/lib/electrum_wallet.dart
+61 -98
@@ -11,7 +11,6 @@ import 'package:blockchain_utils/blockchain_utils.dart';
11 import 'package:collection/collection.dart';
12 import 'package:cw_bitcoin/address_from_output.dart';
13 import 'package:cw_bitcoin/bitcoin_address_record.dart';
14 -import 'package:cw_bitcoin/bitcoin_amount_format.dart';
14 import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
15 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
16 import 'package:cw_bitcoin/bitcoin_unspent.dart';
@@ -597,8 +596,8 @@ abstract class ElectrumWalletBase
596
597 UtxoDetails _createUTXOS({
598 required bool sendAll,
600 - required int credentialsAmount,
599 required bool paysToSilentPayment,
600 + int credentialsAmount = 0,
601 int? inputsCount,
602 UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
603 }) {
@@ -732,13 +731,11 @@ abstract class ElectrumWalletBase
731 List<BitcoinOutput> outputs,
732 int feeRate, {
733 String? memo,
735 - int credentialsAmount = 0,
734 bool hasSilentPayment = false,
735 UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
736 }) async {
737 final utxoDetails = _createUTXOS(
738 sendAll: true,
741 - credentialsAmount: credentialsAmount,
739 paysToSilentPayment: hasSilentPayment,
740 coinTypeToSpendFrom: coinTypeToSpendFrom,
741 );
@@ -764,23 +761,11 @@ abstract class ElectrumWalletBase
761 throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee);
762 }
763
767 - if (amount <= 0) {
768 - throw BitcoinTransactionWrongBalanceException();
769 - }
770 -
764 // Attempting to send less than the dust limit
765 if (_isBelowDust(amount)) {
766 throw BitcoinTransactionNoDustException();
767 }
768
776 - if (credentialsAmount > 0) {
777 - final amountLeftForFee = amount - credentialsAmount;
778 - if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) {
779 - amount -= amountLeftForFee;
780 - fee += amountLeftForFee;
781 - }
782 - }
783 -
769 if (outputs.length == 1) {
770 outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
771 }
@@ -810,6 +795,11 @@ abstract class ElectrumWalletBase
795 bool hasSilentPayment = false,
796 UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
797 }) async {
798 + // Attempting to send less than the dust limit
799 + if (_isBelowDust(credentialsAmount)) {
800 + throw BitcoinTransactionNoDustException();
801 + }
802 +
803 final utxoDetails = _createUTXOS(
804 sendAll: false,
805 credentialsAmount: credentialsAmount,
@@ -894,7 +884,43 @@ abstract class ElectrumWalletBase
884 final lastOutput = updatedOutputs.last;
885 final amountLeftForChange = amountLeftForChangeAndFee - fee;
886
897 - if (!_isBelowDust(amountLeftForChange)) {
887 + if (_isBelowDust(amountLeftForChange)) {
888 + // If has change that is lower than dust, will end up with tx rejected by network rules
889 + // so remove the change amount
890 + updatedOutputs.removeLast();
891 + outputs.removeLast();
892 +
893 + if (amountLeftForChange < 0) {
894 + if (!spendingAllCoins) {
895 + return estimateTxForAmount(
896 + credentialsAmount,
897 + outputs,
898 + updatedOutputs,
899 + feeRate,
900 + inputsCount: utxoDetails.utxos.length + 1,
901 + memo: memo,
902 + useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
903 + hasSilentPayment: hasSilentPayment,
904 + coinTypeToSpendFrom: coinTypeToSpendFrom,
905 + );
906 + } else {
907 + throw BitcoinTransactionWrongBalanceException();
908 + }
909 + }
910 +
911 + return EstimatedTxResult(
912 + utxos: utxoDetails.utxos,
913 + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
914 + publicKeys: utxoDetails.publicKeys,
915 + fee: fee,
916 + amount: amount,
917 + hasChange: false,
918 + isSendAll: spendingAllCoins,
919 + memo: memo,
920 + spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
921 + spendsSilentPayment: utxoDetails.spendsSilentPayment,
922 + );
923 + } else {
924 // Here, lastOutput already is change, return the amount left without the fee to the user's address.
925 updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
926 address: lastOutput.address,
@@ -908,88 +934,20 @@ abstract class ElectrumWalletBase
934 isSilentPayment: lastOutput.isSilentPayment,
935 isChange: true,
936 );
911 - } else {
912 - // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
913 - updatedOutputs.removeLast();
914 - outputs.removeLast();
915 -
916 - // Still has inputs to spend before failing
917 - if (!spendingAllCoins) {
918 - return estimateTxForAmount(
919 - credentialsAmount,
920 - outputs,
921 - updatedOutputs,
922 - feeRate,
923 - inputsCount: utxoDetails.utxos.length + 1,
924 - memo: memo,
925 - hasSilentPayment: hasSilentPayment,
926 - useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
927 - coinTypeToSpendFrom: coinTypeToSpendFrom,
928 - );
929 - }
937
931 - final estimatedSendAll = await estimateSendAllTx(
932 - updatedOutputs,
933 - feeRate,
938 + return EstimatedTxResult(
939 + utxos: utxoDetails.utxos,
940 + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
941 + publicKeys: utxoDetails.publicKeys,
942 + fee: fee,
943 + amount: amount,
944 + hasChange: true,
945 + isSendAll: spendingAllCoins,
946 memo: memo,
935 - coinTypeToSpendFrom: coinTypeToSpendFrom,
936 - );
937 -
938 - if (estimatedSendAll.amount == credentialsAmount) {
939 - return estimatedSendAll;
940 - }
941 -
942 - // Estimate to user how much is needed to send to cover the fee
943 - final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1;
944 - throw BitcoinTransactionNoDustOnChangeException(
945 - bitcoinAmountToString(amount: maxAmountWithReturningChange),
946 - bitcoinAmountToString(amount: estimatedSendAll.amount),
947 + spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
948 + spendsSilentPayment: utxoDetails.spendsSilentPayment,
949 );
950 }
949 -
950 - // Attempting to send less than the dust limit
951 - if (_isBelowDust(amount)) {
952 - throw BitcoinTransactionNoDustException();
953 - }
954 -
955 - final totalAmount = amount + fee;
956 -
957 - if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
958 - throw BitcoinTransactionWrongBalanceException();
959 - }
960 -
961 - if (totalAmount > utxoDetails.allInputsAmount) {
962 - if (spendingAllCoins) {
963 - throw BitcoinTransactionWrongBalanceException();
964 - } else {
965 - updatedOutputs.removeLast();
966 - outputs.removeLast();
967 - return estimateTxForAmount(
968 - credentialsAmount,
969 - outputs,
970 - updatedOutputs,
971 - feeRate,
972 - inputsCount: utxoDetails.utxos.length + 1,
973 - memo: memo,
974 - useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
975 - hasSilentPayment: hasSilentPayment,
976 - coinTypeToSpendFrom: coinTypeToSpendFrom,
977 - );
978 - }
979 - }
980 -
981 - return EstimatedTxResult(
982 - utxos: utxoDetails.utxos,
983 - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
984 - publicKeys: utxoDetails.publicKeys,
985 - fee: fee,
986 - amount: amount,
987 - hasChange: true,
988 - isSendAll: false,
989 - memo: memo,
990 - spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
991 - spendsSilentPayment: utxoDetails.spendsSilentPayment,
992 - );
951 }
952
953 Future<int> calcFee({
@@ -1080,15 +1038,20 @@ abstract class ElectrumWalletBase
1038 : feeRate(transactionCredentials.priority!);
1039
1040 EstimatedTxResult estimatedTx;
1083 - final updatedOutputs =
1084 - outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
1041 + final updatedOutputs = outputs
1042 + .map((e) => BitcoinOutput(
1043 + address: e.address,
1044 + value: e.value,
1045 + isSilentPayment: e.isSilentPayment,
1046 + isChange: e.isChange,
1047 + ))
1048 + .toList();
1049
1050 if (sendAll) {
1051 estimatedTx = await estimateSendAllTx(
1052 updatedOutputs,
1053 feeRateInt,
1054 memo: memo,
1091 - credentialsAmount: credentialsAmount,
1055 hasSilentPayment: hasSilentPayment,
1056 coinTypeToSpendFrom: coinTypeToSpendFrom,
1057 );
cw_bitcoin/pubspec.lock
+2 -2
@@ -568,10 +568,10 @@ packages:
568 dependency: "direct main"
569 description:
570 name: ledger_flutter_plus
571 - sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
571 + sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
572 url: "https://pub.dev"
573 source: hosted
574 - version: "1.2.5"
574 + version: "1.4.1"
575 ledger_litecoin:
576 dependency: "direct main"
577 description: