fix: better handle exchange url launching
What changed, and why it matters
This commit improves how the Cake Wallet app handles failures when opening a third-party cryptocurrency exchange website. It adds error messages so users see clearer warnings (for example, if the exchange blocks Tor connections), fixes a typo in variable names, and prevents a possible app crash when dismissing a wallet-loading warning. There is no direct evidence this fixes an active security vulnerability, but it makes the buy/sell flow more robust and user-friendly.
Treat as a routine robustness improvement. Review whether `provider.launchProvider` can leak sensitive data in exception messages before displaying them, and consider localizing the new hard-coded English error strings. No urgent security response is indicated by the diff alone.
Security signals we found
Error handling added around external URL launching for exchange providers
User-facing error message distinguishes 403/Tor blocking from generic failures
Navigation guard added to prevent pop on unmounted/non-poppable context
No explicit security claim, CVE, or attribution in commit or supplied references
Evidence from the diff
The patch renames isBuySellQuotFailed to isBuySellQuoteFailed and adds an optional errorMessage field to BuySellQuotFailed. The buy/sell page now displays that specific error message instead of a generic unsupported-pair warning. In launchTrade, calls to provider.launchProvider are wrapped in a try/catch; if the exception string contains “403” the UI shows “Using Tor is not supported”, otherwise a generic error. A separate small fix in wallet_loading_service.dart guards a Navigator.pop() with context.mounted and canPop() to avoid calling navigation on an unmounted or non-poppable context.
Changed components
lib/buy/sell_buy_states.dartlib/core/wallet_loading_service.dartlib/src/screens/buy/buy_sell_page.dartlib/view_model/buy/buy_sell_view_model.dartInspect captured patch +40 / −18
diff --git a/lib/buy/sell_buy_states.dart b/lib/buy/sell_buy_states.dart
index 26ea2020..142e13f8 100644
--- a/lib/buy/sell_buy_states.dart
+++ b/lib/buy/sell_buy_states.dart
@@ -17,4 +17,8 @@ class BuySellQuotLoading extends BuySellQuotLoadingState {}
class BuySellQuotLoaded extends BuySellQuotLoadingState {}
-class BuySellQuotFailed extends BuySellQuotLoadingState {}
\ No newline at end of file
+class BuySellQuotFailed extends BuySellQuotLoadingState {
+ final String? errorMessage;
+
+ BuySellQuotFailed({this.errorMessage});
+}
\ No newline at end of file
diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart
index 4284671c..1432ce6f 100644
--- a/lib/core/wallet_loading_service.dart
+++ b/lib/core/wallet_loading_service.dart
@@ -131,7 +131,11 @@ class WalletLoadingService {
alertContent: S.of(context).corrupted_seed_notice,
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).show_seed,
- actionLeftButton: () => Navigator.of(context).pop(),
+ actionLeftButton: () {
+ if (context.mounted && Navigator.of(context).canPop()) {
+ Navigator.of(context).pop();
+ }
+ },
actionRightButton: () => showSeedsPopup(context, msg),
);
});
diff --git a/lib/src/screens/buy/buy_sell_page.dart b/lib/src/screens/buy/buy_sell_page.dart
index af29d02b..53c8cdec 100644
--- a/lib/src/screens/buy/buy_sell_page.dart
+++ b/lib/src/screens/buy/buy_sell_page.dart
@@ -148,7 +148,7 @@ class BuySellPage extends BasePage {
bottomSection: Observer(
builder: (_) => Column(
children: [
- if (buySellViewModel.isBuySellQuotFailed)
+ if (buySellViewModel.isBuySellQuoteFailed)
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Row(
@@ -166,7 +166,8 @@ class BuySellPage extends BasePage {
Expanded(
flex: 8,
child: Text(
- S.of(context).buy_sell_pair_is_not_supported_warning,
+ buySellViewModel.buySellQuoteFailedError ??
+ S.of(context).buy_sell_pair_is_not_supported_warning,
textAlign: TextAlign.center,
softWrap: true,
overflow: TextOverflow.ellipsis,
@@ -188,9 +189,9 @@ class BuySellPage extends BasePage {
},
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
- isDisabled: buySellViewModel.isBuySellQuotFailed,
+ isDisabled: buySellViewModel.isBuySellQuoteFailed,
isLoading:
- !buySellViewModel.isReadyToTrade && !buySellViewModel.isBuySellQuotFailed,
+ !buySellViewModel.isReadyToTrade && !buySellViewModel.isBuySellQuoteFailed,
),
],
),
diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart
index 601fca52..76e6e9ed 100644
--- a/lib/view_model/buy/buy_sell_view_model.dart
+++ b/lib/view_model/buy/buy_sell_view_model.dart
@@ -157,7 +157,12 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
}
@computed
- bool get isBuySellQuotFailed => buySellQuotState is BuySellQuotFailed;
+ bool get isBuySellQuoteFailed => buySellQuotState is BuySellQuotFailed;
+
+ @computed
+ String? get buySellQuoteFailedError => buySellQuotState is BuySellQuotFailed
+ ? (buySellQuotState as BuySellQuotFailed).errorMessage
+ : null;
@action
void reset() {
@@ -201,10 +206,10 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
final enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
- if (!isReadyToTrade && !isBuySellQuotFailed) {
+ if (!isReadyToTrade && !isBuySellQuoteFailed) {
cryptoAmount = S.current.fetching;
return;
- } else if (isBuySellQuotFailed) {
+ } else if (isBuySellQuoteFailed) {
cryptoAmount = '';
return;
}
@@ -232,10 +237,10 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
final enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
- if (!isReadyToTrade && !isBuySellQuotFailed) {
+ if (!isReadyToTrade && !isBuySellQuoteFailed) {
fiatAmount = S.current.fetching;
return;
- } else if (isBuySellQuotFailed) {
+ } else if (isBuySellQuoteFailed) {
fiatAmount = '';
return;
}
@@ -478,12 +483,20 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
@action
Future<void> launchTrade(BuildContext context) async {
final provider = selectedQuote!.provider;
- await provider.launchProvider(
- context: context,
- quote: selectedQuote!,
- amount: amount,
- isBuyAction: isBuyAction,
- cryptoCurrencyAddress: cryptoCurrencyAddress,
- );
+ try {
+ await provider.launchProvider(
+ context: context,
+ quote: selectedQuote!,
+ amount: amount,
+ isBuyAction: isBuyAction,
+ cryptoCurrencyAddress: cryptoCurrencyAddress,
+ );
+ } catch (e) {
+ if (e.toString().contains("403")) {
+ buySellQuotState = BuySellQuotFailed(errorMessage: "Using Tor is not supported");
+ } else {
+ buySellQuotState = BuySellQuotFailed(errorMessage: "Something went wrong please try again later");
+ }
+ }
}
}
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.