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