warn sp dust (#2402)

* feat: warn sp dust * Update lib/view_model/send/send_view_model.dart * fix condition --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

rafael_xmr committed Jul 29, 2025 at 09:50 UTC 10499814bb9d1f58d78a80b971b5be0bbf299266
3 files changed +46 -8
cw_bitcoin/lib/electrum_wallet.dart
+24 -2
@@ -780,7 +780,11 @@ abstract class ElectrumWalletBase
780 }
781
782 if (outputs.length == 1) {
783 - outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
783 + outputs[0] = BitcoinOutput(
784 + address: outputs.last.address,
785 + value: BigInt.from(amount),
786 + isSilentPayment: hasSilentPayment,
787 + );
788 }
789
790 return EstimatedTxResult(
@@ -888,7 +892,17 @@ abstract class ElectrumWalletBase
892 );
893
894 updatedOutputs.clear();
891 - updatedOutputs.addAll(temp);
895 + for (int i = 0; i < temp.length; i++) {
896 + final output = temp[i];
897 + final oldOutput = outputs[i];
898 +
899 + updatedOutputs.add(BitcoinOutput(
900 + address: output.address,
901 + value: output.value,
902 + isSilentPayment: oldOutput.isSilentPayment,
903 + isChange: output.isChange,
904 + ));
905 + }
906
907 if (fee == 0) {
908 throw BitcoinTransactionNoFeeException();
@@ -1088,6 +1102,14 @@ abstract class ElectrumWalletBase
1102 );
1103 }
1104
1105 + for (final output in updatedOutputs) {
1106 + // TODO: get from server
1107 + // if (output.isSilentPayment && output.value.toInt() > silentPaymentsMin) {
1108 + if (output.isSilentPayment && output.value.toInt() <= 1000) {
1109 + throw BitcoinTransactionNoDustException();
1110 + }
1111 + }
1112 +
1113 if (walletInfo.isHardwareWallet) {
1114 final transaction = await buildHardwareWalletTransaction(
1115 utxos: estimatedTx.utxos,
lib/src/screens/send/widgets/send_card.dart
+1 -1
@@ -258,7 +258,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
258 !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
259 currencyValueValidator: output.sendAll
260 ? sendViewModel.allAmountValidator
261 - : sendViewModel.amountValidator,
261 + : sendViewModel.amountValidator(output),
262 allAmountCallback: () async =>
263 output.setSendAll(await sendViewModel.sendingBalance),
264 ),
lib/view_model/send/send_view_model.dart
+21 -5
@@ -182,9 +182,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
182 if (pendingTransaction != null) {
183 final currency = pendingTransactionFeeCurrency(walletType);
184 final fiat = calculateFiatAmount(
185 - price: _fiatConversationStore.prices[currency]!,
186 - cryptoAmount: pendingTransaction!.feeFormattedValue,
187 - );
185 + price: _fiatConversationStore.prices[currency]!,
186 + cryptoAmount: pendingTransaction!.feeFormattedValue,
187 + );
188 return fiat;
189 } else {
190 return '0.00';
@@ -210,8 +210,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
210
211 CryptoCurrency get currency => wallet.currency;
212
213 - Validator<String> get amountValidator =>
214 - AmountValidator(currency: walletTypeToCryptoCurrency(wallet.type));
213 + Validator<String> amountValidator(Output output) => AmountValidator(
214 + currency: walletTypeToCryptoCurrency(wallet.type),
215 + minValue: isSendToSilentPayments(output)
216 + ?
217 + // TODO: get from server
218 + // bitcoin!.silentPaymentsMinAmount
219 + '0.00001'
220 + : null,
221 + );
222
223 Validator<String> get allAmountValidator => AllAmountValidator();
224
@@ -289,6 +296,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
296 // If silent payments scanning, can still send payments
297 (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
298
299 + bool isSendToSilentPayments(Output output) =>
300 + wallet.type == WalletType.bitcoin &&
301 + (RegExp(AddressValidator.silentPaymentAddressPatternMainnet).hasMatch(output.address) ||
302 + RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
303 + .hasMatch(output.extractedAddress) ||
304 + (output.parsedAddress.addresses.isNotEmpty &&
305 + RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
306 + .hasMatch(output.parsedAddress.addresses[0])));
307 +
308 @computed
309 List<Template> get templates => sendTemplateViewModel.templates
310 .where((template) => _isEqualCurrency(template.cryptoCurrency))