mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
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
This commit is contained in:
@@ -2,4 +2,5 @@
|
||||
uri: kaliumapi.appditto.com
|
||||
path: /api
|
||||
useSSL: true
|
||||
is_default: true
|
||||
is_default: true
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
-
|
||||
uri: default-spv-nodes
|
||||
is_default: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: dcrd.sethforprivacy.com:9108
|
||||
useSSL: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
uri: polygon-bor-rpc.publicnode.com
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: polygon.llamarpc.com
|
||||
-
|
||||
uri: matic.nownodes.io
|
||||
uri: matic.nownodes.io
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -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
|
||||
is_default: true
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -6,6 +6,8 @@
|
||||
uri: api.trongrid.io
|
||||
is_default: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: trx.nownodes.io
|
||||
useSSL: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
-
|
||||
uri: 37.27.100.59:10500
|
||||
useSSL: false
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: zano.cakewallet.com:11211
|
||||
is_default: true
|
||||
useSSL: false
|
||||
useSSL: false
|
||||
isEnabledForAutoSwitching: true
|
||||
@@ -203,15 +203,25 @@ class ElectrumClient {
|
||||
return [];
|
||||
});
|
||||
|
||||
Future<Map<String, dynamic>> getBalance(String scriptHash) =>
|
||||
call(method: 'blockchain.scripthash.get_balance', params: [scriptHash])
|
||||
.then((dynamic result) {
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return <String, dynamic>{};
|
||||
});
|
||||
Future<Map<String, dynamic>> getBalance(String scriptHash, {bool throwOnError = false}) async {
|
||||
try {
|
||||
final result = await call(method: 'blockchain.scripthash.get_balance', params: [scriptHash]);
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (throwOnError) {
|
||||
throw Exception('Invalid response format for getBalance');
|
||||
}
|
||||
|
||||
return <String, dynamic>{};
|
||||
} catch (e) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getHistory(String scriptHash) =>
|
||||
call(method: 'blockchain.scripthash.get_history', params: [scriptHash])
|
||||
|
||||
@@ -2300,6 +2300,26 @@ abstract class ElectrumWalletBase
|
||||
await save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -112,4 +112,9 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
||||
bool isTestnet = false;
|
||||
|
||||
bool canSend() => 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<bool> checkNodeHealth();
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ class Libwallet {
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<Map> balance(String walletName) async {
|
||||
Future<Map> balance(String walletName, {bool throwOnError = false}) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
@@ -468,6 +468,9 @@ class Libwallet {
|
||||
try {
|
||||
return jsonDecode(res.payload);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<bool> checkNodeHealth() async => await checkSync();
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
|
||||
|
||||
|
||||
@@ -65,10 +65,13 @@ abstract class EVMChainClient {
|
||||
// });
|
||||
}
|
||||
|
||||
Future<EtherAmount> getBalance(EthereumAddress address) async {
|
||||
Future<EtherAmount> getBalance(EthereumAddress address, {bool throwOnError = false}) async {
|
||||
try {
|
||||
return await _client!.getBalance(address);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return EtherAmount.zero();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,36 @@ abstract class EVMChainWalletBase
|
||||
EncryptionFileUtils encryptionFileUtils,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<bool> 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<void> 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();
|
||||
});
|
||||
|
||||
@@ -184,6 +184,24 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
@override
|
||||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<bool> 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<void> close({bool shouldCleanup = false}) async {
|
||||
if (isHardwareWallet) {
|
||||
|
||||
@@ -92,7 +92,7 @@ class NanoClient {
|
||||
return NanoBalance(currentBalance: cur, receivableBalance: rec);
|
||||
}
|
||||
|
||||
Future<AccountInfoResponse?> getAccountInfo(String address) async {
|
||||
Future<AccountInfoResponse?> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,8 +518,19 @@ abstract class NanoWalletBase
|
||||
return _client.getN2Reps();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
await _client.getAccountInfo(_publicAddress!, throwOnError: true);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
|
||||
@@ -51,7 +51,7 @@ class SolanaWalletClient {
|
||||
}
|
||||
}
|
||||
|
||||
Future<double> getBalance(String walletAddress) async {
|
||||
Future<double> 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<SolanaBalance?> 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<double> getFeeForMessage(String message, Commitment commitment) async {
|
||||
|
||||
@@ -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<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
Future<bool> 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<SPLToken> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,12 +102,15 @@ class TronClient {
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance(TronAddress address) async {
|
||||
Future<BigInt> 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<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress) async {
|
||||
Future<TronBalance> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +276,13 @@ abstract class TronWalletBase
|
||||
Future<void> 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<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
Future<bool> 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<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
|
||||
|
||||
Future<void> addTronToken(TronToken token) async {
|
||||
|
||||
@@ -174,6 +174,24 @@ abstract class WowneroWalletBase
|
||||
@override
|
||||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<bool> 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<void> close({bool shouldCleanup = false}) async {
|
||||
_listener?.stop();
|
||||
|
||||
@@ -411,6 +411,17 @@ abstract class ZanoWalletBase
|
||||
@override
|
||||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
final status = await getWalletStatus();
|
||||
|
||||
return status.isDaemonConnected;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
try {
|
||||
if (_isTransactionUpdating) {
|
||||
|
||||
@@ -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<Node> nodeSource;
|
||||
|
||||
// Track used nodes to cycle through all trusted nodes
|
||||
final Map<WalletType, List<dynamic>> _usedNodeKeys = {};
|
||||
|
||||
void startHealthCheckTimer() {
|
||||
_healthCheckTimer?.cancel();
|
||||
_healthCheckTimer = Timer.periodic(
|
||||
Duration(seconds: _healthCheckIntervalSeconds),
|
||||
(_) => performHealthCheck(),
|
||||
);
|
||||
|
||||
performHealthCheck();
|
||||
}
|
||||
|
||||
void stopHealthCheckTimer() {
|
||||
_healthCheckTimer?.cancel();
|
||||
_healthCheckTimer = null;
|
||||
}
|
||||
|
||||
Future<void> 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<void> _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<void> setup({
|
||||
|
||||
getIt.registerFactory(() => DEuroSavingsPage(getIt<DEuroViewModel>()));
|
||||
|
||||
getIt.registerLazySingleton(() => NodeSwitchingService(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
nodeSource: _nodeSource,
|
||||
));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
||||
@@ -510,7 +510,10 @@ Future<void> defaultSettingsMigration(
|
||||
providerName: "SwapTrade",
|
||||
enabled: true,
|
||||
);
|
||||
break;
|
||||
break;
|
||||
case 50:
|
||||
migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1088,7 +1091,7 @@ Future<void> 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<void> 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<void> removeMoneroWorld(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> migrateExistingNodesToUseAutoSwitching(
|
||||
{required Box<Node> nodes, required Box<Node> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
+4
-1
@@ -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<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
payjoinSessionSource: payjoinSessionSource,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 49,
|
||||
initialMigrationVersion: 50,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -327,6 +328,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
final authService = getIt.get<AuthService>();
|
||||
final linkViewModel = getIt.get<LinkViewModel>();
|
||||
final tradeMonitor = getIt.get<TradeMonitor>();
|
||||
final nodeSwitchingService = getIt.get<NodeSwitchingService>();
|
||||
final settingsStore = appStore.settingsStore;
|
||||
final statusBarColor = Colors.transparent;
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
@@ -351,6 +353,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
authService: authService,
|
||||
linkViewModel: linkViewModel,
|
||||
tradeMonitor: tradeMonitor,
|
||||
nodeSwitchingService: nodeSwitchingService,
|
||||
child: ThemeProvider(
|
||||
themeStore: appStore.themeStore,
|
||||
materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
|
||||
|
||||
@@ -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<void> bootstrapOffline() async {
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
@@ -40,4 +41,6 @@ void bootstrapOnline(GlobalKey<NavigatorState> navigatorKey, {required bool load
|
||||
startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore);
|
||||
startOnCurrentNodeChangeReaction(appStore);
|
||||
startFiatRateUpdate(appStore, settingsStore, fiatConversionStore);
|
||||
|
||||
getIt.get<NodeSwitchingService>().startHealthCheckTimer();
|
||||
}
|
||||
@@ -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: <Widget>[
|
||||
|
||||
@@ -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<Root> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
widget.tradeMonitor.resumeTradeMonitoring();
|
||||
|
||||
// Trigger node health check when app resumes
|
||||
widget.nodeSwitchingService.performHealthCheck();
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<WalletType, TransactionPriority>() {
|
||||
//this.nodes = ObservableMap<WalletType, Node>.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>();
|
||||
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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Node> nodes;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> _nodeSource;
|
||||
|
||||
@@ -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<Node> nodes;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> _nodeSource;
|
||||
|
||||
@@ -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": "ابدأ في مسح المدفوعات الصامتة ، حتى يتم الوصول إلى الطرف",
|
||||
|
||||
@@ -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": "Започнете да сканирате безшумните плащания, докато се достигне съветът",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "मूक भुगतान स्कैनिंग सक्षम करें",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Միացնել Լուռ Վճարումների սկանավորումը",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "先端に達するまで、サイレント決済のスキャンを開始します",
|
||||
|
||||
@@ -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": "사일런트 페이먼트 주소로 전송된 트랜잭션 스캔 시작",
|
||||
|
||||
@@ -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": "အစွန်အဖျားသို့ရောက်ရှိသည်အထိအသံတိတ်ငွေပေးချေမှုကိုစကင်ဖတ်စစ်ဆေးပါ",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Начните сканировать безмолвные платежи, пока не будет достигнут наконечник",
|
||||
|
||||
@@ -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": "เริ่มสแกนการชำระเงินแบบเงียบจนกว่าจะถึงปลาย",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Почніть сканувати мовчазні платежі, поки не буде досягнуто наконечника",
|
||||
|
||||
@@ -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": "خاموش ادائیگیوں کو اسکین کرنا شروع کریں ، جب تک کہ نوک نہ پہنچ جائے",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "开始扫描无声付款,直到达到提示",
|
||||
|
||||
Reference in New Issue
Block a user