CW-974: Add support for automatic node switching. (#2361)

* feat: Add support for automatic node switching. This change: - Adds isTrustedNodeForSwitching property to Node class. - Adds UI Checkbox to mark a node as trusted for automatic switching in Node create or edit form. - Implements checkNodeHealth method in wallet classes to verify node connectivity. - Adds NodeSwitchingService for managing automatic node switching based on health checks. - Adds setting option for user to enable/disable automated node switching * fix(automatic-node-switch): Set node health check interval to 5 minutes in NodeSwitchingService * refactor: Update handler method * feat: Automatic Node Switching Improvements This change: - Moves enable automatic node switch to the Manage Nodes Page - Adds check to verify node health before attempting to sync in EVM, Sol and Tron Wallets. - Modifies trusted ndoes list for monero - Adds trigger for Node Health Check when app resumes from background * fix: Increase default request timeout for Tron and update health check interval for node switch * feat: Enhance Node health check functionality. This change adds additional checks for token balance in EVM, Solana and Tron Wallets in checkNodeHealth. * fix: Update health check interval to 30 seconds in NodeSwitchingService * Update bitcoin_electrum_server_list.yml * fix: Electru wallet not connecting to new node after switching * fix: Automated Node Switch not working on Decred Wallet * fix: Add more nodes for AutoSwitching This change: - Enables more nodes across wallet types for auto switching - Modifies CTA for checkbox - Renames flags to fit * fix: Add migration function for setting up default nodes for autoswitching * fix: Review and testing fixes * chore: Adjust balance and history fetch timer

David Adegoke committed Jul 25, 2025 at 14:42 UTC 4b2af63fa5dc24432dbc86ff0d69c66e17ccbaf1
73 files changed +635 -64
assets/banano_node_list.yml
+2 -1
@@ -2,4 +2,5 @@
2 uri: kaliumapi.appditto.com
3 path: /api
4 useSSL: true
5 - is_default: true
\ No newline at end of file
5 + is_default: true
6 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/bitcoin_cash_electrum_server_list.yml
+2
@@ -2,9 +2,11 @@
2 uri: bitcoincash.stackwallet.com:50002
3 is_default: true
4 useSSL: true
5 + isEnabledForAutoSwitching: true
6 -
7 uri: bch.aftrek.org:50002
8 useSSL: true
9 + isEnabledForAutoSwitching: true
10 -
11 uri: node.minisatoshi.cash:50002
12 useSSL: true
assets/bitcoin_electrum_server_list.yml
+6
@@ -2,8 +2,14 @@
2 uri: btc-electrum.cakewallet.com:50002
3 useSSL: true
4 isDefault: true
5 + isEnabledForAutoSwitching: true
6 -
7 uri: electrs.cakewallet.com:50001
8 -
9 uri: fulcrum.sethforprivacy.com:50002
10 useSSL: true
11 + isEnabledForAutoSwitching: true
12 +-
13 + uri: electrum.cakewallet.com:50002
14 + useSSL: true
15 + isEnabledForAutoSwitching: true
assets/decred_node_list.yml
+3 -1
@@ -1,6 +1,8 @@
1 -
2 uri: default-spv-nodes
3 is_default: true
4 + isEnabledForAutoSwitching: true
5 -
6 uri: dcrd.sethforprivacy.com:9108
6 - useSSL: true
\ No newline at end of file
7 + useSSL: true
8 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/ethereum_server_list.yml
+2
@@ -2,6 +2,7 @@
2 uri: ethereum-rpc.publicnode.com
3 useSSL: true
4 isDefault: true
5 + isEnabledForAutoSwitching: true
6 -
7 uri: eth.llamarpc.com
8 -
@@ -10,5 +11,6 @@
11 uri: eth-mainnet.public.blastapi.io
12 -
13 uri: eth.nownodes.io
14 + isEnabledForAutoSwitching: true
15 -
16 uri: ethereum.publicnode.com
\ No newline at end of file
assets/litecoin_electrum_server_list.yml
+2
@@ -2,9 +2,11 @@
2 uri: ltc-electrum.cakewallet.com:50002
3 useSSL: true
4 isDefault: true
5 + isEnabledForAutoSwitching: true
6 -
7 uri: litecoin.stackwallet.com:20063
8 useSSL: true
9 + isEnabledForAutoSwitching: true
10 -
11 uri: electrum-ltc.bysh.me:50002
12 useSSL: true
assets/nano_node_list.yml
+3
@@ -1,14 +1,17 @@
1 -
2 uri: nano.nownodes.io
3 useSSL: true
4 + isEnabledForAutoSwitching: true
5 -
6 uri: rpc.nano.to
7 is_default: true
8 useSSL: true
9 + isEnabledForAutoSwitching: true
10 -
11 uri: node.nautilus.io
12 path: /api
13 useSSL: true
14 + isEnabledForAutoSwitching: true
15 -
16 uri: app.natrium.io
17 path: /api
assets/nano_pow_node_list.yml
+3
@@ -2,8 +2,11 @@
2 uri: rpc.nano.to
3 useSSL: true
4 is_default: true
5 + isEnabledForAutoSwitching: true
6 -
7 uri: workers.perish.co
8 + isEnabledForAutoSwitching: true
9 -
10 uri: worker.nanoriver.cc
11 useSSL: true
12 + isEnabledForAutoSwitching: true
assets/node_list.yml
+3
@@ -3,6 +3,7 @@
3 is_default: true
4 trusted: true
5 useSSL: true
6 + isEnabledForAutoSwitching: true
7 -
8 uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
9 is_default: false
@@ -10,6 +11,8 @@
11 uri: node.sethforprivacy.com:443
12 useSSL: true
13 is_default: false
14 + isEnabledForAutoSwitching: true
15 -
16 uri: nodes.hashvault.pro:18081
17 is_default: false
18 + isEnabledForAutoSwitching: true
assets/polygon_node_list.yml
+3 -1
@@ -4,7 +4,9 @@
4 uri: polygon-bor-rpc.publicnode.com
5 useSSL: true
6 isDefault: true
7 + isEnabledForAutoSwitching: true
8 -
9 uri: polygon.llamarpc.com
10 -
10 - uri: matic.nownodes.io
\ No newline at end of file
11 + uri: matic.nownodes.io
12 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/solana_node_list.yml
+4 -1
@@ -4,10 +4,13 @@
4 -
5 uri: api.mainnet-beta.solana.com:443
6 useSSL: true
7 + isEnabledForAutoSwitching: true
8 -
9 uri: solana-rpc.publicnode.com:443
10 useSSL: true
11 + isEnabledForAutoSwitching: true
12 -
13 uri: solana-mainnet.core.chainstack.com
14 useSSL: true
13 - is_default: true
\ No newline at end of file
15 + is_default: true
16 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/tron_node_list.yml
+3 -1
@@ -6,6 +6,8 @@
6 uri: api.trongrid.io
7 is_default: true
8 useSSL: true
9 + isEnabledForAutoSwitching: true
10 -
11 uri: trx.nownodes.io
11 - useSSL: true
\ No newline at end of file
12 + useSSL: true
13 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/wownero_node_list.yml
+2
@@ -2,10 +2,12 @@
2 uri: node3.monerodevs.org:34568
3 is_default: true
4 useSSL: false
5 + isEnabledForAutoSwitching: true
6 -
7 uri: node2.monerodevs.org:34568
8 is_default: false
9 useSSL: false
10 + isEnabledForAutoSwitching: true
11 -
12 uri: node.monerodevs.org:34568
13 is_default: false
assets/zano_node_list.yml
+3 -1
@@ -1,7 +1,9 @@
1 -
2 uri: 37.27.100.59:10500
3 useSSL: false
4 + isEnabledForAutoSwitching: true
5 -
6 uri: zano.cakewallet.com:11211
7 is_default: true
7 - useSSL: false
\ No newline at end of file
8 + useSSL: false
9 + isEnabledForAutoSwitching: true
\ No newline at end of file
cw_bitcoin/lib/electrum.dart
+19 -9
@@ -203,15 +203,25 @@ class ElectrumClient {
203 return [];
204 });
205
206 - Future<Map<String, dynamic>> getBalance(String scriptHash) =>
207 - call(method: 'blockchain.scripthash.get_balance', params: [scriptHash])
208 - .then((dynamic result) {
209 - if (result is Map<String, dynamic>) {
210 - return result;
211 - }
212 -
213 - return <String, dynamic>{};
214 - });
206 + Future<Map<String, dynamic>> getBalance(String scriptHash, {bool throwOnError = false}) async {
207 + try {
208 + final result = await call(method: 'blockchain.scripthash.get_balance', params: [scriptHash]);
209 + if (result is Map<String, dynamic>) {
210 + return result;
211 + }
212 +
213 + if (throwOnError) {
214 + throw Exception('Invalid response format for getBalance');
215 + }
216 +
217 + return <String, dynamic>{};
218 + } catch (e) {
219 + if (throwOnError) {
220 + rethrow;
221 + }
222 + return <String, dynamic>{};
223 + }
224 + }
225
226 Future<List<Map<String, dynamic>>> getHistory(String scriptHash) =>
227 call(method: 'blockchain.scripthash.get_history', params: [scriptHash])
cw_bitcoin/lib/electrum_wallet.dart
+20
@@ -2300,6 +2300,26 @@ abstract class ElectrumWalletBase
2300 await save();
2301 }
2302
2303 + @override
2304 + Future<bool> checkNodeHealth() async {
2305 + try {
2306 + final addresses = walletAddresses.allAddresses
2307 + .where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress)
2308 + .toList();
2309 +
2310 + if (addresses.isEmpty) {
2311 + return false;
2312 + }
2313 +
2314 + final firstAddress = addresses.first;
2315 + final sh = firstAddress.getScriptHash(network);
2316 + await electrumClient.getBalance(sh, throwOnError: true);
2317 + return true;
2318 + } catch (e) {
2319 + return false;
2320 + }
2321 + }
2322 +
2323 @override
2324 void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
2325
cw_core/lib/node.dart
+6 -1
@@ -26,6 +26,7 @@ class Node extends HiveObject with Keyable {
26 this.trusted = false,
27 this.socksProxyAddress,
28 this.path = '',
29 + this.isEnabledForAutoSwitching = false,
30 String? uri,
31 WalletType? type,
32 }) {
@@ -44,7 +45,8 @@ class Node extends HiveObject with Keyable {
45 password = map['password'] as String?,
46 useSSL = map['useSSL'] as bool?,
47 trusted = map['trusted'] as bool? ?? false,
47 - socksProxyAddress = map['socksProxyPort'] as String?;
48 + socksProxyAddress = map['socksProxyPort'] as String?,
49 + isEnabledForAutoSwitching = map['isEnabledForAutoSwitching'] as bool? ?? false;
50
51 static const typeId = NODE_TYPE_ID;
52 static const boxName = 'Nodes';
@@ -82,6 +84,9 @@ class Node extends HiveObject with Keyable {
84 @HiveField(10)
85 bool? supportsMweb;
86
87 + @HiveField(11, defaultValue: false)
88 + bool isEnabledForAutoSwitching;
89 +
90 bool get isSSL => useSSL ?? false;
91
92 bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
cw_core/lib/wallet_base.dart
+5
@@ -112,4 +112,9 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
112 bool isTestnet = false;
113
114 bool canSend() => true;
115 +
116 + /// This is used to check if the current node is healthy by making a lightweight RPC call
117 + /// Each wallet implementation should override this to make a single, efficient call
118 + /// Returns true if the node is healthy, false otherwise
119 + Future<bool> checkNodeHealth();
120 }
cw_decred/lib/api/libdcrwallet.dart
+4 -1
@@ -454,7 +454,7 @@ class Libwallet {
454 return res.payload;
455 }
456
457 - Future<Map> balance(String walletName) async {
457 + Future<Map> balance(String walletName, {bool throwOnError = false}) async {
458 if (_closed) throw StateError('Closed');
459 final completer = Completer<Object?>.sync();
460 final id = _idCounter++;
@@ -468,6 +468,9 @@ class Libwallet {
468 try {
469 return jsonDecode(res.payload);
470 } catch (_) {
471 + if (throwOnError) {
472 + rethrow;
473 + }
474 return {};
475 }
476 }
cw_decred/lib/wallet.dart
+3 -1
@@ -26,7 +26,6 @@ import 'package:cw_core/wallet_info.dart';
26 import 'package:cw_core/wallet_base.dart';
27 import 'package:cw_core/transaction_priority.dart';
28 import 'package:cw_core/pending_transaction.dart';
29 -import 'package:cw_core/pathForWallet.dart';
29 import 'package:cw_core/sync_status.dart';
30 import 'package:cw_core/node.dart';
31 import 'package:cw_core/unspent_coins_info.dart';
@@ -594,6 +593,9 @@ abstract class DecredWalletBase
593 );
594 }
595
596 + @override
597 + Future<bool> checkNodeHealth() async => await checkSync();
598 +
599 @override
600 void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
601
cw_evm/lib/evm_chain_client.dart
+4 -1
@@ -65,10 +65,13 @@ abstract class EVMChainClient {
65 // });
66 }
67
68 - Future<EtherAmount> getBalance(EthereumAddress address) async {
68 + Future<EtherAmount> getBalance(EthereumAddress address, {bool throwOnError = false}) async {
69 try {
70 return await _client!.getBalance(address);
71 } catch (_) {
72 + if (throwOnError) {
73 + rethrow;
74 + }
75 return EtherAmount.zero();
76 }
77 }
cw_evm/lib/evm_chain_wallet.dart
+39 -3
@@ -164,6 +164,36 @@ abstract class EVMChainWalletBase
164 EncryptionFileUtils encryptionFileUtils,
165 );
166
167 + @override
168 + Future<bool> checkNodeHealth() async {
169 + try {
170 + // Check native balance
171 + await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
172 +
173 + // Check USDC token balance
174 + String usdcContractAddress;
175 + if (_client.chainId == 1) {
176 + // Ethereum mainnet
177 + usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
178 + } else if (_client.chainId == 137) {
179 + // Polygon mainnet
180 + usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
181 + } else {
182 + // If we are not on Ethereum or Polygon, we skip ERC20 token check
183 + return true;
184 + }
185 +
186 + await _client.fetchERC20Balances(
187 + _evmChainPrivateKey.address,
188 + usdcContractAddress,
189 + );
190 +
191 + return true;
192 + } catch (e) {
193 + return false;
194 + }
195 + }
196 +
197 //! Common Methods across child classes
198
199 String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
@@ -379,15 +409,21 @@ abstract class EVMChainWalletBase
409 Future<void> startSync() async {
410 try {
411 syncStatus = AttemptingSyncStatus();
412 +
413 + // Verify node health before attempting to sync
414 + final isHealthy = await checkNodeHealth();
415 + if (!isHealthy) {
416 + syncStatus = FailedSyncStatus();
417 + return;
418 + }
419 +
420 await _updateBalance();
421 await _updateTransactions();
384 -
422 await _updateEstimatedGasFeeParams();
423
424 _updateFeesTimer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
425 await _updateEstimatedGasFeeParams();
426 });
390 -
427 syncStatus = SyncedSyncStatus();
428 } catch (e) {
429 syncStatus = FailedSyncStatus();
@@ -825,7 +861,7 @@ abstract class EVMChainWalletBase
861 _transactionsUpdateTimer!.cancel();
862 }
863
828 - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 15), (_) {
864 + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) {
865 _updateTransactions();
866 _updateBalance();
867 });
cw_monero/lib/monero_wallet.dart
+18
@@ -184,6 +184,24 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
184 @override
185 Future<void>? updateBalance() => null;
186
187 + @override
188 + Future<bool> checkNodeHealth() async {
189 + try {
190 + // Check if the wallet is currently connected to the daemon
191 + final isConnected = monero_wallet.isConnectedSync();
192 +
193 + if (!isConnected) {
194 + return false; // It's not connected to daemon
195 + }
196 +
197 + // Check to get current node height to ensure daemon is responsive
198 + final nodeHeight = await monero_wallet.getNodeHeight();
199 + return nodeHeight > 0;
200 + } catch (e) {
201 + return false;
202 + }
203 + }
204 +
205 @override
206 Future<void> close({bool shouldCleanup = false}) async {
207 if (isHardwareWallet) {
cw_nano/lib/nano_client.dart
+4 -1
@@ -92,7 +92,7 @@ class NanoClient {
92 return NanoBalance(currentBalance: cur, receivableBalance: rec);
93 }
94
95 - Future<AccountInfoResponse?> getAccountInfo(String address) async {
95 + Future<AccountInfoResponse?> getAccountInfo(String address, {bool throwOnError = false}) async {
96 try {
97 final response = await ProxyWrapper().post(
98 clearnetUri: _node!.uri,
@@ -110,6 +110,9 @@ class NanoClient {
110 return AccountInfoResponse.fromJson(data);
111 } catch (e) {
112 printV("error while getting account info $e");
113 + if (throwOnError) {
114 + rethrow;
115 + }
116 return null;
117 }
118 }
cw_nano/lib/nano_wallet.dart
+11
@@ -518,8 +518,19 @@ abstract class NanoWalletBase
518 return _client.getN2Reps();
519 }
520
521 + @override
522 Future<void>? updateBalance() async => await _updateBalance();
523
524 + @override
525 + Future<bool> checkNodeHealth() async {
526 + try {
527 + await _client.getAccountInfo(_publicAddress!, throwOnError: true);
528 + return true;
529 + } catch (_) {
530 + return false;
531 + }
532 + }
533 +
534 @override
535 Future<void> renameWalletFiles(String newWalletName) async {
536 final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
cw_solana/lib/solana_client.dart
+29 -19
@@ -51,7 +51,7 @@ class SolanaWalletClient {
51 }
52 }
53
54 - Future<double> getBalance(String walletAddress) async {
54 + Future<double> getBalance(String walletAddress, {bool throwOnError = false}) async {
55 try {
56 final balance = await _provider!.requestWithContext(
57 SolanaRPCGetBalance(
@@ -65,6 +65,9 @@ class SolanaWalletClient {
65
66 return solBalance;
67 } catch (_) {
68 + if (throwOnError) {
69 + rethrow;
70 + }
71 return 0.0;
72 }
73 }
@@ -88,31 +91,38 @@ class SolanaWalletClient {
91 }
92
93 Future<SolanaBalance?> getSplTokenBalance(
91 - String mintAddress, String walletAddress) async {
92 - // Fetch the token accounts (a token can have multiple accounts for various uses)
93 - final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress);
94 + String mintAddress, String walletAddress, {bool throwOnError = false}) async {
95 + try {
96 + // Fetch the token accounts (a token can have multiple accounts for various uses)
97 + final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress);
98
95 - // Handle scenario where there is no token account
96 - if (tokenAccounts == null || tokenAccounts.isEmpty) {
97 - return null;
98 - }
99 + // Handle scenario where there is no token account
100 + if (tokenAccounts == null || tokenAccounts.isEmpty) {
101 + return null;
102 + }
103
100 - // Sum the balances of all accounts with the specified mint address
101 - double totalBalance = 0.0;
104 + // Sum the balances of all accounts with the specified mint address
105 + double totalBalance = 0.0;
106
103 - for (var tokenAccount in tokenAccounts) {
104 - final tokenAmountResult = await _provider!.request(
105 - SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey),
106 - );
107 + for (var tokenAccount in tokenAccounts) {
108 + final tokenAmountResult = await _provider!.request(
109 + SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey),
110 + );
111
108 - final balance = tokenAmountResult.uiAmountString;
112 + final balance = tokenAmountResult.uiAmountString;
113
110 - final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0;
114 + final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0;
115
112 - totalBalance += balanceAsDouble;
113 - }
116 + totalBalance += balanceAsDouble;
117 + }
118
115 - return SolanaBalance(totalBalance);
119 + return SolanaBalance(totalBalance);
120 + } catch (_) {
121 + if (throwOnError) {
122 + rethrow;
123 + }
124 + return null;
125 + }
126 }
127
128 Future<double> getFeeForMessage(String message, Commitment commitment) async {
cw_solana/lib/solana_wallet.dart
+25 -1
@@ -377,6 +377,13 @@ abstract class SolanaWalletBase
377 try {
378 syncStatus = AttemptingSyncStatus();
379
380 + // Verify node health before attempting to sync
381 + final isHealthy = await checkNodeHealth();
382 + if (!isHealthy) {
383 + syncStatus = FailedSyncStatus();
384 + return;
385 + }
386 +
387 await Future.wait([
388 _updateBalance(),
389 _updateNativeSOLTransactions(),
@@ -477,6 +484,22 @@ abstract class SolanaWalletBase
484 @override
485 Future<void>? updateBalance() async => await _updateBalance();
486
487 + @override
488 + Future<bool> checkNodeHealth() async {
489 + try {
490 + // Check native balance
491 + await _client.getBalance(solanaAddress, throwOnError: true);
492 +
493 + // Check USDC token balance
494 + const usdcMintAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
495 + await _client.getSplTokenBalance(usdcMintAddress, solanaAddress, throwOnError: true);
496 +
497 + return true;
498 + } catch (e) {
499 + return false;
500 + }
501 + }
502 +
503 List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
504
505 void addInitialTokens() {
@@ -554,6 +577,7 @@ abstract class SolanaWalletBase
577 _updateBalance();
578 _updateNativeSOLTransactions();
579 _updateSPLTokenTransactions();
580 + _getEstimatedFees();
581 });
582 }
583
@@ -564,7 +588,7 @@ abstract class SolanaWalletBase
588
589 // Sign the message bytes with the wallet's private key
590 final signature = (_solanaPrivateKey.sign(messageBytes));
567 -
591 +
592 return Base58Encoder.encode(signature);
593 }
594
cw_tron/lib/tron_client.dart
+8 -2
@@ -102,12 +102,15 @@ class TronClient {
102 }
103 }
104
105 - Future<BigInt> getBalance(TronAddress address) async {
105 + Future<BigInt> getBalance(TronAddress address, {bool throwOnError = false}) async {
106 try {
107 final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
108
109 return accountDetails?.balance ?? BigInt.zero;
110 } catch (_) {
111 + if (throwOnError) {
112 + rethrow;
113 + }
114 return BigInt.zero;
115 }
116 }
@@ -476,7 +479,7 @@ class TronClient {
479 }
480 }
481
479 - Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress) async {
482 + Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress, {bool throwOnError = false}) async {
483 try {
484 final ownerAddress = TronAddress(userAddress);
485
@@ -499,6 +502,9 @@ class TronClient {
502
503 return TronBalance(outputResult);
504 } catch (_) {
505 + if (throwOnError) {
506 + rethrow;
507 + }
508 return TronBalance(BigInt.zero);
509 }
510 }
cw_tron/lib/tron_wallet.dart
+23
@@ -276,6 +276,13 @@ abstract class TronWalletBase
276 Future<void> startSync() async {
277 try {
278 syncStatus = AttemptingSyncStatus();
279 +
280 + // Verify node health before attempting to sync
281 + final isHealthy = await checkNodeHealth();
282 + if (!isHealthy) {
283 + syncStatus = FailedSyncStatus();
284 + return;
285 + }
286 await _updateBalance();
287 await fetchTransactions();
288 fetchTrc20ExcludedTransactions();
@@ -505,6 +512,22 @@ abstract class TronWalletBase
512 @override
513 Future<void>? updateBalance() async => await _updateBalance();
514
515 + @override
516 + Future<bool> checkNodeHealth() async {
517 + try {
518 + // Check native balance
519 + await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true);
520 +
521 + // Check USDT token balance
522 + const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
523 + await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true);
524 +
525 + return true;
526 + } catch (e) {
527 + return false;
528 + }
529 + }
530 +
531 List<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
532
533 Future<void> addTronToken(TronToken token) async {
cw_wownero/lib/wownero_wallet.dart
+18
@@ -174,6 +174,24 @@ abstract class WowneroWalletBase
174 @override
175 Future<void>? updateBalance() => null;
176
177 + @override
178 + Future<bool> checkNodeHealth() async {
179 + try {
180 + // Check if the wallet is currently connected to the daemon
181 + final isConnected = wownero_wallet.isConnectedSync();
182 +
183 + if (!isConnected) {
184 + return false; // It's not connected to daemon
185 + }
186 +
187 + // Check to get current node height to ensure daemon is responsive
188 + final nodeHeight = await wownero_wallet.getNodeHeight();
189 + return nodeHeight > 0;
190 + } catch (e) {
191 + return false;
192 + }
193 + }
194 +
195 @override
196 Future<void> close({bool shouldCleanup = false}) async {
197 _listener?.stop();
cw_zano/lib/zano_wallet.dart
+11
@@ -411,6 +411,17 @@ abstract class ZanoWalletBase
411 @override
412 Future<void>? updateBalance() => null;
413
414 + @override
415 + Future<bool> checkNodeHealth() async {
416 + try {
417 + final status = await getWalletStatus();
418 +
419 + return status.isDaemonConnected;
420 + } catch (_) {
421 + return false;
422 + }
423 + }
424 +
425 Future<void> updateTransactions() async {
426 try {
427 if (_isTransactionUpdating) {
lib/core/node_switching_service.dart new
+126
@@ -0,0 +1,126 @@
1 +import 'dart:async';
2 +import 'package:cw_core/node.dart';
3 +import 'package:cw_core/wallet_type.dart';
4 +import 'package:cake_wallet/store/app_store.dart';
5 +import 'package:cake_wallet/store/settings_store.dart';
6 +import 'package:cw_core/utils/print_verbose.dart';
7 +import 'package:hive/hive.dart';
8 +
9 +class NodeSwitchingService {
10 + NodeSwitchingService({
11 + required this.appStore,
12 + required this.settingsStore,
13 + required this.nodeSource,
14 + });
15 +
16 + static const int _healthCheckIntervalSeconds = 30;
17 +
18 + Timer? _healthCheckTimer;
19 +
20 + bool _isSwitching = false;
21 + bool get isSwitching => _isSwitching;
22 +
23 + final AppStore appStore;
24 + final SettingsStore settingsStore;
25 + final Box<Node> nodeSource;
26 +
27 + // Track used nodes to cycle through all trusted nodes
28 + final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
29 +
30 + void startHealthCheckTimer() {
31 + _healthCheckTimer?.cancel();
32 + _healthCheckTimer = Timer.periodic(
33 + Duration(seconds: _healthCheckIntervalSeconds),
34 + (_) => performHealthCheck(),
35 + );
36 +
37 + performHealthCheck();
38 + }
39 +
40 + void stopHealthCheckTimer() {
41 + _healthCheckTimer?.cancel();
42 + _healthCheckTimer = null;
43 + }
44 +
45 + Future<void> performHealthCheck() async {
46 + if (appStore.wallet == null) return;
47 +
48 + if (!settingsStore.enableAutomaticNodeSwitching) return;
49 +
50 + if (_isSwitching) return;
51 +
52 + try {
53 + final isHealthy = await appStore.wallet!.checkNodeHealth();
54 +
55 + if (!isHealthy) {
56 + await _switchToNextTrustedNode();
57 + }
58 + } catch (e) {
59 + printV('Error during health check: $e');
60 + }
61 + }
62 +
63 + /// Switch to the next available trusted node
64 + Future<void> _switchToNextTrustedNode() async {
65 + _isSwitching = true;
66 +
67 + try {
68 + final walletType = appStore.wallet!.type;
69 + final currentNode = settingsStore.getCurrentNode(walletType);
70 +
71 + // Get all trusted nodes for this wallet type
72 + final trustedNodes = nodeSource.values
73 + .where((node) => node.type == walletType && node.isEnabledForAutoSwitching)
74 + .toList();
75 +
76 + if (trustedNodes.isEmpty) {
77 + printV('No trusted nodes available for switching');
78 + return;
79 + }
80 +
81 + // Initialize used nodes list for this wallet type if it does not exist
82 + _usedNodeKeys.putIfAbsent(walletType, () => []);
83 +
84 + // Add current node to used list if not already there
85 + if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) {
86 + _usedNodeKeys[walletType]!.add(currentNode.key);
87 + }
88 +
89 + // Get next unused trusted node from the list
90 + Node? nextNode;
91 + for (final node in trustedNodes) {
92 + if (!_usedNodeKeys[walletType]!.contains(node.key)) {
93 + nextNode = node;
94 + break;
95 + }
96 + }
97 +
98 + // If all trusted nodes have been used, reset the list and start over
99 + if (nextNode == null) {
100 + printV('All trusted nodes have been tried, resetting and starting over');
101 + _usedNodeKeys[walletType]!.clear();
102 + nextNode = trustedNodes.first;
103 + }
104 +
105 + // Add the next node to used list
106 + _usedNodeKeys[walletType]!.add(nextNode.key);
107 +
108 + printV('Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw}');
109 + printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}');
110 +
111 + // Update the current node in settings
112 + settingsStore.nodes[walletType] = nextNode;
113 +
114 + // Connect the wallet to the new node
115 + await appStore.wallet!.connectToNode(node: nextNode);
116 +
117 + await appStore.wallet!.startSync();
118 +
119 + printV('Successfully switched to node: ${nextNode.uriRaw}');
120 + } catch (e) {
121 + printV('Error switching to next trusted node: $e');
122 + } finally {
123 + _isSwitching = false;
124 + }
125 + }
126 +}
lib/di.dart
+7
@@ -23,6 +23,7 @@ import 'package:cake_wallet/core/totp_request_details.dart';
23 import 'package:cake_wallet/core/wallet_creation_service.dart';
24 import 'package:cake_wallet/core/wallet_loading_service.dart';
25 import 'package:cake_wallet/core/yat_service.dart';
26 +import 'package:cake_wallet/core/node_switching_service.dart';
27 import 'package:cake_wallet/entities/biometric_auth.dart';
28 import 'package:cake_wallet/entities/contact.dart';
29 import 'package:cake_wallet/entities/contact_record.dart';
@@ -1516,5 +1517,11 @@ Future<void> setup({
1517
1518 getIt.registerFactory(() => DEuroSavingsPage(getIt<DEuroViewModel>()));
1519
1520 + getIt.registerLazySingleton(() => NodeSwitchingService(
1521 + appStore: getIt.get<AppStore>(),
1522 + settingsStore: getIt.get<SettingsStore>(),
1523 + nodeSource: _nodeSource,
1524 + ));
1525 +
1526 _isSetupFinished = true;
1527 }
lib/entities/default_settings_migration.dart
+50 -3
@@ -510,7 +510,10 @@ Future<void> defaultSettingsMigration(
510 providerName: "SwapTrade",
511 enabled: true,
512 );
513 - break;
513 + break;
514 + case 50:
515 + migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes);
516 + break;
517 default:
518 break;
519 }
@@ -1088,7 +1091,7 @@ Future<void> checkCurrentNodes(
1091
1092 if (currentBitcoinElectrumServer == null) {
1093 final cakeWalletElectrum =
1091 - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
1094 + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true);
1095 await nodeSource.add(cakeWalletElectrum);
1096 final cakeWalletElectrumTestnet =
1097 Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
@@ -1188,7 +1191,7 @@ Future<void> resetBitcoinElectrumServer(
1191
1192 if (cakeWalletNode == null) {
1193 cakeWalletNode =
1191 - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
1194 + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true);
1195 // final cakeWalletElectrumTestnet =
1196 // Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
1197 // await nodeSource.add(cakeWalletElectrumTestnet);
@@ -1272,3 +1275,47 @@ Future<void> removeMoneroWorld(
1275 );
1276 }
1277 }
1278 +
1279 +Future<void> migrateExistingNodesToUseAutoSwitching(
1280 + {required Box<Node> nodes, required Box<Node> powNodes}) async {
1281 + final listOfDefaultNodesWithAutoSwitching = [
1282 + 'bitcoincash.stackwallet.com:50002',
1283 + 'bch.aftrek.org:50002',
1284 + 'btc-electrum.cakewallet.com:50002',
1285 + 'fulcrum.sethforprivacy.com:50002',
1286 + 'default-spv-nodes',
1287 + 'dcrd.sethforprivacy.com:9108',
1288 + 'ethereum-rpc.publicnode.com',
1289 + 'eth.nownodes.io',
1290 + 'ltc-electrum.cakewallet.com:50002',
1291 + 'litecoin.stackwallet.com:20063',
1292 + 'nano.nownodes.io',
1293 + 'rpc.nano.to',
1294 + 'node.nautilus.io',
1295 + 'rpc.nano.to',
1296 + 'workers.perish.co',
1297 + 'worker.nanoriver.cc',
1298 + 'xmr-node.cakewallet.com:18081',
1299 + 'node.sethforprivacy.com:443',
1300 + 'nodes.hashvault.pro:18081',
1301 + 'polygon-bor-rpc.publicnode.com',
1302 + 'matic.nownodes.io',
1303 + 'api.mainnet-beta.solana.com:443',
1304 + 'solana-rpc.publicnode.com:443',
1305 + 'solana-mainnet.core.chainstack.com',
1306 + 'api.trongrid.io',
1307 + 'trx.nownodes.io',
1308 + 'node3.monerodevs.org:34568',
1309 + 'node2.monerodevs.org:34568',
1310 + '37.27.100.59:10500',
1311 + 'zano.cakewallet.com:11211',
1312 + 'electrum.cakewallet.com:50002',
1313 + ];
1314 + for (var node in [...nodes.values.toList(), ...powNodes.values.toList()]) {
1315 + if (listOfDefaultNodesWithAutoSwitching.contains(node.uriRaw)) {
1316 + node.isEnabledForAutoSwitching = true;
1317 + await node.save();
1318 + }
1319 + }
1320 +}
1321 +
lib/entities/preferences_key.dart
+1
@@ -112,4 +112,5 @@ class PreferencesKey {
112 '${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}';
113 static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}';
114 static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled';
115 + static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching';
116 }
lib/main.dart
+4 -1
@@ -6,6 +6,7 @@ import 'package:cake_wallet/app_scroll_behavior.dart';
6 import 'package:cake_wallet/buy/order.dart';
7 import 'package:cake_wallet/core/auth_service.dart';
8 import 'package:cake_wallet/core/background_sync.dart';
9 +import 'package:cake_wallet/core/node_switching_service.dart';
10 import 'package:cake_wallet/di.dart';
11 import 'package:cake_wallet/entities/contact.dart';
12 import 'package:cake_wallet/entities/default_settings_migration.dart';
@@ -249,7 +250,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
250 payjoinSessionSource: payjoinSessionSource,
251 anonpayInvoiceInfo: anonpayInvoiceInfo,
252 havenSeedStore: havenSeedStore,
252 - initialMigrationVersion: 49,
253 + initialMigrationVersion: 50,
254 );
255 }
256
@@ -327,6 +328,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
328 final authService = getIt.get<AuthService>();
329 final linkViewModel = getIt.get<LinkViewModel>();
330 final tradeMonitor = getIt.get<TradeMonitor>();
331 + final nodeSwitchingService = getIt.get<NodeSwitchingService>();
332 final settingsStore = appStore.settingsStore;
333 final statusBarColor = Colors.transparent;
334 final authenticationStore = getIt.get<AuthenticationStore>();
@@ -351,6 +353,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
353 authService: authService,
354 linkViewModel: linkViewModel,
355 tradeMonitor: tradeMonitor,
356 + nodeSwitchingService: nodeSwitchingService,
357 child: ThemeProvider(
358 themeStore: appStore.themeStore,
359 materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
lib/reactions/bootstrap.dart
+3
@@ -14,6 +14,7 @@ import 'package:cake_wallet/store/app_store.dart';
14 import 'package:cake_wallet/store/settings_store.dart';
15 import 'package:cake_wallet/store/authentication_store.dart';
16 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
17 +import 'package:cake_wallet/core/node_switching_service.dart';
18
19 Future<void> bootstrapOffline() async {
20 final authenticationStore = getIt.get<AuthenticationStore>();
@@ -40,4 +41,6 @@ void bootstrapOnline(GlobalKey<NavigatorState> navigatorKey, {required bool load
41 startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore);
42 startOnCurrentNodeChangeReaction(appStore);
43 startFiatRateUpdate(appStore, settingsStore, fiatConversionStore);
44 +
45 + getIt.get<NodeSwitchingService>().startHealthCheckTimer();
46 }
\ No newline at end of file
lib/src/screens/nodes/widgets/node_form.dart
+20
@@ -32,6 +32,7 @@ class NodeForm extends StatelessWidget {
32 ..setLogin((editingNode!.login ?? ''))
33 ..setSSL((editingNode!.isSSL))
34 ..setTrusted((editingNode!.trusted))
35 + ..setIsEnabledForAutoSwitching((editingNode!.isEnabledForAutoSwitching))
36 ..setSocksProxy((editingNode!.useSocksProxy))
37 ..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
38 }
@@ -153,6 +154,25 @@ class NodeForm extends StatelessWidget {
154 ),
155 ),
156 SizedBox(height: 10.0),
157 + Padding(
158 + padding: EdgeInsets.only(top: 20),
159 + child: Row(
160 + mainAxisAlignment: MainAxisAlignment.start,
161 + mainAxisSize: MainAxisSize.max,
162 + children: [
163 + Observer(
164 + builder: (_) => StandardCheckbox(
165 + value: nodeViewModel.isEnabledForAutoSwitching,
166 + borderColor: Theme.of(context).colorScheme.surfaceContainerHighest,
167 + iconColor: Theme.of(context).colorScheme.primary,
168 + onChanged: (value) => nodeViewModel.isEnabledForAutoSwitching = value,
169 + caption: S.current.enable_for_auto_switching,
170 + ),
171 + ),
172 + ],
173 + ),
174 + ),
175 + SizedBox(height: 10.0),
176 if (nodeViewModel.hasAuthCredentials) ...[
177 Row(
178 children: <Widget>[
lib/src/screens/root/root.dart
+8
@@ -2,8 +2,10 @@ import 'dart:async';
2 import 'dart:io';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/core/auth_service.dart';
5 +import 'package:cake_wallet/core/node_switching_service.dart';
6 import 'package:cake_wallet/core/totp_request_details.dart';
7 import 'package:cake_wallet/core/trade_monitor.dart';
8 +import 'package:cake_wallet/di.dart';
9 import 'package:cake_wallet/utils/device_info.dart';
10 import 'package:cake_wallet/view_model/link_view_model.dart';
11 import 'package:cw_core/utils/print_verbose.dart';
@@ -29,6 +31,7 @@ class Root extends StatefulWidget {
31 required this.authService,
32 required this.linkViewModel,
33 required this.tradeMonitor,
34 + required this.nodeSwitchingService,
35 }) : super(key: key);
36
37 final AuthenticationStore authenticationStore;
@@ -38,6 +41,7 @@ class Root extends StatefulWidget {
41 final Widget child;
42 final LinkViewModel linkViewModel;
43 final TradeMonitor tradeMonitor;
44 + final NodeSwitchingService nodeSwitchingService;
45
46 @override
47 RootState createState() => RootState();
@@ -161,6 +165,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
165 }
166
167 widget.tradeMonitor.resumeTradeMonitoring();
168 +
169 + // Trigger node health check when app resumes
170 + widget.nodeSwitchingService.performHealthCheck();
171 +
172 break;
173 default:
174 break;
lib/src/screens/settings/manage_nodes_page.dart
+18 -1
@@ -2,6 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/routes.dart';
3 import 'package:cake_wallet/src/screens/base_page.dart';
4 import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
5 +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
6 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
7 import 'package:cake_wallet/src/widgets/standard_list.dart';
8 import 'package:cake_wallet/utils/show_pop_up.dart';
@@ -34,7 +35,23 @@ class ManageNodesPage extends BasePage {
35 onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
36 ),
37 ),
37 - SizedBox(height: 20),
38 + Observer(
39 + builder: (_) => SettingsSwitcherCell(
40 + key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'),
41 + title: S.current.enable_auto_node_switching,
42 + value: isPow
43 + ? powNodeListViewModel!.enableAutomaticNodeSwitching
44 + : nodeListViewModel!.enableAutomaticNodeSwitching,
45 + onValueChange: (BuildContext context, bool value) {
46 + if (isPow) {
47 + powNodeListViewModel!.setEnableAutomaticNodeSwitching(value);
48 + } else {
49 + nodeListViewModel!.setEnableAutomaticNodeSwitching(value);
50 + }
51 + },
52 + ),
53 + ),
54 + SizedBox(height: 8),
55 Observer(
56 builder: (BuildContext context) {
57 int itemsCount =
lib/src/screens/settings/other_settings_page.dart
+14 -14
@@ -36,22 +36,22 @@ class OtherSettingsPage extends BasePage {
36 if (_otherSettingsViewModel.displayTransactionPriority)
37 _otherSettingsViewModel.walletType == WalletType.bitcoin ?
38 SettingsPriorityPickerCell(
39 - title: S.current.settings_fee_priority,
40 - items: priorityForWalletType(_otherSettingsViewModel.walletType),
41 - displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
42 - selectedItem: _otherSettingsViewModel.transactionPriority,
43 - customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
44 - onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
45 - customValue: _otherSettingsViewModel.customBitcoinFeeRate,
46 - maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
39 + title: S.current.settings_fee_priority,
40 + items: priorityForWalletType(_otherSettingsViewModel.walletType),
41 + displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
42 + selectedItem: _otherSettingsViewModel.transactionPriority,
43 + customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
44 + onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
45 + customValue: _otherSettingsViewModel.customBitcoinFeeRate,
46 + maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
47 ) :
48 SettingsPickerCell(
49 - title: S.current.settings_fee_priority,
50 - items: priorityForWalletType(_otherSettingsViewModel.walletType),
51 - displayItem: _otherSettingsViewModel.getDisplayPriority,
52 - selectedItem: _otherSettingsViewModel.transactionPriority,
53 - onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
54 - ),
49 + title: S.current.settings_fee_priority,
50 + items: priorityForWalletType(_otherSettingsViewModel.walletType),
51 + displayItem: _otherSettingsViewModel.getDisplayPriority,
52 + selectedItem: _otherSettingsViewModel.transactionPriority,
53 + onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
54 + ),
55 if (_otherSettingsViewModel.changeRepresentativeEnabled)
56 SettingsCellWithArrow(
57 title: S.current.change_rep,
lib/store/settings_store.dart
+16
@@ -128,6 +128,7 @@ abstract class SettingsStoreBase with Store {
128 required this.mwebEnabled,
129 required this.hasEnabledMwebBefore,
130 required this.mwebNodeUri,
131 + required bool initialEnableAutomaticNodeSwitching,
132 TransactionPriority? initialBitcoinTransactionPriority,
133 TransactionPriority? initialMoneroTransactionPriority,
134 TransactionPriority? initialWowneroTransactionPriority,
@@ -186,6 +187,7 @@ abstract class SettingsStoreBase with Store {
187 currentSyncMode = initialSyncMode,
188 currentSyncAll = initialSyncAll,
189 currentBuiltinTor = initialBuiltinTor,
190 + enableAutomaticNodeSwitching = initialEnableAutomaticNodeSwitching,
191 priority = ObservableMap<WalletType, TransactionPriority>() {
192 //this.nodes = ObservableMap<WalletType, Node>.of(nodes);
193
@@ -613,6 +615,11 @@ abstract class SettingsStoreBase with Store {
615 (_) => mwebNodeUri,
616 (String mwebNodeUri) =>
617 _sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri));
618 +
619 + reaction(
620 + (_) => enableAutomaticNodeSwitching,
621 + (bool enableAutomaticNodeSwitching) => _sharedPreferences.setBool(
622 + PreferencesKey.enableAutomaticNodeSwitching, enableAutomaticNodeSwitching));
623
624 this.nodes.observe((change) {
625 if (change.newValue != null && change.key != null) {
@@ -859,6 +866,9 @@ abstract class SettingsStoreBase with Store {
866 @observable
867 String mwebNodeUri;
868
869 + @observable
870 + bool enableAutomaticNodeSwitching;
871 +
872 final SecureStorage _secureStorage;
873 final SharedPreferences _sharedPreferences;
874
@@ -1034,6 +1044,8 @@ abstract class SettingsStoreBase with Store {
1044 sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
1045 final mwebNodeUri = sharedPreferences.getString(PreferencesKey.mwebNodeUri) ??
1046 "ltc-electrum.cakewallet.com:9333";
1047 + final enableAutomaticNodeSwitching =
1048 + sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ?? true;
1049
1050 // If no value
1051 if (pinLength == null || pinLength == 0) {
@@ -1335,6 +1347,7 @@ abstract class SettingsStoreBase with Store {
1347 mwebEnabled: mwebEnabled,
1348 mwebNodeUri: mwebNodeUri,
1349 hasEnabledMwebBefore: hasEnabledMwebBefore,
1350 + initialEnableAutomaticNodeSwitching: enableAutomaticNodeSwitching,
1351 initialMoneroTransactionPriority: moneroTransactionPriority,
1352 initialWowneroTransactionPriority: wowneroTransactionPriority,
1353 initialZanoTransactionPriority: zanoTransactionPriority,
@@ -1478,6 +1491,9 @@ abstract class SettingsStoreBase with Store {
1491 actionlistDisplayMode = ObservableList<ActionListDisplayMode>();
1492 actionlistDisplayMode.addAll(deserializeActionlistDisplayModes(
1493 sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode));
1494 + enableAutomaticNodeSwitching =
1495 + sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ??
1496 + enableAutomaticNodeSwitching;
1497 var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength);
1498 // If no value
1499 if (pinLength == null || pinLength == 0) {
lib/view_model/node_list/node_create_or_edit_view_model.dart
+10
@@ -26,6 +26,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
26 login = '',
27 password = '',
28 trusted = false,
29 + isEnabledForAutoSwitching = false,
30 useSocksProxy = false,
31 socksProxyAddress = '';
32
@@ -56,6 +57,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
57 @observable
58 bool trusted;
59
60 + @observable
61 + bool isEnabledForAutoSwitching;
62 +
63 @observable
64 bool useSocksProxy;
65
@@ -118,6 +122,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
122 password = '';
123 useSSL = false;
124 trusted = false;
125 + isEnabledForAutoSwitching = false;
126 useSocksProxy = false;
127 socksProxyAddress = '';
128 }
@@ -143,6 +148,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
148 @action
149 void setTrusted(bool val) => trusted = val;
150
151 + @action
152 + void setIsEnabledForAutoSwitching(bool val) => isEnabledForAutoSwitching = val;
153 +
154 @action
155 void setSocksProxy(bool val) => useSocksProxy = val;
156
@@ -159,6 +167,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
167 password: password,
168 useSSL: useSSL,
169 trusted: trusted,
170 + isEnabledForAutoSwitching: isEnabledForAutoSwitching,
171 socksProxyAddress: socksProxyAddress);
172 try {
173 state = IsExecutingState();
@@ -190,6 +199,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
199 password: password,
200 useSSL: useSSL,
201 trusted: trusted,
202 + isEnabledForAutoSwitching: isEnabledForAutoSwitching,
203 socksProxyAddress: socksProxyAddress);
204 try {
205 connectionState = IsExecutingState();
lib/view_model/node_list/node_list_view_model.dart
+7
@@ -41,6 +41,13 @@ abstract class NodeListViewModelBase with Store {
41 S.current.change_current_node(uri) +
42 '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}';
43
44 + @computed
45 + bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
46 +
47 + @action
48 + void setEnableAutomaticNodeSwitching(bool value) =>
49 + settingsStore.enableAutomaticNodeSwitching = value;
50 +
51 final ObservableList<Node> nodes;
52 final SettingsStore settingsStore;
53 final Box<Node> _nodeSource;
lib/view_model/node_list/pow_node_list_view_model.dart
+7
@@ -41,6 +41,13 @@ abstract class PowNodeListViewModelBase with Store {
41 S.current.change_current_node(uri) +
42 '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}';
43
44 + @computed
45 + bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching;
46 +
47 + @action
48 + void setEnableAutomaticNodeSwitching(bool value) =>
49 + settingsStore.enableAutomaticNodeSwitching = value;
50 +
51 final ObservableList<Node> nodes;
52 final SettingsStore settingsStore;
53 final Box<Node> _nodeSource;
res/values/strings_ar.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل",
307 "email_address": "عنوان البريد الالكترونى",
308 "enable": "يُمكَِن",
309 + "enable_auto_node_switching": "تمكين تبديل العقدة التلقائية",
310 "enable_builtin_tor": "تمكين بنيت في تور",
311 + "enable_for_auto_switching": "تمكين للتبديل التلقائي",
312 "enable_mempool_api": "MEMPOOL API للحصول على رسوم وتواريخ دقيقة",
313 "enable_replace_by_fee": "تمكين الاستبدال",
314 "enable_silent_payments_scanning": "ابدأ في مسح المدفوعات الصامتة ، حتى يتم الوصول إلى الطرف",
res/values/strings_bg.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят",
307 "email_address": "Имейл адрес",
308 "enable": "Активиране",
309 + "enable_auto_node_switching": "Активиране на автоматично превключване на възли",
310 "enable_builtin_tor": "Активирайте вграден Tor",
311 + "enable_for_auto_switching": "Активирайте за автоматично превключване",
312 "enable_mempool_api": "Mempool API за точни такси и дати",
313 "enable_replace_by_fee": "Активиране на замяна по забрана",
314 "enable_silent_payments_scanning": "Започнете да сканирате безшумните плащания, докато се достигне съветът",
res/values/strings_cs.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují",
307 "email_address": "E-mailová adresa",
308 "enable": "Umožnit",
309 + "enable_auto_node_switching": "Povolit automatické přepínání uzlů",
310 "enable_builtin_tor": "Povolte vestavěné tor",
311 + "enable_for_auto_switching": "Povolit automatické přepínání",
312 "enable_mempool_api": "Mempool API pro přesné poplatky a data",
313 "enable_replace_by_fee": "Povolit výměnu podle poplatku",
314 "enable_silent_payments_scanning": "Začněte skenovat tiché platby, dokud není dosaženo špičky",
res/values/strings_de.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin",
307 "email_address": "E-Mail-Adresse",
308 "enable": "Aktivieren",
309 + "enable_auto_node_switching": "Aktivieren Sie die automatische Knotenschaltung",
310 "enable_builtin_tor": "Aktivieren Sie den bau-in tor",
311 + "enable_for_auto_switching": "Aktivieren Sie die automatische Schaltung",
312 "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten",
313 "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee",
314 "enable_silent_payments_scanning": "Scannen Sie nach Silent Payments ihrer Adresse",
res/values/strings_en.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
307 "email_address": "Email Address",
308 "enable": "Enable",
309 + "enable_auto_node_switching": "Enable Automatic Node Switching",
310 "enable_builtin_tor": "Enable built-in Tor",
311 + "enable_for_auto_switching": "Enable for Auto-Switching",
312 "enable_mempool_api": "Mempool API for accurate fees and dates",
313 "enable_replace_by_fee": "Enable Replace-By-Fee",
314 "enable_silent_payments_scanning": "Start scanning for transactions sent to your Silent Payment address.",
res/values/strings_es.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando",
307 "email_address": "Dirección de correo electrónico",
308 "enable": "Permitir",
309 + "enable_auto_node_switching": "Habilitar la conmutación de nodo automático",
310 "enable_builtin_tor": "Habilitar Tor incorporado",
311 + "enable_for_auto_switching": "Habilitar para el cambio automático",
312 "enable_mempool_api": "API de Mempool para tarifas y fechas precisas",
313 "enable_replace_by_fee": "Habilitar reemplazar por tarea",
314 "enable_silent_payments_scanning": "Comienza a escanear pagos silenciosos, hasta que se alcance la altura actual",
res/values/strings_fr.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner",
307 "email_address": "Adresse e-mail",
308 "enable": "Activer",
309 + "enable_auto_node_switching": "Activer la commutation automatique du nœud",
310 "enable_builtin_tor": "Activer le Tor construit",
311 + "enable_for_auto_switching": "Activer pour le commutation automatique",
312 "enable_mempool_api": "API Mempool pour les frais et dates précis",
313 "enable_replace_by_fee": "Activer Remplace-by-Fee",
314 "enable_silent_payments_scanning": "Commencez à analyser les transactions envoyées à votre adresse de paiement silencieux.",
res/values/strings_ha.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Muna samar da sababbin adireshi duk lokacin da kuka yi amfani da ɗaya, amma adiresoshin da suka gabata suna ci gaba da aiki",
307 "email_address": "Adireshin i-mel",
308 "enable": "Ba dama",
309 + "enable_auto_node_switching": "Yana ba da damar kunna kumburi ta atomatik",
310 "enable_builtin_tor": "Sanya riguna-inf",
311 + "enable_for_auto_switching": "Kunna don sauyawa na atomatik",
312 "enable_mempool_api": "Mampool API don ingantattun kudade da kwanakin",
313 "enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin",
314 "enable_silent_payments_scanning": "Fara bincika biya na shiru, har sai tip ɗin ya kai",
res/values/strings_hi.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं",
307 "email_address": "ईमेल पता",
308 "enable": "सक्षम",
309 + "enable_auto_node_switching": "स्वचालित नोड स्विचिंग सक्षम करें",
310 "enable_builtin_tor": "बिल्ट-इन टोर को सक्षम करें",
311 + "enable_for_auto_switching": "ऑटो-स्विचिंग के लिए सक्षम करें",
312 "enable_mempool_api": "सटीक शुल्क और तिथियों के लिए मेमपूल एपीआई",
313 "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें",
314 "enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें",
res/values/strings_hr.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek",
307 "email_address": "Adresa e-pošte",
308 "enable": "Omogućiti",
309 + "enable_auto_node_switching": "Omogući automatsko prebacivanje čvorova",
310 "enable_builtin_tor": "Omogućite ugrađeni u tor",
311 + "enable_for_auto_switching": "Omogućite automatsko prebacivanje",
312 "enable_mempool_api": "Mempool API za točne naknade i datume",
313 "enable_replace_by_fee": "Omogući zamjenu",
314 "enable_silent_payments_scanning": "Započnite skeniranje tihih plaćanja, dok se ne postigne savjet",
res/values/strings_hy.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Մենք ստեղծում ենք նոր հասցե ամեն անգամ, երբ դուք օգտագործում եք այն, բայց նախորդ հասցեները շարունակում են աշխատել",
307 "email_address": "Էլ. փոստի հասցե",
308 "enable": "Միացնել",
309 + "enable_auto_node_switching": "Միացնել ավտոմատ հանգույցի անջատումը",
310 "enable_builtin_tor": "Միացրեք ներկառուցված ջահը",
311 + "enable_for_auto_switching": "Միացնել ավտոմատ անջատումը",
312 "enable_mempool_api": "Mempool API ճշգրիտ վճարների եւ ամսաթվերի համար",
313 "enable_replace_by_fee": "Միացնել փոխարինումը միջնորդավճարով",
314 "enable_silent_payments_scanning": "Միացնել Լուռ Վճարումների սկանավորումը",
res/values/strings_id.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Kami menghasilkan alamat baru setiap kali Anda menggunakan satu, tetapi alamat sebelumnya tetap berfungsi",
307 "email_address": "Alamat Email",
308 "enable": "Memungkinkan",
309 + "enable_auto_node_switching": "Aktifkan switching simpul otomatis",
310 "enable_builtin_tor": "Aktifkan built-in tor",
311 + "enable_for_auto_switching": "Aktifkan untuk pengalihan otomatis",
312 "enable_mempool_api": "API Mempool untuk biaya dan tanggal yang akurat",
313 "enable_replace_by_fee": "Aktifkan ganti-by-fee",
314 "enable_silent_payments_scanning": "Mulailah memindai pembayaran diam, sampai ujung tercapai",
res/values/strings_it.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare",
307 "email_address": "Indirizzo e-mail",
308 "enable": "Abilita",
309 + "enable_auto_node_switching": "Abilita la commutazione del nodo automatico",
310 "enable_builtin_tor": "Abilita buildi-in tor",
311 + "enable_for_auto_switching": "Abilita per il cambio automatico",
312 "enable_mempool_api": "API Mempool per commissioni e date accurate",
313 "enable_replace_by_fee": "Abilita Replace-By-Fee",
314 "enable_silent_payments_scanning": "Inizia a scansionare le transazioni inviate al tuo indirizzo Silent Payments",
res/values/strings_ja.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します",
307 "email_address": "メールアドレス",
308 "enable": "有効にする",
309 + "enable_auto_node_switching": "自動ノードスイッチングを有効にします",
310 "enable_builtin_tor": "組み込みのTORを有効にします",
311 + "enable_for_auto_switching": "自動スイッチを有効にします",
312 "enable_mempool_api": "正確な料金と日付のMempool API",
313 "enable_replace_by_fee": "交換ごとに有効にします",
314 "enable_silent_payments_scanning": "先端に達するまで、サイレント決済のスキャンを開始します",
res/values/strings_ko.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "사용할 때마다 새 주소를 생성하지만 이전 주소도 계속 작동합니다",
307 "email_address": "이메일 주소",
308 "enable": "활성화",
309 + "enable_auto_node_switching": "자동 노드 스위칭을 활성화합니다",
310 "enable_builtin_tor": "내장 Tor를 활성화하십시오",
311 + "enable_for_auto_switching": "자동 전환을 가능하게합니다",
312 "enable_mempool_api": "정확한 수수료 및 날짜를 위한 Mempool API",
313 "enable_replace_by_fee": "RBF(Replace-By-Fee) 활성화",
314 "enable_silent_payments_scanning": "사일런트 페이먼트 주소로 전송된 트랜잭션 스캔 시작",
res/values/strings_my.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "သင်အသုံးပြုသည့်အချိန်တိုင်းတွင် ကျွန်ုပ်တို့သည် လိပ်စာအသစ်များကို ထုတ်ပေးသော်လည်း ယခင်လိပ်စာများသည် ဆက်လက်အလုပ်လုပ်နေပါသည်။",
307 "email_address": "အီးမေးလ်လိပ်စာ",
308 "enable": "စွမ်းဆောင်နိုင်စေ",
309 + "enable_auto_node_switching": "အလိုအလျောက် node switching ကို enable",
310 "enable_builtin_tor": "built-in Tor ကို Enable လုပ်ပါ",
311 + "enable_for_auto_switching": "Auto-switching အတွက်ဖွင့်ပါ",
312 "enable_mempool_api": "Mempool API တိကျသောအခကြေးငွေနှင့်ရက်စွဲများအတွက်",
313 "enable_replace_by_fee": "အစားထိုး - by- အခကြေးငွေ enable",
314 "enable_silent_payments_scanning": "အစွန်အဖျားသို့ရောက်ရှိသည်အထိအသံတိတ်ငွေပေးချေမှုကိုစကင်ဖတ်စစ်ဆေးပါ",
res/values/strings_nl.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "We genereren nieuwe adressen elke keer dat u er een gebruikt, maar eerdere adressen blijven werken",
307 "email_address": "E-mailadres",
308 "enable": "Inschakelen",
309 + "enable_auto_node_switching": "Schakel automatische knoopschakeling in",
310 "enable_builtin_tor": "Schakel ingebouwde tor in",
311 + "enable_for_auto_switching": "Inschakelen voor automatisch schakelen",
312 "enable_mempool_api": "Mempool API voor nauwkeurige kosten en datums",
313 "enable_replace_by_fee": "Schakel vervangen door een fee",
314 "enable_silent_payments_scanning": "Begin met het scannen van stille betalingen, totdat de tip is bereikt",
res/values/strings_pl.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Za każdym razem, gdy wykorzystasz adres, dla wiekszej prywatności generujemy nowy, ale poprzednie adresy nadal działają, i moga odbierać środki",
307 "email_address": "Adres e-mail",
308 "enable": "Włącz",
309 + "enable_auto_node_switching": "Włącz automatyczne przełączanie węzłów",
310 "enable_builtin_tor": "Włącz wbudowany tor",
311 + "enable_for_auto_switching": "Włącz do automatycznego przełączania",
312 "enable_mempool_api": "Mempool API dla dokładnych opłat i dat",
313 "enable_replace_by_fee": "Włącz wymianę po lewej",
314 "enable_silent_payments_scanning": "Zacznij skanować Silent Payments",
res/values/strings_pt.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando",
307 "email_address": "Endereço de e-mail",
308 "enable": "Habilitar",
309 + "enable_auto_node_switching": "Ativar comutação automática de nó",
310 "enable_builtin_tor": "Ative o Buildi-in Tor",
311 + "enable_for_auto_switching": "Ativar a troca automática",
312 "enable_mempool_api": "Mempool API para taxas e datas precisas",
313 "enable_replace_by_fee": "Habilite substituir por taxa",
314 "enable_silent_payments_scanning": "Comece a escanear pagamentos silenciosos, até que o topo seja alcançada",
res/values/strings_ru.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.",
307 "email_address": "Адрес электронной почты",
308 "enable": "Давать возможность",
309 + "enable_auto_node_switching": "Включить автоматическое переключение узлов",
310 "enable_builtin_tor": "Включить встроенный в Tor",
311 + "enable_for_auto_switching": "Включить для автоматического переключения",
312 "enable_mempool_api": "Mempool API за точные сборы и даты",
313 "enable_replace_by_fee": "Включить замену за пикой",
314 "enable_silent_payments_scanning": "Начните сканировать безмолвные платежи, пока не будет достигнут наконечник",
res/values/strings_th.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "เราสร้างที่อยู่ใหม่ทุกครั้งที่คุณใช้หนึ่งอย่าง แต่ที่อยู่เก่ายังสามารถใช้ได้ต่อไป",
307 "email_address": "ที่อยู่อีเมล",
308 "enable": "เปิดใช้งาน",
309 + "enable_auto_node_switching": "เปิดใช้งานการสลับโหนดอัตโนมัติ",
310 "enable_builtin_tor": "เปิดใช้งาน Tor ในตัว",
311 + "enable_for_auto_switching": "เปิดใช้งานการสลับอัตโนมัติ",
312 "enable_mempool_api": "Mempool API สำหรับค่าธรรมเนียมและวันที่ที่ถูกต้อง",
313 "enable_replace_by_fee": "เปิดใช้งานการเปลี่ยนโดยค่าธรรมเนียม",
314 "enable_silent_payments_scanning": "เริ่มสแกนการชำระเงินแบบเงียบจนกว่าจะถึงปลาย",
res/values/strings_tl.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Bumubuo kami ng mga bagong address sa tuwing gagamit ka ng isa, ngunit ang mga nakaraang address ay patuloy na gumagana",
307 "email_address": "Email Address",
308 "enable": "Paganahin",
309 + "enable_auto_node_switching": "Paganahin ang awtomatikong paglipat ng node",
310 "enable_builtin_tor": "Paganahin ang built-in tor",
311 + "enable_for_auto_switching": "Paganahin para sa auto-switch",
312 "enable_mempool_api": "Mempool API para sa tumpak na bayad at mga petsa",
313 "enable_replace_by_fee": "Paganahin ang Replace-By-Fee",
314 "enable_silent_payments_scanning": "Simulan ang pag -scan ng tahimik na pagbabayad, hanggang sa maabot ang tip",
res/values/strings_tr.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Adresini her kullandığında yeni adres oluşturuyoruz, ancak önceki adresler de çalışmaya devam eder",
307 "email_address": "E-posta Adresi",
308 "enable": "Olanak vermek",
309 + "enable_auto_node_switching": "Otomatik Düğüm Anahtarlamasını Etkinleştir",
310 "enable_builtin_tor": "Yerleşik Tor'u etkinleştirin",
311 + "enable_for_auto_switching": "Otomatik anahtarlama için etkinleştirin",
312 "enable_mempool_api": "Doğru ücretler ve tarihler için Mempool API'si",
313 "enable_replace_by_fee": "Farklı Değiştir'i Etkinleştir",
314 "enable_silent_payments_scanning": "Bahşiş ulaşılıncaya kadar sessiz ödemeleri taramaya başlayın",
res/values/strings_uk.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати",
307 "email_address": "Адреса електронної пошти",
308 "enable": "Ввімкнути",
309 + "enable_auto_node_switching": "Увімкнути автоматичний перемикач вузлів",
310 "enable_builtin_tor": "Увімкнути вбудований tor",
311 + "enable_for_auto_switching": "Увімкнути автоматичне перемикання",
312 "enable_mempool_api": "API Mempool для точних зборів та дат",
313 "enable_replace_by_fee": "Увімкнути заміну з комісією",
314 "enable_silent_payments_scanning": "Почніть сканувати мовчазні платежі, поки не буде досягнуто наконечника",
res/values/strings_ur.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "جب بھی آپ ایک کا استعمال کرتے ہیں تو ہم نئے پتے تیار کرتے ہیں، لیکن پچھلے پتے کام کرتے رہتے ہیں۔",
307 "email_address": "ای میل اڈریس",
308 "enable": "قابل بنائیں",
309 + "enable_auto_node_switching": "خودکار نوڈ سوئچنگ کو فعال کریں",
310 "enable_builtin_tor": "بلٹٹی ان ٹور کو فعال کریں",
311 + "enable_for_auto_switching": "آٹو سوئچنگ کے لئے قابل بنائیں",
312 "enable_mempool_api": "درست فیسوں اور تاریخوں کے لئے میمپول API",
313 "enable_replace_by_fee": "فی فیس کو تبدیل کریں",
314 "enable_silent_payments_scanning": "خاموش ادائیگیوں کو اسکین کرنا شروع کریں ، جب تک کہ نوک نہ پہنچ جائے",
res/values/strings_vi.arb
+2
@@ -305,7 +305,9 @@
305 "electrum_address_disclaimer": "Chúng tôi tạo địa chỉ mới mỗi khi bạn sử dụng, nhưng các địa chỉ cũ vẫn tiếp tục hoạt động",
306 "email_address": "Địa chỉ Email",
307 "enable": "Cho phép",
308 + "enable_auto_node_switching": "Bật chuyển đổi nút tự động",
309 "enable_builtin_tor": "Bật Buildi-In Tor",
310 + "enable_for_auto_switching": "Kích hoạt để chuyển đổi tự động",
311 "enable_mempool_api": "API Mempool cho các khoản phí và ngày chính xác",
312 "enable_replace_by_fee": "Bật Thay thế Bằng Phí",
313 "enable_silent_payments_scanning": "Bật quét thanh toán im lặng",
res/values/strings_yo.arb
+2
@@ -307,7 +307,9 @@
307 "electrum_address_disclaimer": "A dá àwọn àdírẹ́sì títun ní gbogbo àwọn ìgbà t'ẹ́ lo ó kan ṣùgbọ́n ẹ lè tẹ̀síwájú lo àwọn àdírẹ́sì tẹ́lẹ̀tẹ́lẹ̀.",
308 "email_address": "Àdírẹ́sì ímeèlì",
309 "enable": "Mu ṣiṣẹ",
310 + "enable_auto_node_switching": "Mu oju-ọna yiyọ laifọwọyi",
311 "enable_builtin_tor": "Mu ṣiṣẹ",
312 + "enable_for_auto_switching": "Mu ṣiṣẹ fun adaṣe-pada",
313 "enable_mempool_api": "Mempool API fun awọn owo deede ati awọn ọjọ",
314 "enable_replace_by_fee": "Mu ki o rọpo",
315 "enable_silent_payments_scanning": "Bẹrẹ awọn sisanwo ipalọlọ, titi ti o fi de opin",
res/values/strings_zh.arb
+2
@@ -306,7 +306,9 @@
306 "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效",
307 "email_address": "电子邮件地址",
308 "enable": "使能够",
309 + "enable_auto_node_switching": "启用自动节点开关",
310 "enable_builtin_tor": "启用内置的tor",
311 + "enable_for_auto_switching": "启用自动开关",
312 "enable_mempool_api": "Mempool API获得准确的费用和日期",
313 "enable_replace_by_fee": "启用by-Fee替换",
314 "enable_silent_payments_scanning": "开始扫描无声付款,直到达到提示",