From 459f0d352d92150a3b6b96cf94106d060ebd92dd Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 5 Nov 2024 11:33:24 +0200 Subject: [PATCH 1/2] Cw 805 fix bch refund address (#1782) * fix bch address format for exolix, stealth, thorchain, simpleswap * exclude SP and MWEB from the exchange address book * address pr review comments --- lib/core/address_validator.dart | 3 +-- .../provider/exolix_exchange_provider.dart | 7 +++++-- .../provider/simpleswap_exchange_provider.dart | 7 +++++-- .../provider/stealth_ex_exchange_provider.dart | 7 +++++-- .../provider/thorchain_exchange.provider.dart | 11 ++++++----- .../contact_list/contact_list_view_model.dart | 15 ++++++++++----- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 21726fab8..9ca8a41ad 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -106,8 +106,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.wow: pattern = '[0-9a-zA-Z]+'; case CryptoCurrency.bch: - pattern = - '(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}'; + pattern = '^(bitcoincash:)?(q|p)[0-9a-zA-Z]{41,42}'; case CryptoCurrency.bnb: pattern = '[0-9a-zA-Z]+'; case CryptoCurrency.hbar: diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 8f2d5c241..5eeb6f9cf 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -141,8 +141,8 @@ class ExolixExchangeProvider extends ExchangeProvider { 'coinTo': _normalizeCurrency(request.toCurrency), 'networkFrom': _networkFor(request.fromCurrency), 'networkTo': _networkFor(request.toCurrency), - 'withdrawalAddress': request.toAddress, - 'refundAddress': request.refundAddress, + 'withdrawalAddress': _normalizeAddress(request.toAddress), + 'refundAddress': _normalizeAddress(request.refundAddress), 'rateType': _getRateType(isFixedRateMode), 'apiToken': apiKey, }; @@ -275,4 +275,7 @@ class ExolixExchangeProvider extends ExchangeProvider { return tag; } } + + String _normalizeAddress(String address) => + address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address; } diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index be52b73fe..2a72ac793 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -129,8 +129,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { "currency_to": _normalizeCurrency(request.toCurrency), "amount": request.fromAmount, "fixed": isFixedRateMode, - "user_refund_address": request.refundAddress, - "address_to": request.toAddress + "user_refund_address": _normalizeAddress(request.refundAddress), + "address_to": _normalizeAddress(request.toAddress) }; final uri = Uri.https(apiAuthority, createExchangePath, params); @@ -243,4 +243,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { return currency.title.toLowerCase(); } } + + String _normalizeAddress(String address) => + address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address; } diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart index 601735595..123f759ef 100644 --- a/lib/exchange/provider/stealth_ex_exchange_provider.dart +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -129,8 +129,8 @@ class StealthExExchangeProvider extends ExchangeProvider { if (isFixedRateMode) 'rate_id': rateId, 'amount': isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount), - 'address': request.toAddress, - 'refund_address': request.refundAddress, + 'address': _normalizeAddress(request.toAddress), + 'refund_address': _normalizeAddress(request.refundAddress), 'additional_fee_percent': _additionalFeePercent, }; @@ -296,4 +296,7 @@ class StealthExExchangeProvider extends ExchangeProvider { return currency.tag!.toLowerCase(); } + + String _normalizeAddress(String address) => + address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address; } diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart index 897c2fdb9..99b9dcf9f 100644 --- a/lib/exchange/provider/thorchain_exchange.provider.dart +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -116,9 +116,7 @@ class ThorChainExchangeProvider extends ExchangeProvider { required bool isFixedRateMode, required bool isSendAll, }) async { - String formattedToAddress = request.toAddress.startsWith('bitcoincash:') - ? request.toAddress.replaceFirst('bitcoincash:', '') - : request.toAddress; + final formattedFromAmount = double.parse(request.fromAmount); @@ -126,11 +124,11 @@ class ThorChainExchangeProvider extends ExchangeProvider { 'from_asset': _normalizeCurrency(request.fromCurrency), 'to_asset': _normalizeCurrency(request.toCurrency), 'amount': _doubleToThorChainString(formattedFromAmount), - 'destination': formattedToAddress, + 'destination': _normalizeAddress(request.toAddress), 'affiliate': _affiliateName, 'affiliate_bps': _affiliateBps, 'refund_address': - isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '', + isRefundAddressSupported.contains(request.fromCurrency) ? _normalizeAddress(request.refundAddress) : '', }; final responseJSON = await _getSwapQuote(params); @@ -288,4 +286,7 @@ class ThorChainExchangeProvider extends ExchangeProvider { return currentState; } + + String _normalizeAddress(String address) => + address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address; } diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 4089d988b..89192583e 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -99,18 +99,23 @@ abstract class ContactListViewModelBase with Store { Future delete(ContactRecord contact) async => contact.original.delete(); - @computed - List get contactsToShow => - contacts.where((element) => _isValidForCurrency(element)).toList(); + ObservableList get contactsToShow => + ObservableList.of(contacts.where((element) => _isValidForCurrency(element))); @computed List get walletContactsToShow => walletContacts.where((element) => _isValidForCurrency(element)).toList(); bool _isValidForCurrency(ContactBase element) { + if (element.name.contains('Silent Payments')) return false; + if (element.name.contains('MWEB')) return false; + return _currency == null || element.type == _currency || - element.type.title == _currency!.tag || - element.type.tag == _currency!.tag; + (element.type.tag != null && + _currency?.tag != null && + element.type.tag == _currency?.tag) || + _currency?.toString() == element.type.tag || + _currency?.tag == element.type.toString(); } } From 28804b8ff2cf628b13e98a032c37105f052625d0 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Wed, 6 Nov 2024 18:23:05 +0200 Subject: [PATCH 2/2] Improve sending tx for electrum (#1790) * Enhance the code for sending/sending-ALL for Electrum * remove print statements [skip ci] * update bitcoin base and minor reformatting --- cw_bitcoin/lib/electrum_wallet.dart | 157 +++++++++-------------- ios/Podfile.lock | 18 +-- lib/view_model/send/send_view_model.dart | 18 +-- 3 files changed, 77 insertions(+), 116 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 47d69d670..fceaf5d12 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -439,8 +439,8 @@ abstract class ElectrumWalletBase TxCreateUtxoDetails _createUTXOS({ required bool sendAll, - required int credentialsAmount, required bool paysToSilentPayment, + int credentialsAmount = 0, int? inputsCount, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) { @@ -574,13 +574,11 @@ abstract class ElectrumWalletBase List outputs, int feeRate, { String? memo, - int credentialsAmount = 0, bool hasSilentPayment = false, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { final utxoDetails = _createUTXOS( sendAll: true, - credentialsAmount: credentialsAmount, paysToSilentPayment: hasSilentPayment, coinTypeToSpendFrom: coinTypeToSpendFrom, ); @@ -603,23 +601,11 @@ abstract class ElectrumWalletBase throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee); } - if (amount <= 0) { - throw BitcoinTransactionWrongBalanceException(); - } - // Attempting to send less than the dust limit if (_isBelowDust(amount)) { throw BitcoinTransactionNoDustException(); } - if (credentialsAmount > 0) { - final amountLeftForFee = amount - credentialsAmount; - if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) { - amount -= amountLeftForFee; - fee += amountLeftForFee; - } - } - if (outputs.length == 1) { outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); } @@ -649,6 +635,11 @@ abstract class ElectrumWalletBase bool hasSilentPayment = false, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, }) async { + // Attempting to send less than the dust limit + if (_isBelowDust(credentialsAmount)) { + throw BitcoinTransactionNoDustException(); + } + final utxoDetails = _createUTXOS( sendAll: false, credentialsAmount: credentialsAmount, @@ -726,7 +717,43 @@ abstract class ElectrumWalletBase final lastOutput = updatedOutputs.last; 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. updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput( address: lastOutput.address, @@ -740,88 +767,21 @@ abstract class ElectrumWalletBase isSilentPayment: lastOutput.isSilentPayment, 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, + return EstimatedTxResult( + utxos: utxoDetails.utxos, + inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, + publicKeys: utxoDetails.publicKeys, + fee: fee, + amount: amount, + hasChange: true, + isSendAll: spendingAllCoins, memo: memo, + spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, + spendsSilentPayment: utxoDetails.spendsSilentPayment, 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( - BitcoinAmountUtils.bitcoinAmountToString(amount: maxAmountWithReturningChange), - BitcoinAmountUtils.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( - utxos: utxoDetails.utxos, - inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos, - publicKeys: utxoDetails.publicKeys, - fee: fee, - amount: amount, - hasChange: true, - isSendAll: false, - memo: memo, - spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX, - spendsSilentPayment: utxoDetails.spendsSilentPayment, - ); } Future calcFee({ @@ -895,15 +855,20 @@ abstract class ElectrumWalletBase : feeRate(transactionCredentials.priority!); EstimatedTxResult estimatedTx; - final updatedOutputs = - outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList(); + final updatedOutputs = outputs + .map((e) => BitcoinOutput( + address: e.address, + value: e.value, + isSilentPayment: e.isSilentPayment, + isChange: e.isChange, + )) + .toList(); if (sendAll) { estimatedTx = await estimateSendAllTx( updatedOutputs, feeRateInt, memo: memo, - credentialsAmount: credentialsAmount, hasSilentPayment: hasSilentPayment, coinTypeToSpendFrom: coinTypeToSpendFrom, ); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 322ef6f86..a254446ee 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -109,12 +109,7 @@ PODS: - FlutterMacOS - permission_handler_apple (9.1.1): - Flutter - - Protobuf (3.27.2) - ReachabilitySwift (5.2.3) - - reactive_ble_mobile (0.0.1): - - Flutter - - Protobuf (~> 3.5) - - SwiftProtobuf (~> 1.0) - SDWebImage (5.19.4): - SDWebImage/Core (= 5.19.4) - SDWebImage/Core (5.19.4) @@ -132,6 +127,9 @@ PODS: - Toast (4.1.1) - uni_links (0.0.1): - Flutter + - universal_ble (0.0.1): + - Flutter + - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter - wakelock_plus (0.0.1): @@ -161,12 +159,12 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) + - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - workmanager (from `.symlinks/plugins/workmanager/ios`) @@ -178,7 +176,6 @@ SPEC REPOS: - DKPhotoGallery - MTBBarcodeScanner - OrderedSet - - Protobuf - ReachabilitySwift - SDWebImage - SwiftProtobuf @@ -226,8 +223,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - reactive_ble_mobile: - :path: ".symlinks/plugins/reactive_ble_mobile/ios" sensitive_clipboard: :path: ".symlinks/plugins/sensitive_clipboard/ios" share_plus: @@ -238,6 +233,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sp_scanner/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" + universal_ble: + :path: ".symlinks/plugins/universal_ble/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" wakelock_plus: @@ -271,9 +268,7 @@ SPEC CHECKSUMS: package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2 ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 - reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad @@ -283,6 +278,7 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e uni_links: d97da20c7701486ba192624d99bffaaffcfc298a + universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 1403eb109..6ed9249c9 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -128,7 +128,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor if (walletType == WalletType.ethereum && selectedCryptoCurrency == CryptoCurrency.eth) return false; - if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.matic) + if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.matic) return false; return true; @@ -416,7 +416,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor // // state = FailureState(errorMsg); // } else { - state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); + state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); // } } return null; @@ -482,18 +482,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor nano!.updateTransactions(wallet); } - if (pendingTransaction!.id.isNotEmpty) { - final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, - recipientAddress: address, - transactionNote: note)) + id: descriptionKey, + recipientAddress: address, + transactionNote: note, + )) : await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, - transactionNote: note)); + id: descriptionKey, + transactionNote: note, + )); } state = TransactionCommitted();