diff --git a/.github/workflows/no_http_imports.yaml b/.github/workflows/no_http_imports.yaml new file mode 100644 index 0000000000..dad6821ac1 --- /dev/null +++ b/.github/workflows/no_http_imports.yaml @@ -0,0 +1,21 @@ +name: No http imports + +on: [pull_request] + +jobs: + PR_test_build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + - name: Check for http package usage + if: github.event_name == 'pull_request' + run: | + GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)" + [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0 + echo "$GIT_GREP_OUT" + echo "There are .dart files which use http imports" + echo "Using http package breaks proxy integration" + echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart" + exit 1 + \ No newline at end of file diff --git a/.github/workflows/no_print_in_dart.yaml b/.github/workflows/no_print_in_dart.yaml index 9c3d82bc25..507793bd81 100644 --- a/.github/workflows/no_print_in_dart.yaml +++ b/.github/workflows/no_print_in_dart.yaml @@ -15,5 +15,5 @@ jobs: [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0 echo "$GIT_GREP_OUT" echo "There are .dart files which use print() statements" - echo "Please use printV from package: cw_core/utils/print_verbose.dart" + echo "Please use printV from package:cw_core/utils/print_verbose.dart" exit 1 diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 1f5c369e3d..2ddd30df6a 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -5,6 +5,8 @@ import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; @@ -42,7 +44,7 @@ class ElectrumClient { static const aliveTimerDuration = Duration(seconds: 4); bool get isConnected => _isConnected; - Socket? socket; + ProxySocket? socket; void Function(ConnectionStatus)? onConnectionStatusChange; int _id; final Map _tasks; @@ -72,18 +74,11 @@ class ElectrumClient { } catch (_) {} socket = null; + final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))); try { - if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { - socket = await Socket.connect(host, port, timeout: connectionTimeout); - } else { - socket = await SecureSocket.connect( - host, - port, - timeout: connectionTimeout, - onBadCertificate: (_) => true, - ); - } + socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout); } catch (e) { + printV("connect: $e"); if (e is HandshakeException) { useSSL = !(useSSL ?? false); } @@ -105,7 +100,6 @@ class ElectrumClient { // use ping to determine actual connection status since we could've just not timed out yet: // _setConnectionStatus(ConnectionStatus.connected); - socket!.listen( (Uint8List event) { try { diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 35c15682c5..cd953f949d 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -4,8 +4,8 @@ import 'dart:io'; import 'dart:isolate'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; -import 'package:cw_core/format_amount.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart'; @@ -49,7 +49,6 @@ import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; import 'package:hex/hex.dart'; -import 'package:http/http.dart' as http; part 'electrum_wallet.g.dart'; @@ -493,11 +492,12 @@ abstract class ElectrumWalletBase Future updateFeeRates() async { if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) { try { - final response = await http - .get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended")) - .timeout(Duration(seconds: 5)); - - final result = json.decode(response.body) as Map; + final req = await ProxyWrapper().getHttpClient() + .getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended")) + .timeout(Duration(seconds: 15)); + final response = await req.close(); + final stringData = await response.transform(utf8.decoder).join(); + final result = json.decode(stringData) as Map; final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0; int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0; int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0; @@ -1880,24 +1880,25 @@ abstract class ElectrumWalletBase if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) { try { - final blockHash = await http.get( - Uri.parse( - "https://mempool.cakewallet.com/api/v1/block-height/$height", - ), - ); + final req = await ProxyWrapper().getHttpClient() + .getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height")) + .timeout(Duration(seconds: 15)); + final blockHash = await req.close(); + final stringData = await blockHash.transform(utf8.decoder).join(); if (blockHash.statusCode == 200 && - blockHash.body.isNotEmpty && - jsonDecode(blockHash.body) != null) { - final blockResponse = await http.get( - Uri.parse( - "https://mempool.cakewallet.com/api/v1/block/${blockHash.body}", - ), - ); - if (blockResponse.statusCode == 200 && - blockResponse.body.isNotEmpty && - jsonDecode(blockResponse.body)['timestamp'] != null) { - time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString()); + stringData.isNotEmpty && + jsonDecode(stringData) != null) { + final blockResponseReq = await ProxyWrapper().getHttpClient() + .getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/block/${stringData}")) + .timeout(Duration(seconds: 15)); + final blockResponseRes = await blockResponseReq.close(); + final blockResponse = await blockResponseRes.transform(utf8.decoder).join(); + + if (blockResponseRes == 200 && + blockResponse.isNotEmpty && + jsonDecode(blockResponse)['timestamp'] != null) { + time = int.parse(jsonDecode(blockResponse)['timestamp'].toString()); } } } catch (_) {} diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart index a499660b0d..f4d96e4a15 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart @@ -7,11 +7,12 @@ import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; import 'package:cw_bitcoin/psbt/signer.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:payjoin_flutter/bitcoin_ffi.dart'; import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/receive.dart'; import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; +import 'package:http/http.dart' as very_insecure_http_do_not_use; // for eerrors enum PayjoinReceiverRequestTypes { processOriginalTx, @@ -27,7 +28,7 @@ class PayjoinReceiverWorker { final pendingRequests = >{}; PayjoinReceiverWorker._(this.sendPort); - + static final client = ProxyWrapper().getHttpIOClient(); static Future run(List args) async { await pj.core.init(); @@ -41,11 +42,10 @@ class PayjoinReceiverWorker { receivePort.listen(worker.handleMessage); try { - final httpClient = http.Client(); final receiver = Receiver.fromJson(receiverJson); final uncheckedProposal = - await worker.receiveUncheckedProposal(httpClient, receiver); + await worker.receiveUncheckedProposal(receiver); final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); sendPort.send({ @@ -56,14 +56,14 @@ class PayjoinReceiverWorker { final payjoinProposal = await worker.processPayjoinProposal( uncheckedProposal, ); - final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal); + final psbt = await worker.sendFinalProposal(payjoinProposal); sendPort.send({ 'type': PayjoinReceiverRequestTypes.proposalSent, 'psbt': psbt, }); } catch (e) { if (e is HttpException || - (e is http.ClientException && + (e is very_insecure_http_do_not_use.ClientException && e.message.contains("Software caused connection abort"))) { sendPort.send(PayjoinSessionError.recoverable(e.toString())); } else { @@ -97,15 +97,14 @@ class PayjoinReceiverWorker { return completer.future; } - Future receiveUncheckedProposal( - http.Client httpClient, Receiver session) async { + Future receiveUncheckedProposal(Receiver session) async { while (true) { printV("Polling for Proposal (${session.id()})"); final extractReq = await session.extractReq(); final request = extractReq.$1; final url = Uri.parse(request.url.asString()); - final httpRequest = await httpClient.post(url, + final httpRequest = await client.post(url, headers: {'Content-Type': request.contentType}, body: request.body); final proposal = await session.processRes( @@ -114,13 +113,12 @@ class PayjoinReceiverWorker { } } - Future sendFinalProposal( - http.Client httpClient, PayjoinProposal finalProposal) async { + Future sendFinalProposal(PayjoinProposal finalProposal) async { final req = await finalProposal.extractV2Req(); final proposalReq = req.$1; final proposalCtx = req.$2; - final request = await httpClient.post( + final request = await client.post( Uri.parse(proposalReq.url.asString()), headers: {"Content-Type": proposalReq.contentType}, body: proposalReq.body, diff --git a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart index f720bac018..06bb215ee7 100644 --- a/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart +++ b/cw_bitcoin/lib/payjoin/payjoin_send_worker.dart @@ -5,7 +5,7 @@ import 'dart:isolate'; import 'package:cw_bitcoin/payjoin/manager.dart'; import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/send.dart'; import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; @@ -42,19 +42,19 @@ class PayjoinSenderWorker { sendPort.send(e); } } + final client = ProxyWrapper().getHttpIOClient(); /// Run a payjoin sender (V2 protocol first, fallback to V1). Future runSender(Sender sender) async { - final httpClient = http.Client(); try { - return await _runSenderV2(sender, httpClient); + return await _runSenderV2(sender); } catch (e) { printV(e); if (e is PayjoinException && // TODO condition on error type instead of message content e.message?.contains('parse receiver public key') == true) { - return await _runSenderV1(sender, httpClient); + return await _runSenderV1(sender); } else if (e is HttpException) { printV(e); throw Exception(PayjoinSessionError.recoverable(e.toString())); @@ -65,13 +65,13 @@ class PayjoinSenderWorker { } /// Attempt to send payjoin using the V2 of the protocol. - Future _runSenderV2(Sender sender, http.Client httpClient) async { + Future _runSenderV2(Sender sender) async { try { final postRequest = await sender.extractV2( ohttpProxyUrl: await PayjoinManager.randomOhttpRelayUrl(), ); - final postResult = await _postRequest(httpClient, postRequest.$1); + final postResult = await _postRequest(postRequest.$1); final getContext = await postRequest.$2.processResponse(response: postResult); @@ -83,7 +83,7 @@ class PayjoinSenderWorker { final getRequest = await getContext.extractReq( ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), ); - final getRes = await _postRequest(httpClient, getRequest.$1); + final getRes = await _postRequest(getRequest.$1); final proposalPsbt = await getContext.processResponse( response: getRes, ohttpCtx: getRequest.$2, @@ -97,10 +97,10 @@ class PayjoinSenderWorker { } /// Attempt to send payjoin using the V1 of the protocol. - Future _runSenderV1(Sender sender, http.Client httpClient) async { + Future _runSenderV1(Sender sender) async { try { final postRequest = await sender.extractV1(); - final response = await _postRequest(httpClient, postRequest.$1); + final response = await _postRequest(postRequest.$1); sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted}); @@ -110,7 +110,7 @@ class PayjoinSenderWorker { } } - Future> _postRequest(http.Client client, Request req) async { + Future> _postRequest(Request req) async { final httpRequest = await client.post(Uri.parse(req.url.asString()), headers: {'Content-Type': req.contentType}, body: req.body); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index c00c2839ab..30a8c80525 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -935,11 +935,21 @@ packages: socks5_proxy: dependency: transitive description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: "direct main" + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -1033,10 +1043,18 @@ packages: dependency: transitive description: name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.1" + tor: + dependency: transitive + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 + url: "https://pub.dev" + source: hosted + version: "0.0.8" tuple: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index f45258f929..48acd9a48d 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -54,6 +54,10 @@ dependencies: git: url: https://github.com/cake-tech/ledger-flutter-plus-plugins path: packages/ledger-litecoin + socks_socket: + git: + url: https://github.com/sneurlax/socks_socket + ref: e6232c53c1595469931ababa878759a067c02e94 dev_dependencies: flutter_test: diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index aee12b4236..4786336af4 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -1,7 +1,7 @@ +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:intl/intl.dart'; import 'dart:convert'; -import 'package:http/http.dart' as http; // FIXME: Hardcoded values; Works only for monero @@ -234,10 +234,14 @@ int getHavenHeightByDate({required DateTime date}) { } Future getHavenCurrentHeight() async { - final response = await http.get(Uri.parse('https://explorer.havenprotocol.org/api/networkinfo')); + final req = await ProxyWrapper().getHttpClient() + .getUrl(Uri.parse('https://explorer.havenprotocol.org/api/networkinfo')) + .timeout(Duration(seconds: 15)); + final response = await req.close(); + final stringResponse = await response.transform(utf8.decoder).join(); if (response.statusCode == 200) { - final info = jsonDecode(response.body); + final info = jsonDecode(stringResponse); return info['data']['height'] as int; } else { throw Exception('Failed to load current blockchain height'); @@ -269,13 +273,13 @@ const bitcoinDates = { }; Future getBitcoinHeightByDateAPI({required DateTime date}) async { - final response = await http.get( - Uri.parse( - "https://mempool.cakewallet.com/api/v1/mining/blocks/timestamp/${(date.millisecondsSinceEpoch / 1000).round()}", - ), - ); + final req = await ProxyWrapper().getHttpClient() + .getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/mining/blocks/timestamp/${(date.millisecondsSinceEpoch / 1000).round()}")) + .timeout(Duration(seconds: 15)); + final response = await req.close(); + final stringResponse = await response.transform(utf8.decoder).join(); - return jsonDecode(response.body)['height'] as int; + return jsonDecode(stringResponse)['height'] as int; } int getBitcoinHeightByDate({required DateTime date}) { diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 38fcde9e14..fdffb844b3 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -1,12 +1,12 @@ import 'dart:io'; import 'package:cw_core/keyable.dart'; +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'dart:convert'; -import 'package:http/http.dart' as http; import 'package:hive/hive.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:http/io_client.dart' as ioc; import 'dart:math' as math; import 'package:convert/convert.dart'; @@ -184,23 +184,17 @@ class Node extends HiveObject with Keyable { final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"}; try { - final authenticatingClient = HttpClient(); - authenticatingClient.badCertificateCallback = - ((X509Certificate cert, String host, int port) => true); - - final http.Client client = ioc.IOClient(authenticatingClient); - final jsonBody = json.encode(body); - final response = await client.post( - rpcUri, + final response = await ProxyWrapper().post( + clearnetUri: rpcUri, headers: {'Content-Type': 'application/json'}, body: jsonBody, ); - printV("node check response: ${response.body}"); - + final resBody = json.decode(response.body) as Map; + return resBody['result']['height'] != null; } catch (e) { printV("error: $e"); @@ -218,11 +212,7 @@ class Node extends HiveObject with Keyable { final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName}; try { - final authenticatingClient = HttpClient(); - authenticatingClient.badCertificateCallback = - ((X509Certificate cert, String host, int port) => true); - - final http.Client client = ioc.IOClient(authenticatingClient); + final client = ProxyWrapper().getHttpIOClient(); final jsonBody = json.encode(body); @@ -242,15 +232,15 @@ class Node extends HiveObject with Keyable { return !(response['offline'] as bool); } - printV("node check response: ${response.body}"); + final responseString = await response.body; - if ((response.body.contains("400 Bad Request") // Some other generic error + if ((responseString.contains("400 Bad Request") // Some other generic error || - response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare + responseString.contains("plain HTTP request was sent to HTTPS port") // Cloudflare || response.headers["location"] != null // Generic reverse proxy || - response.body + responseString .contains("301 Moved Permanently") // Poorly configured generic reverse proxy ) && !(useSSL ?? false)) { @@ -277,15 +267,16 @@ class Node extends HiveObject with Keyable { } Future requestNodeWithProxy() async { - if (!isValidProxyAddress /* && !Tor.instance.enabled*/) { + if (!isValidProxyAddress && !CakeTor.instance.enabled) { return false; } String? proxy = socksProxyAddress; - // if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { - // proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; - // } + if ((proxy?.isEmpty ?? true) && CakeTor.instance.enabled) { + proxy = "${InternetAddress.loopbackIPv4.address}:${CakeTor.instance.port}"; + } + printV("proxy: $proxy"); if (proxy == null) { return false; } @@ -305,13 +296,9 @@ class Node extends HiveObject with Keyable { // you try to communicate with it Future requestElectrumServer() async { try { - final Socket socket; - if (useSSL == true) { - socket = await SecureSocket.connect(uri.host, uri.port, - timeout: Duration(seconds: 5), onBadCertificate: (_) => true); - } else { - socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5)); - } + final ProxySocket socket; + socket = await ProxyWrapper().getSocksSocket(useSSL ?? false, uri.host, uri.port); + socket.destroy(); return true; @@ -322,8 +309,8 @@ class Node extends HiveObject with Keyable { Future requestNanoNode() async { try { - final response = await http.post( - uri, + final response = await ProxyWrapper().post( + clearnetUri: uri, headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"}, body: jsonEncode( { @@ -332,7 +319,8 @@ class Node extends HiveObject with Keyable { }, ), ); - final data = await jsonDecode(response.body); + + final data = jsonDecode(response.body); if (response.statusCode != 200 || data["error"] != null || data["balance"] == null || @@ -348,13 +336,14 @@ class Node extends HiveObject with Keyable { Future requestEthereumServer() async { try { - final response = await http.get( - uri, - headers: {'Content-Type': 'application/json'}, - ); + final req = await ProxyWrapper().getHttpClient() + .getUrl(uri,) + .timeout(Duration(seconds: 15)); + final response = await req.close(); return response.statusCode >= 200 && response.statusCode < 300; - } catch (_) { + } catch (err) { + printV("Failed to request ethereum server: $err"); return false; } } @@ -462,7 +451,7 @@ class DaemonRpc { /// Perform a JSON-RPC call with Digest Authentication. Future> call(String method, Map params) async { - final http.Client client = http.Client(); + final client = ProxyWrapper().getHttpIOClient(); final DigestAuth digestAuth = DigestAuth(username, password); // Initial request to get the `WWW-Authenticate` header. diff --git a/cw_core/lib/solana_rpc_http_service.dart b/cw_core/lib/solana_rpc_http_service.dart index fbe9a29dcc..6303da586b 100644 --- a/cw_core/lib/solana_rpc_http_service.dart +++ b/cw_core/lib/solana_rpc_http_service.dart @@ -1,25 +1,24 @@ import 'dart:convert'; -import 'package:http/http.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:on_chain/solana/solana.dart'; class SolanaRPCHTTPService implements SolanaJSONRPCService { SolanaRPCHTTPService( - {required this.url, Client? client, this.defaultRequestTimeout = const Duration(seconds: 30)}) - : client = client ?? Client(); + {required this.url, this.defaultRequestTimeout = const Duration(seconds: 30)}); @override final String url; - final Client client; final Duration defaultRequestTimeout; @override Future> call(SolanaRequestDetails params, [Duration? timeout]) async { - final response = await client.post( - Uri.parse(url), + final response = await ProxyWrapper().post( + clearnetUri: Uri.parse(url), body: params.toRequestBody(), headers: { 'Content-Type': 'application/json', }, ).timeout(timeout ?? defaultRequestTimeout); + final data = json.decode(response.body) as Map; return data; } diff --git a/cw_core/lib/utils/proxy_socket/abstract.dart b/cw_core/lib/utils/proxy_socket/abstract.dart new file mode 100644 index 0000000000..b4b628f74c --- /dev/null +++ b/cw_core/lib/utils/proxy_socket/abstract.dart @@ -0,0 +1,47 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cw_core/utils/proxy_socket/insecure.dart'; +import 'package:cw_core/utils/proxy_socket/secure.dart'; +import 'package:cw_core/utils/proxy_socket/socks.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:socks_socket/socks_socket.dart'; + +class ProxyAddress { + final String host; + final int port; + + ProxyAddress({required this.host, required this.port}); +} + +abstract class ProxySocket { + static Future connect(bool sslEnabled, ProxyAddress address, {Duration? connectionTimeout}) async { + if (CakeTor.instance.started) { + var socksSocket = await SOCKSSocket.create( + proxyHost: InternetAddress.loopbackIPv4.address, + proxyPort: CakeTor.instance.port, + sslEnabled: sslEnabled, + ); + await socksSocket.connect(); + await socksSocket.connectTo(address.host, address.port); + return ProxySocketSocks(socksSocket); + } + if (sslEnabled == false) { + return ProxySocketInsecure(await Socket.connect(address.host, address.port, timeout: connectionTimeout)); + } else { + return ProxySocketSecure(await SecureSocket.connect( + address.host, + address.port, + timeout: connectionTimeout, + onBadCertificate: (_) => true, + )); + } + } + + Future close(); + Future destroy(); + Future write(String data); + StreamSubscription> listen(Function(Uint8List event) onData, {Function (Object error)? onError, Function ()? onDone, bool cancelOnError = true}); + ProxyAddress get address; +} \ No newline at end of file diff --git a/cw_core/lib/utils/proxy_socket/insecure.dart b/cw_core/lib/utils/proxy_socket/insecure.dart new file mode 100644 index 0000000000..aeac474d77 --- /dev/null +++ b/cw_core/lib/utils/proxy_socket/insecure.dart @@ -0,0 +1,34 @@ + +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:io'; + +class ProxySocketInsecure implements ProxySocket { + final Socket socket; + + ProxySocketInsecure(this.socket); + + ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort); + + @override + Future close() => socket.close(); + + @override + Future destroy() async => socket.destroy(); + + @override + Future write(String data) async => socket.write(data); + + @override + StreamSubscription> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) { + return socket.listen( + (data) { + onData(Uint8List.fromList(data)); + }, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } +} \ No newline at end of file diff --git a/cw_core/lib/utils/proxy_socket/secure.dart b/cw_core/lib/utils/proxy_socket/secure.dart new file mode 100644 index 0000000000..2efd13ee45 --- /dev/null +++ b/cw_core/lib/utils/proxy_socket/secure.dart @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cw_core/utils/proxy_socket/abstract.dart'; + +class ProxySocketSecure implements ProxySocket { + final SecureSocket socket; + + ProxySocketSecure(this.socket); + + ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort); + + @override + Future close() => socket.close(); + + @override + Future destroy() async => socket.destroy(); + + @override + Future write(String data) async => socket.write(data); + + @override + StreamSubscription> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) { + return socket.listen( + (data) { + onData(Uint8List.fromList(data)); + }, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } +} diff --git a/cw_core/lib/utils/proxy_socket/socks.dart b/cw_core/lib/utils/proxy_socket/socks.dart new file mode 100644 index 0000000000..a4e5ddeb66 --- /dev/null +++ b/cw_core/lib/utils/proxy_socket/socks.dart @@ -0,0 +1,36 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'package:socks_socket/socks_socket.dart'; + +class ProxySocketSocks implements ProxySocket { + final SOCKSSocket socket; + + ProxySocketSocks(this.socket); + + @override + ProxyAddress get address => ProxyAddress(host: socket.proxyHost, port: socket.proxyPort); + + @override + Future close() => socket.close(); + + @override + Future destroy() => close(); + + @override + Future write(String data) async => socket.write(data); + + @override + StreamSubscription> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) { + return socket.listen( + (data) { + onData(Uint8List.fromList(data)); + }, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } +} + diff --git a/cw_core/lib/utils/proxy_wrapper.dart b/cw_core/lib/utils/proxy_wrapper.dart new file mode 100644 index 0000000000..4422458238 --- /dev/null +++ b/cw_core/lib/utils/proxy_wrapper.dart @@ -0,0 +1,387 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:cw_core/utils/proxy_socket/abstract.dart'; +import 'package:http/http.dart'; +import 'package:socks5_proxy/socks_client.dart'; +import 'package:tor/tor.dart'; +import 'package:http/io_client.dart' as ioc; + +class ProxyWrapper { + static final ProxyWrapper _proxyWrapper = ProxyWrapper._internal(); + + factory ProxyWrapper() { + return _proxyWrapper; + } + + ProxyWrapper._internal(); + Future getSocksSocket(bool sslEnabled, String host, int port, {Duration? connectionTimeout}) async { + return ProxySocket.connect(sslEnabled, ProxyAddress(host: host, port: port), connectionTimeout: connectionTimeout); + } + + ioc.IOClient getHttpIOClient({int? portOverride}) { + // ignore: deprecated_member_use_from_same_package + final httpClient = ProxyWrapper().getHttpClient(portOverride: portOverride); + return ioc.IOClient(httpClient); + } + + int getPort() => CakeTor.instance.port; + + @Deprecated('Use ProxyWrapper().get/post/put methods instead, and provide proper clearnet and onion uri.') + HttpClient getHttpClient({int? portOverride}) { + final torClient = HttpClient(); + + if (CakeTor.instance.started) { + // Assign connection factory. + SocksTCPClient.assignToHttpClient(torClient, [ + ProxySettings( + InternetAddress.loopbackIPv4, + portOverride ?? getPort(), + password: null, + ), + ]); + } + + return torClient; + } + + Future _makeGet({ + required ioc.IOClient client, + required Uri uri, + required Map? headers, + }) async { + final request = await client.get( + uri, + headers: headers, + ); + return request; + } + + Future _makePost({ + required ioc.IOClient client, + required Uri uri, + required Map? headers, + String? body, + }) async { + final request = await client.post( + uri, + headers: headers, + body: body, + ); + return request; + } + + Future _makePut({ + required ioc.IOClient client, + required Uri uri, + required Map? headers, + String? body, + }) async { + final request = await client.put( + uri, + headers: headers, + body: body, + ); + return request; + } + + Future _makeDelete({ + required ioc.IOClient client, + required Uri uri, + required Map? headers, + String? body, + }) async { + final request = await client.delete( + uri, + headers: headers, + body: body, + ); + return request; + } + + Future get({ + Map? headers, + int? portOverride, + Uri? clearnetUri, + Uri? onionUri, + }) async { + ioc.IOClient? torClient; + bool torEnabled = CakeTor.instance.started; + + if (CakeTor.instance.started) { + torEnabled = true; + } else { + torEnabled = false; + } + + // if tor is enabled, try to connect to the onion url first: + if (torEnabled) { + try { + // ignore: deprecated_member_use_from_same_package + torClient = await getHttpIOClient(portOverride: portOverride); + } catch (_) { + rethrow; + } + + if (onionUri != null) { + try { + return await _makeGet( + client: torClient, + uri: onionUri, + headers: headers, + ); + } catch (_) { + rethrow; + } + } + + if (clearnetUri != null) { + try { + return await _makeGet( + client: torClient, + uri: clearnetUri, + headers: headers, + ); + } catch (_) { + rethrow; + } + } + } + + if (clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () async { + return await _makeGet( + client: ioc.IOClient(), + uri: clearnetUri, + headers: headers, + ); + }, + ); + } catch (_) { + // we weren't able to get a response: + rethrow; + } + } + + throw Exception("Unable to connect to server"); + } + + + Future post({ + Map? headers, + int? portOverride, + Uri? clearnetUri, + Uri? onionUri, + String? body, + bool allowMitmMoneroBypassSSLCheck = false, + }) async { + HttpClient? torHttpClient; + ioc.IOClient? torClient; + HttpClient cleatnetHttpClient = HttpClient(); + if (allowMitmMoneroBypassSSLCheck) { + cleatnetHttpClient.badCertificateCallback = + ((X509Certificate cert, String host, int port) => true); + } + + ioc.IOClient clearnetClient = ioc.IOClient(cleatnetHttpClient); + + + bool torEnabled = CakeTor.instance.started; + + if (torEnabled) { + try { + // ignore: deprecated_member_use_from_same_package + torHttpClient = await getHttpClient(portOverride: portOverride); + } catch (_) { + rethrow; + } + if (allowMitmMoneroBypassSSLCheck) { + torHttpClient.badCertificateCallback = + ((X509Certificate cert, String host, int port) => true); + } + if (onionUri != null) { + try { + return await _makePost( + client: ioc.IOClient(torHttpClient), + uri: onionUri, + headers: headers, + body: body, + ); + } catch (_) { + rethrow; + } + } + + if (clearnetUri != null) { + try { + return await _makePost( + client: ioc.IOClient(torHttpClient), + uri: clearnetUri, + headers: headers, + body: body, + ); + } catch (_) { + rethrow; + } + } + } + + if (clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () async { + return await _makePost( + client: clearnetClient, + uri: clearnetUri, + headers: headers, + body: body, + ); + }, + ); + } catch (_) { + rethrow; + } + } + + throw Exception("Unable to connect to server"); + } + + Future put({ + Map? headers, + int? portOverride, + Uri? clearnetUri, + Uri? onionUri, + String? body, + }) async { + ioc.IOClient? torClient; + bool torEnabled = CakeTor.instance.started; + + if (torEnabled) { + try { + // ignore: deprecated_member_use_from_same_package + torClient = await getHttpIOClient(portOverride: portOverride); + } catch (_) {} + + if (onionUri != null) { + try { + return await _makePut( + client: torClient!, + uri: onionUri, + headers: headers, + body: body, + ); + } catch (_) { + rethrow; + } + } + + if (clearnetUri != null) { + try { + return await _makePut( + client: torClient!, + uri: clearnetUri, + headers: headers, + body: body, + ); + } catch (_) { + rethrow; + } + } + } + + if (clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () async { + return await _makePut( + client: ioc.IOClient(), + uri: clearnetUri, + headers: headers, + body: body, + ); + }, + ); + } catch (_) { + // we weren't able to get a response: + rethrow; + } + } + + throw Exception("Unable to connect to server"); + } + + Future delete({ + Map? headers, + int? portOverride, + Uri? clearnetUri, + Uri? onionUri, + }) async { + ioc.IOClient? torClient; + bool torEnabled = CakeTor.instance.started; + + if (CakeTor.instance.started) { + torEnabled = true; + } else { + torEnabled = false; + } + + // if tor is enabled, try to connect to the onion url first: + if (torEnabled) { + try { + // ignore: deprecated_member_use_from_same_package + torClient = await getHttpIOClient(portOverride: portOverride); + } catch (_) { + rethrow; + } + + if (onionUri != null) { + try { + return await _makeDelete( + client: torClient, + uri: onionUri, + headers: headers, + ); + } catch (_) { + rethrow; + } + } + + if (clearnetUri != null) { + try { + return await _makeDelete( + client: torClient, + uri: clearnetUri, + headers: headers, + ); + } catch (_) { + rethrow; + } + } + } + + if (clearnetUri != null) { + try { + return HttpOverrides.runZoned( + () async { + return await _makeDelete( + client: ioc.IOClient(), + uri: clearnetUri, + headers: headers, + ); + }, + ); + } catch (_) { + // we weren't able to get a response: + rethrow; + } + } + + throw Exception("Unable to connect to server"); + } +} + + +class CakeTor { + static final Tor instance = Tor.instance; +} \ No newline at end of file diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 36bba72b6a..fc8b9a8eeb 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -635,11 +635,21 @@ packages: socks5_proxy: dependency: "direct main" description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: "direct main" + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -716,10 +726,18 @@ packages: dependency: transitive description: name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + tor: + dependency: "direct main" + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "0.0.8" tuple: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 9b38b61a7c..d994e10453 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -25,16 +25,20 @@ dependencies: url: https://github.com/cake-tech/cake_backup.git ref: main version: 1.0.0 - socks5_proxy: ^1.0.4 + socks5_proxy: + git: + url: https://github.com/cake-tech/socks_dart.git + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 unorm_dart: ^0.3.0 on_chain: git: url: https://github.com/cake-tech/on_chain.git ref: cake-update-v2 -# tor: -# git: -# url: https://github.com/cake-tech/tor.git -# ref: main + tor: any + socks_socket: + git: + url: https://github.com/sneurlax/socks_socket + ref: e6232c53c1595469931ababa878759a067c02e94 dev_dependencies: flutter_test: diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 007d9cfb2f..1bea690e56 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -666,11 +666,21 @@ packages: socks5_proxy: dependency: transitive description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: transitive + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -751,6 +761,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tor: + dependency: transitive + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 + url: "https://pub.dev" + source: hosted + version: "0.0.8" tuple: dependency: transitive description: @@ -849,4 +867,4 @@ packages: version: "2.2.2" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=3.27.4" + flutter: ">=3.24.0" diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index 1298d54f55..f810d6ea36 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -19,8 +19,7 @@ class EthereumClient extends EVMChainClient { Future> fetchTransactions(String address, {String? contractAddress}) async { try { - final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { - "chainid": "$chainId", + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { "module": "account", "action": contractAddress != null ? "tokentx" : "txlist", if (contractAddress != null) "contractaddress": contractAddress, @@ -51,7 +50,7 @@ class EthereumClient extends EVMChainClient { @override Future> fetchInternalTransactions(String address) async { try { - final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { "chainid": "$chainId", "module": "account", "action": "txlistinternal", diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index 7e6caf374c..efb64faf12 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -5,6 +5,7 @@ import 'dart:developer'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/node.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; @@ -12,13 +13,12 @@ import 'package:cw_evm/pending_evm_chain_transaction.dart'; import 'package:cw_evm/.secrets.g.dart' as secrets; import 'package:flutter/foundation.dart'; import 'package:hex/hex.dart' as hex; -import 'package:http/http.dart'; import 'package:web3dart/web3dart.dart'; import 'contract/erc20.dart'; abstract class EVMChainClient { - final httpClient = Client(); + late final client = ProxyWrapper().getHttpIOClient(); Web3Client? _client; //! To be overridden by all child classes @@ -47,7 +47,7 @@ abstract class EVMChainClient { } _client = - Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), httpClient); + Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client); return true; } catch (e) { @@ -293,7 +293,7 @@ abstract class EVMChainClient { }, ); - final response = await httpClient.get( + final response = await client.get( uri, headers: { "Accept": "application/json", diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 8c5ab2d411..2bb7d2c52f 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -146,7 +146,25 @@ int getUnlockedBalance({int accountIndex = 0}) => int getCurrentHeight() => currentWallet?.blockChainHeight() ?? 0; -int getNodeHeightSync() => currentWallet?.daemonBlockChainHeight() ?? 0; + +int cachedNodeHeight = 0; +bool isHeightRefreshing = false; +int getNodeHeightSync() { + if (isHeightRefreshing == false) { + (() async { + try { + isHeightRefreshing = true; + final wptrAddress = currentWallet!.ffiAddress(); + cachedNodeHeight = await Isolate.run(() async { + return monero.Wallet_daemonBlockChainHeight(Pointer.fromAddress(wptrAddress)); + }); + } finally { + isHeightRefreshing = false; + } + })(); + } + return cachedNodeHeight; +} bool isConnectedSync() => currentWallet?.connected() != 0; @@ -202,7 +220,6 @@ Future setupNodeSync( } void startRefreshSync() { - currentWallet!.refreshAsync(); currentWallet!.startRefresh(); } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 1e6d3f6a31..97a1e8664a 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -17,6 +17,7 @@ import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; @@ -41,6 +42,7 @@ import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; import 'package:monero/monero.dart' as monero; +import 'package:tor/tor.dart'; part 'monero_wallet.g.dart'; @@ -207,6 +209,14 @@ abstract class MoneroWalletBase extends WalletBase connectToNode({required Node node}) async { + String socksProxy = node.socksProxyAddress ?? ''; + printV("bootstrapped: ${CakeTor.instance.bootstrapped}"); + printV(" enabled: ${CakeTor.instance.enabled}"); + printV(" port: ${CakeTor.instance.port}"); + printV(" started: ${CakeTor.instance.started}"); + if (CakeTor.instance.enabled) { + socksProxy = "127.0.0.1:${CakeTor.instance.port}"; + } try { syncStatus = ConnectingSyncStatus(); await monero_wallet.setupNodeSync( @@ -216,7 +226,7 @@ abstract class MoneroWalletBase extends WalletBase getBalance(String address) async { - final response = await http.post( - _node!.uri, + final response = await ProxyWrapper().post( + clearnetUri: _node!.uri, headers: getHeaders(_node!.uri.host), body: jsonEncode( { @@ -76,7 +76,8 @@ class NanoClient { }, ), ); - final data = await jsonDecode(response.body); + + final data = jsonDecode(response.body) as Map; if (response.statusCode != 200 || data["error"] != null || data["balance"] == null || @@ -93,8 +94,8 @@ class NanoClient { Future getAccountInfo(String address) async { try { - final response = await http.post( - _node!.uri, + final response = await ProxyWrapper().post( + clearnetUri: _node!.uri, headers: getHeaders(_node!.uri.host), body: jsonEncode( { @@ -104,8 +105,9 @@ class NanoClient { }, ), ); - final data = await jsonDecode(response.body); - return AccountInfoResponse.fromJson(data as Map); + + final data = jsonDecode(response.body) as Map; + return AccountInfoResponse.fromJson(data); } catch (e) { printV("error while getting account info $e"); return null; @@ -114,8 +116,8 @@ class NanoClient { Future getBlockContents(String block) async { try { - final response = await http.post( - _node!.uri, + final response = await ProxyWrapper().post( + clearnetUri: _node!.uri, headers: getHeaders(_node!.uri.host), body: jsonEncode( { @@ -125,7 +127,8 @@ class NanoClient { }, ), ); - final data = await jsonDecode(response.body); + + final data = jsonDecode(response.body) as Map; return BlockContentsResponse.fromJson(data["contents"] as Map); } catch (e) { printV("error while getting block info $e"); @@ -181,8 +184,8 @@ class NanoClient { } Future requestWork(String hash) async { - final response = await http.post( - _powNode!.uri, + final response = await ProxyWrapper().post( + clearnetUri: _powNode!.uri, headers: getHeaders(_powNode!.uri.host), body: json.encode( { @@ -191,8 +194,9 @@ class NanoClient { }, ), ); + if (response.statusCode == 200) { - final Map decoded = json.decode(response.body) as Map; + final decoded = jsonDecode(response.body) as Map; if (decoded.containsKey("error")) { throw Exception("Received error ${decoded["error"]}"); } @@ -224,13 +228,13 @@ class NanoClient { "block": block, }); - final processResponse = await http.post( - _node!.uri, + final processResponse = await ProxyWrapper().post( + clearnetUri: _node!.uri, headers: getHeaders(_node!.uri.host), body: processBody, ); - final Map decoded = json.decode(processResponse.body) as Map; + final Map decoded = jsonDecode(processResponse.body) as Map; if (decoded.containsKey("error")) { throw Exception("Received error ${decoded["error"]}"); } @@ -423,12 +427,11 @@ class NanoClient { "subtype": "receive", "block": receiveBlock, }); - final processResponse = await http.post( - _node!.uri, + final processResponse = await ProxyWrapper().post( + clearnetUri: _node!.uri, headers: getHeaders(_node!.uri.host), body: processBody, ); - final Map decoded = json.decode(processResponse.body) as Map; if (decoded.containsKey("error")) { throw Exception("Received error ${decoded["error"]}"); @@ -440,16 +443,17 @@ class NanoClient { required String destinationAddress, required String privateKey, }) async { - final receivableResponse = await http.post(_node!.uri, - headers: getHeaders(_node!.uri.host), - body: jsonEncode({ - "action": "receivable", - "account": destinationAddress, - "count": "-1", - "source": true, - })); - - final receivableData = await jsonDecode(receivableResponse.body); + final receivableResponse = await ProxyWrapper().post( + clearnetUri: _node!.uri, + headers: getHeaders(_node!.uri.host), + body: jsonEncode({ + "action": "receivable", + "account": destinationAddress, + "count": "-1", + "source": true, + }), + ); + final receivableData = jsonDecode(receivableResponse.body) as Map; if (receivableData["blocks"] == "" || receivableData["blocks"] == null) { return 0; } @@ -492,15 +496,18 @@ class NanoClient { Future> fetchTransactions(String address) async { try { - final response = await http.post(_node!.uri, - headers: getHeaders(_node!.uri.host), - body: jsonEncode({ - "action": "account_history", - "account": address, - "count": "100", - // "raw": true, - })); - final data = await jsonDecode(response.body); + final response = await ProxyWrapper().post( + clearnetUri: _node!.uri, + headers: getHeaders(_node!.uri.host), + body: jsonEncode({ + "action": "account_history", + "account": address, + "count": "100", + // "raw": true, + }), + ); + + final data = jsonDecode(response.body) as Map; final transactions = data["history"] is List ? data["history"] as List : []; // Map the transactions list to NanoTransactionModel using the factory @@ -516,13 +523,14 @@ class NanoClient { Future> getN2Reps() async { final uri = Uri.parse(N2_REPS_ENDPOINT); - final response = await http.post( - uri, + final response = await ProxyWrapper().post( + clearnetUri: uri, headers: getHeaders(uri.host), body: jsonEncode({"action": "reps"}), ); try { - final List nodes = (json.decode(response.body) as List) + + final List nodes = (jsonDecode(response.body) as List) .map((dynamic e) => N2Node.fromJson(e as Map)) .toList(); return nodes; @@ -533,8 +541,8 @@ class NanoClient { Future getRepScore(String rep) async { final uri = Uri.parse(N2_REPS_ENDPOINT); - final response = await http.post( - uri, + final response = await ProxyWrapper().post( + clearnetUri: uri, headers: getHeaders(uri.host), body: jsonEncode({ "action": "rep_info", @@ -542,7 +550,8 @@ class NanoClient { }), ); try { - final N2Node node = N2Node.fromJson(json.decode(response.body) as Map); + + final N2Node node = N2Node.fromJson(jsonDecode(response.body) as Map); return node.score ?? 100; } catch (error) { return 100; diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 1eb90852da..4cf232a747 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -771,11 +771,21 @@ packages: socks5_proxy: dependency: transitive description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: transitive + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -852,10 +862,18 @@ packages: dependency: transitive description: name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.1" + tor: + dependency: transitive + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 + url: "https://pub.dev" + source: hosted + version: "0.0.8" tuple: dependency: transitive description: @@ -946,4 +964,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=3.27.4" + flutter: ">=3.24.0" diff --git a/cw_polygon/lib/polygon_client.dart b/cw_polygon/lib/polygon_client.dart index 7e9e882fa5..5c344debe7 100644 --- a/cw_polygon/lib/polygon_client.dart +++ b/cw_polygon/lib/polygon_client.dart @@ -40,7 +40,7 @@ class PolygonClient extends EVMChainClient { Future> fetchTransactions(String address, {String? contractAddress}) async { try { - final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { "chainid": "$chainId", "module": "account", "action": contractAddress != null ? "tokentx" : "txlist", @@ -68,7 +68,7 @@ class PolygonClient extends EVMChainClient { @override Future> fetchInternalTransactions(String address) async { try { - final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { "chainid": "$chainId", "module": "account", "action": "txlistinternal", diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 05b0cec823..b704bded62 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; +import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/solana_rpc_http_service.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_solana/pending_solana_transaction.dart'; @@ -11,14 +13,12 @@ import 'package:cw_solana/solana_balance.dart'; import 'package:cw_solana/solana_exceptions.dart'; import 'package:cw_solana/solana_transaction_model.dart'; import 'package:cw_solana/spl_token.dart'; -import 'package:http/http.dart' as http; import 'package:on_chain/solana/solana.dart'; import 'package:on_chain/solana/src/models/pda/pda.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; import '.secrets.g.dart' as secrets; + class SolanaWalletClient { - final httpClient = http.Client(); SolanaRPC? _provider; bool connect(Node node) { @@ -921,7 +921,8 @@ class SolanaWalletClient { if (uri.isEmpty || uri == '…') return null; try { - final response = await httpClient.get(Uri.parse(uri)); + final client = ProxyWrapper().getHttpIOClient(); + final response = await client.get(Uri.parse(uri)); final jsonResponse = json.decode(response.body) as Map; diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index ee93fbd538..cceec5327f 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -5,6 +5,7 @@ import 'dart:developer'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/node.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_tron/pending_tron_transaction.dart'; import 'package:cw_tron/tron_abi.dart'; import 'package:cw_tron/tron_balance.dart'; @@ -13,12 +14,12 @@ import 'package:cw_tron/tron_token.dart'; import 'package:cw_tron/tron_transaction_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart'; import '.secrets.g.dart' as secrets; import 'package:on_chain/on_chain.dart'; class TronClient { - final httpClient = Client(); + late final client = ProxyWrapper().getHttpIOClient(); + TronProvider? _provider; // This is an internal tracker, so we don't have to "refetch". int _nativeTxEstimatedFee = 0; @@ -28,7 +29,7 @@ class TronClient { Future> fetchTransactions(String address, {String? contractAddress}) async { try { - final response = await httpClient.get( + final response = await client.get( Uri.https( "api.trongrid.io", "/v1/accounts/$address/transactions", @@ -61,7 +62,7 @@ class TronClient { Future> fetchTrc20ExcludedTransactions(String address) async { try { - final response = await httpClient.get( + final response = await client.get( Uri.https( "api.trongrid.io", "/v1/accounts/$address/transactions/trc20", diff --git a/cw_tron/lib/tron_http_provider.dart b/cw_tron/lib/tron_http_provider.dart index 8a3301f878..30207e6b58 100644 --- a/cw_tron/lib/tron_http_provider.dart +++ b/cw_tron/lib/tron_http_provider.dart @@ -1,18 +1,16 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:on_chain/tron/tron.dart'; import '.secrets.g.dart' as secrets; class TronHTTPProvider implements TronServiceProvider { TronHTTPProvider( {required this.url, - http.Client? client, - this.defaultRequestTimeout = const Duration(seconds: 30)}) - : client = client ?? http.Client(); + this.defaultRequestTimeout = const Duration(seconds: 30)}); @override final String url; - final http.Client client; + late final client = ProxyWrapper().getHttpIOClient(); final Duration defaultRequestTimeout; @override diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index 20783490dc..a4ba8cbb2e 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -110,7 +110,16 @@ int getUnlockedBalance({int accountIndex = 0}) => int getCurrentHeight() => wownero.Wallet_blockChainHeight(wptr!); -int getNodeHeightSync() => wownero.Wallet_daemonBlockChainHeight(wptr!); +int cachedNodeHeight = 0; +int getNodeHeightSync() { + (() async { + final wptrAddress = wptr!.address; + cachedNodeHeight = await Isolate.run(() async { + return wownero.Wallet_daemonBlockChainHeight(Pointer.fromAddress(wptrAddress)); + }); + })(); + return cachedNodeHeight; +} bool isConnectedSync() => wownero.Wallet_connected(wptr!) != 0; @@ -154,7 +163,7 @@ Future setupNodeSync( } void startRefreshSync() { - wownero.Wallet_refreshAsync(wptr!); + // wownero.Wallet_refreshAsync(wptr!); wownero.Wallet_startRefresh(wptr!); } diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 66fd1fc567..5c8408e73b 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -670,11 +670,21 @@ packages: socks5_proxy: dependency: transitive description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: transitive + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -751,10 +761,18 @@ packages: dependency: transitive description: name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + tor: + dependency: transitive + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "0.0.8" tuple: dependency: transitive description: diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index ec99b272ab..3346e8ad4c 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -667,11 +667,21 @@ packages: socks5_proxy: dependency: transitive description: - name: socks5_proxy - sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" - url: "https://pub.dev" - source: hosted - version: "1.0.6" + path: "." + ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + resolved-ref: d304fcfcc97cb7212bcd347aeb5d96792c128ff3 + url: "https://github.com/cake-tech/socks_dart.git" + source: git + version: "1.0.4" + socks_socket: + dependency: transitive + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" source_gen: dependency: transitive description: @@ -752,6 +762,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tor: + dependency: transitive + description: + name: tor + sha256: eeed80e5c912a1806c2f81825c12e84f4dc5a0b50aebedea59e3a8ba53df3142 + url: "https://pub.dev" + source: hosted + version: "0.0.8" tuple: dependency: transitive description: @@ -842,4 +860,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=3.27.4" + flutter: ">=3.24.0" diff --git a/integration_test/robots/wallet_keys_robot.dart b/integration_test/robots/wallet_keys_robot.dart index 189929737e..38a605b9db 100644 --- a/integration_test/robots/wallet_keys_robot.dart +++ b/integration_test/robots/wallet_keys_robot.dart @@ -70,7 +70,10 @@ class WalletKeysAndSeedPageRobot { if (walletType == WalletType.bitcoin || walletType == WalletType.litecoin || walletType == WalletType.bitcoinCash) { - commonTestCases.hasText(appStore.wallet!.seed!); + final seedWords = appStore.wallet!.seed!.split(" "); + for (var seedWord in seedWords) { + commonTestCases.hasTextAtLestOnce(seedWord); + } tester.printToConsole('$walletName wallet has seeds properly displayed'); } @@ -78,10 +81,14 @@ class WalletKeysAndSeedPageRobot { walletType == WalletType.solana || walletType == WalletType.tron) { if (hasSeed) { - commonTestCases.hasText(appStore.wallet!.seed!); + final seedWords = appStore.wallet!.seed!.split(" "); + for (var seedWord in seedWords) { + commonTestCases.hasTextAtLestOnce(seedWord); + } tester.printToConsole('$walletName wallet has seeds properly displayed'); } if (hasPrivateKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(appStore.wallet!.privateKey!); tester.printToConsole('$walletName wallet has private key properly displayed'); } @@ -89,14 +96,19 @@ class WalletKeysAndSeedPageRobot { if (walletType == WalletType.nano || walletType == WalletType.banano) { if (hasSeed) { - commonTestCases.hasText(appStore.wallet!.seed!); + final seedWords = appStore.wallet!.seed!.split(" "); + for (var seedWord in seedWords) { + commonTestCases.hasTextAtLestOnce(seedWord); + } tester.printToConsole('$walletName wallet has seeds properly displayed'); } if (hasHexSeed) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(appStore.wallet!.hexSeed!); tester.printToConsole('$walletName wallet has hexSeed properly displayed'); } if (hasPrivateKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(appStore.wallet!.privateKey!); tester.printToConsole('$walletName wallet has private key properly displayed'); } @@ -129,35 +141,39 @@ class WalletKeysAndSeedPageRobot { final hasSeedLegacy = Polyseed.isValidSeed(seed); if (hasPublicSpendKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(keys.publicSpendKey); tester.printToConsole('$walletName wallet has public spend key properly displayed'); } if (hasPrivateSpendKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(keys.privateSpendKey); tester.printToConsole('$walletName wallet has private spend key properly displayed'); } if (hasPublicViewKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(keys.publicViewKey); tester.printToConsole('$walletName wallet has public view key properly displayed'); } if (hasPrivateViewKey) { + await commonTestCases.tapItemByKey('wallet_keys_page_keys'); commonTestCases.hasText(keys.privateViewKey); tester.printToConsole('$walletName wallet has private view key properly displayed'); } if (hasSeeds) { - await commonTestCases.dragUntilVisible( - '${walletName}_wallet_seed_item_key', - 'wallet_keys_page_credentials_list_view_key', - ); - commonTestCases.hasText(seed); + await commonTestCases.tapItemByKey('wallet_keys_page_seed'); + final seedWords = seed.split(" "); + for (var seedWord in seedWords) { + commonTestCases.hasTextAtLestOnce(seedWord); + } tester.printToConsole('$walletName wallet has seeds properly displayed'); } if (hasSeedLegacy) { - await commonTestCases.dragUntilVisible( - '${walletName}_wallet_seed_legacy_item_key', - 'wallet_keys_page_credentials_list_view_key', - ); - commonTestCases.hasText(legacySeed); + await commonTestCases.tapItemByKey('wallet_keys_page_seed_legacy'); + final seedWords = legacySeed.split(" "); + for (var seedWord in seedWords) { + commonTestCases.hasTextAtLestOnce(seedWord); + } tester.printToConsole('$walletName wallet has legacy seeds properly displayed'); } } diff --git a/lib/anonpay/anonpay_api.dart b/lib/anonpay/anonpay_api.dart index acab662d14..6d669d97bb 100644 --- a/lib/anonpay/anonpay_api.dart +++ b/lib/anonpay/anonpay_api.dart @@ -6,8 +6,8 @@ import 'package:cake_wallet/anonpay/anonpay_status_response.dart'; import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:http/http.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -29,8 +29,11 @@ class AnonPayApi { static const apiKey = secrets.trocadorApiKey; Future paymentStatus(String id) async { - final authority = await _getAuthority(); - final response = await get(Uri.https(authority, "$anonPayStatus/$id")); + final response = await ProxyWrapper().get( + clearnetUri: Uri.https(clearNetAuthority, "$anonPayStatus/$id"), + onionUri: Uri.https(onionApiAuthority, "$anonPayStatus/$id"), + ); + final responseJSON = json.decode(response.body) as Map; final status = responseJSON['Status'] as String; final fiatAmount = responseJSON['Fiat_Amount'] as double?; @@ -69,10 +72,11 @@ class AnonPayApi { if (request.fiatEquivalent != null) { body['fiat_equiv'] = request.fiatEquivalent; } - final authority = await _getAuthority(); - - final response = await get(Uri.https(authority, anonPayPath, body)); - + final response = await ProxyWrapper().get( + clearnetUri: Uri.https(clearNetAuthority, anonPayPath, body), + onionUri: Uri.https(onionApiAuthority, anonPayPath, body), + ); + final responseJSON = json.decode(response.body) as Map; final id = responseJSON['ID'] as String; final url = responseJSON['url'] as String; @@ -146,17 +150,16 @@ class AnonPayApi { 'name': cryptoCurrency.name, }; - final String apiAuthority = await _getAuthority(); - final uri = Uri.https(apiAuthority, coinPath, params); - - final response = await get(uri); - + final response = await ProxyWrapper().get( + clearnetUri: Uri.https(clearNetAuthority, coinPath, params), + onionUri: Uri.https(onionApiAuthority, coinPath, params), + ); + + final responseJSON = json.decode(response.body) as List; if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } - final responseJSON = json.decode(response.body) as List; - if (responseJSON.isEmpty) { throw Exception('No data'); } @@ -197,17 +200,4 @@ class AnonPayApi { return tag.toLowerCase(); } } - - Future _getAuthority() async { - try { - if (useTorOnly) { - return onionApiAuthority; - } - final uri = Uri.https(onionApiAuthority, '/anonpay'); - await get(uri); - return onionApiAuthority; - } catch (e) { - return clearNetAuthority; - } - } } diff --git a/lib/anypay/anypay_api.dart b/lib/anypay/anypay_api.dart index 0b81d24c29..20187484b6 100644 --- a/lib/anypay/anypay_api.dart +++ b/lib/anypay/anypay_api.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/anypay/any_pay_payment.dart'; import 'package:cake_wallet/anypay/any_pay_trasnaction.dart'; @@ -53,8 +53,12 @@ class AnyPayApi { final body = { 'chain': chainByScheme(scheme), 'currency': currencyByScheme(scheme).title}; - final response = await post(url, headers: headers, body: utf8.encode(json.encode(body))); - + final response = await ProxyWrapper().post( + clearnetUri: url, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 200) { await ExceptionHandler.onError(FlutterErrorDetails(exception: response)); throw Exception('Unexpected response http code: ${response.statusCode}'); @@ -79,7 +83,12 @@ class AnyPayApi { 'chain': chain, 'currency': currency, 'transactions': transactions.map((tx) => {'tx': tx.tx, 'tx_hash': tx.id, 'tx_key': tx.key}).toList()}; - final response = await post(Uri.parse(uri), headers: headers, body: utf8.encode(json.encode(body))); + final response = await ProxyWrapper().post( + clearnetUri: Uri.parse(uri), + headers: headers, + body: json.encode(body), + ); + if (response.statusCode == 400) { final decodedBody = json.decode(response.body) as Map; throw Exception(decodedBody['message'] as String? ?? 'Unexpected response\nError code: 400'); diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index 150f731dce..eed527cf12 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -17,7 +18,6 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class DFXBuyProvider extends BuyProvider { @@ -100,11 +100,12 @@ class DFXBuyProvider extends BuyProvider { }); final uri = Uri.https(_baseUrl, _authPath); - var response = await http.post( - uri, + final response = await ProxyWrapper().post( + clearnetUri: uri, headers: {'Content-Type': 'application/json'}, body: requestBody, ); + if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); @@ -137,8 +138,10 @@ class DFXBuyProvider extends BuyProvider { final url = Uri.https(_baseUrl, '/v1/fiat'); try { - final response = await http.get(url, headers: {'accept': 'application/json'}); - + final response = await ProxyWrapper().get( + clearnetUri: url, + headers: {'accept': 'application/json'}); + if (response.statusCode == 200) { final data = jsonDecode(response.body) as List; for (final item in data) { @@ -160,8 +163,8 @@ class DFXBuyProvider extends BuyProvider { final url = Uri.https(_baseUrl, '/v1/asset', {'blockchains': blockchain}); try { - final response = await http.get(url, headers: {'accept': 'application/json'}); - + final response = await ProxyWrapper().get(clearnetUri: url, headers: {'accept': 'application/json'}); + if (response.statusCode == 200) { final responseData = jsonDecode(response.body); @@ -271,7 +274,12 @@ class DFXBuyProvider extends BuyProvider { }); try { - final response = await http.put(url, headers: headers, body: body); + final response = await ProxyWrapper().put( + clearnetUri: url, + headers: headers, + body: body, + ); + final responseData = jsonDecode(response.body); if (response.statusCode == 200) { diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart index 5e58ce1902..1d2d10f581 100644 --- a/lib/buy/kryptonim/kryptonim.dart +++ b/lib/buy/kryptonim/kryptonim.dart @@ -9,10 +9,10 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'dart:developer'; -import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class KryptonimBuyProvider extends BuyProvider { @@ -74,9 +74,14 @@ class KryptonimBuyProvider extends BuyProvider { }); try { - final response = await http.post(url, headers: headers, body: body); + final response = await ProxyWrapper().post( + clearnetUri: url, + headers: headers, + body: body, + ); if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 401) { + return jsonDecode(response.body) as Map; } else { return {}; diff --git a/lib/buy/meld/meld_buy_provider.dart b/lib/buy/meld/meld_buy_provider.dart index db593d3580..002ab04655 100644 --- a/lib/buy/meld/meld_buy_provider.dart +++ b/lib/buy/meld/meld_buy_provider.dart @@ -8,13 +8,13 @@ import 'package:cake_wallet/buy/payment_method.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'dart:developer'; -import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class MeldBuyProvider extends BuyProvider { @@ -71,8 +71,8 @@ class MeldBuyProvider extends BuyProvider { final url = Uri.https(_baseUrl, path, params); try { - final response = await http.get( - url, + final response = await ProxyWrapper().get( + clearnetUri: url, headers: { 'Authorization': _isProduction ? '' : _testApiKey, 'Meld-Version': '2023-12-19', @@ -80,6 +80,7 @@ class MeldBuyProvider extends BuyProvider { 'content-type': 'application/json', }, ); + if (response.statusCode == 200) { final data = jsonDecode(response.body) as List; @@ -130,7 +131,12 @@ class MeldBuyProvider extends BuyProvider { }); try { - final response = await http.post(url, headers: headers, body: body); + final response = await ProxyWrapper().post( + clearnetUri: url, + headers: headers, + body: body, + ); + if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 1cfef72023..8e87130691 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -16,12 +16,12 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart'; import 'package:url_launcher/url_launcher.dart'; class MoonPayProvider extends BuyProvider { @@ -101,9 +101,12 @@ class MoonPayProvider extends BuyProvider { Future getMoonpaySignature(String query) async { final uri = Uri.https(_cIdBaseUrl, "/api/moonpay"); - final response = await post(uri, - headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey}, - body: json.encode({'query': query})); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey}, + body: json.encode({'query': query}), + ); + if (response.statusCode == 200) { return (jsonDecode(response.body) as Map)['signature'] as String; @@ -123,7 +126,11 @@ class MoonPayProvider extends BuyProvider { final url = Uri.https(_baseUrl, path, params); try { - final response = await get(url, headers: {'accept': 'application/json'}); + final response = await ProxyWrapper().get( + clearnetUri: url, + headers: {'accept': 'application/json'}, + ); + if (response.statusCode == 200) { return jsonDecode(response.body) as Map; } else { @@ -194,8 +201,8 @@ class MoonPayProvider extends BuyProvider { final path = '$_currenciesPath/$formattedCryptoCurrency$quotePath'; final url = Uri.https(_baseUrl, path, params); try { - final response = await get(url); - + final response = await ProxyWrapper().get(clearnetUri: url); + if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; @@ -311,7 +318,8 @@ class MoonPayProvider extends BuyProvider { Future findOrderById(String id) async { final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; final uri = Uri.parse(url); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { throw BuyException(title: providerDescription, content: 'Transaction $id is not found!'); diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 35683f01de..90a6d36cc4 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -10,11 +10,11 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class OnRamperBuyProvider extends BuyProvider { @@ -68,7 +68,8 @@ class OnRamperBuyProvider extends BuyProvider { try { final response = - await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + await ProxyWrapper().get(clearnetUri: url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + if (response.statusCode == 200) { final Map data = jsonDecode(response.body) as Map; @@ -103,8 +104,8 @@ class OnRamperBuyProvider extends BuyProvider { try { final response = - await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); - + await ProxyWrapper().get(clearnetUri: url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + if (response.statusCode == 200) { final Map data = jsonDecode(response.body) as Map; final List message = data['message'] as List; @@ -133,7 +134,8 @@ class OnRamperBuyProvider extends BuyProvider { try { final response = - await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + await ProxyWrapper().get(clearnetUri: url, headers: {'Authorization': _apiKey, 'accept': 'application/json'}); + if (response.statusCode == 200) { final Map data = jsonDecode(response.body) as Map; @@ -196,8 +198,8 @@ class OnRamperBuyProvider extends BuyProvider { final headers = {'Authorization': _apiKey, 'accept': 'application/json'}; try { - final response = await http.get(url, headers: headers); - + final response = await ProxyWrapper().get(clearnetUri: url, headers: headers); + if (response.statusCode == 200) { final data = jsonDecode(response.body) as List; if (data.isEmpty) return null; diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 4945529ef5..4b9c1aa708 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -18,7 +19,6 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; class RobinhoodBuyProvider extends BuyProvider { @@ -69,7 +69,8 @@ class RobinhoodBuyProvider extends BuyProvider { final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId}); try { - final response = await http.get(uri, headers: {'accept': 'application/json'}); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'}); + if (response.statusCode == 200) { final responseData = jsonDecode(response.body) as Map; @@ -122,12 +123,14 @@ class RobinhoodBuyProvider extends BuyProvider { final uri = Uri.https(_cIdBaseUrl, "/api/robinhood"); - var response = await http.post(uri, - headers: {'Content-Type': 'application/json'}, - body: json - .encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature})); + var response = await ProxyWrapper().post( + clearnetUri: uri, + headers: {'Content-Type': 'application/json'}, + body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}), + ); if (response.statusCode == 200) { + return (jsonDecode(response.body) as Map)['connectId'] as String; } else { throw Exception('Provider currently unavailable. Status: ${response.statusCode}'); @@ -219,7 +222,8 @@ class RobinhoodBuyProvider extends BuyProvider { Uri.https('api.robinhood.com', '/catpay/v1/${cryptoCurrency.title}/quote/', queryParams); try { - final response = await http.get(uri, headers: {'accept': 'application/json'}); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'}); + final responseData = jsonDecode(response.body) as Map; if (response.statusCode == 200) { diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index 7fe6f4be3d..3f9844df66 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/buy/buy_exception.dart'; import 'package:cake_wallet/buy/pairs_utils.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:http/http.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart'; @@ -73,18 +73,21 @@ class WyreBuyProvider extends BuyProvider { 'referrerAccountId': _accountId, 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] }; - final response = await post(uri, - headers: { - 'Authorization': 'Bearer $_secretKey', - 'Content-Type': 'application/json', - 'cache-control': 'no-cache' - }, - body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: { + 'Authorization': 'Bearer $_secretKey', + 'Content-Type': 'application/json', + 'cache-control': 'no-cache' + }, + body: json.encode(body), + ); if (response.statusCode != 200) { throw BuyException(title: providerDescription, content: 'Url $url is not found!'); } + final responseJSON = json.decode(response.body) as Map; final urlFromResponse = responseJSON['url'] as String; return urlFromResponse; @@ -101,18 +104,21 @@ class WyreBuyProvider extends BuyProvider { 'country': _countryCode }; final uri = Uri.parse(quoteUrl); - final response = await post(uri, - headers: { - 'Authorization': 'Bearer $_secretKey', - 'Content-Type': 'application/json', - 'cache-control': 'no-cache' - }, - body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: { + 'Authorization': 'Bearer $_secretKey', + 'Content-Type': 'application/json', + 'cache-control': 'no-cache' + }, + body: json.encode(body), + ); if (response.statusCode != 200) { throw BuyException(title: providerDescription, content: 'Quote is not found!'); } + final responseJSON = json.decode(response.body) as Map; final sourceAmount = responseJSON['sourceAmount'] as double; final destAmount = responseJSON['destAmount'] as double; @@ -125,8 +131,7 @@ class WyreBuyProvider extends BuyProvider { Future findOrderById(String id) async { final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; final orderUri = Uri.parse(orderUrl); - final orderResponse = await get(orderUri); - + final orderResponse = await ProxyWrapper().get(clearnetUri: orderUri); if (orderResponse.statusCode != 200) { throw BuyException(title: providerDescription, content: 'Order $id is not found!'); } @@ -142,8 +147,7 @@ class WyreBuyProvider extends BuyProvider { final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix; final transferUri = Uri.parse(transferUrl); - final transferResponse = await get(transferUri); - + final transferResponse = await ProxyWrapper().get(clearnetUri: transferUri); if (transferResponse.statusCode != 200) { throw BuyException(title: providerDescription, content: 'Transfer $transferId is not found!'); } diff --git a/lib/cake_pay/cake_pay_api.dart b/lib/cake_pay/cake_pay_api.dart index 5f1a350c00..a3c7f48d08 100644 --- a/lib/cake_pay/cake_pay_api.dart +++ b/lib/cake_pay/cake_pay_api.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'package:cake_wallet/cake_pay/cake_pay_order.dart'; import 'package:cake_wallet/cake_pay/cake_pay_user_credentials.dart'; import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cake_wallet/entities/country.dart'; -import 'package:http/http.dart' as http; class CakePayApi { static const testBaseUri = false; @@ -32,12 +32,17 @@ class CakePayApi { 'Content-Type': 'application/json', 'Authorization': 'Api-Key $apiKey', }; - final response = await http.post(uri, headers: headers, body: json.encode({'email': email})); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode({'email': email}), + ); if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } + final bodyJson = json.decode(response.body) as Map; if (bodyJson.containsKey('user') && bodyJson['user']['email'] != null) { @@ -64,12 +69,17 @@ class CakePayApi { }; final query = {'email': email, 'otp': code}; - final response = await http.post(uri, headers: headers, body: json.encode(query)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(query), + ); if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } + final bodyJson = json.decode(response.body) as Map; if (bodyJson.containsKey('error')) { @@ -116,9 +126,14 @@ class CakePayApi { }; try { - final response = await http.post(uri, headers: headers, body: json.encode(query)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(query), + ); if (response.statusCode != 201) { + final responseBody = json.decode(response.body); if (responseBody is List) { throw '${responseBody[0]}'; @@ -127,6 +142,7 @@ class CakePayApi { } } + final bodyJson = json.decode(response.body) as Map; return CakePayOrder.fromMap(bodyJson); } catch (e) { @@ -145,7 +161,8 @@ class CakePayApi { 'X-CSRFToken': CSRFToken, }; - final response = await http.get(uri, headers: headers); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + printV('Response: ${response.statusCode}'); @@ -168,7 +185,11 @@ class CakePayApi { }; try { - final response = await http.post(uri, headers: headers, body: json.encode({'email': email})); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode({'email': email}), + ); if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); @@ -187,8 +208,8 @@ class CakePayApi { 'Authorization': 'Api-Key $apiKey', }; - final response = await http.get(uri, headers: headers); - + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } @@ -234,14 +255,15 @@ class CakePayApi { 'Authorization': 'Api-Key $apiKey', }; - var response = await http.get(uri, headers: headers); + var response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + if (response.statusCode != 200) { throw Exception( 'Failed to fetch vendors: statusCode - ${response.statusCode}, queryParams -$queryParams, response - ${response.body}'); } - final bodyJson = json.decode(utf8.decode(response.bodyBytes)); + final bodyJson = json.decode(response.body); if (bodyJson is List && bodyJson.isEmpty) { return []; diff --git a/lib/core/background_sync.dart b/lib/core/background_sync.dart index 12eb81f995..a503f5c1b7 100644 --- a/lib/core/background_sync.dart +++ b/lib/core/background_sync.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/feature_flag.dart'; +import 'package:cake_wallet/utils/tor.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cw_core/sync_status.dart'; @@ -15,6 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter/foundation.dart'; class BackgroundSync { final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -90,6 +92,11 @@ class BackgroundSync { } Future sync() async { + final settingsStore = getIt.get(); + if (settingsStore.currentBuiltinTor) { + printV("Starting Tor"); + await ensureTorStarted(context: null); + } printV("Background sync started"); await _syncWallets(); printV("Background sync completed"); @@ -100,7 +107,6 @@ class BackgroundSync { final walletListViewModel = getIt.get(); final settingsStore = getIt.get(); - final List moneroWallets = walletListViewModel.wallets .where((element) => !element.isHardware) .where((element) => ![WalletType.haven, WalletType.decred].contains(element.type)) diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 8a37175b45..6c3654ebb5 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -1,18 +1,14 @@ +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com'; const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion'; const _fiatApiPath = '/v2/rates'; -Future _fetchPrice(Map args) async { - final crypto = args['crypto'] as String; - final fiat = args['fiat'] as String; - final torOnly = args['torOnly'] as bool; +Future _fetchPrice(String crypto, String fiat, bool torOnly) async { final Map queryParams = { 'interval_count': '1', @@ -24,14 +20,14 @@ Future _fetchPrice(Map args) async { num price = 0.0; try { - late final Uri uri; - if (torOnly) { - uri = Uri.http(_fiatApiOnionAuthority, _fiatApiPath, queryParams); - } else { - uri = Uri.https(_fiatApiClearNetAuthority, _fiatApiPath, queryParams); - } + final onionUri = Uri.http(_fiatApiOnionAuthority, _fiatApiPath, queryParams); + final clearnetUri = Uri.https(_fiatApiClearNetAuthority, _fiatApiPath, queryParams); - final response = await get(uri); + final response = await ProxyWrapper().get( + onionUri: onionUri, + clearnetUri: torOnly ? onionUri : clearnetUri, + ); + if (response.statusCode != 200) { return 0.0; @@ -50,18 +46,11 @@ Future _fetchPrice(Map args) async { } } -Future _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async => - compute(_fetchPrice, { - 'fiat': fiat.toString(), - 'crypto': crypto.toString(), - 'torOnly': torOnly, - }); - class FiatConversionService { static Future fetchPrice({ required CryptoCurrency crypto, required FiatCurrency fiat, required bool torOnly, }) async => - await _fetchPriceAsync(crypto, fiat, torOnly); + await _fetchPrice(crypto.toString(), fiat.toString(), torOnly); } diff --git a/lib/core/open_crypto_pay/open_cryptopay_service.dart b/lib/core/open_crypto_pay/open_cryptopay_service.dart index 6449bbf90c..df7314c68f 100644 --- a/lib/core/open_crypto_pay/open_cryptopay_service.dart +++ b/lib/core/open_crypto_pay/open_cryptopay_service.dart @@ -5,14 +5,15 @@ import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart'; import 'package:cake_wallet/core/open_crypto_pay/lnurl.dart'; import 'package:cake_wallet/core/open_crypto_pay/models.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:http/http.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; class OpenCryptoPayService { static bool isOpenCryptoPayQR(String value) => value.toLowerCase().contains("lightning=lnurl") || value.toLowerCase().startsWith("lnurl"); - final Client _httpClient = Client(); + // ignore: deprecated_member_use + final _httpClient = ProxyWrapper().getHttpClient(); Future commitOpenCryptoPayRequest( String txHex, { @@ -31,7 +32,8 @@ class OpenCryptoPayService { queryParams['tx'] = txId; final response = - await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + await ProxyWrapper().get(clearnetUri: Uri.https(uri.authority, uri.path, queryParams)); + if (response.statusCode == 200) { final body = jsonDecode(response.body) as Map; @@ -40,13 +42,13 @@ class OpenCryptoPayService { throw OpenCryptoPayException(body.toString()); } throw OpenCryptoPayException( - "Unexpected status code ${response.statusCode} ${response.body}"); + "Unexpected status code ${response.statusCode} ${response}"); } Future cancelOpenCryptoPayRequest(OpenCryptoPayRequest request) async { final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/cancel/")); - await _httpClient.delete(uri); + await ProxyWrapper().delete(clearnetUri: uri); } Future getOpenCryptoPayInvoice(String lnUrl) async { @@ -73,7 +75,8 @@ class OpenCryptoPayService { Future<(_OpenCryptoPayQuote, Map>)> _getOpenCryptoPayParams(Uri uri) async { - final response = await _httpClient.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 200) { final responseBody = jsonDecode(response.body) as Map; @@ -119,8 +122,8 @@ class OpenCryptoPayService { queryParams['asset'] = asset.title; queryParams['method'] = _getMethod(asset); - final response = - await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + final response = await ProxyWrapper().get(clearnetUri: Uri.https(uri.authority, uri.path, queryParams)); + if (response.statusCode == 200) { final responseBody = jsonDecode(response.body) as Map; diff --git a/lib/core/yat_service.dart b/lib/core/yat_service.dart index 92e81e5eff..84aac16265 100644 --- a/lib/core/yat_service.dart +++ b/lib/core/yat_service.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:cake_wallet/entities/yat_record.dart'; -import 'package:http/http.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; class YatService { static bool isDevMode = false; @@ -33,7 +33,8 @@ class YatService { final yatRecords = []; try { - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + final resBody = json.decode(response.body) as Map; final results = resBody["result"] as Map; // Favour a subaddress over a standard address. @@ -42,7 +43,7 @@ class YatService { results[MONERO_STD_ADDRESS] ?? results[tag]) as Map; - if (yatRecord != null) { + if (yatRecord.isNotEmpty) { yatRecords.add(YatRecord.fromJson(yatRecord)); } diff --git a/lib/di.dart b/lib/di.dart index c8b0020081..eb11364fbc 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; +import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart'; @@ -45,6 +46,7 @@ import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; +import 'package:cake_wallet/view_model/start_tor_view_model.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; @@ -135,7 +137,6 @@ import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart'; -import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart'; @@ -663,7 +664,6 @@ Future setup({ return walletKitService; }); - getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => BalancePage( nftViewModel: getIt.get(), dashboardViewModel: getIt.get(), @@ -1481,7 +1481,7 @@ Future setup({ () => WalletConnectConnectionsView(walletKitService: getIt.get()), ); - getIt.registerFactory(() => TorPage(getIt.get())); + getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => SignViewModel(getIt.get().wallet!)); @@ -1498,6 +1498,8 @@ Future setup({ getIt.registerFactory(() => BackgroundSyncLogsViewModel()); getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get())); + + getIt.registerFactory(() => StartTorPage(StartTorViewModel(),)); _isSetupFinished = true; } diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 512244c1ba..78b0f4178b 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -1,14 +1,16 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:ens_dart/ens_dart.dart'; -import 'package:http/http.dart'; import 'package:web3dart/web3dart.dart'; class EnsRecord { + static Future fetchEnsAddress(String name, {WalletBase? wallet}) async { + Web3Client? _client; if (wallet != null && wallet.type == WalletType.ethereum) { @@ -20,7 +22,9 @@ class EnsRecord { } if (_client == null) { - _client = Web3Client("https://ethereum-rpc.publicnode.com", Client()); + late final client = ProxyWrapper().getHttpIOClient(); + + _client = Web3Client("https://ethereum-rpc.publicnode.com", client); } try { diff --git a/lib/entities/fio_address_provider.dart b/lib/entities/fio_address_provider.dart index a88804c97f..dcfd79c96d 100644 --- a/lib/entities/fio_address_provider.dart +++ b/lib/entities/fio_address_provider.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; class FioAddressProvider { static const apiAuthority = 'fio.blockpane.com'; @@ -13,13 +13,17 @@ class FioAddressProvider { final body = {"fio_name": fioAddress}; final uri = Uri.https(apiAuthority, availCheck); - final response = - await http.post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); if (response.statusCode != 200) { return isFioRegistered; } + final responseJSON = json.decode(response.body) as Map; isFioRegistered = responseJSON['is_registered'] as int == 1; @@ -35,9 +39,13 @@ class FioAddressProvider { }; final uri = Uri.https(apiAuthority, getAddress); - final response = - await http.post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error'] as String; diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 494888a860..740e901bd4 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -64,6 +64,7 @@ class PreferencesKey { static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; static const syncModeKey = 'sync_mode'; static const syncAllKey = 'sync_all'; + static const builtinTorKey = 'builtin_tor'; static const lastPopupDate = 'last_popup_date'; static const lastAppReviewDate = 'last_app_review_date'; static const sortBalanceBy = 'sort_balance_by'; diff --git a/lib/entities/unstoppable_domain_address.dart b/lib/entities/unstoppable_domain_address.dart index a047c85d9a..cf6eb89459 100644 --- a/lib/entities/unstoppable_domain_address.dart +++ b/lib/entities/unstoppable_domain_address.dart @@ -1,15 +1,16 @@ import 'dart:convert'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; Future fetchUnstoppableDomainAddress(String domain, String ticker) async { var address = ''; try { final uri = Uri.parse("https://api.unstoppabledomains.com/profile/public/${Uri.encodeQueryComponent(domain)}?fields=records"); - final jsonString = await http.read(uri); - final jsonParsed = json.decode(jsonString) as Map; + final response = await ProxyWrapper().get(clearnetUri: uri); + + final jsonParsed = json.decode(response.body) as Map; if (jsonParsed["records"] == null) { throw Exception(".records response from $uri is empty"); }; diff --git a/lib/entities/wellknown_record.dart b/lib/entities/wellknown_record.dart index dbe808281c..6dd94440b3 100644 --- a/lib/entities/wellknown_record.dart +++ b/lib/entities/wellknown_record.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; class WellKnownRecord { WellKnownRecord({ @@ -40,14 +40,15 @@ class WellKnownRecord { } // lookup domain/.well-known/nano-currency.json and check if it has a nano address: - final http.Response response = await http.get( - Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"), + final response = await ProxyWrapper().get( + clearnetUri: Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"), headers: {"Accept": "application/json"}, ); if (response.statusCode != 200) { return null; } + final Map decoded = json.decode(response.body) as Map; // Access the first element in the names array and retrieve its address diff --git a/lib/entities/zano_alias.dart b/lib/entities/zano_alias.dart index 1ddf951784..ec966e225b 100644 --- a/lib/entities/zano_alias.dart +++ b/lib/entities/zano_alias.dart @@ -1,14 +1,14 @@ import 'dart:convert'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; class ZanoAlias { static Future fetchZanoAliasAddress(String alias) async { try { final uri = Uri.parse("http://zano.cakewallet.com:11211/json_rpc"); - final response = await http.post( - uri, + final response = await ProxyWrapper().post( + clearnetUri: uri, body: json.encode({ "id": 0, "jsonrpc": "2.0", @@ -16,6 +16,7 @@ class ZanoAlias { "params": {"alias": alias} }), ); + final jsonParsed = json.decode(response.body) as Map; return jsonParsed['result']['alias_details']['address'] as String?; diff --git a/lib/exchange/provider/chainflip_exchange_provider.dart b/lib/exchange/provider/chainflip_exchange_provider.dart index a2c27b745b..c2e2f385cf 100644 --- a/lib/exchange/provider/chainflip_exchange_provider.dart +++ b/lib/exchange/provider/chainflip_exchange_provider.dart @@ -12,7 +12,7 @@ import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; class ChainflipExchangeProvider extends ExchangeProvider { ChainflipExchangeProvider({required this.tradesStore}) @@ -275,7 +275,8 @@ class ChainflipExchangeProvider extends ExchangeProvider { Future> _getRequest(String path, Map params) async { final uri = Uri.https(_baseURL, path, params); - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if ((response.statusCode != 200) || (response.body.contains('error'))) { throw Exception('Unexpected response: ${response.statusCode} / ${uri.toString()} / ${response.body}'); @@ -287,7 +288,8 @@ class ChainflipExchangeProvider extends ExchangeProvider { Future?> _getStatus(Map params) async { final uri = Uri.https(_baseURL, _txInfoPath, params); - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 404) return null; diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index 79f8d70d4c..2cbd97dd25 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -13,11 +13,11 @@ import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/distribution_info.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart'; - +import 'package:cw_core/utils/proxy_wrapper.dart'; class ChangeNowExchangeProvider extends ExchangeProvider { ChangeNowExchangeProvider({required SettingsStore settingsStore}) : _settingsStore = settingsStore, @@ -73,7 +73,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { 'flow': _getFlow(isFixedRateMode) }; final uri = Uri.https(apiAuthority, rangePath, params); - final response = await get(uri, headers: headers); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; @@ -118,7 +119,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { params['fromAmount'] = amount.toString(); final uri = Uri.https(apiAuthority, estimatedAmountPath, params); - final response = await get(uri, headers: headers); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + final responseJSON = json.decode(response.body) as Map; final fromAmount = double.parse(responseJSON['fromAmount'].toString()); final toAmount = double.parse(responseJSON['toAmount'].toString()); @@ -177,7 +179,12 @@ class ChangeNowExchangeProvider extends ExchangeProvider { } final uri = Uri.https(apiAuthority, createTradePath); - final response = await post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; @@ -220,7 +227,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final headers = {apiHeaderKey: apiKey}; final params = {'id': id}; final uri = Uri.https(apiAuthority, findTradeByIdPath, params); - final response = await get(uri, headers: headers); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + if (response.statusCode == 404) throw TradeNotFoundException(id, provider: description); diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 43f63b8cab..2d5011fe0c 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -9,9 +9,9 @@ import 'package:cake_wallet/exchange/trade_not_found_exception.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart'; class ExolixExchangeProvider extends ExchangeProvider { ExolixExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -86,8 +86,9 @@ class ExolixExchangeProvider extends ExchangeProvider { // Maximum of 2 attempts to fetch limits for (int i = 0; i < 2; i++) { final uri = Uri.https(apiBaseUrl, ratePath, params); - final response = await get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + + if (response.statusCode == 200) { final responseJSON = json.decode(response.body) as Map; final minAmount = responseJSON['minAmount']; @@ -133,7 +134,8 @@ class ExolixExchangeProvider extends ExchangeProvider { params['amount'] = amount.toString(); final uri = Uri.https(apiBaseUrl, ratePath, params); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + final responseJSON = json.decode(response.body) as Map; if (response.statusCode != 200) { @@ -172,7 +174,12 @@ class ExolixExchangeProvider extends ExchangeProvider { body['amount'] = request.fromAmount; final uri = Uri.https(apiBaseUrl, transactionsPath); - final response = await post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; @@ -214,8 +221,8 @@ class ExolixExchangeProvider extends ExchangeProvider { Future findTradeById({required String id}) async { final findTradeByIdPath = '$transactionsPath/$id'; final uri = Uri.https(apiBaseUrl, findTradeByIdPath); - final response = await get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 404) throw TradeNotFoundException(id, provider: description); if (response.statusCode == 400) { diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart index d07297fcf9..1fe1e9e35d 100644 --- a/lib/exchange/provider/letsexchange_exchange_provider.dart +++ b/lib/exchange/provider/letsexchange_exchange_provider.dart @@ -10,9 +10,9 @@ import 'package:cake_wallet/exchange/trade_not_created_exception.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; class LetsExchangeExchangeProvider extends ExchangeProvider { LetsExchangeExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -152,7 +152,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { final uri = Uri.https(_baseUrl, isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath, tradeParams); - final response = await http.post(uri, headers: headers); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + ); + if (response.statusCode != 200) { throw Exception('LetsExchange create trade failed: ${response.body}'); @@ -218,7 +222,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { }; final url = Uri.https(_baseUrl, '$_getTransactionPath/$id'); - final response = await http.get(url, headers: headers); + final response = await ProxyWrapper().get(clearnetUri: url, headers: headers); + if (response.statusCode != 200) { throw Exception('LetsExchange fetch trade failed: ${response.body}'); @@ -266,7 +271,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { try { final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath, params); - final response = await http.post(uri, headers: headers); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + ); + if (response.statusCode != 200) { throw Exception('LetsExchange fetch info failed: ${response.body}'); } diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index 12ec591000..3c9b2d9510 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; @@ -11,9 +10,9 @@ import 'package:cake_wallet/exchange/trade_not_found_exception.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart'; class SideShiftExchangeProvider extends ExchangeProvider { SideShiftExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -60,8 +59,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { Future checkIsAvailable() async { const url = apiBaseUrl + permissionPath; final uri = Uri.parse(url); - final response = await get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 500) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error']['message'] as String; @@ -90,7 +89,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { "$apiBaseUrl$rangePath/${fromCurrency.title.toLowerCase()}-$fromNetwork/${toCurrency.title.toLowerCase()}-$toNetwork"; final uri = Uri.parse(url); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 500) { final responseJSON = json.decode(response.body) as Map; @@ -137,7 +137,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { "$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork?amount=$amount"; final uri = Uri.parse(url); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + final responseJSON = json.decode(response.body) as Map; if (response.statusCode == 500) { @@ -186,7 +187,12 @@ class SideShiftExchangeProvider extends ExchangeProvider { final headers = {'Content-Type': 'application/json'}; final uri = Uri.parse(url); - final response = await post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 201) { if (response.statusCode == 400) { @@ -227,8 +233,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { Future findTradeById({required String id}) async { final url = apiBaseUrl + orderPath + '/' + id; final uri = Uri.parse(url); - final response = await get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 404) { throw TradeNotFoundException(id, provider: description); } @@ -281,7 +287,12 @@ class SideShiftExchangeProvider extends ExchangeProvider { 'depositNetwork': _networkFor(request.fromCurrency), }; final uri = Uri.parse(url); - final response = await post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 201) { if (response.statusCode == 400) { diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index 5391d5f897..cc02ac799f 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -11,8 +11,8 @@ import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:http/http.dart'; class SimpleSwapExchangeProvider extends ExchangeProvider { SimpleSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -48,7 +48,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { @override Future checkIsAvailable() async { final uri = Uri.https(apiAuthority, getEstimatePath, {'api_key': apiKey}); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); return !(response.statusCode == 403); } @@ -66,7 +66,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(apiAuthority, rangePath, params); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 500) { final responseJSON = json.decode(response.body) as Map; @@ -104,10 +105,10 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { 'fixed': isFixedRateMode.toString() }; final uri = Uri.https(apiAuthority, getEstimatePath, params); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.body == "null") return 0.00; - final data = json.decode(response.body) as String; return double.parse(data) / amount; @@ -134,7 +135,12 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(apiAuthority, createExchangePath, params); - final response = await post(uri, headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 200 && response.statusCode != 201) { if (response.statusCode == 400) { @@ -176,8 +182,9 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { Future findTradeById({required String id}) async { final params = {'api_key': apiKey, 'id': id}; final uri = Uri.https(apiAuthority, getExchangePath, params); - final response = await get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + + if (response.statusCode == 404) { throw TradeNotFoundException(id, provider: description); } diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart index 11b8d768a2..8c7efae65d 100644 --- a/lib/exchange/provider/stealth_ex_exchange_provider.dart +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -10,8 +10,8 @@ import 'package:cake_wallet/exchange/trade_not_created_exception.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:http/http.dart' as http; class StealthExExchangeProvider extends ExchangeProvider { StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -63,8 +63,12 @@ class StealthExExchangeProvider extends ExchangeProvider { }; try { - final response = await http.post(Uri.parse(_baseUrl + _rangePath), - headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: Uri.parse(_baseUrl + _rangePath), + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 200) { throw Exception('StealthEx fetch limits failed: ${response.body}'); } @@ -134,8 +138,12 @@ class StealthExExchangeProvider extends ExchangeProvider { 'additional_fee_percent': _additionalFeePercent, }; - final response = await http.post(Uri.parse(_baseUrl + _exchangesPath), - headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: Uri.parse(_baseUrl + _exchangesPath), + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 201) { throw Exception('StealthEx create trade failed: ${response.body}'); @@ -202,8 +210,9 @@ class StealthExExchangeProvider extends ExchangeProvider { final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'}; final uri = Uri.parse('$_baseUrl$_exchangesPath/$id'); - final response = await http.get(uri, headers: headers); - + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers); + + if (response.statusCode != 200) { throw Exception('StealthEx fetch trade failed: ${response.body}'); } @@ -260,8 +269,12 @@ class StealthExExchangeProvider extends ExchangeProvider { }; try { - final response = await http.post(Uri.parse(_baseUrl + _amountPath), - headers: headers, body: json.encode(body)); + final response = await ProxyWrapper().post( + clearnetUri: Uri.parse(_baseUrl + _amountPath), + headers: headers, + body: json.encode(body), + ); + if (response.statusCode != 200) return {}; final responseJSON = json.decode(response.body) as Map; final rate = responseJSON['rate'] as Map?; diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart index d3f64b7127..f6c8332dba 100644 --- a/lib/exchange/provider/swaptrade_exchange_provider.dart +++ b/lib/exchange/provider/swaptrade_exchange_provider.dart @@ -10,9 +10,9 @@ import 'package:cake_wallet/exchange/trade_not_found_exception.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart'; class SwapTradeExchangeProvider extends ExchangeProvider { SwapTradeExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -69,7 +69,8 @@ class SwapTradeExchangeProvider extends ExchangeProvider { }) async { try { final uri = Uri.https(apiAuthority, getCoins); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + final responseJSON = json.decode(response.body) as Map; @@ -116,7 +117,12 @@ class SwapTradeExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(apiAuthority, getRate, params); - final response = await post(uri, body: body, headers: headers); + final response = await ProxyWrapper().post( + clearnetUri: uri, + body: json.encode(body), + headers: headers, + ); + final responseBody = json.decode(response.body) as Map; if (response.statusCode != 200) @@ -153,7 +159,12 @@ class SwapTradeExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(apiAuthority, createOrder, params); - final response = await post(uri, body: body, headers: headers); + final response = await ProxyWrapper().post( + clearnetUri: uri, + body: json.encode(body), + headers: headers, + ); + final responseBody = json.decode(response.body) as Map; if (response.statusCode == 400 || responseBody["success"] == false) { @@ -196,7 +207,12 @@ class SwapTradeExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(apiAuthority, order, params); - final response = await post(uri, body: body, headers: headers); + final response = await ProxyWrapper().post( + clearnetUri: uri, + body: json.encode(body), + headers: headers, + ); + final responseBody = json.decode(response.body) as Map; if (response.statusCode == 400 || responseBody["success"] == false) { diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart index aa7ab2d274..0cdb2a4233 100644 --- a/lib/exchange/provider/thorchain_exchange.provider.dart +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -7,10 +7,10 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; class ThorChainExchangeProvider extends ExchangeProvider { ThorChainExchangeProvider({required this.tradesStore}) @@ -164,7 +164,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { if (id.isEmpty) throw Exception('Trade id is empty'); final formattedId = id.startsWith('0x') ? id.substring(2) : id; final uri = Uri.https(_baseNodeURL, '$_txInfoPath$formattedId'); - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode == 404) { throw Exception('Trade not found for id: $formattedId'); @@ -217,8 +218,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { static Future?>? lookupAddressByName(String name) async { final uri = Uri.https(_baseURL, '$_nameLookUpPath$name'); - final response = await http.get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { return null; } @@ -244,8 +245,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { Future> _getSwapQuote(Map params) async { Uri uri = Uri.https(_baseNodeURL, _quotePath, params); - final response = await http.get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { throw Exception('Unexpected HTTP status: ${response.statusCode}'); } diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index 26a9b2e350..b2251e01e8 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -8,9 +8,9 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart'; class TrocadorExchangeProvider extends ExchangeProvider { TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}}) @@ -97,7 +97,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { }; final uri = await _getUri(coinPath, params); - final response = await get(uri, headers: {'API-Key': apiKey}); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}); + if (response.statusCode != 200) throw Exception('Unexpected http status: ${response.statusCode}'); @@ -138,12 +139,10 @@ class TrocadorExchangeProvider extends ExchangeProvider { }; final uri = await _getUri(newRatePath, params); - final response = await get(uri, headers: {'API-Key': apiKey}); + final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}); + final responseJSON = json.decode(response.body) as Map; - - if (responseJSON['error'] != null) throw Exception(responseJSON['error']); - final fromAmount = double.parse(responseJSON['amount_from'].toString()); final toAmount = double.parse(responseJSON['amount_to'].toString()); final rateId = responseJSON['trade_id'] as String? ?? ''; @@ -206,8 +205,9 @@ class TrocadorExchangeProvider extends ExchangeProvider { params['provider'] = _provider.first as String; final uri = await _getUri(createTradePath, params); - final response = await get(uri, headers: {'API-Key': apiKey}); - + final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}); + + if (response.statusCode == 400) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error'] as String; @@ -253,9 +253,10 @@ class TrocadorExchangeProvider extends ExchangeProvider { @override Future findTradeById({required String id}) async { final uri = await _getUri(tradePath, {'id': id}); - return get(uri, headers: {'API-Key': apiKey}).then((response) { + return ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async { if (response.statusCode != 200) throw Exception('Unexpected http status: ${response.statusCode}'); + final responseListJson = json.decode(response.body) as List; final responseJSON = responseListJson.first; @@ -290,7 +291,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { Future> fetchProviders() async { final uri = await _getUri(providersListPath, {'api_key': apiKey}); - final response = await get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) throw Exception('Unexpected http status: ${response.statusCode}'); @@ -353,7 +355,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { if (useTorOnly) return uri; try { - await get(uri); + await ProxyWrapper().get(clearnetUri: uri); return uri; } catch (e) { diff --git a/lib/exchange/provider/xoswap_exchange_provider.dart b/lib/exchange/provider/xoswap_exchange_provider.dart index 5611d68553..e70eab5681 100644 --- a/lib/exchange/provider/xoswap_exchange_provider.dart +++ b/lib/exchange/provider/xoswap_exchange_provider.dart @@ -10,8 +10,7 @@ import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; - +import 'package:cw_core/utils/proxy_wrapper.dart'; class XOSwapExchangeProvider extends ExchangeProvider { XOSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported)); @@ -72,7 +71,8 @@ class XOSwapExchangeProvider extends ExchangeProvider { final uri = Uri.https(_apiAuthority, _apiPath + _assets, {'networks': normalizedNetwork, 'query': currency.title}); - final response = await http.get(uri, headers: _headers); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { throw Exception('Failed to fetch assets for ${currency.title} on ${currency.tag}'); } @@ -102,7 +102,8 @@ class XOSwapExchangeProvider extends ExchangeProvider { if (curFrom == null || curTo == null) return []; final pairId = curFrom + '_' + curTo; final uri = Uri.https(_apiAuthority, '$_apiPath$_pairsPath/$pairId$_ratePath'); - final response = await http.get(uri, headers: _headers); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) return []; return json.decode(response.body) as List; } catch (e) { @@ -205,7 +206,12 @@ class XOSwapExchangeProvider extends ExchangeProvider { 'pairId': pairId, }; - final response = await http.post(uri, headers: _headers, body: json.encode(payload)); + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: _headers, + body: json.encode(payload), + ); + if (response.statusCode != 201) { final responseJSON = json.decode(response.body) as Map; final error = responseJSON['error'] ?? 'Unknown error'; @@ -254,7 +260,8 @@ class XOSwapExchangeProvider extends ExchangeProvider { Future findTradeById({required String id}) async { try { final uri = Uri.https(_apiAuthority, '$_apiPath$_orders/$id'); - final response = await http.get(uri, headers: _headers); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { final responseJSON = json.decode(response.body) as Map; if (responseJSON.containsKey('code') && responseJSON['code'] == 'NOT_FOUND') { diff --git a/lib/main.dart b/lib/main.dart index 43b5951df4..e9ecd9020a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,6 +25,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; @@ -278,7 +279,11 @@ Future initialSetup( navigatorKey: navigatorKey, secureStorage: secureStorage, ); - await bootstrap(navigatorKey, loadWallet: loadWallet); + await bootstrapOffline(); + final settingsStore = getIt(); + if (!settingsStore.currentBuiltinTor) { + bootstrapOnline(navigatorKey, loadWallet: loadWallet); + } } class App extends StatefulWidget { @@ -301,7 +306,7 @@ class AppState extends State with SingleTickerProviderStateMixin { final authenticationStore = getIt.get(); final initialRoute = authenticationStore.state == AuthenticationState.uninitialized ? Routes.welcome - : Routes.login; + : settingsStore.currentBuiltinTor ? Routes.startTor : Routes.login; final currentTheme = settingsStore.currentTheme; final statusBarBrightness = currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; diff --git a/lib/mastodon/mastodon_api.dart b/lib/mastodon/mastodon_api.dart index a2fdc97bdc..58767eea7b 100644 --- a/lib/mastodon/mastodon_api.dart +++ b/lib/mastodon/mastodon_api.dart @@ -1,6 +1,6 @@ import 'dart:convert'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/print_verbose.dart'; -import 'package:http/http.dart' as http; import 'package:cake_wallet/mastodon/mastodon_user.dart'; class MastodonAPI { @@ -20,7 +20,8 @@ class MastodonAPI { queryParameters: queryParams, ); - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) return null; @@ -47,7 +48,8 @@ class MastodonAPI { queryParameters: queryParams, ); - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + if (response.statusCode != 200) { throw Exception('Unexpected HTTP status: ${response.statusCode}'); diff --git a/lib/reactions/bootstrap.dart b/lib/reactions/bootstrap.dart index e767433aa2..6fb4c5ab20 100644 --- a/lib/reactions/bootstrap.dart +++ b/lib/reactions/bootstrap.dart @@ -15,24 +15,29 @@ 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'; -Future bootstrap(GlobalKey navigatorKey, {required bool loadWallet}) async { - final appStore = getIt.get(); +Future bootstrapOffline() async { final authenticationStore = getIt.get(); - final settingsStore = getIt.get(); - final fiatConversionStore = getIt.get(); final currentWalletName = getIt.get().getString(PreferencesKey.currentWalletName); if (currentWalletName != null) { authenticationStore.installed(); } +} + +void bootstrapOnline(GlobalKey navigatorKey, {required bool loadWallet}) { + final appStore = getIt.get(); + final authenticationStore = getIt.get(); + final settingsStore = getIt.get(); + final fiatConversionStore = getIt.get(); if (loadWallet) { startAuthenticationStateChange(authenticationStore, navigatorKey); } + startCurrentWalletChangeReaction(appStore, settingsStore, fiatConversionStore); startCurrentFiatChangeReaction(appStore, settingsStore, fiatConversionStore); startCurrentFiatApiModeChangeReaction(appStore, settingsStore, fiatConversionStore); startOnCurrentNodeChangeReaction(appStore); startFiatRateUpdate(appStore, settingsStore, fiatConversionStore); -} +} \ No newline at end of file diff --git a/lib/reactions/check_connection.dart b/lib/reactions/check_connection.dart index 1e8fa88fa6..4591bb9ae4 100644 --- a/lib/reactions/check_connection.dart +++ b/lib/reactions/check_connection.dart @@ -1,11 +1,15 @@ import 'dart:async'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/utils/tor.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; Timer? _checkConnectionTimer; @@ -36,6 +40,10 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore final alive = await settingsStore.getCurrentNode(wallet.type).requestNode(); if (alive) { + if (settingsStore.currentBuiltinTor) { + await ensureTorStarted(context: null); + } + await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type)); } } diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 6a0be67e97..d1dbd48c71 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/utils/tor.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/balance.dart'; @@ -83,6 +84,10 @@ void startCurrentWalletChangeReaction( bitcoin!.updatePayjoinState(wallet, settingsStore.usePayjoin); } + if (settingsStore.currentBuiltinTor) { + await ensureTorStarted(context: null); + } + await wallet.connectToNode(node: node); if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) { final powNode = settingsStore.getCurrentPowNode(wallet.type); diff --git a/lib/router.dart b/lib/router.dart index 9cb65e3f4e..1989a0282a 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -90,7 +90,6 @@ import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart'; -import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart'; @@ -98,6 +97,7 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; +import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart'; import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart'; @@ -808,9 +808,6 @@ Route createRoute(RouteSettings settings) { ), ); - case Routes.torPage: - return MaterialPageRoute(builder: (_) => getIt.get()); - case Routes.signPage: return MaterialPageRoute( builder: (_) => SignPage( @@ -867,6 +864,11 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(), ); + + case Routes.startTor: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); default: return MaterialPageRoute( diff --git a/lib/routes.dart b/lib/routes.dart index bbaca6618c..aba1c5a4ef 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -110,11 +110,12 @@ class Routes { static const walletConnectConnectionsListing = '/wallet-connect-connections-listing'; static const nftDetailsPage = '/nft_details_page'; static const importNFTPage = '/import_nft_page'; - static const torPage = '/tor_page'; static const backgroundSync = '/background_sync'; + static const startTor = '/start_tor'; static const devMoneroBackgroundSync = '/dev/monero_background_sync'; static const devMoneroCallProfiler = '/dev/monero_call_profiler'; + static const devSharedPreferences = '/dev/shared_preferences'; static const devSecurePreferences = '/dev/secure_preferences'; static const devBackgroundSyncLogs = '/dev/background_sync_logs'; diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart index eeda073afd..48849e9f35 100644 --- a/lib/src/screens/nodes/widgets/node_form.dart +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:mobx/mobx.dart'; +import 'package:tor/tor.dart'; class NodeForm extends StatelessWidget { NodeForm({ @@ -193,8 +194,27 @@ class NodeForm extends StatelessWidget { Observer( builder: (_) => Column( children: [ + if (nodeViewModel.usesEmbeddedProxy) ...[ + Padding( + padding: EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + StandardCheckbox( + value: nodeViewModel.usesEmbeddedProxy, + gradientBackground: false, + borderColor: Theme.of(context).dividerColor, + iconColor: Theme.of(context).colorScheme.primary, + onChanged: null, + caption: 'Embedded Tor SOCKS Proxy', + ), + ], + ), + ), + ], Padding( - padding: EdgeInsets.only(top: 20), + padding: EdgeInsets.only(top: 10), child: Row( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 098f80644c..5cb0afcb2e 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -76,10 +76,13 @@ class ConnectionSyncPage extends BasePage { ), ], if (FeatureFlag.isInAppTorEnabled) - SettingsCellWithArrow( - title: S.current.tor_connection, - handler: (context) => Navigator.of(context).pushNamed(Routes.torPage), - ), + Observer(builder: (context) { + return SettingsSwitcherCell( + title: S.current.enable_builtin_tor, + value: dashboardViewModel.builtinTor, + onValueChange: (_, bool value) => dashboardViewModel.setBuiltinTor(value, context), + ); + }), ], ), ); diff --git a/lib/src/screens/settings/tor_page.dart b/lib/src/screens/settings/tor_page.dart deleted file mode 100644 index 2eb8d6c11a..0000000000 --- a/lib/src/screens/settings/tor_page.dart +++ /dev/null @@ -1,268 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/store/app_store.dart'; -import 'package:cw_core/utils/print_verbose.dart'; -import 'package:flutter/material.dart'; -// import 'package:tor/tor.dart'; - -class TorPage extends BasePage { - final AppStore appStore; - - TorPage(this.appStore); - - @override - Widget body(BuildContext context) { - return TorPageBody(appStore); - } -} - -class TorPageBody extends StatefulWidget { - final AppStore appStore; - - const TorPageBody(this.appStore, {Key? key}) : super(key: key); - - @override - State createState() => _TorPageBodyState(); -} - -class _TorPageBodyState extends State { - bool torEnabled = false; - bool connecting = false; - - // Set the default text for the host input field. - final hostController = TextEditingController(text: 'https://icanhazip.com/'); - - // https://check.torproject.org is another good option. - - Future startTor() async { - setState(() { - connecting = true; // Update flag - }); - - // await Tor.init(); - // - // // Start the proxy - // await Tor.instance.start(); - // - // // Toggle started flag. - // setState(() { - // torEnabled = Tor.instance.enabled; // Update flag - // connecting = false; - // }); - // - // final node = widget.appStore.settingsStore.getCurrentNode(widget.appStore.wallet!.type); - // if (node.socksProxyAddress?.isEmpty ?? true) { - // node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; - // } - // widget.appStore.wallet!.connectToNode(node: node); - - printV('Done awaiting; tor should be running'); - } - - Future endTor() async { - // // Start the proxy - // Tor.instance.disable(); - // - // // Toggle started flag. - // setState(() { - // torEnabled = Tor.instance.enabled; // Update flag - // }); - // - // printV('Done awaiting; tor should be stopped'); - } - // - // @override - // void initState() { - // super.initState(); - // - // torEnabled = Tor.instance.enabled; - // } - - @override - void dispose() { - // Clean up the controller when the widget is disposed. - hostController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Container( - padding: const EdgeInsets.all(10), - child: connecting - ? ConnectingScreen() - : torEnabled - ? DisconnectScreen(disconnect: endTor) - : ConnectScreen(connect: startTor), - ), - ); - } -} - -class ConnectScreen extends StatelessWidget { - final Function() connect; - - const ConnectScreen({super.key, required this.connect}); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 200, - height: 200, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue, - ), - child: Icon( - Icons.lock, - color: Colors.white, - size: 100, - ), - ), - SizedBox(height: 20), - Text( - 'Connect to Tor', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 10), - Text( - 'Your connection to the Tor network ensures privacy and security.', - style: TextStyle( - fontSize: 16, - color: Colors.grey, - ), - textAlign: TextAlign.center, - ), - SizedBox(height: 30), - ElevatedButton( - onPressed: connect, - style: ElevatedButton.styleFrom( - // primary: Colors.blue, - padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - child: Text( - 'Connect', - style: TextStyle( - fontSize: 18, - color: Colors.white, - ), - ), - ), - ], - ), - ); - } -} - -class DisconnectScreen extends StatelessWidget { - final Function() disconnect; - - const DisconnectScreen({super.key, required this.disconnect}); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 200, - height: 200, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.green, - ), - child: Icon( - Icons.check, - color: Colors.white, - size: 100, - ), - ), - SizedBox(height: 20), - Text( - 'Connected to Tor', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 10), - Text( - 'You are currently connected to the Tor network.', - style: TextStyle( - fontSize: 16, - color: Colors.grey, - ), - textAlign: TextAlign.center, - ), - SizedBox(height: 30), - ElevatedButton( - onPressed: disconnect, - style: ElevatedButton.styleFrom( - // primary: Colors.red, - padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - child: Text( - 'Disconnect', - style: TextStyle( - fontSize: 18, - color: Colors.white, - ), - ), - ), - ], - ), - ); - } -} - -class ConnectingScreen extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 200, - height: 200, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.yellow, - ), - child: Icon( - Icons.hourglass_bottom, - color: Colors.white, - size: 100, - ), - ), - SizedBox(height: 20), - Text( - 'Connecting...', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 10), - ], - ), - ); - } -} diff --git a/lib/src/screens/start_tor/start_tor_page.dart b/lib/src/screens/start_tor/start_tor_page.dart new file mode 100644 index 0000000000..71e3c3dd6c --- /dev/null +++ b/lib/src/screens/start_tor/start_tor_page.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/view_model/start_tor_view_model.dart'; + +class StartTorPage extends BasePage { + StartTorPage(this.startTorViewModel); + + final StartTorViewModel startTorViewModel; + + @override + String get title => S.current.tor_connection; + + @override + Widget leading(BuildContext context) { + return Container(); + } + + @override + Widget body(BuildContext context) { + startTorViewModel.startTor(context); + return Container( + padding: EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + children: [ + SizedBox(width: double.maxFinite), + if (startTorViewModel.isLoading) ...[ + CircularProgressIndicator(), + SizedBox(height: 20), + _buildWaitingText(context), + ], + if (startTorViewModel.showOptions) ...[ + _buildOptionsButtons(context), + ], + ], + ), + ], + ), + ); + } + + Widget _buildWaitingText(BuildContext context) { + return Observer( + builder: (_) { + return Column( + children: [ + Text( + S.current.establishing_tor_connection, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ); + } + + Widget _buildOptionsButtons(BuildContext context) { + return Column( + children: [ + Text( + S.current.tor_connection_timeout, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24), + PrimaryButton( + onPressed: () => startTorViewModel.disableTor(context), + text: S.current.disable_tor, + color: Theme.of(context).colorScheme.primary, + textColor: Colors.white, + ), + SizedBox(height: 16), + PrimaryButton( + onPressed: () => startTorViewModel.ignoreAndLaunchApp(context), + text: S.current.ignor, + color: Theme.of(context).colorScheme.secondary, + textColor: Colors.white, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart index 05c2a1f553..426c1580fa 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:eth_sig_util/eth_sig_util.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:reown_walletkit/reown_walletkit.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; @@ -41,7 +41,7 @@ class EvmChainServiceImpl { }) : ethClient = web3Client ?? Web3Client( appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(), - http.Client(), + ProxyWrapper().getHttpIOClient(), ) { for (final event in EventsConstants.allEvents) { walletKit.registerEventEmitter( @@ -563,14 +563,15 @@ class EvmChainServiceImpl { }, ); - final response = await http.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); + final decodedResponse = jsonDecode(response.body)[0] as Map; final symbol = (decodedResponse['symbol'] ?? '') as String; diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 29a1bfb6f9..4eee645165 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -128,9 +128,9 @@ class _WalletKeysPageBodyState extends State dividerColor: Colors.transparent, padding: EdgeInsets.zero, tabs: [ - Tab(text: S.of(context).widgets_seed), - if (showKeyTab) Tab(text: S.of(context).keys), - if (showLegacySeedTab) Tab(text: S.of(context).legacy), + Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), + if (showKeyTab) Tab(text: S.of(context).keys, key: ValueKey('wallet_keys_page_keys'),), + if (showLegacySeedTab) Tab(text: S.of(context).legacy, key: ValueKey('wallet_keys_page_seed_legacy')), ], ), ), diff --git a/lib/src/widgets/standard_checkbox.dart b/lib/src/widgets/standard_checkbox.dart index 2b06ff1c66..a8d489a013 100644 --- a/lib/src/widgets/standard_checkbox.dart +++ b/lib/src/widgets/standard_checkbox.dart @@ -18,7 +18,7 @@ class StandardCheckbox extends StatelessWidget { final Color? borderColor; final Color? iconColor; final Color? captionColor; - final Function(bool) onChanged; + final Function(bool)? onChanged; @override Widget build(BuildContext context) { @@ -41,7 +41,7 @@ class StandardCheckbox extends StatelessWidget { BoxDecoration(border: boxBorder, borderRadius: BorderRadius.all(Radius.circular(8.0))); return GestureDetector( - onTap: () => onChanged(!value), + onTap: onChanged == null ? null : () => onChanged!(!value), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 6a9525f5c4..3d5a2b1bce 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -84,6 +84,7 @@ abstract class SettingsStoreBase with Store { required String initialLanguageCode, required SyncMode initialSyncMode, required bool initialSyncAll, + required bool initialBuiltinTor, // required String initialCurrentLocale, required this.appVersion, required this.deviceName, @@ -187,6 +188,7 @@ abstract class SettingsStoreBase with Store { initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings, currentSyncMode = initialSyncMode, currentSyncAll = initialSyncAll, + currentBuiltinTor = initialBuiltinTor, priority = ObservableMap() { //this.nodes = ObservableMap.of(nodes); @@ -407,6 +409,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); }); + reaction((_) => currentBuiltinTor, (bool builtinTor) { + sharedPreferences.setBool(PreferencesKey.builtinTorKey, builtinTor); + }); + + reaction( (_) => exchangeStatus, (ExchangeApiMode mode) => @@ -817,6 +824,9 @@ abstract class SettingsStoreBase with Store { @observable bool currentSyncAll; + @observable + bool currentBuiltinTor; + String appVersion; String deviceName; @@ -1173,6 +1183,7 @@ abstract class SettingsStoreBase with Store { return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync }); final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; + final builtinTor = sharedPreferences.getBool(PreferencesKey.builtinTorKey) ?? false; // migrated to secure: final timeOutDuration = await SecureKey.getInt( @@ -1357,6 +1368,7 @@ abstract class SettingsStoreBase with Store { initialSyncAll: savedSyncAll, shouldShowYatPopup: shouldShowYatPopup, shouldShowRepWarning: shouldShowRepWarning, + initialBuiltinTor: builtinTor, ); } diff --git a/lib/store/yat/yat_store.dart b/lib/store/yat/yat_store.dart index 964b96db37..6b73c6211b 100644 --- a/lib/store/yat/yat_store.dart +++ b/lib/store/yat/yat_store.dart @@ -5,12 +5,8 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cake_wallet/store/app_store.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_type.dart'; -import 'dart:convert'; -import 'package:cake_wallet/store/yat/yat_exception.dart'; -import 'package:http/http.dart'; import 'dart:async'; part 'yat_store.g.dart'; diff --git a/lib/twitter/twitter_api.dart b/lib/twitter/twitter_api.dart index 5acb00e2a0..cccf769ad0 100644 --- a/lib/twitter/twitter_api.dart +++ b/lib/twitter/twitter_api.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/twitter/twitter_user.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; class TwitterApi { static const twitterBearerToken = secrets.twitterBearerToken; @@ -23,13 +23,14 @@ class TwitterApi { path: userPath + userName, queryParameters: queryParams); - final response = await http.get(uri, headers: headers).catchError((error) { + final response = await ProxyWrapper().get(clearnetUri: uri, headers: headers).catchError((error) { throw Exception('HTTP request failed: $error'); }); if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } + final Map responseJSON = jsonDecode(response.body) as Map; if (responseJSON['errors'] != null && diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index c968c5d755..5d1c03d777 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -1,10 +1,11 @@ import 'package:flutter/foundation.dart'; +import 'dart:io'; class FeatureFlag { static const bool isCakePayEnabled = false; static const bool isExolixEnabled = true; - static const bool isInAppTorEnabled = false; static const bool isBackgroundSyncEnabled = true; + static final bool isInAppTorEnabled = (Platform.isAndroid || Platform.isIOS); static const int verificationWordsCount = kDebugMode ? 0 : 2; static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode); } \ No newline at end of file diff --git a/lib/utils/tor.dart b/lib/utils/tor.dart new file mode 100644 index 0000000000..3f46c627c2 --- /dev/null +++ b/lib/utils/tor.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'dart:isolate'; + +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:tor/tor.dart'; + +bool didTorStart = false; +Future ensureTorStopped({required BuildContext? context}) async { + if (!didTorStart) { + printV("Tor hasn't been initialized yet, so it can't be stopped."); + return; + } + BuildContext? dialogContext; + if (context != null) dialogContext = await showFullscreenDialog(context); + didTorStart = false; + printV("Stopping tor"); + await CakeTor.instance.stop(); + printV("Tor stopped"); + if (context != null) dismissFullscreenDialog(dialogContext!); +} + +Future ensureTorStarted({required BuildContext? context}) async { + if (didTorStart) { + printV("Tor has already started"); + return; + } + BuildContext? dialogContext; + if (context != null) dialogContext = await showFullscreenDialog(context); + didTorStart = true; + printV("Initializing tor"); + await Tor.init(); + printV("Starting tor"); + // var rootToken = RootIsolateToken.instance!; + // await Isolate.run(() async { + // BackgroundIsolateBinaryMessenger.ensureInitialized(rootToken); + // await CakeTor.instance.start(); + // }); + // second start is fast but populates the values on current thread + await CakeTor.instance.start(); + printV("Tor started"); + while (!CakeTor.instance.started) { + printV("Waiting for tor to start (part 1)"); + await Future.delayed(const Duration(seconds: 1)); + } + while (CakeTor.instance.port == -1) { + printV("Waiting for tor to start (listening on port)"); + await Future.delayed(const Duration(seconds: 1)); + } + printV("Tor started on port ${CakeTor.instance.port}"); + if (context != null) dismissFullscreenDialog(dialogContext!); +} + +Future showFullscreenDialog(BuildContext context) async { + BuildContext? dialogContext; + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + dialogContext = context; + return PopScope( + canPop: false, + child: Container( + color: Colors.transparent, + child: Center( + child: CircularProgressIndicator( + color: Colors.white, + ), + ), + ), + ); + }, + ), + ); + await Future.delayed(const Duration(seconds: 1)); + return dialogContext!; +} + +Future dismissFullscreenDialog(BuildContext context) async { + await Future.delayed(const Duration(seconds: 1)); + Navigator.of(context).pop(); +} \ No newline at end of file diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 058fb25f9d..e0531854bf 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -13,6 +13,9 @@ import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cake_wallet/utils/tor.dart'; +import 'package:cake_wallet/wownero/wownero.dart' as wow; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -48,10 +51,9 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_daemon/flutter_daemon.dart'; -import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -1008,6 +1010,25 @@ abstract class DashboardViewModelBase with Store { @computed bool get syncAll => settingsStore.currentSyncAll; + @computed + bool get builtinTor => settingsStore.currentBuiltinTor; + + @action + void setBuiltinTor(bool value, BuildContext context) { + settingsStore.currentBuiltinTor = value; + if (value) { + unawaited(ensureTorStarted(context: context).then((_) async { + if (settingsStore.currentBuiltinTor == false) return; // return when tor got disabled in the meantime; + await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type)); + })); + } else { + unawaited(ensureTorStopped(context: context).then((_) async { + if (settingsStore.currentBuiltinTor == true) return; // return when tor got enabled in the meantime; + await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type)); + })); + } + } + @action void setSyncAll(bool value) => settingsStore.currentSyncAll = value; @@ -1054,7 +1075,17 @@ abstract class DashboardViewModelBase with Store { } } + static ServicesResponse? cachedServicesResponse; + Future getServicesStatus() async { + if (cachedServicesResponse != null) { + return cachedServicesResponse!; + } + cachedServicesResponse = await _getServicesStatus(); + return cachedServicesResponse!; + } + + Future _getServicesStatus() async { try { if (isEnabledBulletinAction) { final uri = Uri.https( @@ -1063,8 +1094,7 @@ abstract class DashboardViewModelBase with Store { {'key': secrets.fiatApiKey}, ); - final res = await http.get(uri); - + final res = await ProxyWrapper().get(clearnetUri: uri); if (res.statusCode < 200 || res.statusCode >= 300) { throw res.body; } diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 4794746c45..f16d2aa958 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -19,7 +20,6 @@ import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; -import 'package:http/http.dart' as http; import 'package:cake_wallet/.secrets.g.dart' as secrets; part 'home_settings_view_model.g.dart'; @@ -240,14 +240,14 @@ abstract class HomeSettingsViewModelBase with Store { ); try { - final response = await http.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); - + final decodedResponse = jsonDecode(response.body); final tokenInfo = Erc20TokenInfoMoralis.fromJson(decodedResponse[0] as Map); @@ -309,8 +309,8 @@ abstract class HomeSettingsViewModelBase with Store { ); try { - final response = await http.get(uri); - + final response = await ProxyWrapper().get(clearnetUri: uri); + final decodedResponse = jsonDecode(response.body) as Map; if (decodedResponse['status'] != '1') { @@ -351,7 +351,8 @@ abstract class HomeSettingsViewModelBase with Store { ); try { - final response = await http.get(uri); + final response = await ProxyWrapper().get(clearnetUri: uri); + final decodedResponse = jsonDecode(response.body) as Map; diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index 7da63d3993..733cb4f47e 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -7,7 +7,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:http/http.dart' as http; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -80,15 +80,16 @@ abstract class NFTViewModelBase with Store { isLoading = true; - final response = await http.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); + - final decodedResponse = jsonDecode(response.body); + final decodedResponse = jsonDecode(response.body) as Map; if (walletType == WalletType.solana) { final results = await Future.wait( @@ -131,14 +132,14 @@ abstract class NFTViewModelBase with Store { '/nft/$chainName/$address/metadata', ); - final response = await http.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); - + final decodedResponse = jsonDecode(response.body) as Map; return SolanaNFTAssetModel.fromJson(decodedResponse); @@ -171,15 +172,14 @@ abstract class NFTViewModelBase with Store { "normalizeMetadata": "true", }, ); - - final response = await http.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); - + final decodedResponse = jsonDecode(response.body) as Map; final nftAsset = NFTAssetModel.fromJson(decodedResponse); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 58b5e5756b..91cffe3677 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/core/create_trade_result.dart'; import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -16,7 +17,6 @@ import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -939,8 +939,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } Future _isContractAddress(String chainName, String contractAddress) async { - final httpClient = http.Client(); - final uri = Uri.https( 'deep-index.moralis.io', '/api/v2.2/erc20/metadata', @@ -951,13 +949,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with ); try { - final response = await httpClient.get( - uri, + final response = await ProxyWrapper().get( + clearnetUri: uri, headers: { "Accept": "application/json", "X-API-Key": secrets.moralisApiKey, }, ); + final decodedResponse = jsonDecode(response.body)[0] as Map; 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 7e4e739150..78544aa82a 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 @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -58,6 +59,9 @@ abstract class NodeCreateOrEditViewModelBase with Store { @observable bool useSocksProxy; + @computed + bool get usesEmbeddedProxy => CakeTor.instance.started; + @observable String socksProxyAddress; 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 9df5f2980b..76cf445059 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/utils/mobx.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; @@ -9,6 +10,7 @@ import 'package:cw_core/node.dart'; import 'package:cake_wallet/entities/node_list.dart'; import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:tor/tor.dart'; part 'node_list_view_model.g.dart'; @@ -38,7 +40,7 @@ abstract class NodeListViewModelBase with Store { String getAlertContent(String uri) => S.current.change_current_node(uri) + - '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}'; + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}'; final ObservableList nodes; final SettingsStore settingsStore; 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 5467be31a8..61d7ba22c7 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 @@ -1,6 +1,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/utils/mobx.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_base.dart'; @@ -9,6 +10,7 @@ import 'package:cw_core/node.dart'; import 'package:cake_wallet/entities/node_list.dart'; import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:tor/tor.dart'; part 'pow_node_list_view_model.g.dart'; @@ -38,7 +40,7 @@ abstract class PowNodeListViewModelBase with Store { String getAlertContent(String uri) => S.current.change_current_node(uri) + - '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}'; + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + (CakeTor.instance.enabled ? '' : S.current.orbot_running_alert) : ''}'; final ObservableList nodes; final SettingsStore settingsStore; diff --git a/lib/view_model/start_tor_view_model.dart b/lib/view_model/start_tor_view_model.dart new file mode 100644 index 0000000000..1ef819d5fb --- /dev/null +++ b/lib/view_model/start_tor_view_model.dart @@ -0,0 +1,95 @@ +import 'dart:async'; + +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/main.dart'; +import 'package:cake_wallet/reactions/bootstrap.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/tor.dart'; +import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; + +part 'start_tor_view_model.g.dart'; + +class StartTorViewModel = StartTorViewModelBase with _$StartTorViewModel; + +abstract class StartTorViewModelBase with Store { + StartTorViewModelBase() { + _startTimer(); + } + + Timer? _timer; + final int waitTimeInSeconds = 15; + + @observable + bool isLoading = true; + + @observable + bool timeoutReached = false; + + @observable + int remainingSeconds = 15; + + @computed + bool get showOptions => timeoutReached; + + @action + void _startTimer() { + remainingSeconds = waitTimeInSeconds; + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + remainingSeconds -= 1; + + if (remainingSeconds <= 0) { + timer.cancel(); + timeoutReached = true; + } + }); + } + + @observable + bool didStartTor = false; + + @action + Future startTor(BuildContext context) async { + if (didStartTor) { + return; + } + await ensureTorStarted(context: null); + while (true) { + await Future.delayed(Duration(milliseconds: 250)); + if (CakeTor.instance.port != -1 && CakeTor.instance.started) { + break; + } + } + didStartTor = true; + final appStore = getIt.get(); + bootstrapOnline(navigatorKey, loadWallet: true); + appStore.wallet?.connectToNode(node: appStore.settingsStore.getCurrentNode(appStore.wallet!.type)); + Navigator.pushReplacementNamed(context, Routes.login); + } + + @action + void disableTor(BuildContext context) { + final settingsStore = getIt.get(); + settingsStore.currentBuiltinTor = false; + bootstrapOnline(navigatorKey, loadWallet: true); + final appStore = getIt.get(); + appStore.wallet?.connectToNode(node: appStore.settingsStore.getCurrentNode(appStore.wallet!.type)); + Navigator.pushReplacementNamed(context, Routes.login); + } + + @action + void ignoreAndLaunchApp(BuildContext context) { + bootstrapOnline(navigatorKey, loadWallet: true); + final appStore = getIt.get(); + appStore.wallet?.connectToNode(node: appStore.settingsStore.getCurrentNode(appStore.wallet!.type)); + Navigator.pushReplacementNamed(context, Routes.login); + } + + void dispose() { + _timer?.cancel(); + _timer = null; + } +} \ No newline at end of file diff --git a/pubspec_base.yaml b/pubspec_base.yaml index e43de5ca1d..dcab7dd3da 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -100,13 +100,17 @@ dependencies: # git: # url: https://github.com/cake-tech/tor.git # ref: main - socks5_proxy: ^1.0.4 + socks5_proxy: + git: + url: https://github.com/LacticWhale/socks_dart.git + ref: 27ad7c2efae8d7460325c74b90f660085cbd0685 flutter_svg: ^2.0.9 polyseed: ^0.0.7 nostr_tools: git: url: https://github.com/MrCyjaneK/nostr_tools.git ref: 089d5a2dd751429a040ba10fb24fcbae564053e5 + solana: ^0.31.0+1 ledger_flutter_plus: git: url: https://github.com/vespr-wallet/ledger-flutter-plus @@ -126,6 +130,10 @@ dependencies: url: https://github.com/MrCyjaneK/flutter_daemon ref: 6d5270d64b5dd588fce12fd0a0c7314c37e6cff1 flutter_local_notifications: ^19.0.0 + tor: + git: + url: https://github.com/MrCyjaneK/tor/ + ref: ec1119ef85045524e48654782b69086a798c8e5e dev_dependencies: flutter_test: @@ -162,11 +170,19 @@ dependency_overrides: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v9 ffi: 2.1.0 + web_socket_channel: ^3.0.2 + tor: + git: + url: https://github.com/MrCyjaneK/tor/ + ref: ec1119ef85045524e48654782b69086a798c8e5e ledger_flutter_plus: git: url: https://github.com/vespr-wallet/ledger-flutter-plus ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 - web_socket_channel: ^3.0.2 + socks5_proxy: + git: + url: https://github.com/LacticWhale/socks_dart.git + ref: 27ad7c2efae8d7460325c74b90f660085cbd0685 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7caf3a1263..0a64c75ad2 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "من خلال إيقاف تشغيل هذا ، قد تكون معدلات الرسوم غير دقيقة في بعض الحالات ، لذلك قد ينتهي بك الأمر إلى دفع مبالغ زائدة أو دفع رسوم المعاملات الخاصة بك", "disable_fiat": "تعطيل fiat", "disable_sell": "قم بتعطيل إجراء البيع", + "disable_tor": "تعطيل تور", "disable_trade_option": "تعطيل خيار التجارة", "disableBatteryOptimization": "تعطيل تحسين البطارية", "disableBatteryOptimizationDescription": "هل تريد تعطيل تحسين البطارية من أجل جعل الخلفية مزامنة تعمل بحرية وسلاسة؟", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل", "email_address": "عنوان البريد الالكترونى", "enable": "يُمكَِن", + "enable_builtin_tor": "تمكين بنيت في تور", "enable_mempool_api": "MEMPOOL API للحصول على رسوم وتواريخ دقيقة", "enable_replace_by_fee": "تمكين الاستبدال", "enable_silent_payments_scanning": "ابدأ في مسح المدفوعات الصامتة ، حتى يتم الوصول إلى الطرف", @@ -317,6 +319,7 @@ "error_while_processing": "حدث خطأ أثناء التنقل", "errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ", "errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ", + "establishing_tor_connection": "ارتياح توصيل تور", "estimated": "مُقدَّر", "estimated_new_fee": "رسوم جديدة مقدرة", "estimated_receive_amount": "مقدرة المبلغ الاستقبال", @@ -830,6 +833,7 @@ "sort_by": "ترتيب حسب", "spend_key_private": "مفتاح الإنفاق (خاص)", "spend_key_public": "مفتاح الإنفاق (عام)", + "starting_tor_proxy": "بدء الوكيل Tor", "status": "الحالة:", "string_default": "تقصير", "subaddress_title": "قائمة العناوين الفرعية", @@ -884,6 +888,7 @@ "token_symbol": "رمز العملة ، على سبيل المثال: USDT", "tokenID": "ﻒﻳﺮﻌﺗ ﺔﻗﺎﻄﺑ", "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ", + "tor_connection_timeout": "Tor Torn Connection Timeout", "tor_only": "Tor فقط", "total": "المجموع", "total_saving": "إجمالي المدخرات", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ae08c4ac4c..298d48319d 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Като изключите това, таксите могат да бъдат неточни в някои случаи, така че може да се препланите или да не плащате таксите за вашите транзакции", "disable_fiat": "Деактивиране на fiat", "disable_sell": "Деактивирайте действието за продажба", + "disable_tor": "Деактивирайте Tor", "disable_trade_option": "Деактивирайте опцията за търговия", "disableBatteryOptimization": "Деактивирайте оптимизацията на батерията", "disableBatteryOptimizationDescription": "Искате ли да деактивирате оптимизацията на батерията, за да направите синхронизирането на фона да работи по -свободно и гладко?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят", "email_address": "Имейл адрес", "enable": "Активиране", + "enable_builtin_tor": "Активирайте вграден Tor", "enable_mempool_api": "Mempool API за точни такси и дати", "enable_replace_by_fee": "Активиране на замяна по забрана", "enable_silent_payments_scanning": "Започнете да сканирате безшумните плащания, докато се достигне съветът", @@ -317,6 +319,7 @@ "error_while_processing": "Възникна грешка при приемане", "errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни", "errorSigningTransaction": "Възникна грешка при подписване на транзакция", + "establishing_tor_connection": "Естибилиране на TOR връзка", "estimated": "Изчислено", "estimated_new_fee": "Прогнозна нова такса", "estimated_receive_amount": "Прогнозна сума за получаване", @@ -830,6 +833,7 @@ "sort_by": "Сортирай по", "spend_key_private": "Spend key (таен)", "spend_key_public": "Spend key (публичен)", + "starting_tor_proxy": "Стартиране на прокси", "status": "Статус: ", "string_default": "По подразбиране", "subaddress_title": "Лист от подадреси", @@ -884,6 +888,7 @@ "token_symbol": "Символ на токена, напр.: USDT", "tokenID": "документ за самоличност", "tor_connection": "Tor връзка", + "tor_connection_timeout": "TOR ВРЕМЕ НА ВРЕМЕТО ВРЕМЕ", "tor_only": "Само чрез Tor", "total": "Обща сума", "total_saving": "Общо спестявания", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index e9a5bfb55a..aa918532cb 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Tímto vypnutím by sazby poplatků mohly být v některých případech nepřesné, takže byste mohli skončit přepláváním nebo nedoplatkem poplatků za vaše transakce", "disable_fiat": "Zakázat fiat", "disable_sell": "Zakázat akci prodeje", + "disable_tor": "Zakázat tor", "disable_trade_option": "Zakázat možnost TRADE", "disableBatteryOptimization": "Zakázat optimalizaci baterie", "disableBatteryOptimizationDescription": "Chcete deaktivovat optimalizaci baterie, aby se synchronizovala pozadí volně a hladce?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Povolte vestavěné tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Při převádění došlo k chybě", "errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů", "errorSigningTransaction": "Při podepisování transakce došlo k chybě", + "establishing_tor_connection": "Estabilizující připojení Tor", "estimated": "Odhadováno", "estimated_new_fee": "Odhadovaný nový poplatek", "estimated_receive_amount": "Odhadovaná částka přijímání", @@ -830,6 +833,7 @@ "sort_by": "Seřazeno podle", "spend_key_private": "Klíč pro platby (soukromý)", "spend_key_public": "Klíč pro platby (veřejný)", + "starting_tor_proxy": "Zahájení proxy Tor", "status": "Status: ", "string_default": "Výchozí", "subaddress_title": "Seznam subadres", @@ -884,6 +888,7 @@ "token_symbol": "Symbol tokenu, např.: USDT", "tokenID": "ID", "tor_connection": "Připojení Tor", + "tor_connection_timeout": "Časový limit připojení", "tor_only": "Pouze Tor", "total": "Celkový", "total_saving": "Celkem ušetřeno", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e35cf08ee7..efba465da9 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Wenn dies ausgeschaltet wird, sind die Gebührenquoten in einigen Fällen möglicherweise ungenau, sodass Sie die Gebühren für Ihre Transaktionen möglicherweise überbezahlt oder unterzahlt", "disable_fiat": "Fiat deaktivieren", "disable_sell": "Verkaufsaktion deaktivieren", + "disable_tor": "Tor deaktivieren", "disable_trade_option": "Handelsoption deaktivieren", "disableBatteryOptimization": "Batterieoptimierung deaktivieren", "disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung reibungsloser zu gestalten?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Aktivieren Sie den bau-in tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Ein Fehler beim Proceessing trat ein Fehler auf", "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", + "establishing_tor_connection": "Einstellung der TOR -Verbindung", "estimated": "Geschätzt", "estimated_new_fee": "Geschätzte neue Gebühr", "estimated_receive_amount": "Geschätzter Empfangsbetrag", @@ -831,6 +834,7 @@ "sort_by": "Sortiere nach", "spend_key_private": "Spend Key (geheim)", "spend_key_public": "Spend Key (öffentlich)", + "starting_tor_proxy": "TOR -Proxy beginnen", "status": "Status: ", "string_default": "Standard", "subaddress_title": "Unteradressenliste", @@ -885,6 +889,7 @@ "token_symbol": "Token-Symbol, z. B.: USDT", "tokenID": "AUSWEIS", "tor_connection": "Tor-Verbindung", + "tor_connection_timeout": "TOR -Verbindungs ​​-Zeitüberschreitung", "tor_only": "Nur Tor", "total": "Gesamt", "total_saving": "Gesamteinsparungen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index a71aa5f540..3b1de035fb 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "By turning this off, the fee rates might be inaccurate in some cases, so you might end up overpaying or underpaying the fees for your transactions", "disable_fiat": "Disable fiat", "disable_sell": "Disable sell action", + "disable_tor": "Disable Tor", "disable_trade_option": "Disable trade option", "disableBatteryOptimization": "Disable Battery Optimization", "disableBatteryOptimizationDescription": "Do you want to disable battery optimization in order to make background sync run more freely and smoothly?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Enable built-in Tor", "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.", @@ -317,6 +319,7 @@ "error_while_processing": "An error occurred while proceessing", "errorGettingCredentials": "Failed: Error while getting credentials", "errorSigningTransaction": "An error has occured while signing transaction", + "establishing_tor_connection": "Estabilishing Tor connection", "estimated": "Estimated", "estimated_new_fee": "Estimated new fee", "estimated_receive_amount": "Estimated receive amount", @@ -831,6 +834,7 @@ "sort_by": "Sort by", "spend_key_private": "Spend key (private)", "spend_key_public": "Spend key (public)", + "starting_tor_proxy": "Starting Tor proxy", "status": "Status: ", "string_default": "Default", "subaddress_title": "Subaddress list", @@ -885,6 +889,7 @@ "token_symbol": "Token symbol eg: USDT", "tokenID": "ID", "tor_connection": "Tor connection", + "tor_connection_timeout": "Tor connection timeout", "tor_only": "Tor only", "total": "Total", "total_saving": "Total Savings", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 23dad880ee..7335ecdfd1 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Al apagar esto, las tasas de tarifas pueden ser inexactas en algunos casos, por lo que puede terminar pagando en exceso o pagando menos las tarifas por sus transacciones", "disable_fiat": "Deshabilitar fiat", "disable_sell": "Desactivar acción de venta", + "disable_tor": "Deshabilitar tor", "disable_trade_option": "Deshabilitar la opción de comercio", "disableBatteryOptimization": "Deshabilitar la optimización de la batería", "disableBatteryOptimizationDescription": "¿Desea deshabilitar la optimización de la batería para que la sincronización de fondo se ejecute más libremente y sin problemas?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Habilitar buildi-in tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Se produjo un error mientras procesaba", "errorGettingCredentials": "Error: error al obtener las credenciales", "errorSigningTransaction": "Se ha producido un error al firmar la transacción.", + "establishing_tor_connection": "Estabilización de la conexión para", "estimated": "Estimado", "estimated_new_fee": "Nueva tarifa estimada", "estimated_receive_amount": "Cantidad de recepción estimada", @@ -831,6 +834,7 @@ "sort_by": "Ordenar por", "spend_key_private": "Llave de gasto (privada)", "spend_key_public": "Llave de gasto (pública)", + "starting_tor_proxy": "Inicio Tor Proxy", "status": "Estado: ", "string_default": "Por defecto", "subaddress_title": "Lista de subdirecciones", @@ -885,6 +889,7 @@ "token_symbol": "Símbolo de token, por ejemplo: USDT", "tokenID": "IDENTIFICACIÓN", "tor_connection": "conexión tor", + "tor_connection_timeout": "Tiempo de espera de conexión de Tor", "tor_only": "solo Tor", "total": "Total", "total_saving": "Ahorro Total", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index d2b1c5356d..a32a952e90 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "En désactivant cela, les taux de frais peuvent être inexacts dans certains cas, vous pourriez donc finir par payer trop ou sous-paiement les frais pour vos transactions", "disable_fiat": "Désactiver les montants en fiat", "disable_sell": "Désactiver l'action de vente", + "disable_tor": "Désactiver Tor", "disable_trade_option": "Désactiver l'option de commerce", "disableBatteryOptimization": "Désactiver l'optimisation de la batterie", "disableBatteryOptimizationDescription": "Voulez-vous désactiver l'optimisation de la batterie afin de faire fonctionner la synchronisation d'arrière-plan plus librement et en douceur?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Activer le Tor construit", "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.", @@ -317,6 +319,7 @@ "error_while_processing": "Une erreur s'est produite lors de la procédure", "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", "errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction", + "establishing_tor_connection": "Établir la connexion TOR", "estimated": "Estimé", "estimated_new_fee": "De nouveaux frais estimés", "estimated_receive_amount": "Recevoir estimé le montant", @@ -830,6 +833,7 @@ "sort_by": "Trier par", "spend_key_private": "Clef de dépense (spend key) (privée)", "spend_key_public": "Clef de dépense (spend key) (publique)", + "starting_tor_proxy": "Démarrer le proxy Tor", "status": "Statut: ", "string_default": "Défaut", "subaddress_title": "Liste des sous-adresses", @@ -884,6 +888,7 @@ "token_symbol": "Symbole de token, par exemple : USDT", "tokenID": "IDENTIFIANT", "tor_connection": "Connexion Tor", + "tor_connection_timeout": "Tor Connection Timeout", "tor_only": "Tor uniquement", "total": "Total", "total_saving": "Économies totales", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index ee1fed998a..3a5e148bba 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Ta hanyar juya wannan kashe, kudaden da zai iya zama ba daidai ba a wasu halaye, saboda haka zaku iya ƙare da overpaying ko a ƙarƙashin kudaden don ma'amaloli", "disable_fiat": "Dakatar da fiat", "disable_sell": "Kashe karbuwa", + "disable_tor": "Musaki tor", "disable_trade_option": "Musaki zaɓi na kasuwanci", "disableBatteryOptimization": "Kashe ingantawa baturi", "disableBatteryOptimizationDescription": "Shin kana son kashe ingantawa baturi don yin setnc bankwali gudu da yar kyauta da kyau?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Sanya riguna-inf", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Kuskure ya faru yayin bincike", "errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida", "errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki", + "establishing_tor_connection": "Kafa Tor Haɗaɗɗa", "estimated": "Kiyasta", "estimated_new_fee": "An kiyasta sabon kudin", "estimated_receive_amount": "Kiyasta samun adadin", @@ -832,6 +835,7 @@ "sort_by": "Kasa", "spend_key_private": "makullin biya (maɓallin kalmar sirri)", "spend_key_public": "makullin biya (maɓallin jama'a)", + "starting_tor_proxy": "Fara tor proxy", "status": "Matsayi:", "string_default": "Ƙin cika alƙawari", "subaddress_title": "Jagorar subaddress", @@ -886,6 +890,7 @@ "token_symbol": "Alamar alama misali: USDT", "tokenID": "ID", "tor_connection": "Tor haɗin gwiwa", + "tor_connection_timeout": "A lokacin Tor Haɗin", "tor_only": "Tor kawai", "total": "Duka", "total_saving": "Jimlar Adana", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 41c2dfd45c..83d32040bf 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "इसे बंद करने से, कुछ मामलों में शुल्क दरें गलत हो सकती हैं, इसलिए आप अपने लेनदेन के लिए फीस को कम कर सकते हैं या कम कर सकते हैं", "disable_fiat": "िएट को अक्षम करें", "disable_sell": "बेचने की कार्रवाई अक्षम करें", + "disable_tor": "टोर को अक्षम करें", "disable_trade_option": "व्यापार विकल्प अक्षम करें", "disableBatteryOptimization": "बैटरी अनुकूलन अक्षम करें", "disableBatteryOptimizationDescription": "क्या आप बैकग्राउंड सिंक को अधिक स्वतंत्र और सुचारू रूप से चलाने के लिए बैटरी ऑप्टिमाइज़ेशन को अक्षम करना चाहते हैं?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", "email_address": "ईमेल पता", "enable": "सक्षम", + "enable_builtin_tor": "बिल्ट-इन टोर को सक्षम करें", "enable_mempool_api": "सटीक शुल्क और तिथियों के लिए मेमपूल एपीआई", "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें", "enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें", @@ -317,6 +319,7 @@ "error_while_processing": "प्रोकसिंग करते समय एक त्रुटि हुई", "errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि", "errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है", + "establishing_tor_connection": "टोर -कनेक्शन", "estimated": "अनुमानित", "estimated_new_fee": "अनुमानित नया शुल्क", "estimated_receive_amount": "अनुमानित राशि", @@ -832,6 +835,7 @@ "sort_by": "इसके अनुसार क्रमबद्ध करें", "spend_key_private": "खर्च करना (निजी)", "spend_key_public": "खर्च करना (जनता)", + "starting_tor_proxy": "टोर प्रॉक्सी शुरू करना", "status": "स्थिति: ", "string_default": "गलती करना", "subaddress_title": "उपखंड सूची", @@ -886,6 +890,7 @@ "token_symbol": "टोकन प्रतीक जैसे: यूएसडीटी", "tokenID": "पहचान", "tor_connection": "टोर कनेक्शन", + "tor_connection_timeout": "टोर कनेक्शन टाइमआउट", "tor_only": "Tor केवल", "total": "कुल", "total_saving": "कुल बचत", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index d8c2a255f8..ef07a221e0 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Isključivanjem ovoga, stope naknade u nekim bi slučajevima mogle biti netočne, tako da biste mogli preplatiti ili predati naknadu za vaše transakcije", "disable_fiat": "Isključi, fiat", "disable_sell": "Onemogući akciju prodaje", + "disable_tor": "Onemogućiti tor", "disable_trade_option": "Onemogući trgovinsku opciju", "disableBatteryOptimization": "Onemogući optimizaciju baterije", "disableBatteryOptimizationDescription": "Želite li onemogućiti optimizaciju baterije kako bi se pozadinska sinkronizacija radila slobodnije i glatko?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Omogućite ugrađeni u tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Došlo je do pogreške tijekom prožimanja", "errorGettingCredentials": "Neuspješno: Pogreška prilikom dobivanja vjerodajnica", "errorSigningTransaction": "Došlo je do pogreške prilikom potpisivanja transakcije", + "establishing_tor_connection": "Uspostavljanje torbice", "estimated": "procijenjen", "estimated_new_fee": "Procijenjena nova naknada", "estimated_receive_amount": "Procijenjeni iznos primanja", @@ -830,6 +833,7 @@ "sort_by": "Poredaj po", "spend_key_private": "Spend key (privatni)", "spend_key_public": "Spend key (javni)", + "starting_tor_proxy": "Početak Tor proxy", "status": "Status: ", "string_default": "Zadano", "subaddress_title": "Lista podadresa", @@ -884,6 +888,7 @@ "token_symbol": "Simbol tokena npr.: USDT", "tokenID": "iskaznica", "tor_connection": "Tor veza", + "tor_connection_timeout": "TOR TIMPOUCEN", "tor_only": "Samo Tor", "total": "Ukupno", "total_saving": "Ukupna ušteda", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index b39e9092fc..91116d6e9f 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Դրանից անջատելով, վճարների տեմպերը որոշ դեպքերում կարող են անճիշտ լինել, այնպես որ դուք կարող եք վերջ տալ ձեր գործարքների համար վճարների գերավճարների կամ գերավճարների վրա", "disable_fiat": "Անջատել ֆիատ", "disable_sell": "Անջատել վաճառք գործողությունը", + "disable_tor": "Անջատեք Tor- ը", "disable_trade_option": "Անջատեք առեւտրի տարբերակը", "disableBatteryOptimization": "Անջատել մարտկոցի օպտիմիզացիան", "disableBatteryOptimizationDescription": "Դուք ցանկանում եք անջատել մարտկոցի օպտիմիզացիան ֆոնային համաժամացման ավելի ազատ և հարթ ընթացքի համար?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "Մենք ստեղծում ենք նոր հասցե ամեն անգամ, երբ դուք օգտագործում եք այն, բայց նախորդ հասցեները շարունակում են աշխատել", "email_address": "Էլ. փոստի հասցե", "enable": "Միացնել", + "enable_builtin_tor": "Միացրեք ներկառուցված ջահը", "enable_mempool_api": "Mempool API ճշգրիտ վճարների եւ ամսաթվերի համար", "enable_replace_by_fee": "Միացնել փոխարինումը միջնորդավճարով", "enable_silent_payments_scanning": "Միացնել Լուռ Վճարումների սկանավորումը", @@ -317,6 +319,7 @@ "error_while_processing": "Նախագծման ժամանակ սխալ է տեղի ունեցել", "errorGettingCredentials": "Սխալ. ծանրաբեռնված վստահագրեր ստանալիս", "errorSigningTransaction": "Սխալ է տեղի ունեցել գործարքը ստորագրելիս", + "establishing_tor_connection": "Չափայնացնում է Tor կապը", "estimated": "Գնահատված", "estimated_new_fee": "Գնահատված նոր միջնորդավճար", "estimated_receive_amount": "Գնահատված ստացված գումար", @@ -828,6 +831,7 @@ "sort_by": "Դասավորել ըստ", "spend_key_private": "Վճարման բանալի (գախտնի)", "spend_key_public": "Վճարման բանալի (հանրային)", + "starting_tor_proxy": "Սկսելով Tor վստահված անձը", "status": "Կարգավիճակ՝ ", "string_default": "Լռելայն", "subaddress_title": "Ենթահասցեների ցանկ", @@ -882,6 +886,7 @@ "token_symbol": "Token-ի նշան, օրինակ՝ USDT", "tokenID": "ID", "tor_connection": "Tor կապ", + "tor_connection_timeout": "Tor կապի ժամկետ", "tor_only": "Միայն Tor", "total": "Ընդհանուր", "total_saving": "Ընդհանուր խնայողություն", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b77dc2cf02..8d92e8ad22 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Dengan mematikan ini, tarif biaya mungkin tidak akurat dalam beberapa kasus, jadi Anda mungkin akan membayar lebih atau membayar biaya untuk transaksi Anda", "disable_fiat": "Nonaktifkan fiat", "disable_sell": "Nonaktifkan aksi jual", + "disable_tor": "Nonaktifkan tor", "disable_trade_option": "Nonaktifkan opsi perdagangan", "disableBatteryOptimization": "Nonaktifkan optimasi baterai", "disableBatteryOptimizationDescription": "Apakah Anda ingin menonaktifkan optimasi baterai untuk membuat sinkronisasi latar belakang berjalan lebih bebas dan lancar?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "Kami menghasilkan alamat baru setiap kali Anda menggunakan satu, tetapi alamat sebelumnya tetap berfungsi", "email_address": "Alamat Email", "enable": "Memungkinkan", + "enable_builtin_tor": "Aktifkan built-in tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Terjadi kesalahan saat prosedur", "errorGettingCredentials": "Gagal: Terjadi kesalahan saat mendapatkan kredensial", "errorSigningTransaction": "Terjadi kesalahan saat menandatangani transaksi", + "establishing_tor_connection": "Estabilishing Tor Connection", "estimated": "Diperkirakan", "estimated_new_fee": "Perkiraan biaya baru", "estimated_receive_amount": "Diperkirakan jumlah menerima", @@ -833,6 +836,7 @@ "sort_by": "Sortir dengan", "spend_key_private": "Kunci pengeluaran (privat)", "spend_key_public": "Kunci pengeluaran (publik)", + "starting_tor_proxy": "Mulai untuk proxy", "status": "Status: ", "string_default": "Bawaan", "subaddress_title": "Daftar sub-alamat", @@ -887,6 +891,7 @@ "token_symbol": "Simbol token misalnya: USDT", "tokenID": "PENGENAL", "tor_connection": "koneksi Tor", + "tor_connection_timeout": "Tor Connection Timeout", "tor_only": "Hanya Tor", "total": "Total", "total_saving": "Total Pembayaran", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a2753277f5..b8e2633b0d 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Disattivando quest'opzione, i tassi delle commissioni potrebbero essere inaccurati in alcuni casi, quindi potresti finire per pagare troppo o troppo poco le commissioni per le tue transazioni", "disable_fiat": "Disabilita fiat", "disable_sell": "Disabilita l'azione di vendita", + "disable_tor": "Disabilita Tor", "disable_trade_option": "Disabilita l'opzione di scambio", "disableBatteryOptimization": "Disabilita l'ottimizzazione della batteria", "disableBatteryOptimizationDescription": "Vuoi disabilitare l'ottimizzazione della batteria per migliorare la sincronizzazione in background?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Abilita buildi-in tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Si è verificato un errore durante la promozione", "errorGettingCredentials": "Non riuscito: errore durante il recupero delle credenziali", "errorSigningTransaction": "Si è verificato un errore durante la firma della transazione", + "establishing_tor_connection": "Connessione TOR di esame", "estimated": "Stimato", "estimated_new_fee": "Nuova commissione stimata", "estimated_receive_amount": "Importo di ricezione stimato", @@ -831,6 +834,7 @@ "sort_by": "Ordina per", "spend_key_private": "Chiave di spesa (privata)", "spend_key_public": "Chiave di spesa (pubblica)", + "starting_tor_proxy": "Avvio del proxy di Tor", "status": "Stato: ", "string_default": "Predefinito", "subaddress_title": "Lista sottoindirizzi", @@ -885,6 +889,7 @@ "token_symbol": "Simbolo del token, ad esempio: USDT", "tokenID": "ID", "tor_connection": "Connessione Tor", + "tor_connection_timeout": "Timeout di connessione TOR", "tor_only": "Solo Tor", "total": "Totale", "total_saving": "Risparmio totale", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index c2ab7c2be0..aad8769449 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "これをオフにすることで、料金金利は場合によっては不正確になる可能性があるため、取引の費用が過払いまたは不足している可能性があります", "disable_fiat": "フィアットを無効にする", "disable_sell": "販売アクションを無効にする", + "disable_tor": "torを無効にします", "disable_trade_option": "取引オプションを無効にします", "disableBatteryOptimization": "バッテリーの最適化を無効にします", "disableBatteryOptimizationDescription": "バックグラウンドシンクをより自由かつスムーズに実行するために、バッテリーの最適化を無効にしたいですか?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", "email_address": "メールアドレス", "enable": "有効にする", + "enable_builtin_tor": "組み込みのTORを有効にします", "enable_mempool_api": "正確な料金と日付のMempool API", "enable_replace_by_fee": "交換ごとに有効にします", "enable_silent_payments_scanning": "先端に達するまで、サイレント決済のスキャンを開始します", @@ -317,6 +319,7 @@ "error_while_processing": "処理中にエラーが発生しました", "errorGettingCredentials": "失敗: 認証情報の取得中にエラーが発生しました", "errorSigningTransaction": "トランザクションの署名中にエラーが発生しました", + "establishing_tor_connection": "TOR接続を確立します", "estimated": "推定", "estimated_new_fee": "推定新しい料金", "estimated_receive_amount": "推定受信金額", @@ -831,6 +834,7 @@ "sort_by": "並び替え", "spend_key_private": "キーを使う (プライベート)", "spend_key_public": "キーを使う (パブリック)", + "starting_tor_proxy": "プロキシを開始します", "status": "状態: ", "string_default": "デフォルト", "subaddress_title": "サブアドレス一覧", @@ -885,6 +889,7 @@ "token_symbol": "トークンシンボル 例: USDT", "tokenID": "ID", "tor_connection": "Tor接続", + "tor_connection_timeout": "TOR接続タイムアウト", "tor_only": "Torのみ", "total": "合計", "total_saving": "合計節約額", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 841cf7a37e..24c8534068 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -252,7 +252,8 @@ "disable_exchange_option": "교환 옵션 비활성화", "disable_fee_api_warning": "이 기능을 끄면 경우에 따라 수수료율이 정확하지 않을 수 있으며, 트랜잭션 수수료를 과다 또는 과소 지불할 수 있습니다.", "disable_fiat": "법정화폐 비활성화", - "disable_sell": "판매 기능 비활성화", + "disable_sell": "판매 조치 비활성화", + "disable_tor": "Tor를 비활성화하십시오", "disable_trade_option": "거래 옵션 비활성화", "disableBatteryOptimization": "배터리 최적화 비활성화", "disableBatteryOptimizationDescription": "백그라운드 동기화가 더 자유롭고 원활하게 실행되도록 배터리 최적화를 비활성화하시겠습니까?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "사용할 때마다 새 주소를 생성하지만 이전 주소도 계속 작동합니다", "email_address": "이메일 주소", "enable": "활성화", + "enable_builtin_tor": "내장 Tor를 활성화하십시오", "enable_mempool_api": "정확한 수수료 및 날짜를 위한 Mempool API", "enable_replace_by_fee": "RBF(Replace-By-Fee) 활성화", "enable_silent_payments_scanning": "사일런트 페이먼트 주소로 전송된 트랜잭션 스캔 시작", @@ -317,6 +319,7 @@ "error_while_processing": "처리 중 오류가 발생했습니다", "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류 발생", "errorSigningTransaction": "트랜잭션 서명 중 오류가 발생했습니다", + "establishing_tor_connection": "Tor 연결을 실행합니다", "estimated": "예상", "estimated_new_fee": "예상 새 수수료", "estimated_receive_amount": "예상 수령 금액", @@ -831,6 +834,7 @@ "sort_by": "정렬 기준", "spend_key_private": "지출 키 (개인)", "spend_key_public": "지출 키 (공개)", + "starting_tor_proxy": "Tor 프록시 시작", "status": "상태: ", "string_default": "기본값", "subaddress_title": "하위 주소 목록", @@ -885,6 +889,7 @@ "token_symbol": "토큰 심볼 (예: USDT)", "tokenID": "ID", "tor_connection": "Tor 연결", + "tor_connection_timeout": "Tor 연결 시간 초과", "tor_only": "Tor 전용", "total": "합계", "total_saving": "총 절약액", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 54ad17d2b1..a682b96286 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "ဤအရာကိုဖွင့်ခြင်းအားဖြင့်အချို့သောကိစ္စရပ်များတွင်အခကြေးငွေနှုန်းထားများသည်တိကျမှုရှိနိုင်သည်,", "disable_fiat": "Fiat ကိုပိတ်ပါ။", "disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။", + "disable_tor": "Tor ကိုပိတ်ပါ", "disable_trade_option": "ကုန်သွယ်ရေး option ကိုပိတ်ပါ", "disableBatteryOptimization": "ဘက်ထရီ optimization ကိုပိတ်ပါ", "disableBatteryOptimizationDescription": "နောက်ခံထပ်တူပြုခြင်းနှင့်ချောချောမွေ့မွေ့ပြုလုပ်နိုင်ရန်ဘက်ထရီ optimization ကိုသင်ပိတ်ထားလိုပါသလား။", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "သင်အသုံးပြုသည့်အချိန်တိုင်းတွင် ကျွန်ုပ်တို့သည် လိပ်စာအသစ်များကို ထုတ်ပေးသော်လည်း ယခင်လိပ်စာများသည် ဆက်လက်အလုပ်လုပ်နေပါသည်။", "email_address": "အီးမေးလ်လိပ်စာ", "enable": "စွမ်းဆောင်နိုင်စေ", + "enable_builtin_tor": "built-in Tor ကို Enable လုပ်ပါ", "enable_mempool_api": "Mempool API တိကျသောအခကြေးငွေနှင့်ရက်စွဲများအတွက်", "enable_replace_by_fee": "အစားထိုး - by- အခကြေးငွေ enable", "enable_silent_payments_scanning": "အစွန်အဖျားသို့ရောက်ရှိသည်အထိအသံတိတ်ငွေပေးချေမှုကိုစကင်ဖတ်စစ်ဆေးပါ", @@ -317,6 +319,7 @@ "error_while_processing": "procesing နေစဉ်အမှားတစ်ခုဖြစ်ပွားခဲ့သည်", "errorGettingCredentials": "မအောင်မြင်ပါ- အထောက်အထားများ ရယူနေစဉ် အမှားအယွင်း", "errorSigningTransaction": "ငွေပေးငွေယူ လက်မှတ်ထိုးစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။", + "establishing_tor_connection": "Tor connection ကို estabilishing", "estimated": "ခန့်မှန်း", "estimated_new_fee": "ခန့်မှန်းသစ်ခန့်မှန်း", "estimated_receive_amount": "ခန့်မှန်းရရှိသောပမာဏ", @@ -830,6 +833,7 @@ "sort_by": "အလိုက်စဥ်သည်", "spend_key_private": "သော့သုံးရန် (သီးသန့်)", "spend_key_public": "သုံးစွဲရန်သော့ (အများပြည်သူ)", + "starting_tor_proxy": "Tor proxy ကိုစတင်ခြင်း", "status": "အခြေအနေ:", "string_default": "ပျက်ကွက်ခြင်း", "subaddress_title": "လိပ်စာစာရင်း", @@ -884,6 +888,7 @@ "token_symbol": "တိုကင်သင်္ကေတ ဥပမာ- USDT", "tokenID": "အမှတ်သညာ", "tor_connection": "Tor ချိတ်ဆက်မှု", + "tor_connection_timeout": "Tor Connection အချိန်ကုန်", "tor_only": "Tor သာ", "total": "လုံးဝသော", "total_saving": "စုစုပေါင်းစုဆောင်းငွေ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index aa489d10d4..509593b0fe 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Door dit uit te schakelen, kunnen de tarieven in sommige gevallen onnauwkeurig zijn, dus u kunt de vergoedingen voor uw transacties te veel betalen of te weinig betalen", "disable_fiat": "Schakel Fiat uit", "disable_sell": "Verkoopactie uitschakelen", + "disable_tor": "Schakel Tor uit", "disable_trade_option": "Schakel handelsoptie uit", "disableBatteryOptimization": "Schakel de batterijoptimalisatie uit", "disableBatteryOptimizationDescription": "Wilt u de optimalisatie van de batterij uitschakelen om achtergrondsynchronisatie te laten werken, vrijer en soepeler?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Schakel ingebouwde tor in", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Er is een fout opgetreden tijdens het procederen", "errorGettingCredentials": "Mislukt: fout bij het ophalen van inloggegevens", "errorSigningTransaction": "Er is een fout opgetreden tijdens het ondertekenen van de transactie", + "establishing_tor_connection": "Estabilishing Tor -verbinding", "estimated": "Geschatte", "estimated_new_fee": "Geschatte nieuwe vergoeding", "estimated_receive_amount": "Geschat ontvangen bedrag", @@ -830,6 +833,7 @@ "sort_by": "Sorteer op", "spend_key_private": "Sleutel uitgeven (privaat)", "spend_key_public": "Sleutel uitgeven (openbaar)", + "starting_tor_proxy": "Tor Proxy starten", "status": "Staat: ", "string_default": "Standaard", "subaddress_title": "Subadreslijst", @@ -884,6 +888,7 @@ "token_symbol": "Tokensymbool bijv.: USDT", "tokenID": "ID kaart", "tor_connection": "Tor-verbinding", + "tor_connection_timeout": "Tor -verbindingstime -out", "tor_only": "Alleen Tor", "total": "Totaal", "total_saving": "Totale besparingen", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b8e02aacc3..e1a3cd6729 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Wyłączając tą opcję, stawki opłaty mogą być w niektórych przypadkach niedokładne, więc możesz przepłacić opłatę za transakcje", "disable_fiat": "Wyłącz waluty FIAT", "disable_sell": "Wyłącz akcję sprzedaży", + "disable_tor": "Wyłącz Tor", "disable_trade_option": "Wyłącz opcję handlu", "disableBatteryOptimization": "Wyłącz optymalizację baterii", "disableBatteryOptimizationDescription": "Czy chcesz wyłączyć optymalizację baterii, aby synchronizacja tła działała swobodniej i płynnie?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Włącz wbudowany tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Wystąpił błąd podczas przeróbki", "errorGettingCredentials": "Niepowodzenie: Błąd podczas uzyskiwania poświadczeń", "errorSigningTransaction": "Wystąpił błąd podczas podpisywania transakcji", + "establishing_tor_connection": "Ustalenie połączenia TOR", "estimated": "Oszacowano", "estimated_new_fee": "Szacowana nowa opłata", "estimated_receive_amount": "Szacowana kwota otrzymania", @@ -830,6 +833,7 @@ "sort_by": "Sortuj według", "spend_key_private": "Klucz prywatny", "spend_key_public": "Klucz publiczny", + "starting_tor_proxy": "Rozpoczęcie proxy TOR", "status": "Status: ", "string_default": "Domyślny", "subaddress_title": "Lista podadresów", @@ -884,6 +888,7 @@ "token_symbol": "Symbol tokena np.: USDT", "tokenID": "ID", "tor_connection": "Połączenie przez Tor", + "tor_connection_timeout": "Tor Connection Timeout", "tor_only": "Tylko sieć Tor", "total": "Całkowity", "total_saving": "Całkowite oszczędności", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index c38a368db2..2a6844f165 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Ao desativar isso, as taxas de taxas podem ser imprecisas em alguns casos, para que você possa acabar pagando demais ou pagando as taxas por suas transações", "disable_fiat": "Desativar fiat", "disable_sell": "Desativar ação de venda", + "disable_tor": "Desativar tor", "disable_trade_option": "Desativar a opção comercial", "disableBatteryOptimization": "Desative a otimização da bateria", "disableBatteryOptimizationDescription": "Deseja desativar a otimização da bateria para fazer a sincronização de fundo funcionar de forma mais livre e suave?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Ative o Buildi-in Tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "Ocorreu um erro ao procurar", "errorGettingCredentials": "Falha: Erro ao obter credenciais", "errorSigningTransaction": "Ocorreu um erro ao assinar a transação", + "establishing_tor_connection": "Estabilizar a conexão com", "estimated": "Estimado", "estimated_new_fee": "Nova taxa estimada", "estimated_receive_amount": "Valor estimado de recebimento", @@ -832,6 +835,7 @@ "sort_by": "Ordenar por", "spend_key_private": "Chave de gastos (privada)", "spend_key_public": "Chave de gastos (pública)", + "starting_tor_proxy": "Começando por proxy", "status": "Status: ", "string_default": "Padrão", "subaddress_title": "Sub-endereços", @@ -886,6 +890,7 @@ "token_symbol": "Símbolo de token, por exemplo: USDT", "tokenID": "EU IA", "tor_connection": "Conexão Tor", + "tor_connection_timeout": "Tor de tempo limite da conexão", "tor_only": "Tor apenas", "total": "Total", "total_saving": "Economia total", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index f532783ee3..cab529799a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Выключив это, в некоторых случаях ставки платы могут быть неточными, так что вы можете в конечном итоге переплачивать или недоплачивать сборы за ваши транзакции", "disable_fiat": "Отключить фиат", "disable_sell": "Отключить действие продажи", + "disable_tor": "Отключить Tor", "disable_trade_option": "Отключить возможность торговли", "disableBatteryOptimization": "Отключить оптимизацию батареи", "disableBatteryOptimizationDescription": "Вы хотите отключить оптимизацию батареи, чтобы сделать фона синхронизации более свободно и плавно?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", "email_address": "Адрес электронной почты", "enable": "Давать возможность", + "enable_builtin_tor": "Включить встроенный в Tor", "enable_mempool_api": "Mempool API за точные сборы и даты", "enable_replace_by_fee": "Включить замену за пикой", "enable_silent_payments_scanning": "Начните сканировать безмолвные платежи, пока не будет достигнут наконечник", @@ -317,6 +319,7 @@ "error_while_processing": "Произошла ошибка во время выплаты", "errorGettingCredentials": "Не удалось: ошибка при получении учетных данных.", "errorSigningTransaction": "Произошла ошибка при подписании транзакции", + "establishing_tor_connection": "Остановка связи", "estimated": "Примерно", "estimated_new_fee": "Расчетная новая плата", "estimated_receive_amount": "Расчетная сумма получения", @@ -831,6 +834,7 @@ "sort_by": "Сортировать по", "spend_key_private": "Приватный ключ траты", "spend_key_public": "Публичный ключ траты", + "starting_tor_proxy": "Запуск прокси", "status": "Статус: ", "string_default": "По умолчанию", "subaddress_title": "Список субадресов", @@ -885,6 +889,7 @@ "token_symbol": "Символ токена, например: USDT", "tokenID": "ИДЕНТИФИКАТОР", "tor_connection": "Тор соединение", + "tor_connection_timeout": "TOR Timeout", "tor_only": "Только Tor", "total": "Общий", "total_saving": "Общая экономия", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 60508d827d..6f19539ff4 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "โดยการปิดสิ่งนี้อัตราค่าธรรมเนียมอาจไม่ถูกต้องในบางกรณีดังนั้นคุณอาจจบลงด้วยการจ่ายเงินมากเกินไปหรือจ่ายค่าธรรมเนียมสำหรับการทำธุรกรรมของคุณมากเกินไป", "disable_fiat": "ปิดใช้งานสกุลเงินตรา", "disable_sell": "ปิดการใช้งานการขาย", + "disable_tor": "ปิดการใช้งาน TOR", "disable_trade_option": "ปิดใช้งานตัวเลือกการค้า", "disableBatteryOptimization": "ปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่", "disableBatteryOptimizationDescription": "คุณต้องการปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่เพื่อให้การซิงค์พื้นหลังทำงานได้อย่างอิสระและราบรื่นมากขึ้นหรือไม่?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "เราสร้างที่อยู่ใหม่ทุกครั้งที่คุณใช้หนึ่งอย่าง แต่ที่อยู่เก่ายังสามารถใช้ได้ต่อไป", "email_address": "ที่อยู่อีเมล", "enable": "เปิดใช้งาน", + "enable_builtin_tor": "เปิดใช้งาน Tor ในตัว", "enable_mempool_api": "Mempool API สำหรับค่าธรรมเนียมและวันที่ที่ถูกต้อง", "enable_replace_by_fee": "เปิดใช้งานการเปลี่ยนโดยค่าธรรมเนียม", "enable_silent_payments_scanning": "เริ่มสแกนการชำระเงินแบบเงียบจนกว่าจะถึงปลาย", @@ -317,6 +319,7 @@ "error_while_processing": "เกิดข้อผิดพลาดในขณะที่ proceessing", "errorGettingCredentials": "ล้มเหลว: เกิดข้อผิดพลาดขณะรับข้อมูลรับรอง", "errorSigningTransaction": "เกิดข้อผิดพลาดขณะลงนามธุรกรรม", + "establishing_tor_connection": "การเชื่อมต่อ Tor", "estimated": "ประมาณการ", "estimated_new_fee": "ค่าธรรมเนียมใหม่โดยประมาณ", "estimated_receive_amount": "โดยประมาณว่าจำนวนเงินที่ได้รับ", @@ -830,6 +833,7 @@ "sort_by": "เรียงตาม", "spend_key_private": "คีย์จ่าย (ส่วนตัว)", "spend_key_public": "คีย์จ่าย (สาธารณะ)", + "starting_tor_proxy": "เริ่มพร็อกซี TOR", "status": "สถานะ: ", "string_default": "ค่าเริ่มต้น", "subaddress_title": "รายการที่อยู่ย่อย", @@ -884,6 +888,7 @@ "token_symbol": "สัญลักษณ์โทเค็น เช่น USDT", "tokenID": "บัตรประจำตัวประชาชน", "tor_connection": "การเชื่อมต่อทอร์", + "tor_connection_timeout": "หมดเวลาเชื่อมต่อ tor", "tor_only": "Tor เท่านั้น", "total": "ทั้งหมด", "total_saving": "ประหยัดรวม", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 14b8e37685..75f4c772f2 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Sa pamamagitan ng pag -off nito, ang mga rate ng bayad ay maaaring hindi tumpak sa ilang mga kaso, kaya maaari mong tapusin ang labis na bayad o pagsuporta sa mga bayarin para sa iyong mga transaksyon", "disable_fiat": "Huwag paganahin ang fiat", "disable_sell": "Huwag paganahin ang pagkilos ng pagbebenta", + "disable_tor": "Huwag paganahin ang tor", "disable_trade_option": "Huwag paganahin ang pagpipilian sa kalakalan", "disableBatteryOptimization": "Huwag Paganahin ang Pag-optimize ng Baterya", "disableBatteryOptimizationDescription": "Nais mo bang huwag paganahin ang pag-optimize ng baterya upang gawing mas malaya at maayos ang background sync?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Paganahin ang built-in tor", "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", @@ -317,6 +319,7 @@ "error_while_processing": "May naganap na error habang nagpapatuloy", "errorGettingCredentials": "Nabigo: Error habang kumukuha ng mga kredensyal", "errorSigningTransaction": "Error habang pinipirmahan ang transaksyon", + "establishing_tor_connection": "Koneksyon ng Tor", "estimated": "Tinatayang", "estimated_new_fee": "Tinatayang bagong fee", "estimated_receive_amount": "Tinatayang natanggap na halaga", @@ -830,6 +833,7 @@ "sort_by": "Pag-uri-uriin sa pamamagitan ng", "spend_key_private": "Spend key (private)", "spend_key_public": "Spend key (public)", + "starting_tor_proxy": "Simula sa Tor Proxy", "status": "Katayuan: ", "string_default": "Default", "subaddress_title": "Listahan ng Subaddress", @@ -884,6 +888,7 @@ "token_symbol": "Simbolo ng token, halimbawa: USDT", "tokenID": "ID", "tor_connection": "Koneksyon ng Tor", + "tor_connection_timeout": "Oras ng koneksyon sa tor", "tor_only": "Tor lamang", "total": "Kabuuan", "total_saving": "Kabuuang ipon", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 6a9b433b3e..694e400a5f 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Bunu kapatarak, ücret oranları bazı durumlarda yanlış olabilir, bu nedenle işlemleriniz için ücretleri fazla ödeyebilir veya az ödeyebilirsiniz.", "disable_fiat": "İtibari paraları devre dışı bırak", "disable_sell": "Satış işlemini devre dışı bırak", + "disable_tor": "Tor'u devre dışı bırak", "disable_trade_option": "Ticaret seçeneğini devre dışı bırakın", "disableBatteryOptimization": "Pil optimizasyonunu devre dışı bırakın", "disableBatteryOptimizationDescription": "Arka plan senkronizasyonunu daha özgür ve sorunsuz bir şekilde çalıştırmak için pil optimizasyonunu devre dışı bırakmak istiyor musunuz?", @@ -277,6 +278,7 @@ "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_builtin_tor": "Yerleşik Tor'u 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", @@ -317,6 +319,7 @@ "error_while_processing": "ProceSting sırasında bir hata oluştu", "errorGettingCredentials": "Başarısız: Kimlik bilgileri alınırken hata oluştu", "errorSigningTransaction": "İşlem imzalanırken bir hata oluştu", + "establishing_tor_connection": "Fazla Tor Bağlantısı", "estimated": "Tahmini", "estimated_new_fee": "Tahmini yeni ücret", "estimated_receive_amount": "Tahmini alma miktarı", @@ -830,6 +833,7 @@ "sort_by": "Göre sırala", "spend_key_private": "Harcama anahtarı (özel)", "spend_key_public": "Harcama anahtarı (genel)", + "starting_tor_proxy": "Tor Proxy'yi Başlatma", "status": "Durum: ", "string_default": "Varsayılan", "subaddress_title": "Alt adres listesi", @@ -884,6 +888,7 @@ "token_symbol": "Jeton sembolü, örneğin: USDT", "tokenID": "İD", "tor_connection": "Tor bağlantısı", + "tor_connection_timeout": "Tor bağlantı zaman aşımı", "tor_only": "Yalnızca Tor", "total": "Toplam", "total_saving": "Toplam Tasarruf", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 9a03526b16..6736381d3e 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Вимкнувши це, ставки плати в деяких випадках можуть бути неточними, тому ви можете переплатити або недооплатити плату за свої транзакції", "disable_fiat": "Вимкнути фиат", "disable_sell": "Вимкнути дію продажу", + "disable_tor": "Вимкнути Тор", "disable_trade_option": "Вимкнути можливість торгівлі", "disableBatteryOptimization": "Вимкнути оптимізацію акумулятора", "disableBatteryOptimizationDescription": "Ви хочете відключити оптимізацію акумулятора, щоб зробити фонову синхронізацію більш вільно та плавно?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", "email_address": "Адреса електронної пошти", "enable": "Ввімкнути", + "enable_builtin_tor": "Увімкнути вбудований tor", "enable_mempool_api": "API Mempool для точних зборів та дат", "enable_replace_by_fee": "Увімкнути заміну з комісією", "enable_silent_payments_scanning": "Почніть сканувати мовчазні платежі, поки не буде досягнуто наконечника", @@ -317,6 +319,7 @@ "error_while_processing": "Помилка сталася під час проектування", "errorGettingCredentials": "Помилка: помилка під час отримання облікових даних", "errorSigningTransaction": "Під час підписання транзакції сталася помилка", + "establishing_tor_connection": "Закінчення підключення до TOR", "estimated": "Приблизно ", "estimated_new_fee": "Орієнтовна нова комісія", "estimated_receive_amount": "Орієнтовна сума отримує", @@ -831,6 +834,7 @@ "sort_by": "Сортувати за", "spend_key_private": "Приватний ключ витрати", "spend_key_public": "Публічний ключ витрати", + "starting_tor_proxy": "Початок Tor Proxy", "status": "Статус: ", "string_default": "За замовчуванням", "subaddress_title": "Список Субадрес", @@ -885,6 +889,7 @@ "token_symbol": "Символ маркера, наприклад: USDT", "tokenID": "ID", "tor_connection": "Підключення Tor", + "tor_connection_timeout": "Тайм -аут підключення TOR", "tor_only": "Тільки Tor", "total": "Загальний", "total_saving": "Загальна економія", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 65fb7db399..41551206f1 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "اس کو بند کرنے سے ، کچھ معاملات میں فیس کی شرح غلط ہوسکتی ہے ، لہذا آپ اپنے لین دین کے لئے فیسوں کو زیادہ ادائیگی یا ادائیگی ختم کرسکتے ہیں۔", "disable_fiat": "فیاٹ کو غیر فعال کریں۔", "disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔", + "disable_tor": "ٹور کو غیر فعال کریں", "disable_trade_option": "تجارت کے آپشن کو غیر فعال کریں", "disableBatteryOptimization": "بیٹری کی اصلاح کو غیر فعال کریں", "disableBatteryOptimizationDescription": "کیا آپ پس منظر کی مطابقت پذیری کو زیادہ آزادانہ اور آسانی سے چلانے کے لئے بیٹری کی اصلاح کو غیر فعال کرنا چاہتے ہیں؟", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "جب بھی آپ ایک کا استعمال کرتے ہیں تو ہم نئے پتے تیار کرتے ہیں، لیکن پچھلے پتے کام کرتے رہتے ہیں۔", "email_address": "ای میل اڈریس", "enable": "قابل بنائیں", + "enable_builtin_tor": "بلٹٹی ان ٹور کو فعال کریں", "enable_mempool_api": "درست فیسوں اور تاریخوں کے لئے میمپول API", "enable_replace_by_fee": "فی فیس کو تبدیل کریں", "enable_silent_payments_scanning": "خاموش ادائیگیوں کو اسکین کرنا شروع کریں ، جب تک کہ نوک نہ پہنچ جائے", @@ -317,6 +319,7 @@ "error_while_processing": "ایک غلطی پیش کرتے وقت ہوئی", "errorGettingCredentials": "۔ﯽﺑﺍﺮﺧ ﮟﯿﻣ ﮯﻧﺮﮐ ﻞﺻﺎﺣ ﺩﺎﻨﺳﺍ :ﻡﺎﮐﺎﻧ", "errorSigningTransaction": "۔ﮯﮨ ﯽﺌﮔﺁ ﺶﯿﭘ ﯽﺑﺍﺮﺧ ﮏﯾﺍ ﺖﻗﻭ ﮯﺗﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", + "establishing_tor_connection": "ٹور کنکشن کو کھڑا کرنا", "estimated": "تخمینہ لگایا", "estimated_new_fee": "تخمینہ شدہ نئی فیس", "estimated_receive_amount": "تخمینہ وصول کی رقم", @@ -832,6 +835,7 @@ "sort_by": "ترتیب دیں", "spend_key_private": "خرچ کی کلید (نجی)", "spend_key_public": "خرچ کی کلید (عوامی)", + "starting_tor_proxy": "ٹور پراکسی شروع کرنا", "status": "حالت:", "string_default": "پہلے سے طے شدہ", "subaddress_title": "ذیلی ایڈریس کی فہرست", @@ -886,6 +890,7 @@ "token_symbol": "ٹوکن کی علامت جیسے: USDT", "tokenID": "ID", "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ", + "tor_connection_timeout": "ٹور کنکشن ٹائم آؤٹ", "tor_only": "صرف Tor", "total": "کل", "total_saving": "کل بچت", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index f65192fb96..62a7c7e764 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -252,6 +252,7 @@ "disable_fee_api_warning": "Khi tắt chức năng này, tỉ lệ phí có thể không chính xác trong một số trường hợp, dẫn đến bạn trả quá hoặc không đủ phí cho giao dịch của mình.", "disable_fiat": "Vô hiệu hóa tiền tệ fiat", "disable_sell": "Vô hiệu hóa chức năng bán", + "disable_tor": "Tắt Tor", "disable_trade_option": "Tắt tùy chọn thương mại", "disableBatteryOptimization": "Vô hiệu hóa Tối ưu hóa Pin", "disableBatteryOptimizationDescription": "Bạn có muốn vô hiệu hóa tối ưu hóa pin để đồng bộ hóa nền hoạt động mượt mà hơn không?", @@ -276,6 +277,7 @@ "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_builtin_tor": "Bật Buildi-In Tor", "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", @@ -316,6 +318,7 @@ "error_while_processing": "Xảy ra lỗi trong khi sử dụng", "errorGettingCredentials": "Không thành công: Lỗi khi nhận thông tin xác thực", "errorSigningTransaction": "Đã xảy ra lỗi khi ký giao dịch", + "establishing_tor_connection": "Kết nối tor", "estimated": "Ước tính", "estimated_new_fee": "Phí mới ước tính", "estimated_receive_amount": "Số tiền nhận ước tính", @@ -827,6 +830,7 @@ "sort_by": "Sắp xếp theo", "spend_key_private": "Khóa chi tiêu (riêng tư)", "spend_key_public": "Khóa chi tiêu (công khai)", + "starting_tor_proxy": "Bắt đầu proxy tor", "status": "Trạng thái: ", "string_default": "Mặc định", "subaddress_title": "Danh sách địa chỉ phụ", @@ -881,6 +885,7 @@ "token_symbol": "Ký hiệu token ví dụ: USDT", "tokenID": "ID", "tor_connection": "Kết nối Tor", + "tor_connection_timeout": "Thời gian chờ kết nối tor", "tor_only": "Chỉ Tor", "total": "Tổng cộng", "total_saving": "Tiết kiệm tổng cộng", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b7282aeaba..2818d5a6a4 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "Nipa yiyi eyi kuro, awọn oṣuwọn owo naa le jẹ aiṣe deede ni awọn ọrọ kan, nitorinaa o le pari apọju tabi awọn idiyele ti o ni agbara fun awọn iṣowo rẹ", "disable_fiat": "Pa owó tí ìjọba pàṣẹ wa lò", "disable_sell": "Ko iṣọrọ iṣọrọ", + "disable_tor": "Mu lile", "disable_trade_option": "Mu aṣayan iṣowo ṣiṣẹ", "disableBatteryOptimization": "Mu Ifasi batiri", "disableBatteryOptimizationDescription": "Ṣe o fẹ lati mu iṣapelo batiri si lati le ṣiṣe ayẹwo ẹhin ati laisiyonu?", @@ -278,6 +279,7 @@ "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_builtin_tor": "Mu ṣiṣẹ", "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", @@ -318,6 +320,7 @@ "error_while_processing": "Aṣiṣe kan waye lakoko ti o duro", "errorGettingCredentials": "Kuna: Aṣiṣe lakoko gbigba awọn iwe-ẹri", "errorSigningTransaction": "Aṣiṣe kan ti waye lakoko ti o fowo si iṣowo", + "establishing_tor_connection": "Isopọ Stanishing", "estimated": "Ó tó a fojú díwọ̀n", "estimated_new_fee": "Ifoju tuntun owo tuntun", "estimated_receive_amount": "Ifoju gba iye", @@ -831,6 +834,7 @@ "sort_by": "Sa pelu", "spend_key_private": "Kọ́kọ́rọ́ sísan (àdáni)", "spend_key_public": "Kọ́kọ́rọ́ sísan (kò àdáni)", + "starting_tor_proxy": "Bibẹrẹ Oluṣaaju Aṣoju", "status": "Tó ń ṣẹlẹ̀: ", "string_default": "Aiyipada", "subaddress_title": "Àkọsílẹ̀ ni nínú àwọn àdírẹ́sì tíwọn rẹ̀lẹ̀", @@ -885,6 +889,7 @@ "token_symbol": "Aami aami fun apẹẹrẹ: USDT", "tokenID": "ID", "tor_connection": "Tor asopọ", + "tor_connection_timeout": "Akoko asopọ asopọ", "tor_only": "Tor nìkan", "total": "Apapọ", "total_saving": "Owó t'ẹ́ ti pamọ́", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 09ca7cd11c..4ba432e884 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -253,6 +253,7 @@ "disable_fee_api_warning": "通过将其关闭,在某些情况下,收费率可能不准确,因此您最终可能会超额付款或支付交易费用", "disable_fiat": "禁用法令", "disable_sell": "禁用卖出操作", + "disable_tor": "禁用TOR", "disable_trade_option": "禁用贸易选项", "disableBatteryOptimization": "禁用电池优化", "disableBatteryOptimizationDescription": "您是否要禁用电池优化以使背景同步更加自由,平稳地运行?", @@ -277,6 +278,7 @@ "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", "email_address": "电子邮件地址", "enable": "使能够", + "enable_builtin_tor": "启用内置的tor", "enable_mempool_api": "Mempool API获得准确的费用和日期", "enable_replace_by_fee": "启用by-Fee替换", "enable_silent_payments_scanning": "开始扫描无声付款,直到达到提示", @@ -317,6 +319,7 @@ "error_while_processing": "发动机时发生错误", "errorGettingCredentials": "失败:获取凭据时出错", "errorSigningTransaction": "签署交易时发生错误", + "establishing_tor_connection": "tor连接", "estimated": "估计值", "estimated_new_fee": "估计新费用", "estimated_receive_amount": "估计接收金额", @@ -830,6 +833,7 @@ "sort_by": "排序方式", "spend_key_private": "Spend 密钥 (私钥)", "spend_key_public": "Spend 密钥 (公钥)", + "starting_tor_proxy": "启动Tor代理", "status": "状态: ", "string_default": "默认", "subaddress_title": "子地址列表", @@ -884,6 +888,7 @@ "token_symbol": "代币符号例如:USDT", "tokenID": "ID", "tor_connection": "Tor连接", + "tor_connection_timeout": "TOR连接超时", "tor_only": "仅限 Tor", "total": "全部的", "total_saving": "总储蓄",