From 4b2af63fa5dc24432dbc86ff0d69c66e17ccbaf1 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:42:59 +0100 Subject: [PATCH] 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 --- assets/banano_node_list.yml | 3 +- assets/bitcoin_cash_electrum_server_list.yml | 2 + assets/bitcoin_electrum_server_list.yml | 6 + assets/decred_node_list.yml | 4 +- assets/ethereum_server_list.yml | 2 + assets/litecoin_electrum_server_list.yml | 2 + assets/nano_node_list.yml | 3 + assets/nano_pow_node_list.yml | 3 + assets/node_list.yml | 3 + assets/polygon_node_list.yml | 4 +- assets/solana_node_list.yml | 5 +- assets/tron_node_list.yml | 4 +- assets/wownero_node_list.yml | 2 + assets/zano_node_list.yml | 4 +- cw_bitcoin/lib/electrum.dart | 28 ++-- cw_bitcoin/lib/electrum_wallet.dart | 20 +++ cw_core/lib/node.dart | 7 +- cw_core/lib/wallet_base.dart | 5 + cw_decred/lib/api/libdcrwallet.dart | 5 +- cw_decred/lib/wallet.dart | 4 +- cw_evm/lib/evm_chain_client.dart | 5 +- cw_evm/lib/evm_chain_wallet.dart | 42 +++++- cw_monero/lib/monero_wallet.dart | 18 +++ cw_nano/lib/nano_client.dart | 5 +- cw_nano/lib/nano_wallet.dart | 11 ++ cw_solana/lib/solana_client.dart | 56 ++++---- cw_solana/lib/solana_wallet.dart | 26 +++- cw_tron/lib/tron_client.dart | 10 +- cw_tron/lib/tron_wallet.dart | 23 ++++ cw_wownero/lib/wownero_wallet.dart | 18 +++ cw_zano/lib/zano_wallet.dart | 11 ++ lib/core/node_switching_service.dart | 126 ++++++++++++++++++ lib/di.dart | 7 + lib/entities/default_settings_migration.dart | 53 +++++++- lib/entities/preferences_key.dart | 1 + lib/main.dart | 5 +- lib/reactions/bootstrap.dart | 3 + lib/src/screens/nodes/widgets/node_form.dart | 20 +++ lib/src/screens/root/root.dart | 8 ++ .../screens/settings/manage_nodes_page.dart | 19 ++- .../screens/settings/other_settings_page.dart | 28 ++-- lib/store/settings_store.dart | 16 +++ .../node_create_or_edit_view_model.dart | 10 ++ .../node_list/node_list_view_model.dart | 7 + .../node_list/pow_node_list_view_model.dart | 7 + res/values/strings_ar.arb | 2 + res/values/strings_bg.arb | 2 + res/values/strings_cs.arb | 2 + res/values/strings_de.arb | 2 + res/values/strings_en.arb | 2 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 2 + res/values/strings_ha.arb | 2 + res/values/strings_hi.arb | 2 + res/values/strings_hr.arb | 2 + res/values/strings_hy.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 2 + res/values/strings_ja.arb | 2 + res/values/strings_ko.arb | 2 + res/values/strings_my.arb | 2 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 2 + res/values/strings_pt.arb | 2 + res/values/strings_ru.arb | 2 + res/values/strings_th.arb | 2 + res/values/strings_tl.arb | 2 + res/values/strings_tr.arb | 2 + res/values/strings_uk.arb | 2 + res/values/strings_ur.arb | 2 + res/values/strings_vi.arb | 2 + res/values/strings_yo.arb | 2 + res/values/strings_zh.arb | 2 + 73 files changed, 639 insertions(+), 68 deletions(-) create mode 100644 lib/core/node_switching_service.dart diff --git a/assets/banano_node_list.yml b/assets/banano_node_list.yml index dc6852867..a7178963f 100644 --- a/assets/banano_node_list.yml +++ b/assets/banano_node_list.yml @@ -2,4 +2,5 @@ uri: kaliumapi.appditto.com path: /api useSSL: true - is_default: true \ No newline at end of file + is_default: true + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/assets/bitcoin_cash_electrum_server_list.yml b/assets/bitcoin_cash_electrum_server_list.yml index 948e5f3dc..770bdbc05 100644 --- a/assets/bitcoin_cash_electrum_server_list.yml +++ b/assets/bitcoin_cash_electrum_server_list.yml @@ -2,9 +2,11 @@ uri: bitcoincash.stackwallet.com:50002 is_default: true useSSL: true + isEnabledForAutoSwitching: true - uri: bch.aftrek.org:50002 useSSL: true + isEnabledForAutoSwitching: true - uri: node.minisatoshi.cash:50002 useSSL: true diff --git a/assets/bitcoin_electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml index 83da6a0b2..c3a92e948 100644 --- a/assets/bitcoin_electrum_server_list.yml +++ b/assets/bitcoin_electrum_server_list.yml @@ -2,8 +2,14 @@ uri: btc-electrum.cakewallet.com:50002 useSSL: true isDefault: true + isEnabledForAutoSwitching: true - uri: electrs.cakewallet.com:50001 - uri: fulcrum.sethforprivacy.com:50002 useSSL: true + isEnabledForAutoSwitching: true +- + uri: electrum.cakewallet.com:50002 + useSSL: true + isEnabledForAutoSwitching: true diff --git a/assets/decred_node_list.yml b/assets/decred_node_list.yml index cb171e701..9f5938e8c 100644 --- a/assets/decred_node_list.yml +++ b/assets/decred_node_list.yml @@ -1,6 +1,8 @@ - uri: default-spv-nodes is_default: true + isEnabledForAutoSwitching: true - uri: dcrd.sethforprivacy.com:9108 - useSSL: true \ No newline at end of file + useSSL: true + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/assets/ethereum_server_list.yml b/assets/ethereum_server_list.yml index ed425c3c7..2eae58bc5 100644 --- a/assets/ethereum_server_list.yml +++ b/assets/ethereum_server_list.yml @@ -2,6 +2,7 @@ uri: ethereum-rpc.publicnode.com useSSL: true isDefault: true + isEnabledForAutoSwitching: true - uri: eth.llamarpc.com - @@ -10,5 +11,6 @@ uri: eth-mainnet.public.blastapi.io - uri: eth.nownodes.io + isEnabledForAutoSwitching: true - uri: ethereum.publicnode.com \ No newline at end of file diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml index 550b900e1..18b982aa1 100644 --- a/assets/litecoin_electrum_server_list.yml +++ b/assets/litecoin_electrum_server_list.yml @@ -2,9 +2,11 @@ uri: ltc-electrum.cakewallet.com:50002 useSSL: true isDefault: true + isEnabledForAutoSwitching: true - uri: litecoin.stackwallet.com:20063 useSSL: true + isEnabledForAutoSwitching: true - uri: electrum-ltc.bysh.me:50002 useSSL: true diff --git a/assets/nano_node_list.yml b/assets/nano_node_list.yml index cda931b5e..aa1d8e3c9 100644 --- a/assets/nano_node_list.yml +++ b/assets/nano_node_list.yml @@ -1,14 +1,17 @@ - uri: nano.nownodes.io useSSL: true + isEnabledForAutoSwitching: true - uri: rpc.nano.to is_default: true useSSL: true + isEnabledForAutoSwitching: true - uri: node.nautilus.io path: /api useSSL: true + isEnabledForAutoSwitching: true - uri: app.natrium.io path: /api diff --git a/assets/nano_pow_node_list.yml b/assets/nano_pow_node_list.yml index 3bbc7c3fb..ec1b10ff9 100644 --- a/assets/nano_pow_node_list.yml +++ b/assets/nano_pow_node_list.yml @@ -2,8 +2,11 @@ uri: rpc.nano.to useSSL: true is_default: true + isEnabledForAutoSwitching: true - uri: workers.perish.co + isEnabledForAutoSwitching: true - uri: worker.nanoriver.cc useSSL: true + isEnabledForAutoSwitching: true diff --git a/assets/node_list.yml b/assets/node_list.yml index 917dadfd9..851d69042 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -3,6 +3,7 @@ is_default: true trusted: true useSSL: true + isEnabledForAutoSwitching: true - uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 is_default: false @@ -10,6 +11,8 @@ uri: node.sethforprivacy.com:443 useSSL: true is_default: false + isEnabledForAutoSwitching: true - uri: nodes.hashvault.pro:18081 is_default: false + isEnabledForAutoSwitching: true diff --git a/assets/polygon_node_list.yml b/assets/polygon_node_list.yml index 3b2cdcdc3..74dc23ef4 100644 --- a/assets/polygon_node_list.yml +++ b/assets/polygon_node_list.yml @@ -4,7 +4,9 @@ uri: polygon-bor-rpc.publicnode.com useSSL: true isDefault: true + isEnabledForAutoSwitching: true - uri: polygon.llamarpc.com - - uri: matic.nownodes.io \ No newline at end of file + uri: matic.nownodes.io + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml index 3ba74d980..e1d7971c6 100644 --- a/assets/solana_node_list.yml +++ b/assets/solana_node_list.yml @@ -4,10 +4,13 @@ - uri: api.mainnet-beta.solana.com:443 useSSL: true + isEnabledForAutoSwitching: true - uri: solana-rpc.publicnode.com:443 useSSL: true + isEnabledForAutoSwitching: true - uri: solana-mainnet.core.chainstack.com useSSL: true - is_default: true \ No newline at end of file + is_default: true + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/assets/tron_node_list.yml b/assets/tron_node_list.yml index 1e34de712..595bba971 100644 --- a/assets/tron_node_list.yml +++ b/assets/tron_node_list.yml @@ -6,6 +6,8 @@ uri: api.trongrid.io is_default: true useSSL: true + isEnabledForAutoSwitching: true - uri: trx.nownodes.io - useSSL: true \ No newline at end of file + useSSL: true + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/assets/wownero_node_list.yml b/assets/wownero_node_list.yml index a4873b3e4..893f82984 100644 --- a/assets/wownero_node_list.yml +++ b/assets/wownero_node_list.yml @@ -2,10 +2,12 @@ uri: node3.monerodevs.org:34568 is_default: true useSSL: false + isEnabledForAutoSwitching: true - uri: node2.monerodevs.org:34568 is_default: false useSSL: false + isEnabledForAutoSwitching: true - uri: node.monerodevs.org:34568 is_default: false diff --git a/assets/zano_node_list.yml b/assets/zano_node_list.yml index f7b874fcb..26a1b797d 100644 --- a/assets/zano_node_list.yml +++ b/assets/zano_node_list.yml @@ -1,7 +1,9 @@ - uri: 37.27.100.59:10500 useSSL: false + isEnabledForAutoSwitching: true - uri: zano.cakewallet.com:11211 is_default: true - useSSL: false \ No newline at end of file + useSSL: false + isEnabledForAutoSwitching: true \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 2ddd30df6..4cd42a0fe 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -203,15 +203,25 @@ class ElectrumClient { return []; }); - Future> getBalance(String scriptHash) => - call(method: 'blockchain.scripthash.get_balance', params: [scriptHash]) - .then((dynamic result) { - if (result is Map) { - return result; - } - - return {}; - }); + Future> getBalance(String scriptHash, {bool throwOnError = false}) async { + try { + final result = await call(method: 'blockchain.scripthash.get_balance', params: [scriptHash]); + if (result is Map) { + return result; + } + + if (throwOnError) { + throw Exception('Invalid response format for getBalance'); + } + + return {}; + } catch (e) { + if (throwOnError) { + rethrow; + } + return {}; + } + } Future>> getHistory(String scriptHash) => call(method: 'blockchain.scripthash.get_history', params: [scriptHash]) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index bf20cac80..28d0e323a 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -2300,6 +2300,26 @@ abstract class ElectrumWalletBase await save(); } + @override + Future checkNodeHealth() async { + try { + final addresses = walletAddresses.allAddresses + .where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress) + .toList(); + + if (addresses.isEmpty) { + return false; + } + + final firstAddress = addresses.first; + final sh = firstAddress.getScriptHash(network); + await electrumClient.getBalance(sh, throwOnError: true); + return true; + } catch (e) { + return false; + } + } + @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index fdffb844b..ff94001f2 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -26,6 +26,7 @@ class Node extends HiveObject with Keyable { this.trusted = false, this.socksProxyAddress, this.path = '', + this.isEnabledForAutoSwitching = false, String? uri, WalletType? type, }) { @@ -44,7 +45,8 @@ class Node extends HiveObject with Keyable { password = map['password'] as String?, useSSL = map['useSSL'] as bool?, trusted = map['trusted'] as bool? ?? false, - socksProxyAddress = map['socksProxyPort'] as String?; + socksProxyAddress = map['socksProxyPort'] as String?, + isEnabledForAutoSwitching = map['isEnabledForAutoSwitching'] as bool? ?? false; static const typeId = NODE_TYPE_ID; static const boxName = 'Nodes'; @@ -82,6 +84,9 @@ class Node extends HiveObject with Keyable { @HiveField(10) bool? supportsMweb; + @HiveField(11, defaultValue: false) + bool isEnabledForAutoSwitching; + bool get isSSL => useSSL ?? false; bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 0ca7ff2b1..0d7650e15 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -112,4 +112,9 @@ abstract class WalletBase true; + + /// This is used to check if the current node is healthy by making a lightweight RPC call + /// Each wallet implementation should override this to make a single, efficient call + /// Returns true if the node is healthy, false otherwise + Future checkNodeHealth(); } diff --git a/cw_decred/lib/api/libdcrwallet.dart b/cw_decred/lib/api/libdcrwallet.dart index b5e5bbe73..a6d1e1017 100644 --- a/cw_decred/lib/api/libdcrwallet.dart +++ b/cw_decred/lib/api/libdcrwallet.dart @@ -454,7 +454,7 @@ class Libwallet { return res.payload; } - Future balance(String walletName) async { + Future balance(String walletName, {bool throwOnError = false}) async { if (_closed) throw StateError('Closed'); final completer = Completer.sync(); final id = _idCounter++; @@ -468,6 +468,9 @@ class Libwallet { try { return jsonDecode(res.payload); } catch (_) { + if (throwOnError) { + rethrow; + } return {}; } } diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index 97aee775d..fb177f4ca 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -26,7 +26,6 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -594,6 +593,9 @@ abstract class DecredWalletBase ); } + @override + Future checkNodeHealth() async => await checkSync(); + @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError; diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index b7377c54e..29a88dcce 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -65,10 +65,13 @@ abstract class EVMChainClient { // }); } - Future getBalance(EthereumAddress address) async { + Future getBalance(EthereumAddress address, {bool throwOnError = false}) async { try { return await _client!.getBalance(address); } catch (_) { + if (throwOnError) { + rethrow; + } return EtherAmount.zero(); } } diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index fe97e4ea8..9f998f714 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -164,6 +164,36 @@ abstract class EVMChainWalletBase EncryptionFileUtils encryptionFileUtils, ); + @override + Future checkNodeHealth() async { + try { + // Check native balance + await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true); + + // Check USDC token balance + String usdcContractAddress; + if (_client.chainId == 1) { + // Ethereum mainnet + usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + } else if (_client.chainId == 137) { + // Polygon mainnet + usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"; + } else { + // If we are not on Ethereum or Polygon, we skip ERC20 token check + return true; + } + + await _client.fetchERC20Balances( + _evmChainPrivateKey.address, + usdcContractAddress, + ); + + return true; + } catch (e) { + return false; + } + } + //! Common Methods across child classes String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name'; @@ -379,15 +409,21 @@ abstract class EVMChainWalletBase Future startSync() async { try { syncStatus = AttemptingSyncStatus(); + + // Verify node health before attempting to sync + final isHealthy = await checkNodeHealth(); + if (!isHealthy) { + syncStatus = FailedSyncStatus(); + return; + } + await _updateBalance(); await _updateTransactions(); - await _updateEstimatedGasFeeParams(); _updateFeesTimer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { await _updateEstimatedGasFeeParams(); }); - syncStatus = SyncedSyncStatus(); } catch (e) { syncStatus = FailedSyncStatus(); @@ -825,7 +861,7 @@ abstract class EVMChainWalletBase _transactionsUpdateTimer!.cancel(); } - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 15), (_) { + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) { _updateTransactions(); _updateBalance(); }); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 72d02c506..029b642d5 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -184,6 +184,24 @@ abstract class MoneroWalletBase extends WalletBase? updateBalance() => null; + @override + Future checkNodeHealth() async { + try { + // Check if the wallet is currently connected to the daemon + final isConnected = monero_wallet.isConnectedSync(); + + if (!isConnected) { + return false; // It's not connected to daemon + } + + // Check to get current node height to ensure daemon is responsive + final nodeHeight = await monero_wallet.getNodeHeight(); + return nodeHeight > 0; + } catch (e) { + return false; + } + } + @override Future close({bool shouldCleanup = false}) async { if (isHardwareWallet) { diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index c852a8c0d..812291f93 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -92,7 +92,7 @@ class NanoClient { return NanoBalance(currentBalance: cur, receivableBalance: rec); } - Future getAccountInfo(String address) async { + Future getAccountInfo(String address, {bool throwOnError = false}) async { try { final response = await ProxyWrapper().post( clearnetUri: _node!.uri, @@ -110,6 +110,9 @@ class NanoClient { return AccountInfoResponse.fromJson(data); } catch (e) { printV("error while getting account info $e"); + if (throwOnError) { + rethrow; + } return null; } } diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index eb2d2581c..a07612544 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -518,8 +518,19 @@ abstract class NanoWalletBase return _client.getN2Reps(); } + @override Future? updateBalance() async => await _updateBalance(); + @override + Future checkNodeHealth() async { + try { + await _client.getAccountInfo(_publicAddress!, throwOnError: true); + return true; + } catch (_) { + return false; + } + } + @override Future renameWalletFiles(String newWalletName) async { final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index d57da5af0..f91f66223 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -51,7 +51,7 @@ class SolanaWalletClient { } } - Future getBalance(String walletAddress) async { + Future getBalance(String walletAddress, {bool throwOnError = false}) async { try { final balance = await _provider!.requestWithContext( SolanaRPCGetBalance( @@ -65,6 +65,9 @@ class SolanaWalletClient { return solBalance; } catch (_) { + if (throwOnError) { + rethrow; + } return 0.0; } } @@ -88,31 +91,38 @@ class SolanaWalletClient { } Future getSplTokenBalance( - String mintAddress, String walletAddress) async { - // Fetch the token accounts (a token can have multiple accounts for various uses) - final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress); + String mintAddress, String walletAddress, {bool throwOnError = false}) async { + try { + // Fetch the token accounts (a token can have multiple accounts for various uses) + final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress); - // Handle scenario where there is no token account - if (tokenAccounts == null || tokenAccounts.isEmpty) { + // Handle scenario where there is no token account + if (tokenAccounts == null || tokenAccounts.isEmpty) { + return null; + } + + // Sum the balances of all accounts with the specified mint address + double totalBalance = 0.0; + + for (var tokenAccount in tokenAccounts) { + final tokenAmountResult = await _provider!.request( + SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey), + ); + + final balance = tokenAmountResult.uiAmountString; + + final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0; + + totalBalance += balanceAsDouble; + } + + return SolanaBalance(totalBalance); + } catch (_) { + if (throwOnError) { + rethrow; + } return null; } - - // Sum the balances of all accounts with the specified mint address - double totalBalance = 0.0; - - for (var tokenAccount in tokenAccounts) { - final tokenAmountResult = await _provider!.request( - SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey), - ); - - final balance = tokenAmountResult.uiAmountString; - - final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0; - - totalBalance += balanceAsDouble; - } - - return SolanaBalance(totalBalance); } Future getFeeForMessage(String message, Commitment commitment) async { diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 455f510ac..87acaf4d5 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -377,6 +377,13 @@ abstract class SolanaWalletBase try { syncStatus = AttemptingSyncStatus(); + // Verify node health before attempting to sync + final isHealthy = await checkNodeHealth(); + if (!isHealthy) { + syncStatus = FailedSyncStatus(); + return; + } + await Future.wait([ _updateBalance(), _updateNativeSOLTransactions(), @@ -477,6 +484,22 @@ abstract class SolanaWalletBase @override Future? updateBalance() async => await _updateBalance(); + @override + Future checkNodeHealth() async { + try { + // Check native balance + await _client.getBalance(solanaAddress, throwOnError: true); + + // Check USDC token balance + const usdcMintAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; + await _client.getSplTokenBalance(usdcMintAddress, solanaAddress, throwOnError: true); + + return true; + } catch (e) { + return false; + } + } + List get splTokenCurrencies => splTokensBox.values.toList(); void addInitialTokens() { @@ -554,6 +577,7 @@ abstract class SolanaWalletBase _updateBalance(); _updateNativeSOLTransactions(); _updateSPLTokenTransactions(); + _getEstimatedFees(); }); } @@ -564,7 +588,7 @@ abstract class SolanaWalletBase // Sign the message bytes with the wallet's private key final signature = (_solanaPrivateKey.sign(messageBytes)); - + return Base58Encoder.encode(signature); } diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index f73e5a306..c7c530d53 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -102,12 +102,15 @@ class TronClient { } } - Future getBalance(TronAddress address) async { + Future getBalance(TronAddress address, {bool throwOnError = false}) async { try { final accountDetails = await _provider!.request(TronRequestGetAccount(address: address)); return accountDetails?.balance ?? BigInt.zero; } catch (_) { + if (throwOnError) { + rethrow; + } return BigInt.zero; } } @@ -476,7 +479,7 @@ class TronClient { } } - Future fetchTronTokenBalances(String userAddress, String contractAddress) async { + Future fetchTronTokenBalances(String userAddress, String contractAddress, {bool throwOnError = false}) async { try { final ownerAddress = TronAddress(userAddress); @@ -499,6 +502,9 @@ class TronClient { return TronBalance(outputResult); } catch (_) { + if (throwOnError) { + rethrow; + } return TronBalance(BigInt.zero); } } diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index eb80696ce..ce1b22d0e 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -276,6 +276,13 @@ abstract class TronWalletBase Future startSync() async { try { syncStatus = AttemptingSyncStatus(); + + // Verify node health before attempting to sync + final isHealthy = await checkNodeHealth(); + if (!isHealthy) { + syncStatus = FailedSyncStatus(); + return; + } await _updateBalance(); await fetchTransactions(); fetchTrc20ExcludedTransactions(); @@ -505,6 +512,22 @@ abstract class TronWalletBase @override Future? updateBalance() async => await _updateBalance(); + @override + Future checkNodeHealth() async { + try { + // Check native balance + await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true); + + // Check USDT token balance + const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; + await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true); + + return true; + } catch (e) { + return false; + } + } + List get tronTokenCurrencies => tronTokensBox.values.toList(); Future addTronToken(TronToken token) async { diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index 2befaa20d..a90d32ee3 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -174,6 +174,24 @@ abstract class WowneroWalletBase @override Future? updateBalance() => null; + @override + Future checkNodeHealth() async { + try { + // Check if the wallet is currently connected to the daemon + final isConnected = wownero_wallet.isConnectedSync(); + + if (!isConnected) { + return false; // It's not connected to daemon + } + + // Check to get current node height to ensure daemon is responsive + final nodeHeight = await wownero_wallet.getNodeHeight(); + return nodeHeight > 0; + } catch (e) { + return false; + } + } + @override Future close({bool shouldCleanup = false}) async { _listener?.stop(); diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index 7f68b71e6..c5534f6c8 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -411,6 +411,17 @@ abstract class ZanoWalletBase @override Future? updateBalance() => null; + @override + Future checkNodeHealth() async { + try { + final status = await getWalletStatus(); + + return status.isDaemonConnected; + } catch (_) { + return false; + } + } + Future updateTransactions() async { try { if (_isTransactionUpdating) { diff --git a/lib/core/node_switching_service.dart b/lib/core/node_switching_service.dart new file mode 100644 index 000000000..ae5d21762 --- /dev/null +++ b/lib/core/node_switching_service.dart @@ -0,0 +1,126 @@ +import 'dart:async'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:hive/hive.dart'; + +class NodeSwitchingService { + NodeSwitchingService({ + required this.appStore, + required this.settingsStore, + required this.nodeSource, + }); + + static const int _healthCheckIntervalSeconds = 30; + + Timer? _healthCheckTimer; + + bool _isSwitching = false; + bool get isSwitching => _isSwitching; + + final AppStore appStore; + final SettingsStore settingsStore; + final Box nodeSource; + + // Track used nodes to cycle through all trusted nodes + final Map> _usedNodeKeys = {}; + + void startHealthCheckTimer() { + _healthCheckTimer?.cancel(); + _healthCheckTimer = Timer.periodic( + Duration(seconds: _healthCheckIntervalSeconds), + (_) => performHealthCheck(), + ); + + performHealthCheck(); + } + + void stopHealthCheckTimer() { + _healthCheckTimer?.cancel(); + _healthCheckTimer = null; + } + + Future performHealthCheck() async { + if (appStore.wallet == null) return; + + if (!settingsStore.enableAutomaticNodeSwitching) return; + + if (_isSwitching) return; + + try { + final isHealthy = await appStore.wallet!.checkNodeHealth(); + + if (!isHealthy) { + await _switchToNextTrustedNode(); + } + } catch (e) { + printV('Error during health check: $e'); + } + } + + /// Switch to the next available trusted node + Future _switchToNextTrustedNode() async { + _isSwitching = true; + + try { + final walletType = appStore.wallet!.type; + final currentNode = settingsStore.getCurrentNode(walletType); + + // Get all trusted nodes for this wallet type + final trustedNodes = nodeSource.values + .where((node) => node.type == walletType && node.isEnabledForAutoSwitching) + .toList(); + + if (trustedNodes.isEmpty) { + printV('No trusted nodes available for switching'); + return; + } + + // Initialize used nodes list for this wallet type if it does not exist + _usedNodeKeys.putIfAbsent(walletType, () => []); + + // Add current node to used list if not already there + if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) { + _usedNodeKeys[walletType]!.add(currentNode.key); + } + + // Get next unused trusted node from the list + Node? nextNode; + for (final node in trustedNodes) { + if (!_usedNodeKeys[walletType]!.contains(node.key)) { + nextNode = node; + break; + } + } + + // If all trusted nodes have been used, reset the list and start over + if (nextNode == null) { + printV('All trusted nodes have been tried, resetting and starting over'); + _usedNodeKeys[walletType]!.clear(); + nextNode = trustedNodes.first; + } + + // Add the next node to used list + _usedNodeKeys[walletType]!.add(nextNode.key); + + printV('Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw}'); + printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}'); + + // Update the current node in settings + settingsStore.nodes[walletType] = nextNode; + + // Connect the wallet to the new node + await appStore.wallet!.connectToNode(node: nextNode); + + await appStore.wallet!.startSync(); + + printV('Successfully switched to node: ${nextNode.uriRaw}'); + } catch (e) { + printV('Error switching to next trusted node: $e'); + } finally { + _isSwitching = false; + } + } +} diff --git a/lib/di.dart b/lib/di.dart index 6440285e6..32f7cab0e 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/core/yat_service.dart'; +import 'package:cake_wallet/core/node_switching_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; @@ -1516,5 +1517,11 @@ Future setup({ getIt.registerFactory(() => DEuroSavingsPage(getIt())); + getIt.registerLazySingleton(() => NodeSwitchingService( + appStore: getIt.get(), + settingsStore: getIt.get(), + nodeSource: _nodeSource, + )); + _isSetupFinished = true; } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index a5dfec97b..35634c391 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -510,7 +510,10 @@ Future defaultSettingsMigration( providerName: "SwapTrade", enabled: true, ); - break; + break; + case 50: + migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes); + break; default: break; } @@ -1088,7 +1091,7 @@ Future checkCurrentNodes( if (currentBitcoinElectrumServer == null) { final cakeWalletElectrum = - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true); await nodeSource.add(cakeWalletElectrum); final cakeWalletElectrumTestnet = Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false); @@ -1188,7 +1191,7 @@ Future resetBitcoinElectrumServer( if (cakeWalletNode == null) { cakeWalletNode = - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false); + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false, isEnabledForAutoSwitching: true); // final cakeWalletElectrumTestnet = // Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false); // await nodeSource.add(cakeWalletElectrumTestnet); @@ -1272,3 +1275,47 @@ Future removeMoneroWorld( ); } } + +Future migrateExistingNodesToUseAutoSwitching( + {required Box nodes, required Box powNodes}) async { + final listOfDefaultNodesWithAutoSwitching = [ + 'bitcoincash.stackwallet.com:50002', + 'bch.aftrek.org:50002', + 'btc-electrum.cakewallet.com:50002', + 'fulcrum.sethforprivacy.com:50002', + 'default-spv-nodes', + 'dcrd.sethforprivacy.com:9108', + 'ethereum-rpc.publicnode.com', + 'eth.nownodes.io', + 'ltc-electrum.cakewallet.com:50002', + 'litecoin.stackwallet.com:20063', + 'nano.nownodes.io', + 'rpc.nano.to', + 'node.nautilus.io', + 'rpc.nano.to', + 'workers.perish.co', + 'worker.nanoriver.cc', + 'xmr-node.cakewallet.com:18081', + 'node.sethforprivacy.com:443', + 'nodes.hashvault.pro:18081', + 'polygon-bor-rpc.publicnode.com', + 'matic.nownodes.io', + 'api.mainnet-beta.solana.com:443', + 'solana-rpc.publicnode.com:443', + 'solana-mainnet.core.chainstack.com', + 'api.trongrid.io', + 'trx.nownodes.io', + 'node3.monerodevs.org:34568', + 'node2.monerodevs.org:34568', + '37.27.100.59:10500', + 'zano.cakewallet.com:11211', + 'electrum.cakewallet.com:50002', + ]; + for (var node in [...nodes.values.toList(), ...powNodes.values.toList()]) { + if (listOfDefaultNodesWithAutoSwitching.contains(node.uriRaw)) { + node.isEnabledForAutoSwitching = true; + await node.save(); + } + } +} + diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index aca8f79b4..d0e6a56fc 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -112,4 +112,5 @@ class PreferencesKey { '${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}'; static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}'; static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled'; + static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching'; } diff --git a/lib/main.dart b/lib/main.dart index 67110ee0c..0b9f1b323 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/background_sync.dart'; +import 'package:cake_wallet/core/node_switching_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/default_settings_migration.dart'; @@ -249,7 +250,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { payjoinSessionSource: payjoinSessionSource, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 49, + initialMigrationVersion: 50, ); } @@ -327,6 +328,7 @@ class AppState extends State with SingleTickerProviderStateMixin { final authService = getIt.get(); final linkViewModel = getIt.get(); final tradeMonitor = getIt.get(); + final nodeSwitchingService = getIt.get(); final settingsStore = appStore.settingsStore; final statusBarColor = Colors.transparent; final authenticationStore = getIt.get(); @@ -351,6 +353,7 @@ class AppState extends State with SingleTickerProviderStateMixin { authService: authService, linkViewModel: linkViewModel, tradeMonitor: tradeMonitor, + nodeSwitchingService: nodeSwitchingService, child: ThemeProvider( themeStore: appStore.themeStore, materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp( diff --git a/lib/reactions/bootstrap.dart b/lib/reactions/bootstrap.dart index 6fb4c5ab2..5f16c5b54 100644 --- a/lib/reactions/bootstrap.dart +++ b/lib/reactions/bootstrap.dart @@ -14,6 +14,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/core/node_switching_service.dart'; Future bootstrapOffline() async { final authenticationStore = getIt.get(); @@ -40,4 +41,6 @@ void bootstrapOnline(GlobalKey navigatorKey, {required bool load startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore); startOnCurrentNodeChangeReaction(appStore); startFiatRateUpdate(appStore, settingsStore, fiatConversionStore); + + getIt.get().startHealthCheckTimer(); } \ No newline at end of file diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart index 11bfea20a..5036d8a2a 100644 --- a/lib/src/screens/nodes/widgets/node_form.dart +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -32,6 +32,7 @@ class NodeForm extends StatelessWidget { ..setLogin((editingNode!.login ?? '')) ..setSSL((editingNode!.isSSL)) ..setTrusted((editingNode!.trusted)) + ..setIsEnabledForAutoSwitching((editingNode!.isEnabledForAutoSwitching)) ..setSocksProxy((editingNode!.useSocksProxy)) ..setSocksProxyAddress((editingNode!.socksProxyAddress ?? '')); } @@ -153,6 +154,25 @@ class NodeForm extends StatelessWidget { ), ), SizedBox(height: 10.0), + Padding( + padding: EdgeInsets.only(top: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Observer( + builder: (_) => StandardCheckbox( + value: nodeViewModel.isEnabledForAutoSwitching, + borderColor: Theme.of(context).colorScheme.surfaceContainerHighest, + iconColor: Theme.of(context).colorScheme.primary, + onChanged: (value) => nodeViewModel.isEnabledForAutoSwitching = value, + caption: S.current.enable_for_auto_switching, + ), + ), + ], + ), + ), + SizedBox(height: 10.0), if (nodeViewModel.hasAuthCredentials) ...[ Row( children: [ diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 19e8cab5f..a73c20fb8 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/core/node_switching_service.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/trade_monitor.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -29,6 +31,7 @@ class Root extends StatefulWidget { required this.authService, required this.linkViewModel, required this.tradeMonitor, + required this.nodeSwitchingService, }) : super(key: key); final AuthenticationStore authenticationStore; @@ -38,6 +41,7 @@ class Root extends StatefulWidget { final Widget child; final LinkViewModel linkViewModel; final TradeMonitor tradeMonitor; + final NodeSwitchingService nodeSwitchingService; @override RootState createState() => RootState(); @@ -161,6 +165,10 @@ class RootState extends State with WidgetsBindingObserver { } widget.tradeMonitor.resumeTradeMonitoring(); + + // Trigger node health check when app resumes + widget.nodeSwitchingService.performHealthCheck(); + break; default: break; diff --git a/lib/src/screens/settings/manage_nodes_page.dart b/lib/src/screens/settings/manage_nodes_page.dart index a5db773cc..92d1d5e76 100644 --- a/lib/src/screens/settings/manage_nodes_page.dart +++ b/lib/src/screens/settings/manage_nodes_page.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -34,7 +35,23 @@ class ManageNodesPage extends BasePage { onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode), ), ), - SizedBox(height: 20), + Observer( + builder: (_) => SettingsSwitcherCell( + key: ValueKey('manage_nodes_page_enable_auto_node_switching_button_key'), + title: S.current.enable_auto_node_switching, + value: isPow + ? powNodeListViewModel!.enableAutomaticNodeSwitching + : nodeListViewModel!.enableAutomaticNodeSwitching, + onValueChange: (BuildContext context, bool value) { + if (isPow) { + powNodeListViewModel!.setEnableAutomaticNodeSwitching(value); + } else { + nodeListViewModel!.setEnableAutomaticNodeSwitching(value); + } + }, + ), + ), + SizedBox(height: 8), Observer( builder: (BuildContext context) { int itemsCount = diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index 2ff9dcdaf..78125fbeb 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -36,22 +36,22 @@ class OtherSettingsPage extends BasePage { if (_otherSettingsViewModel.displayTransactionPriority) _otherSettingsViewModel.walletType == WalletType.bitcoin ? SettingsPriorityPickerCell( - title: S.current.settings_fee_priority, - items: priorityForWalletType(_otherSettingsViewModel.walletType), - displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, - selectedItem: _otherSettingsViewModel.transactionPriority, - customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, - onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected, - customValue: _otherSettingsViewModel.customBitcoinFeeRate, - maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(), + title: S.current.settings_fee_priority, + items: priorityForWalletType(_otherSettingsViewModel.walletType), + displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, + selectedItem: _otherSettingsViewModel.transactionPriority, + customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, + onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected, + customValue: _otherSettingsViewModel.customBitcoinFeeRate, + maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(), ) : SettingsPickerCell( - title: S.current.settings_fee_priority, - items: priorityForWalletType(_otherSettingsViewModel.walletType), - displayItem: _otherSettingsViewModel.getDisplayPriority, - selectedItem: _otherSettingsViewModel.transactionPriority, - onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, - ), + title: S.current.settings_fee_priority, + items: priorityForWalletType(_otherSettingsViewModel.walletType), + displayItem: _otherSettingsViewModel.getDisplayPriority, + selectedItem: _otherSettingsViewModel.transactionPriority, + onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, + ), if (_otherSettingsViewModel.changeRepresentativeEnabled) SettingsCellWithArrow( title: S.current.change_rep, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 219ef962d..37e118b4e 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -128,6 +128,7 @@ abstract class SettingsStoreBase with Store { required this.mwebEnabled, required this.hasEnabledMwebBefore, required this.mwebNodeUri, + required bool initialEnableAutomaticNodeSwitching, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialWowneroTransactionPriority, @@ -186,6 +187,7 @@ abstract class SettingsStoreBase with Store { currentSyncMode = initialSyncMode, currentSyncAll = initialSyncAll, currentBuiltinTor = initialBuiltinTor, + enableAutomaticNodeSwitching = initialEnableAutomaticNodeSwitching, priority = ObservableMap() { //this.nodes = ObservableMap.of(nodes); @@ -613,6 +615,11 @@ abstract class SettingsStoreBase with Store { (_) => mwebNodeUri, (String mwebNodeUri) => _sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri)); + + reaction( + (_) => enableAutomaticNodeSwitching, + (bool enableAutomaticNodeSwitching) => _sharedPreferences.setBool( + PreferencesKey.enableAutomaticNodeSwitching, enableAutomaticNodeSwitching)); this.nodes.observe((change) { if (change.newValue != null && change.key != null) { @@ -859,6 +866,9 @@ abstract class SettingsStoreBase with Store { @observable String mwebNodeUri; + @observable + bool enableAutomaticNodeSwitching; + final SecureStorage _secureStorage; final SharedPreferences _sharedPreferences; @@ -1034,6 +1044,8 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false; final mwebNodeUri = sharedPreferences.getString(PreferencesKey.mwebNodeUri) ?? "ltc-electrum.cakewallet.com:9333"; + final enableAutomaticNodeSwitching = + sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ?? true; // If no value if (pinLength == null || pinLength == 0) { @@ -1335,6 +1347,7 @@ abstract class SettingsStoreBase with Store { mwebEnabled: mwebEnabled, mwebNodeUri: mwebNodeUri, hasEnabledMwebBefore: hasEnabledMwebBefore, + initialEnableAutomaticNodeSwitching: enableAutomaticNodeSwitching, initialMoneroTransactionPriority: moneroTransactionPriority, initialWowneroTransactionPriority: wowneroTransactionPriority, initialZanoTransactionPriority: zanoTransactionPriority, @@ -1478,6 +1491,9 @@ abstract class SettingsStoreBase with Store { actionlistDisplayMode = ObservableList(); actionlistDisplayMode.addAll(deserializeActionlistDisplayModes( sharedPreferences.getInt(PreferencesKey.displayActionListModeKey) ?? defaultActionsMode)); + enableAutomaticNodeSwitching = + sharedPreferences.getBool(PreferencesKey.enableAutomaticNodeSwitching) ?? + enableAutomaticNodeSwitching; var pinLength = sharedPreferences.getInt(PreferencesKey.currentPinLength); // If no value if (pinLength == null || pinLength == 0) { diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 78544aa82..715f7e05f 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -26,6 +26,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { login = '', password = '', trusted = false, + isEnabledForAutoSwitching = false, useSocksProxy = false, socksProxyAddress = ''; @@ -56,6 +57,9 @@ abstract class NodeCreateOrEditViewModelBase with Store { @observable bool trusted; + @observable + bool isEnabledForAutoSwitching; + @observable bool useSocksProxy; @@ -118,6 +122,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { password = ''; useSSL = false; trusted = false; + isEnabledForAutoSwitching = false; useSocksProxy = false; socksProxyAddress = ''; } @@ -143,6 +148,9 @@ abstract class NodeCreateOrEditViewModelBase with Store { @action void setTrusted(bool val) => trusted = val; + @action + void setIsEnabledForAutoSwitching(bool val) => isEnabledForAutoSwitching = val; + @action void setSocksProxy(bool val) => useSocksProxy = val; @@ -159,6 +167,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { password: password, useSSL: useSSL, trusted: trusted, + isEnabledForAutoSwitching: isEnabledForAutoSwitching, socksProxyAddress: socksProxyAddress); try { state = IsExecutingState(); @@ -190,6 +199,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { password: password, useSSL: useSSL, trusted: trusted, + isEnabledForAutoSwitching: isEnabledForAutoSwitching, socksProxyAddress: socksProxyAddress); try { connectionState = IsExecutingState(); diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 9cd9ce959..463def285 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -41,6 +41,13 @@ abstract class NodeListViewModelBase with Store { S.current.change_current_node(uri) + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}'; + @computed + bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching; + + @action + void setEnableAutomaticNodeSwitching(bool value) => + settingsStore.enableAutomaticNodeSwitching = value; + final ObservableList nodes; final SettingsStore settingsStore; final Box _nodeSource; diff --git a/lib/view_model/node_list/pow_node_list_view_model.dart b/lib/view_model/node_list/pow_node_list_view_model.dart index 5100676f4..310a0d311 100644 --- a/lib/view_model/node_list/pow_node_list_view_model.dart +++ b/lib/view_model/node_list/pow_node_list_view_model.dart @@ -41,6 +41,13 @@ abstract class PowNodeListViewModelBase with Store { S.current.change_current_node(uri) + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}'; + @computed + bool get enableAutomaticNodeSwitching => settingsStore.enableAutomaticNodeSwitching; + + @action + void setEnableAutomaticNodeSwitching(bool value) => + settingsStore.enableAutomaticNodeSwitching = value; + final ObservableList nodes; final SettingsStore settingsStore; final Box _nodeSource; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 82c64614b..071fe5efa 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل", "email_address": "عنوان البريد الالكترونى", "enable": "يُمكَِن", + "enable_auto_node_switching": "تمكين تبديل العقدة التلقائية", "enable_builtin_tor": "تمكين بنيت في تور", + "enable_for_auto_switching": "تمكين للتبديل التلقائي", "enable_mempool_api": "MEMPOOL API للحصول على رسوم وتواريخ دقيقة", "enable_replace_by_fee": "تمكين الاستبدال", "enable_silent_payments_scanning": "ابدأ في مسح المدفوعات الصامتة ، حتى يتم الوصول إلى الطرف", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 66f0e102f..b05a4c788 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят", "email_address": "Имейл адрес", "enable": "Активиране", + "enable_auto_node_switching": "Активиране на автоматично превключване на възли", "enable_builtin_tor": "Активирайте вграден Tor", + "enable_for_auto_switching": "Активирайте за автоматично превключване", "enable_mempool_api": "Mempool API за точни такси и дати", "enable_replace_by_fee": "Активиране на замяна по забрана", "enable_silent_payments_scanning": "Започнете да сканирате безшумните плащания, докато се достигне съветът", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index daa5836fa..fc5ef5ca3 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují", "email_address": "E-mailová adresa", "enable": "Umožnit", + "enable_auto_node_switching": "Povolit automatické přepínání uzlů", "enable_builtin_tor": "Povolte vestavěné tor", + "enable_for_auto_switching": "Povolit automatické přepínání", "enable_mempool_api": "Mempool API pro přesné poplatky a data", "enable_replace_by_fee": "Povolit výměnu podle poplatku", "enable_silent_payments_scanning": "Začněte skenovat tiché platby, dokud není dosaženo špičky", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index ca9e443f7..82a66c708 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "email_address": "E-Mail-Adresse", "enable": "Aktivieren", + "enable_auto_node_switching": "Aktivieren Sie die automatische Knotenschaltung", "enable_builtin_tor": "Aktivieren Sie den bau-in tor", + "enable_for_auto_switching": "Aktivieren Sie die automatische Schaltung", "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enable_silent_payments_scanning": "Scannen Sie nach Silent Payments ihrer Adresse", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 5287ed915..ac1062e30 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", "email_address": "Email Address", "enable": "Enable", + "enable_auto_node_switching": "Enable Automatic Node Switching", "enable_builtin_tor": "Enable built-in Tor", + "enable_for_auto_switching": "Enable for Auto-Switching", "enable_mempool_api": "Mempool API for accurate fees and dates", "enable_replace_by_fee": "Enable Replace-By-Fee", "enable_silent_payments_scanning": "Start scanning for transactions sent to your Silent Payment address.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 9b74a0155..1d5d3ed8b 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", "email_address": "Dirección de correo electrónico", "enable": "Permitir", + "enable_auto_node_switching": "Habilitar la conmutación de nodo automático", "enable_builtin_tor": "Habilitar Tor incorporado", + "enable_for_auto_switching": "Habilitar para el cambio automático", "enable_mempool_api": "API de Mempool para tarifas y fechas precisas", "enable_replace_by_fee": "Habilitar reemplazar por tarea", "enable_silent_payments_scanning": "Comienza a escanear pagos silenciosos, hasta que se alcance la altura actual", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index dc24a7384..702d7cd15 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -306,7 +306,9 @@ "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", "email_address": "Adresse e-mail", "enable": "Activer", + "enable_auto_node_switching": "Activer la commutation automatique du nœud", "enable_builtin_tor": "Activer le Tor construit", + "enable_for_auto_switching": "Activer pour le commutation automatique", "enable_mempool_api": "API Mempool pour les frais et dates précis", "enable_replace_by_fee": "Activer Remplace-by-Fee", "enable_silent_payments_scanning": "Commencez à analyser les transactions envoyées à votre adresse de paiement silencieux.", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 55fd6affc..0b3cdbd1d 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -306,7 +306,9 @@ "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", "email_address": "Adireshin i-mel", "enable": "Ba dama", + "enable_auto_node_switching": "Yana ba da damar kunna kumburi ta atomatik", "enable_builtin_tor": "Sanya riguna-inf", + "enable_for_auto_switching": "Kunna don sauyawa na atomatik", "enable_mempool_api": "Mampool API don ingantattun kudade da kwanakin", "enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin", "enable_silent_payments_scanning": "Fara bincika biya na shiru, har sai tip ɗin ya kai", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index a758041ae..c8afe7792 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", "email_address": "ईमेल पता", "enable": "सक्षम", + "enable_auto_node_switching": "स्वचालित नोड स्विचिंग सक्षम करें", "enable_builtin_tor": "बिल्ट-इन टोर को सक्षम करें", + "enable_for_auto_switching": "ऑटो-स्विचिंग के लिए सक्षम करें", "enable_mempool_api": "सटीक शुल्क और तिथियों के लिए मेमपूल एपीआई", "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें", "enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 67e677bf0..2566c9182 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", "email_address": "Adresa e-pošte", "enable": "Omogućiti", + "enable_auto_node_switching": "Omogući automatsko prebacivanje čvorova", "enable_builtin_tor": "Omogućite ugrađeni u tor", + "enable_for_auto_switching": "Omogućite automatsko prebacivanje", "enable_mempool_api": "Mempool API za točne naknade i datume", "enable_replace_by_fee": "Omogući zamjenu", "enable_silent_payments_scanning": "Započnite skeniranje tihih plaćanja, dok se ne postigne savjet", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 46eb69490..ed49e4c47 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Մենք ստեղծում ենք նոր հասցե ամեն անգամ, երբ դուք օգտագործում եք այն, բայց նախորդ հասցեները շարունակում են աշխատել", "email_address": "Էլ. փոստի հասցե", "enable": "Միացնել", + "enable_auto_node_switching": "Միացնել ավտոմատ հանգույցի անջատումը", "enable_builtin_tor": "Միացրեք ներկառուցված ջահը", + "enable_for_auto_switching": "Միացնել ավտոմատ անջատումը", "enable_mempool_api": "Mempool API ճշգրիտ վճարների եւ ամսաթվերի համար", "enable_replace_by_fee": "Միացնել փոխարինումը միջնորդավճարով", "enable_silent_payments_scanning": "Միացնել Լուռ Վճարումների սկանավորումը", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index a1e6018df..b18cdc4a8 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Kami menghasilkan alamat baru setiap kali Anda menggunakan satu, tetapi alamat sebelumnya tetap berfungsi", "email_address": "Alamat Email", "enable": "Memungkinkan", + "enable_auto_node_switching": "Aktifkan switching simpul otomatis", "enable_builtin_tor": "Aktifkan built-in tor", + "enable_for_auto_switching": "Aktifkan untuk pengalihan otomatis", "enable_mempool_api": "API Mempool untuk biaya dan tanggal yang akurat", "enable_replace_by_fee": "Aktifkan ganti-by-fee", "enable_silent_payments_scanning": "Mulailah memindai pembayaran diam, sampai ujung tercapai", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 3ab18f7b9..33698fee8 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", "email_address": "Indirizzo e-mail", "enable": "Abilita", + "enable_auto_node_switching": "Abilita la commutazione del nodo automatico", "enable_builtin_tor": "Abilita buildi-in tor", + "enable_for_auto_switching": "Abilita per il cambio automatico", "enable_mempool_api": "API Mempool per commissioni e date accurate", "enable_replace_by_fee": "Abilita Replace-By-Fee", "enable_silent_payments_scanning": "Inizia a scansionare le transazioni inviate al tuo indirizzo Silent Payments", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 96a2d0019..7baa5eefa 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", "email_address": "メールアドレス", "enable": "有効にする", + "enable_auto_node_switching": "自動ノードスイッチングを有効にします", "enable_builtin_tor": "組み込みのTORを有効にします", + "enable_for_auto_switching": "自動スイッチを有効にします", "enable_mempool_api": "正確な料金と日付のMempool API", "enable_replace_by_fee": "交換ごとに有効にします", "enable_silent_payments_scanning": "先端に達するまで、サイレント決済のスキャンを開始します", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 27fa0e894..51e53a705 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "사용할 때마다 새 주소를 생성하지만 이전 주소도 계속 작동합니다", "email_address": "이메일 주소", "enable": "활성화", + "enable_auto_node_switching": "자동 노드 스위칭을 활성화합니다", "enable_builtin_tor": "내장 Tor를 활성화하십시오", + "enable_for_auto_switching": "자동 전환을 가능하게합니다", "enable_mempool_api": "정확한 수수료 및 날짜를 위한 Mempool API", "enable_replace_by_fee": "RBF(Replace-By-Fee) 활성화", "enable_silent_payments_scanning": "사일런트 페이먼트 주소로 전송된 트랜잭션 스캔 시작", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a8bcd850f..43ac42edd 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "သင်အသုံးပြုသည့်အချိန်တိုင်းတွင် ကျွန်ုပ်တို့သည် လိပ်စာအသစ်များကို ထုတ်ပေးသော်လည်း ယခင်လိပ်စာများသည် ဆက်လက်အလုပ်လုပ်နေပါသည်။", "email_address": "အီးမေးလ်လိပ်စာ", "enable": "စွမ်းဆောင်နိုင်စေ", + "enable_auto_node_switching": "အလိုအလျောက် node switching ကို enable", "enable_builtin_tor": "built-in Tor ကို Enable လုပ်ပါ", + "enable_for_auto_switching": "Auto-switching အတွက်ဖွင့်ပါ", "enable_mempool_api": "Mempool API တိကျသောအခကြေးငွေနှင့်ရက်စွဲများအတွက်", "enable_replace_by_fee": "အစားထိုး - by- အခကြေးငွေ enable", "enable_silent_payments_scanning": "အစွန်အဖျားသို့ရောက်ရှိသည်အထိအသံတိတ်ငွေပေးချေမှုကိုစကင်ဖတ်စစ်ဆေးပါ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 3934be573..fe96778d1 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "We genereren nieuwe adressen elke keer dat u er een gebruikt, maar eerdere adressen blijven werken", "email_address": "E-mailadres", "enable": "Inschakelen", + "enable_auto_node_switching": "Schakel automatische knoopschakeling in", "enable_builtin_tor": "Schakel ingebouwde tor in", + "enable_for_auto_switching": "Inschakelen voor automatisch schakelen", "enable_mempool_api": "Mempool API voor nauwkeurige kosten en datums", "enable_replace_by_fee": "Schakel vervangen door een fee", "enable_silent_payments_scanning": "Begin met het scannen van stille betalingen, totdat de tip is bereikt", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 810f70500..0fe61f3bd 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -306,7 +306,9 @@ "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", "email_address": "Adres e-mail", "enable": "Włącz", + "enable_auto_node_switching": "Włącz automatyczne przełączanie węzłów", "enable_builtin_tor": "Włącz wbudowany tor", + "enable_for_auto_switching": "Włącz do automatycznego przełączania", "enable_mempool_api": "Mempool API dla dokładnych opłat i dat", "enable_replace_by_fee": "Włącz wymianę po lewej", "enable_silent_payments_scanning": "Zacznij skanować Silent Payments", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 423ff9800..b265e71b9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", "email_address": "Endereço de e-mail", "enable": "Habilitar", + "enable_auto_node_switching": "Ativar comutação automática de nó", "enable_builtin_tor": "Ative o Buildi-in Tor", + "enable_for_auto_switching": "Ativar a troca automática", "enable_mempool_api": "Mempool API para taxas e datas precisas", "enable_replace_by_fee": "Habilite substituir por taxa", "enable_silent_payments_scanning": "Comece a escanear pagamentos silenciosos, até que o topo seja alcançada", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 4563d4f0e..bc03c3e1e 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", "email_address": "Адрес электронной почты", "enable": "Давать возможность", + "enable_auto_node_switching": "Включить автоматическое переключение узлов", "enable_builtin_tor": "Включить встроенный в Tor", + "enable_for_auto_switching": "Включить для автоматического переключения", "enable_mempool_api": "Mempool API за точные сборы и даты", "enable_replace_by_fee": "Включить замену за пикой", "enable_silent_payments_scanning": "Начните сканировать безмолвные платежи, пока не будет достигнут наконечник", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index adeb7169d..c774facc6 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "เราสร้างที่อยู่ใหม่ทุกครั้งที่คุณใช้หนึ่งอย่าง แต่ที่อยู่เก่ายังสามารถใช้ได้ต่อไป", "email_address": "ที่อยู่อีเมล", "enable": "เปิดใช้งาน", + "enable_auto_node_switching": "เปิดใช้งานการสลับโหนดอัตโนมัติ", "enable_builtin_tor": "เปิดใช้งาน Tor ในตัว", + "enable_for_auto_switching": "เปิดใช้งานการสลับอัตโนมัติ", "enable_mempool_api": "Mempool API สำหรับค่าธรรมเนียมและวันที่ที่ถูกต้อง", "enable_replace_by_fee": "เปิดใช้งานการเปลี่ยนโดยค่าธรรมเนียม", "enable_silent_payments_scanning": "เริ่มสแกนการชำระเงินแบบเงียบจนกว่าจะถึงปลาย", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 18d783306..e4896c675 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Bumubuo kami ng mga bagong address sa tuwing gagamit ka ng isa, ngunit ang mga nakaraang address ay patuloy na gumagana", "email_address": "Email Address", "enable": "Paganahin", + "enable_auto_node_switching": "Paganahin ang awtomatikong paglipat ng node", "enable_builtin_tor": "Paganahin ang built-in tor", + "enable_for_auto_switching": "Paganahin para sa auto-switch", "enable_mempool_api": "Mempool API para sa tumpak na bayad at mga petsa", "enable_replace_by_fee": "Paganahin ang Replace-By-Fee", "enable_silent_payments_scanning": "Simulan ang pag -scan ng tahimik na pagbabayad, hanggang sa maabot ang tip", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index d312e98fe..ba7fcdc05 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Adresini her kullandığında yeni adres oluşturuyoruz, ancak önceki adresler de çalışmaya devam eder", "email_address": "E-posta Adresi", "enable": "Olanak vermek", + "enable_auto_node_switching": "Otomatik Düğüm Anahtarlamasını Etkinleştir", "enable_builtin_tor": "Yerleşik Tor'u etkinleştirin", + "enable_for_auto_switching": "Otomatik anahtarlama için etkinleştirin", "enable_mempool_api": "Doğru ücretler ve tarihler için Mempool API'si", "enable_replace_by_fee": "Farklı Değiştir'i Etkinleştir", "enable_silent_payments_scanning": "Bahşiş ulaşılıncaya kadar sessiz ödemeleri taramaya başlayın", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 8a71a9025..e1c6bb7e4 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", "email_address": "Адреса електронної пошти", "enable": "Ввімкнути", + "enable_auto_node_switching": "Увімкнути автоматичний перемикач вузлів", "enable_builtin_tor": "Увімкнути вбудований tor", + "enable_for_auto_switching": "Увімкнути автоматичне перемикання", "enable_mempool_api": "API Mempool для точних зборів та дат", "enable_replace_by_fee": "Увімкнути заміну з комісією", "enable_silent_payments_scanning": "Почніть сканувати мовчазні платежі, поки не буде досягнуто наконечника", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 3c9ed3ca0..aac1688f3 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "جب بھی آپ ایک کا استعمال کرتے ہیں تو ہم نئے پتے تیار کرتے ہیں، لیکن پچھلے پتے کام کرتے رہتے ہیں۔", "email_address": "ای میل اڈریس", "enable": "قابل بنائیں", + "enable_auto_node_switching": "خودکار نوڈ سوئچنگ کو فعال کریں", "enable_builtin_tor": "بلٹٹی ان ٹور کو فعال کریں", + "enable_for_auto_switching": "آٹو سوئچنگ کے لئے قابل بنائیں", "enable_mempool_api": "درست فیسوں اور تاریخوں کے لئے میمپول API", "enable_replace_by_fee": "فی فیس کو تبدیل کریں", "enable_silent_payments_scanning": "خاموش ادائیگیوں کو اسکین کرنا شروع کریں ، جب تک کہ نوک نہ پہنچ جائے", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index a92de3214..1c3a5af9f 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -305,7 +305,9 @@ "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", "email_address": "Địa chỉ Email", "enable": "Cho phép", + "enable_auto_node_switching": "Bật chuyển đổi nút tự động", "enable_builtin_tor": "Bật Buildi-In Tor", + "enable_for_auto_switching": "Kích hoạt để chuyển đổi tự động", "enable_mempool_api": "API Mempool cho các khoản phí và ngày chính xác", "enable_replace_by_fee": "Bật Thay thế Bằng Phí", "enable_silent_payments_scanning": "Bật quét thanh toán im lặng", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index c770daf39..21b04e777 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -307,7 +307,9 @@ "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ẹ̀.", "email_address": "Àdírẹ́sì ímeèlì", "enable": "Mu ṣiṣẹ", + "enable_auto_node_switching": "Mu oju-ọna yiyọ laifọwọyi", "enable_builtin_tor": "Mu ṣiṣẹ", + "enable_for_auto_switching": "Mu ṣiṣẹ fun adaṣe-pada", "enable_mempool_api": "Mempool API fun awọn owo deede ati awọn ọjọ", "enable_replace_by_fee": "Mu ki o rọpo", "enable_silent_payments_scanning": "Bẹrẹ awọn sisanwo ipalọlọ, titi ti o fi de opin", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e90424082..826e366f3 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -306,7 +306,9 @@ "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", "email_address": "电子邮件地址", "enable": "使能够", + "enable_auto_node_switching": "启用自动节点开关", "enable_builtin_tor": "启用内置的tor", + "enable_for_auto_switching": "启用自动开关", "enable_mempool_api": "Mempool API获得准确的费用和日期", "enable_replace_by_fee": "启用by-Fee替换", "enable_silent_payments_scanning": "开始扫描无声付款,直到达到提示",