Cleanup and Bug fixes

OmarHatem committed Oct 27, 2025 at 16:34 UTC 7b9cd9ec2baf990b007416bc5055a0e1c0020aa5
7 files changed +158 -153
cw_bitcoin/lib/payjoin/manager.dart
+24 -16
@@ -165,25 +165,33 @@ class PayjoinManager {
165 return initReceiver(address);
166 }
167
168 - Future<Receiver> initReceiver(String address, [bool isTestnet = false]) async {
169 - final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
170 - ohttpRelay: await randomOhttpRelayUrl(),
171 - payjoinDirectory: payjoinDirectoryUrl,
172 - );
168 + Future<Receiver> initReceiver(String address, [bool isTestnet = false, int retryCount = 0]) async {
169 + try {
170 + final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
171 + ohttpRelay: await randomOhttpRelayUrl(),
172 + payjoinDirectory: payjoinDirectoryUrl,
173 + );
174
174 - final newReceiver = await NewReceiver.create(
175 - address: address,
176 - network: isTestnet ? Network.testnet : Network.bitcoin,
177 - directory: payjoinDirectoryUrl,
178 - ohttpKeys: ohttpKeys,
179 - );
180 - final persister = PayjoinReceiverPersister.impl();
181 - final receiverToken = await newReceiver.persist(persister: persister);
182 - final receiver = await Receiver.load(persister: persister, token: receiverToken);
175 + final newReceiver = await NewReceiver.create(
176 + address: address,
177 + network: isTestnet ? Network.testnet : Network.bitcoin,
178 + directory: payjoinDirectoryUrl,
179 + ohttpKeys: ohttpKeys,
180 + );
181 + final persister = PayjoinReceiverPersister.impl();
182 + final receiverToken = await newReceiver.persist(persister: persister);
183 + final receiver = await Receiver.load(persister: persister, token: receiverToken);
184
184 - await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
185 + await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
186
186 - return receiver;
187 + return receiver;
188 + } catch (e) {
189 + if (e.toString().contains("error sending request for url") && retryCount < 5) {
190 + return initReceiver(address, isTestnet, ++retryCount);
191 + } else {
192 + rethrow;
193 + }
194 + }
195 }
196
197 Future<void> spawnReceiver({
cw_nano/lib/nano_wallet.dart
+2
@@ -261,6 +261,8 @@ abstract class NanoWalletBase
261 }
262 }
263
264 + Future<void> updateTransactionsHistory() async => await updateTransactions();
265 +
266 Future<bool> updateTransactions() async {
267 try {
268 if (_isTransactionUpdating) {
ios/Podfile.lock
+31 -31
@@ -231,46 +231,46 @@ EXTERNAL SOURCES:
231 :path: ".symlinks/plugins/wakelock_plus/ios"
232
233 SPEC CHECKSUMS:
234 - bitbox_flutter: 9505732798041c413152669751beeaecc5fe400f
235 - connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
234 + bitbox_flutter: 506f80b961ddf646b0d80cef9f6eadaab96d91b0
235 + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
236 CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90
237 - cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6
238 - cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85
239 - device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f
240 - device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
241 - devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8
237 + cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a
238 + cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3
239 + device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
240 + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
241 + devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
242 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
243 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
244 dnssec_proof: d461cac7bd3301eb7447f87936745a0c1ae0a67e
245 - fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3
246 - file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
245 + fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc
246 + file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
247 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
248 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
249 - flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f
250 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
251 - flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4
252 - flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
253 - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
254 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
255 - in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
256 - integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
248 + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
249 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
250 + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
251 + flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
252 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
253 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
254 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
255 + in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc
256 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
257 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
258 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
259 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
258 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
259 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
260 payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
261 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
262 - reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
261 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
262 + reown_yttrium: c0e87e5965fa60a3559564cc35cffbba22976089
263 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
264 - sensitive_clipboard: 161e9abc3d56b3131309d8a321eb4690a803c16b
265 - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
266 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
267 - sp_scanner: b1bc9321690980bdb44bba7ec85d5543e716d1b5
264 + sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
265 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
266 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
267 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
268 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
269 - torch_dart: f4620705d10f05492fab047f2fa1c3a600e7d17d
270 - uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152
271 - universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
272 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
273 - wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
269 + torch_dart: d2cf778332cc6e6a3b362dcf45e4dde52bc34e35
270 + uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
271 + universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
272 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
273 + wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
274 YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
275
276 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
lib/src/screens/exchange/exchange_page.dart
+1 -17
@@ -629,7 +629,7 @@ class ExchangePage extends BasePage {
629
630 void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel,
631 GlobalKey<ExchangeCardState> key) {
632 - final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
632 + final isCurrentTypeWallet = exchangeViewModel.isDepositSameCurrency;
633
634 key.currentState!.changeSelectedCurrency(currency);
635 key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
@@ -708,22 +708,6 @@ class ExchangePage extends BasePage {
708 isMoneroWallet: exchangeViewModel.isMoneroWallet,
709 currencies: exchangeViewModel.depositCurrencies,
710 onCurrencySelected: (currency) {
711 - // FIXME: need to move it into view model
712 - if (currency == CryptoCurrency.xmr &&
713 - exchangeViewModel.wallet.type != WalletType.monero) {
714 - showPopUp<void>(
715 - context: context,
716 - builder: (dialogContext) {
717 - return AlertWithOneAction(
718 - alertTitle: S.of(context).error,
719 - alertContent: S.of(context).exchange_incorrect_current_wallet_for_xmr,
720 - buttonText: S.of(context).ok,
721 - buttonAction: () => Navigator.of(dialogContext).pop(),
722 - );
723 - });
724 - return;
725 - }
726 -
711 exchangeViewModel.changeDepositCurrency(currency: currency);
712 },
713 currencyButtonColor: Colors.transparent,
lib/src/screens/send/widgets/send_card.dart
+3 -1
@@ -243,7 +243,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
243 PaymentRequest paymentRequest,
244 PaymentFlowResult result,
245 ) async {
246 - Navigator.of(context).pop();
246 + if (context.mounted && Navigator.of(context).canPop()) {
247 + Navigator.of(context).pop();
248 + }
249
250 if (result.wallet != null) {
251 walletSwitcherViewModel.selectWallet(result.wallet!);
lib/view_model/exchange/exchange_view_model.dart
+20 -11
@@ -150,12 +150,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
150 }
151 });
152
153 - isDepositAddressEnabled = !(depositCurrency == wallet.currency);
153 + isDepositAddressEnabled = !(isDepositSameCurrency);
154 depositAmount = '';
155 receiveAmount = '';
156 receiveAddress = '';
157 - depositAddress =
158 - depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
157 + depositAddress = isDepositSameCurrency ? wallet.walletAddresses.addressForExchange : '';
158
159 provider = providerList.firstOrNull;
160 final initialProvider = provider;
@@ -190,6 +189,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
189 }
190 }
191
192 + bool get isDepositSameCurrency =>
193 + depositCurrency == wallet.currency ||
194 + depositCurrency.tag == wallet.currency.tag ||
195 + depositCurrency.tag == wallet.currency.title;
196 +
197 bool get isElectrumWallet => [
198 WalletType.bitcoin,
199 WalletType.litecoin,
@@ -412,10 +416,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
416
417 @action
418 void changeDepositCurrency({required CryptoCurrency currency}) {
419 + print("@@@@@@@@");
420 + print(currency);
421 + print(currency.tag);
422 + print(currency.title);
423 + print(wallet.currency.tag);
424 + print(wallet.currency.title);
425 depositCurrency = currency;
426 isFixedRateMode = false;
427 _onPairChange();
418 - isDepositAddressEnabled = !(depositCurrency == wallet.currency);
428 + isDepositAddressEnabled = !(isDepositSameCurrency);
429 }
430
431 @action
@@ -423,7 +433,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
433 receiveCurrency = currency;
434 isFixedRateMode = false;
435 _onPairChange();
426 - isDepositAddressEnabled = !(depositCurrency == wallet.currency);
436 + isDepositAddressEnabled = !(isDepositSameCurrency);
437 }
438
439 @action
@@ -534,12 +544,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
544 _providers.map(
545 (element) => element
546 .fetchRate(
537 - from: depositCurrency,
538 - to: receiveCurrency,
539 - amount: amount,
540 - isFixedRateMode: isFixedRateMode,
541 - isReceiveAmount: isFixedRateMode
542 - )
547 + from: depositCurrency,
548 + to: receiveCurrency,
549 + amount: amount,
550 + isFixedRateMode: isFixedRateMode,
551 + isReceiveAmount: isFixedRateMode)
552 .timeout(
553 Duration(seconds: 7),
554 onTimeout: () => 0.0,
lib/view_model/send/send_view_model.dart
+77 -77
@@ -730,79 +730,64 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
730 }
731 }
732
733 - @action
734 - Future<void> commitTransaction(BuildContext context) async {
735 - if (pendingTransaction == null) {
736 - throw Exception("Pending transaction doesn't exist. It should not be happened.");
733 + Future<void> _handleOcpRequest() async {
734 + if (OpenCryptoPayService.requiresClientCommit(selectedCryptoCurrency)) {
735 + await pendingTransaction!.commit();
736 }
737
739 - if (ocpRequest != null) {
740 - state = TransactionCommitting();
741 - if (OpenCryptoPayService.requiresClientCommit(selectedCryptoCurrency)) {
742 - await pendingTransaction!.commit();
743 - }
744 -
745 - await _ocpService.commitOpenCryptoPayRequest(
746 - pendingTransaction!.hex,
747 - txId: pendingTransaction!.id,
748 - request: ocpRequest!,
749 - asset: selectedCryptoCurrency,
750 - );
751 -
752 - state = TransactionCommitted();
738 + await _ocpService.commitOpenCryptoPayRequest(
739 + pendingTransaction!.hex,
740 + txId: pendingTransaction!.id,
741 + request: ocpRequest!,
742 + asset: selectedCryptoCurrency,
743 + );
744 + }
745
754 - return;
746 + Future<void> _commitApprovalTransaction() async {
747 + if (_pendingApprovalTx != null) {
748 + await _pendingApprovalTx!.commit();
749 + _pendingApprovalTx = null;
750 + // Small pause to ensure allowance is indexed
751 + await Future.delayed(const Duration(milliseconds: 300));
752 }
753
757 - // Swaps.xyz approval (if any), then commit the prebuilt router tx
758 - if (_isSwapsXYZCallDataTx) {
759 - if (_pendingApprovalTx != null) {
760 - await _pendingApprovalTx!.commit();
761 - _pendingApprovalTx = null;
762 - // Small pause to ensure allowance is indexed
763 - await Future.delayed(const Duration(milliseconds: 300));
764 - }
765 -
766 - await pendingTransaction!.commit();
767 - _isSwapsXYZCallDataTx = false;
754 + await pendingTransaction!.commit();
755 + _isSwapsXYZCallDataTx = false;
756 + }
757
769 - state = TransactionCommitted();
770 - return; // skip the regular flow below
758 + Future<void> _commitUR(BuildContext context) async {
759 + final urstr = await pendingTransaction!.commitUR();
760 + final result = await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr);
761 + if (result == null) {
762 + throw "Canceled by user";
763 }
764 + }
765
773 - String address = outputs.fold('', (acc, value) {
774 - return value.isParsedAddress
775 - ? '$acc${value.address}\n${value.extractedAddress}\n\n'
776 - : '$acc${value.address}\n\n';
777 - });
778 -
779 - address = address.trim();
780 -
781 - String note = outputs.fold('', (acc, value) => '$acc${value.note}\n');
782 -
783 - note = note.trim();
766 + @action
767 + Future<void> commitTransaction(BuildContext context) async {
768 + if (pendingTransaction == null) {
769 + throw Exception("Pending transaction doesn't exist. It should not be happened.");
770 + }
771
772 try {
773 state = TransactionCommitting();
774
788 - if (pendingTransaction!.shouldCommitUR()) {
789 - final urstr = await pendingTransaction!.commitUR();
790 - final result =
791 - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr);
792 - if (result == null) {
793 - state = FailureState("Canceled by user");
794 - return;
795 - }
775 + if (ocpRequest != null) {
776 + await _handleOcpRequest();
777 + } else if (_isSwapsXYZCallDataTx) {
778 + // Swaps.xyz approval (if any), then commit the prebuilt router tx
779 + await _commitApprovalTransaction();
780 + } else if (pendingTransaction!.shouldCommitUR()) {
781 + await _commitUR(context);
782 } else {
783 await pendingTransaction!.commit();
784 }
785
800 - if (walletType == WalletType.nano) {
801 - nano!.updateTransactions(wallet);
802 - }
786 + state = TransactionCommitted();
787
804 - // Immediate transaction update for EVM chains, Solana, and Tron
805 - if (isEVMWallet || walletType == WalletType.solana || walletType == WalletType.tron) {
788 + // Immediate transaction update for EVM chains, Solana, Tron, and Nano
789 + if (isEVMWallet ||
790 + [WalletType.solana, WalletType.tron, WalletType.nano].contains(walletType)) {
791 Future.delayed(Duration(seconds: 4), () async {
792 try {
793 await wallet.updateTransactionsHistory();
@@ -813,36 +798,51 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
798 }
799
800 if (pendingTransaction!.id.isNotEmpty) {
816 - TransactionInfo? tx;
817 - if (walletType == WalletType.monero) {
818 - await Future.delayed(Duration(milliseconds: 450));
819 - await wallet.fetchTransactions();
820 - final txhistory = monero!.getTransactionHistory(wallet);
821 - tx = txhistory.transactions.values.last;
822 - }
823 - final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
824 - _settingsStore.shouldSaveRecipientAddress
825 - ? await transactionDescriptionBox.add(TransactionDescription(
826 - id: descriptionKey,
827 - recipientAddress: address,
828 - transactionNote: note,
829 - transactionKey: tx?.additionalInfo["key"] as String?,
830 - ))
831 - : await transactionDescriptionBox.add(TransactionDescription(
832 - id: descriptionKey,
833 - transactionNote: note,
834 - transactionKey: tx?.additionalInfo["key"] as String?,
835 - ));
801 + _addTransactionDescription();
802 }
803 final sharedPreferences = await SharedPreferences.getInstance();
804 await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
805 DateTime.now().add(Duration(minutes: 1)).toIso8601String());
840 - state = TransactionCommitted();
806 } catch (e) {
807 state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
808 }
809 }
810
811 + Future<void> _addTransactionDescription() async {
812 + String address = outputs.fold('', (acc, value) {
813 + return value.isParsedAddress
814 + ? '$acc${value.address}\n${value.extractedAddress}\n\n'
815 + : '$acc${value.address}\n\n';
816 + });
817 +
818 + address = address.trim();
819 +
820 + String note = outputs.fold('', (acc, value) => '$acc${value.note}\n');
821 +
822 + note = note.trim();
823 +
824 + TransactionInfo? tx;
825 + if (walletType == WalletType.monero) {
826 + await Future.delayed(Duration(milliseconds: 450));
827 + await wallet.fetchTransactions();
828 + final txhistory = monero!.getTransactionHistory(wallet);
829 + tx = txhistory.transactions.values.last;
830 + }
831 + final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
832 + _settingsStore.shouldSaveRecipientAddress
833 + ? await transactionDescriptionBox.add(TransactionDescription(
834 + id: descriptionKey,
835 + recipientAddress: address,
836 + transactionNote: note,
837 + transactionKey: tx?.additionalInfo["key"] as String?,
838 + ))
839 + : await transactionDescriptionBox.add(TransactionDescription(
840 + id: descriptionKey,
841 + transactionNote: note,
842 + transactionKey: tx?.additionalInfo["key"] as String?,
843 + ));
844 + }
845 +
846 Object _credentials([ExchangeProvider? provider]) {
847 final priority = _settingsStore.priority[wallet.type];
848