feat: restrict history api calls when toggle is off in privacy settings (#3615)
What changed, and why it matters
This commit adds a privacy toggle that stops EVM and Tron wallets from calling third-party blockchain history APIs (like Etherscan, PolygonScan, TronGrid) when the user turns the feature off in privacy settings. It also fixes preference key names for Base and Arbitrum scan providers so the toggle actually controls the right service. Previously, even with the toggle off, the app may have kept querying these external providers, potentially leaking the user's wallet address and transaction history to them.
Treat as a privacy-hardening fix. Verify that the renamed preference keys match the values written by the settings view model and that migration/clearing of old keys is handled, otherwise users who previously toggled the setting may see reversed behavior. Review whether removing `_transactionsUpdateTimer?.cancel()` in the disabled branch is safe and does not cause stale periodic fetches.
Security signals we found
Privacy leak mitigation: prevents address/transaction history disclosure to third-party scan providers when user disables the option
Preference key mismatch fix: old keys `use_basescan`/`use_arbiscan` did not align with documented/expected keys, so toggles may have been ineffective
Fail-closed behavior: `checkIfScanProviderIsEnabled()` returns `false` on SharedPreferences read errors
No cryptographic or authentication changes
Evidence from the diff
The patch gates fetchTransactions() and fetchTrc20ExcludedTransactions() on a new checkIfScanProviderIsEnabled() helper that reads per-chain SharedPreferences keys. For EVM chains it returns an empty map when disabled; for Tron it returns the existing local transaction history. It also renames preference keys from use_basescan/use_arbiscan to use_base_scan/use_arbitrum_scan and updates the UI binding in connection_sync_page.dart. A side change removes timer cancellation in the disabled branch, which appears to be an intentional refactor rather than a security fix.
Changed components
cw_evm/lib/evm_chain_wallet.dartcw_evm/lib/utils/evm_chain_utils.dartcw_tron/lib/tron_wallet.dartcw_tron/pubspec.yamllib/src/screens/settings/connection_sync_page.dartInspect captured patch +36 / −10
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index d66aa6fd..93a3b975 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -267,7 +267,13 @@ abstract class EVMChainWalletBase
Future<bool> checkIfScanProviderIsEnabled() async {
final key = EVMChainUtils.getScanProviderPreferenceKey(selectedChainId);
- return (await sharedPrefs.future).getBool(key) ?? true;
+
+ try {
+ return (await sharedPrefs.future).getBool(key) ?? true;
+ } catch (e) {
+ printV("Could not read the $key preference: $e");
+ return false;
+ }
}
EVMChainTransactionInfo getTransactionInfo(
@@ -1124,6 +1130,10 @@ abstract class EVMChainWalletBase
@override
Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
+ if (!await checkIfScanProviderIsEnabled()) {
+ return {};
+ }
+
final List<EVMChainTransactionModel> transactions = [];
final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
@@ -1557,8 +1567,6 @@ abstract class EVMChainWalletBase
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
- } else {
- _transactionsUpdateTimer?.cancel();
}
}
diff --git a/cw_evm/lib/utils/evm_chain_utils.dart b/cw_evm/lib/utils/evm_chain_utils.dart
index 969ffc7b..39e52d50 100644
--- a/cw_evm/lib/utils/evm_chain_utils.dart
+++ b/cw_evm/lib/utils/evm_chain_utils.dart
@@ -46,8 +46,8 @@ class EVMChainUtils {
static String getScanProviderPreferenceKey(int chainId) => switch (chainId) {
1 => "use_etherscan",
137 => "use_polygonscan",
- 8453 => "use_basescan",
- 42161 => "use_arbiscan",
+ 8453 => "use_base_scan",
+ 42161 => "use_arbitrum_scan",
56 => "use_bscscan",
_ => "use_etherscan",
};
diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart
index 32ff8c75..ba6d2a44 100644
--- a/cw_tron/lib/tron_wallet.dart
+++ b/cw_tron/lib/tron_wallet.dart
@@ -13,6 +13,7 @@ import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
+import "package:cw_core/utils/print_verbose.dart";
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
@@ -30,6 +31,7 @@ import 'package:cw_tron/tron_transaction_info.dart';
import 'package:cw_tron/tron_wallet_addresses.dart';
import 'package:mobx/mobx.dart';
import 'package:on_chain/on_chain.dart';
+import "package:shared_preferences/shared_preferences.dart";
part 'tron_wallet.g.dart';
@@ -373,8 +375,21 @@ abstract class TronWalletBase
]);
}
+ Future<bool> checkIfScanProviderIsEnabled() async {
+ try {
+ return (await SharedPreferences.getInstance()).getBool("use_trongrid") ?? true;
+ } catch (e) {
+ printV("Could not read the TronGrid preference: $e");
+ return false;
+ }
+ }
+
@override
Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
+ if (!await checkIfScanProviderIsEnabled()) {
+ return transactionHistory.transactions;
+ }
+
final address = _tronAddress;
final transactions = await _client.fetchTransactions(address);
@@ -441,6 +456,10 @@ abstract class TronWalletBase
}
Future<void> fetchTrc20ExcludedTransactions() async {
+ if (!await checkIfScanProviderIsEnabled()) {
+ return;
+ }
+
final address = _tronAddress;
final transactions = await _client.fetchTrc20ExcludedTransactions(address);
@@ -645,8 +664,6 @@ abstract class TronWalletBase
fetchTransactions();
fetchTrc20ExcludedTransactions();
_setTransactionUpdateTimer();
- } else {
- _transactionsUpdateTimer?.cancel();
}
}
diff --git a/cw_tron/pubspec.yaml b/cw_tron/pubspec.yaml
index c765ea78..a5368b97 100644
--- a/cw_tron/pubspec.yaml
+++ b/cw_tron/pubspec.yaml
@@ -26,6 +26,7 @@ dependencies:
mobx: ^2.3.0+1
bip39: ^1.0.6
hive: ^2.2.3
+ shared_preferences: ^2.0.15
dev_dependencies:
flutter_test:
diff --git a/docs/ADDING_EVM_L2_WALLET_NETWORK_TYPES.md b/docs/ADDING_EVM_L2_WALLET_NETWORK_TYPES.md
index c5ef9747..dd8cc92b 100644
--- a/docs/ADDING_EVM_L2_WALLET_NETWORK_TYPES.md
+++ b/docs/ADDING_EVM_L2_WALLET_NETWORK_TYPES.md
@@ -275,8 +275,8 @@ static String getScanProviderPreferenceKey(int chainId) {
return switch (chainId) {
1 => 'use_etherscan',
137 => 'use_polygonscan',
- 8453 => 'use_basescan',
- 42161 => 'use_arbiscan',
+ 8453 => 'use_base_scan',
+ 42161 => 'use_arbitrum_scan',
10 => 'use_optimismscan', // NEW
_ => 'use_etherscan', // Default
};
diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart
index fb251f55..ca9c4616 100644
--- a/lib/src/screens/settings/connection_sync_page.dart
+++ b/lib/src/screens/settings/connection_sync_page.dart
@@ -89,7 +89,7 @@ class ConnectionSyncPage extends BasePage {
ListItemToggle(
keyValue: "can_use_basescan",
label: S.of(context).basescan_history,
- value: _connectionSyncViewModel.canUseBaseScan,
+ value: _connectionSyncViewModel.useBaseScan,
onChanged: (val) {
_connectionSyncViewModel.setUseBaseScan(val);
}),
Why this scored 41/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.