From 7b9cd9ec2baf990b007416bc5055a0e1c0020aa5 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Mon, 27 Oct 2025 16:34:49 +0100 Subject: [PATCH] Cleanup and Bug fixes --- cw_bitcoin/lib/payjoin/manager.dart | 40 +++-- cw_nano/lib/nano_wallet.dart | 2 + ios/Podfile.lock | 62 +++---- lib/src/screens/exchange/exchange_page.dart | 18 +- lib/src/screens/send/widgets/send_card.dart | 4 +- .../exchange/exchange_view_model.dart | 31 ++-- lib/view_model/send/send_view_model.dart | 164 +++++++++--------- 7 files changed, 163 insertions(+), 158 deletions(-) diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart index b1886cb4b..ca7e262c1 100644 --- a/cw_bitcoin/lib/payjoin/manager.dart +++ b/cw_bitcoin/lib/payjoin/manager.dart @@ -165,25 +165,33 @@ class PayjoinManager { return initReceiver(address); } - Future initReceiver(String address, [bool isTestnet = false]) async { - final ohttpKeys = await PayjoinUri.fetchOhttpKeys( - ohttpRelay: await randomOhttpRelayUrl(), - payjoinDirectory: payjoinDirectoryUrl, - ); + Future initReceiver(String address, [bool isTestnet = false, int retryCount = 0]) async { + try { + final ohttpKeys = await PayjoinUri.fetchOhttpKeys( + ohttpRelay: await randomOhttpRelayUrl(), + payjoinDirectory: payjoinDirectoryUrl, + ); - final newReceiver = await NewReceiver.create( - address: address, - network: isTestnet ? Network.testnet : Network.bitcoin, - directory: payjoinDirectoryUrl, - ohttpKeys: ohttpKeys, - ); - final persister = PayjoinReceiverPersister.impl(); - final receiverToken = await newReceiver.persist(persister: persister); - final receiver = await Receiver.load(persister: persister, token: receiverToken); + final newReceiver = await NewReceiver.create( + address: address, + network: isTestnet ? Network.testnet : Network.bitcoin, + directory: payjoinDirectoryUrl, + ohttpKeys: ohttpKeys, + ); + final persister = PayjoinReceiverPersister.impl(); + final receiverToken = await newReceiver.persist(persister: persister); + final receiver = await Receiver.load(persister: persister, token: receiverToken); - await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); + await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); - return receiver; + return receiver; + } catch (e) { + if (e.toString().contains("error sending request for url") && retryCount < 5) { + return initReceiver(address, isTestnet, ++retryCount); + } else { + rethrow; + } + } } Future spawnReceiver({ diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index a07612544..cab02921b 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -261,6 +261,8 @@ abstract class NanoWalletBase } } + Future updateTransactionsHistory() async => await updateTransactions(); + Future updateTransactions() async { try { if (_isTransactionUpdating) { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7919129cd..50399668d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -231,46 +231,46 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: - bitbox_flutter: 9505732798041c413152669751beeaecc5fe400f - connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + bitbox_flutter: 506f80b961ddf646b0d80cef9f6eadaab96d91b0 + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6 - cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85 - device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f - device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 - devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8 + cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a + cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 dnssec_proof: d461cac7bd3301eb7447f87936745a0c1ae0a67e - fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3 - file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 + fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc + file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4 - flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a - in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a - sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5 + sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - torch_dart: f4620705d10f05492fab047f2fa1c3a600e7d17d - uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152 - universal_ble: ff19787898040d721109c6324472e5dd4bc86adc - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 + torch_dart: d2cf778332cc6e6a3b362dcf45e4dde52bc34e35 + uni_links: d97da20c7701486ba192624d99bffaaffcfc298a + universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index db0114a87..823706d94 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -629,7 +629,7 @@ class ExchangePage extends BasePage { void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel, GlobalKey key) { - final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency; + final isCurrentTypeWallet = exchangeViewModel.isDepositSameCurrency; key.currentState!.changeSelectedCurrency(currency); key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : ''); @@ -708,22 +708,6 @@ class ExchangePage extends BasePage { isMoneroWallet: exchangeViewModel.isMoneroWallet, currencies: exchangeViewModel.depositCurrencies, onCurrencySelected: (currency) { - // FIXME: need to move it into view model - if (currency == CryptoCurrency.xmr && - exchangeViewModel.wallet.type != WalletType.monero) { - showPopUp( - context: context, - builder: (dialogContext) { - return AlertWithOneAction( - alertTitle: S.of(context).error, - alertContent: S.of(context).exchange_incorrect_current_wallet_for_xmr, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(dialogContext).pop(), - ); - }); - return; - } - exchangeViewModel.changeDepositCurrency(currency: currency); }, currencyButtonColor: Colors.transparent, diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index dee54419f..fed3ebfd2 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -243,7 +243,9 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin + depositCurrency == wallet.currency || + depositCurrency.tag == wallet.currency.tag || + depositCurrency.tag == wallet.currency.title; + bool get isElectrumWallet => [ WalletType.bitcoin, WalletType.litecoin, @@ -412,10 +416,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action void changeDepositCurrency({required CryptoCurrency currency}) { + print("@@@@@@@@"); + print(currency); + print(currency.tag); + print(currency.title); + print(wallet.currency.tag); + print(wallet.currency.title); depositCurrency = currency; isFixedRateMode = false; _onPairChange(); - isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isDepositAddressEnabled = !(isDepositSameCurrency); } @action @@ -423,7 +433,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with receiveCurrency = currency; isFixedRateMode = false; _onPairChange(); - isDepositAddressEnabled = !(depositCurrency == wallet.currency); + isDepositAddressEnabled = !(isDepositSameCurrency); } @action @@ -534,12 +544,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with _providers.map( (element) => element .fetchRate( - from: depositCurrency, - to: receiveCurrency, - amount: amount, - isFixedRateMode: isFixedRateMode, - isReceiveAmount: isFixedRateMode - ) + from: depositCurrency, + to: receiveCurrency, + amount: amount, + isFixedRateMode: isFixedRateMode, + isReceiveAmount: isFixedRateMode) .timeout( Duration(seconds: 7), onTimeout: () => 0.0, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index cc9da2809..e2a0cf2b1 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -730,46 +730,85 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } + Future _handleOcpRequest() async { + if (OpenCryptoPayService.requiresClientCommit(selectedCryptoCurrency)) { + await pendingTransaction!.commit(); + } + + await _ocpService.commitOpenCryptoPayRequest( + pendingTransaction!.hex, + txId: pendingTransaction!.id, + request: ocpRequest!, + asset: selectedCryptoCurrency, + ); + } + + Future _commitApprovalTransaction() async { + if (_pendingApprovalTx != null) { + await _pendingApprovalTx!.commit(); + _pendingApprovalTx = null; + // Small pause to ensure allowance is indexed + await Future.delayed(const Duration(milliseconds: 300)); + } + + await pendingTransaction!.commit(); + _isSwapsXYZCallDataTx = false; + } + + Future _commitUR(BuildContext context) async { + final urstr = await pendingTransaction!.commitUR(); + final result = await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr); + if (result == null) { + throw "Canceled by user"; + } + } + @action Future commitTransaction(BuildContext context) async { if (pendingTransaction == null) { throw Exception("Pending transaction doesn't exist. It should not be happened."); } - if (ocpRequest != null) { + try { state = TransactionCommitting(); - if (OpenCryptoPayService.requiresClientCommit(selectedCryptoCurrency)) { + + if (ocpRequest != null) { + await _handleOcpRequest(); + } else if (_isSwapsXYZCallDataTx) { + // Swaps.xyz approval (if any), then commit the prebuilt router tx + await _commitApprovalTransaction(); + } else if (pendingTransaction!.shouldCommitUR()) { + await _commitUR(context); + } else { await pendingTransaction!.commit(); } - await _ocpService.commitOpenCryptoPayRequest( - pendingTransaction!.hex, - txId: pendingTransaction!.id, - request: ocpRequest!, - asset: selectedCryptoCurrency, - ); - state = TransactionCommitted(); - return; - } - - // Swaps.xyz approval (if any), then commit the prebuilt router tx - if (_isSwapsXYZCallDataTx) { - if (_pendingApprovalTx != null) { - await _pendingApprovalTx!.commit(); - _pendingApprovalTx = null; - // Small pause to ensure allowance is indexed - await Future.delayed(const Duration(milliseconds: 300)); + // Immediate transaction update for EVM chains, Solana, Tron, and Nano + if (isEVMWallet || + [WalletType.solana, WalletType.tron, WalletType.nano].contains(walletType)) { + Future.delayed(Duration(seconds: 4), () async { + try { + await wallet.updateTransactionsHistory(); + } catch (e) { + printV('Failed to update transactions after send: $e'); + } + }); } - await pendingTransaction!.commit(); - _isSwapsXYZCallDataTx = false; - - state = TransactionCommitted(); - return; // skip the regular flow below + if (pendingTransaction!.id.isNotEmpty) { + _addTransactionDescription(); + } + final sharedPreferences = await SharedPreferences.getInstance(); + await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), + DateTime.now().add(Duration(minutes: 1)).toIso8601String()); + } catch (e) { + state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); } + } + Future _addTransactionDescription() async { String address = outputs.fold('', (acc, value) { return value.isParsedAddress ? '$acc${value.address}\n${value.extractedAddress}\n\n' @@ -782,65 +821,26 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor note = note.trim(); - try { - state = TransactionCommitting(); - - if (pendingTransaction!.shouldCommitUR()) { - final urstr = await pendingTransaction!.commitUR(); - final result = - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr); - if (result == null) { - state = FailureState("Canceled by user"); - return; - } - } else { - await pendingTransaction!.commit(); - } - - if (walletType == WalletType.nano) { - nano!.updateTransactions(wallet); - } - - // Immediate transaction update for EVM chains, Solana, and Tron - if (isEVMWallet || walletType == WalletType.solana || walletType == WalletType.tron) { - Future.delayed(Duration(seconds: 4), () async { - try { - await wallet.updateTransactionsHistory(); - } catch (e) { - printV('Failed to update transactions after send: $e'); - } - }); - } - - if (pendingTransaction!.id.isNotEmpty) { - TransactionInfo? tx; - if (walletType == WalletType.monero) { - await Future.delayed(Duration(milliseconds: 450)); - await wallet.fetchTransactions(); - final txhistory = monero!.getTransactionHistory(wallet); - tx = txhistory.transactions.values.last; - } - final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; - _settingsStore.shouldSaveRecipientAddress - ? await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, - recipientAddress: address, - transactionNote: note, - transactionKey: tx?.additionalInfo["key"] as String?, - )) - : await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, - transactionNote: note, - transactionKey: tx?.additionalInfo["key"] as String?, - )); - } - final sharedPreferences = await SharedPreferences.getInstance(); - await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), - DateTime.now().add(Duration(minutes: 1)).toIso8601String()); - state = TransactionCommitted(); - } catch (e) { - state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); + TransactionInfo? tx; + if (walletType == WalletType.monero) { + await Future.delayed(Duration(milliseconds: 450)); + await wallet.fetchTransactions(); + final txhistory = monero!.getTransactionHistory(wallet); + tx = txhistory.transactions.values.last; } + final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; + _settingsStore.shouldSaveRecipientAddress + ? await transactionDescriptionBox.add(TransactionDescription( + id: descriptionKey, + recipientAddress: address, + transactionNote: note, + transactionKey: tx?.additionalInfo["key"] as String?, + )) + : await transactionDescriptionBox.add(TransactionDescription( + id: descriptionKey, + transactionNote: note, + transactionKey: tx?.additionalInfo["key"] as String?, + )); } Object _credentials([ExchangeProvider? provider]) {