mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +00:00
quick (quite quick) and dirty (very dirty) tor/clearnet/both config option for coin network/node connections
This commit is contained in:
parent
6cfe9e9c0f
commit
d6d4df7822
19 changed files with 536 additions and 45 deletions
|
@ -77,6 +77,8 @@ class DbVersionMigrator with WalletDB {
|
||||||
name: e.name,
|
name: e.name,
|
||||||
id: e.id,
|
id: e.id,
|
||||||
useSSL: e.useSSL,
|
useSSL: e.useSSL,
|
||||||
|
torEnabled: e.torEnabled,
|
||||||
|
clearEnabled: e.plainEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -88,6 +90,8 @@ class DbVersionMigrator with WalletDB {
|
||||||
name: node.name,
|
name: node.name,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
useSSL: node.useSSL,
|
useSSL: node.useSSL,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.plainEnabled,
|
||||||
),
|
),
|
||||||
prefs: prefs,
|
prefs: prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:async';
|
||||||
import 'package:electrum_adapter/electrum_adapter.dart';
|
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||||
|
|
||||||
import '../utilities/logger.dart';
|
import '../utilities/logger.dart';
|
||||||
|
import '../utilities/prefs.dart';
|
||||||
|
import '../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
|
||||||
class ClientManager {
|
class ClientManager {
|
||||||
|
@ -10,6 +12,7 @@ class ClientManager {
|
||||||
static final ClientManager sharedInstance = ClientManager._();
|
static final ClientManager sharedInstance = ClientManager._();
|
||||||
|
|
||||||
final Map<String, ElectrumClient> _map = {};
|
final Map<String, ElectrumClient> _map = {};
|
||||||
|
final Map<String, TorPlainNetworkOption> _mapNet = {};
|
||||||
final Map<String, int> _heights = {};
|
final Map<String, int> _heights = {};
|
||||||
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
|
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
|
||||||
final Map<String, Completer<int>> _heightCompleters = {};
|
final Map<String, Completer<int>> _heightCompleters = {};
|
||||||
|
@ -24,18 +27,37 @@ class ClientManager {
|
||||||
|
|
||||||
ElectrumClient? getClient({
|
ElectrumClient? getClient({
|
||||||
required CryptoCurrency cryptoCurrency,
|
required CryptoCurrency cryptoCurrency,
|
||||||
}) =>
|
required TorPlainNetworkOption netType,
|
||||||
_map[_keyHelper(cryptoCurrency)];
|
}) {
|
||||||
|
final _key = _keyHelper(cryptoCurrency);
|
||||||
|
|
||||||
void addClient(
|
if (netType == _mapNet[_key]) {
|
||||||
|
return _map[_key];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addClient(
|
||||||
ElectrumClient client, {
|
ElectrumClient client, {
|
||||||
required CryptoCurrency cryptoCurrency,
|
required CryptoCurrency cryptoCurrency,
|
||||||
}) {
|
required TorPlainNetworkOption netType,
|
||||||
|
}) async {
|
||||||
final key = _keyHelper(cryptoCurrency);
|
final key = _keyHelper(cryptoCurrency);
|
||||||
if (_map[key] != null) {
|
if (_map[key] != null) {
|
||||||
throw Exception("ElectrumX Client for $key already exists.");
|
if (_mapNet[key] == netType) {
|
||||||
|
throw Exception(
|
||||||
|
"ElectrumX Client for $key and $netType already exists.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await remove(cryptoCurrency: cryptoCurrency);
|
||||||
|
|
||||||
|
_map[key] = client;
|
||||||
|
_mapNet[key] = netType;
|
||||||
} else {
|
} else {
|
||||||
_map[key] = client;
|
_map[key] = client;
|
||||||
|
_mapNet[key] = netType;
|
||||||
}
|
}
|
||||||
|
|
||||||
_heightCompleters[key] = Completer<int>();
|
_heightCompleters[key] = Completer<int>();
|
||||||
|
@ -68,10 +90,24 @@ class ClientManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Prefs.instance.useTor) {
|
||||||
|
if (_mapNet[key]! == TorPlainNetworkOption.clear) {
|
||||||
|
throw Exception(
|
||||||
|
"Non-TOR only client for $key found.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_mapNet[key]! == TorPlainNetworkOption.tor) {
|
||||||
|
throw Exception(
|
||||||
|
"TOR only client for $key found.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _heights[key] ?? await _heightCompleters[key]!.future;
|
return _heights[key] ?? await _heightCompleters[key]!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumClient?> remove({
|
Future<(ElectrumClient?, TorPlainNetworkOption?)> remove({
|
||||||
required CryptoCurrency cryptoCurrency,
|
required CryptoCurrency cryptoCurrency,
|
||||||
}) async {
|
}) async {
|
||||||
final key = _keyHelper(cryptoCurrency);
|
final key = _keyHelper(cryptoCurrency);
|
||||||
|
@ -80,7 +116,7 @@ class ClientManager {
|
||||||
_heights.remove(key);
|
_heights.remove(key);
|
||||||
_heightCompleters.remove(key);
|
_heightCompleters.remove(key);
|
||||||
|
|
||||||
return _map.remove(key);
|
return (_map.remove(key), _mapNet.remove(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> closeAll() async {
|
Future<void> closeAll() async {
|
||||||
|
@ -99,6 +135,7 @@ class ClientManager {
|
||||||
_heightCompleters.clear();
|
_heightCompleters.clear();
|
||||||
_heights.clear();
|
_heights.clear();
|
||||||
_subscriptions.clear();
|
_subscriptions.clear();
|
||||||
|
_mapNet.clear();
|
||||||
_map.clear();
|
_map.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import '../utilities/amount/amount.dart';
|
||||||
import '../utilities/extensions/impl/string.dart';
|
import '../utilities/extensions/impl/string.dart';
|
||||||
import '../utilities/logger.dart';
|
import '../utilities/logger.dart';
|
||||||
import '../utilities/prefs.dart';
|
import '../utilities/prefs.dart';
|
||||||
|
import '../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||||
import 'client_manager.dart';
|
import 'client_manager.dart';
|
||||||
|
@ -42,6 +43,10 @@ typedef SparkMempoolData = ({
|
||||||
|
|
||||||
class WifiOnlyException implements Exception {}
|
class WifiOnlyException implements Exception {}
|
||||||
|
|
||||||
|
class TorOnlyException implements Exception {}
|
||||||
|
|
||||||
|
class ClearnetOnlyException implements Exception {}
|
||||||
|
|
||||||
class ElectrumXNode {
|
class ElectrumXNode {
|
||||||
ElectrumXNode({
|
ElectrumXNode({
|
||||||
required this.address,
|
required this.address,
|
||||||
|
@ -49,12 +54,16 @@ class ElectrumXNode {
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.useSSL,
|
required this.useSSL,
|
||||||
|
required this.torEnabled,
|
||||||
|
required this.clearEnabled,
|
||||||
});
|
});
|
||||||
final String address;
|
final String address;
|
||||||
final int port;
|
final int port;
|
||||||
final String name;
|
final String name;
|
||||||
final String id;
|
final String id;
|
||||||
final bool useSSL;
|
final bool useSSL;
|
||||||
|
final bool torEnabled;
|
||||||
|
final bool clearEnabled;
|
||||||
|
|
||||||
factory ElectrumXNode.from(ElectrumXNode node) {
|
factory ElectrumXNode.from(ElectrumXNode node) {
|
||||||
return ElectrumXNode(
|
return ElectrumXNode(
|
||||||
|
@ -63,6 +72,8 @@ class ElectrumXNode {
|
||||||
name: node.name,
|
name: node.name,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
useSSL: node.useSSL,
|
useSSL: node.useSSL,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.clearEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +85,7 @@ class ElectrumXNode {
|
||||||
|
|
||||||
class ElectrumXClient {
|
class ElectrumXClient {
|
||||||
final CryptoCurrency cryptoCurrency;
|
final CryptoCurrency cryptoCurrency;
|
||||||
|
final TorPlainNetworkOption netType;
|
||||||
|
|
||||||
String get host => _host;
|
String get host => _host;
|
||||||
late String _host;
|
late String _host;
|
||||||
|
@ -90,6 +102,7 @@ class ElectrumXClient {
|
||||||
ElectrumClient? getElectrumAdapter() =>
|
ElectrumClient? getElectrumAdapter() =>
|
||||||
ClientManager.sharedInstance.getClient(
|
ClientManager.sharedInstance.getClient(
|
||||||
cryptoCurrency: cryptoCurrency,
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
netType: netType,
|
||||||
);
|
);
|
||||||
|
|
||||||
late Prefs _prefs;
|
late Prefs _prefs;
|
||||||
|
@ -119,6 +132,7 @@ class ElectrumXClient {
|
||||||
required int port,
|
required int port,
|
||||||
required bool useSSL,
|
required bool useSSL,
|
||||||
required Prefs prefs,
|
required Prefs prefs,
|
||||||
|
required this.netType,
|
||||||
required List<ElectrumXNode> failovers,
|
required List<ElectrumXNode> failovers,
|
||||||
required this.cryptoCurrency,
|
required this.cryptoCurrency,
|
||||||
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
||||||
|
@ -168,6 +182,7 @@ class ElectrumXClient {
|
||||||
_electrumAdapterChannel = null;
|
_electrumAdapterChannel = null;
|
||||||
await (await ClientManager.sharedInstance
|
await (await ClientManager.sharedInstance
|
||||||
.remove(cryptoCurrency: cryptoCurrency))
|
.remove(cryptoCurrency: cryptoCurrency))
|
||||||
|
.$1
|
||||||
?.close();
|
?.close();
|
||||||
|
|
||||||
// Also close any chain height services that are currently open.
|
// Also close any chain height services that are currently open.
|
||||||
|
@ -193,6 +208,10 @@ class ElectrumXClient {
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
globalEventBusForTesting: globalEventBusForTesting,
|
globalEventBusForTesting: globalEventBusForTesting,
|
||||||
cryptoCurrency: cryptoCurrency,
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
netType: TorPlainNetworkOption.fromNodeData(
|
||||||
|
node.torEnabled,
|
||||||
|
node.clearEnabled,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +255,18 @@ class ElectrumXClient {
|
||||||
// Get the proxy info from the TorService.
|
// Get the proxy info from the TorService.
|
||||||
proxyInfo = _torService.getProxyInfo();
|
proxyInfo = _torService.getProxyInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (netType == TorPlainNetworkOption.clear) {
|
||||||
|
_electrumAdapterChannel = null;
|
||||||
|
await ClientManager.sharedInstance
|
||||||
|
.remove(cryptoCurrency: cryptoCurrency);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (netType == TorPlainNetworkOption.tor) {
|
||||||
|
_electrumAdapterChannel = null;
|
||||||
|
await ClientManager.sharedInstance
|
||||||
|
.remove(cryptoCurrency: cryptoCurrency);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current ElectrumAdapterClient is closed, create a new one.
|
// If the current ElectrumAdapterClient is closed, create a new one.
|
||||||
|
@ -288,9 +319,10 @@ class ElectrumXClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientManager.sharedInstance.addClient(
|
await ClientManager.sharedInstance.addClient(
|
||||||
newClient,
|
newClient,
|
||||||
cryptoCurrency: cryptoCurrency,
|
cryptoCurrency: cryptoCurrency,
|
||||||
|
netType: netType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,6 +384,10 @@ class ElectrumXClient {
|
||||||
return response;
|
return response;
|
||||||
} on WifiOnlyException {
|
} on WifiOnlyException {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
} on ClearnetOnlyException {
|
||||||
|
rethrow;
|
||||||
|
} on TorOnlyException {
|
||||||
|
rethrow;
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
// likely timed out so then retry
|
// likely timed out so then retry
|
||||||
if (retries > 0) {
|
if (retries > 0) {
|
||||||
|
@ -442,6 +478,10 @@ class ElectrumXClient {
|
||||||
return response;
|
return response;
|
||||||
} on WifiOnlyException {
|
} on WifiOnlyException {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
} on ClearnetOnlyException {
|
||||||
|
rethrow;
|
||||||
|
} on TorOnlyException {
|
||||||
|
rethrow;
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
// likely timed out so then retry
|
// likely timed out so then retry
|
||||||
if (retries > 0) {
|
if (retries > 0) {
|
||||||
|
@ -488,10 +528,10 @@ class ElectrumXClient {
|
||||||
return await request(
|
return await request(
|
||||||
requestID: requestID,
|
requestID: requestID,
|
||||||
command: 'server.ping',
|
command: 'server.ping',
|
||||||
requestTimeout: const Duration(seconds: 2),
|
requestTimeout: const Duration(seconds: 3),
|
||||||
retries: retryCount,
|
retries: retryCount,
|
||||||
).timeout(
|
).timeout(
|
||||||
const Duration(seconds: 2),
|
const Duration(seconds: 3),
|
||||||
onTimeout: () {
|
onTimeout: () {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
|
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
class NodeTorMismatchConfigException implements Exception {}
|
|
@ -18,14 +18,17 @@ import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../../models/node_model.dart';
|
import '../../../../models/node_model.dart';
|
||||||
import '../../../../notifications/show_flush_bar.dart';
|
import '../../../../notifications/show_flush_bar.dart';
|
||||||
|
import '../../../../providers/global/active_wallet_provider.dart';
|
||||||
import '../../../../providers/global/secure_store_provider.dart';
|
import '../../../../providers/global/secure_store_provider.dart';
|
||||||
import '../../../../providers/providers.dart';
|
import '../../../../providers/providers.dart';
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
import '../../../../utilities/assets.dart';
|
import '../../../../utilities/assets.dart';
|
||||||
import '../../../../utilities/constants.dart';
|
import '../../../../utilities/constants.dart';
|
||||||
|
import '../../../../utilities/enums/sync_type_enum.dart';
|
||||||
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
||||||
import '../../../../utilities/test_node_connection.dart';
|
import '../../../../utilities/test_node_connection.dart';
|
||||||
import '../../../../utilities/text_styles.dart';
|
import '../../../../utilities/text_styles.dart';
|
||||||
|
import '../../../../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../../../../utilities/util.dart';
|
import '../../../../utilities/util.dart';
|
||||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||||
|
@ -229,6 +232,11 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final torEnabled = formData.netOption == TorPlainNetworkOption.tor ||
|
||||||
|
formData.netOption == TorPlainNetworkOption.both;
|
||||||
|
final plainEnabled = formData.netOption == TorPlainNetworkOption.clear ||
|
||||||
|
formData.netOption == TorPlainNetworkOption.both;
|
||||||
|
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
case AddEditNodeViewType.add:
|
case AddEditNodeViewType.add:
|
||||||
final NodeModel node = NodeModel(
|
final NodeModel node = NodeModel(
|
||||||
|
@ -243,6 +251,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
isFailover: formData.isFailover!,
|
isFailover: formData.isFailover!,
|
||||||
trusted: formData.trusted!,
|
trusted: formData.trusted!,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: torEnabled,
|
||||||
|
plainEnabled: plainEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||||
|
@ -250,6 +260,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
formData.password,
|
formData.password,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
await _notifyWalletsOfUpdatedNode();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||||
|
@ -268,6 +279,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
isFailover: formData.isFailover!,
|
isFailover: formData.isFailover!,
|
||||||
trusted: formData.trusted!,
|
trusted: formData.trusted!,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: torEnabled,
|
||||||
|
plainEnabled: plainEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||||
|
@ -275,6 +288,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
formData.password,
|
formData.password,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
await _notifyWalletsOfUpdatedNode();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||||
|
@ -283,6 +297,39 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _notifyWalletsOfUpdatedNode() async {
|
||||||
|
final wallets =
|
||||||
|
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
|
||||||
|
final prefs = ref.read(prefsChangeNotifierProvider);
|
||||||
|
|
||||||
|
switch (prefs.syncType) {
|
||||||
|
case SyncingType.currentWalletOnly:
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
} else {
|
||||||
|
unawaited(wallet.updateNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.selectedWalletsAtStartup:
|
||||||
|
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
if (walletIdsToSync.contains(wallet.walletId)) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
} else {
|
||||||
|
unawaited(wallet.updateNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.allWalletsOnStartup:
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
isDesktop = Util.isDesktop;
|
isDesktop = Util.isDesktop;
|
||||||
|
@ -568,10 +615,11 @@ class NodeFormData {
|
||||||
String? name, host, login, password;
|
String? name, host, login, password;
|
||||||
int? port;
|
int? port;
|
||||||
bool? useSSL, isFailover, trusted;
|
bool? useSSL, isFailover, trusted;
|
||||||
|
TorPlainNetworkOption? netOption;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted }";
|
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted, netOption: $netOption }";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,6 +663,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
bool _trusted = false;
|
bool _trusted = false;
|
||||||
int? port;
|
int? port;
|
||||||
late bool enableSSLCheckbox;
|
late bool enableSSLCheckbox;
|
||||||
|
late TorPlainNetworkOption netOption;
|
||||||
|
|
||||||
late final bool enableAuthFields;
|
late final bool enableAuthFields;
|
||||||
|
|
||||||
|
@ -672,6 +721,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
ref.read(nodeFormDataProvider).useSSL = _useSSL;
|
ref.read(nodeFormDataProvider).useSSL = _useSSL;
|
||||||
ref.read(nodeFormDataProvider).isFailover = _isFailover;
|
ref.read(nodeFormDataProvider).isFailover = _isFailover;
|
||||||
ref.read(nodeFormDataProvider).trusted = _trusted;
|
ref.read(nodeFormDataProvider).trusted = _trusted;
|
||||||
|
ref.read(nodeFormDataProvider).netOption = netOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -704,6 +754,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
_useSSL = node.useSSL;
|
_useSSL = node.useSSL;
|
||||||
_isFailover = node.isFailover;
|
_isFailover = node.isFailover;
|
||||||
_trusted = node.trusted ?? false;
|
_trusted = node.trusted ?? false;
|
||||||
|
|
||||||
|
if (node.torEnabled && !node.plainEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.tor;
|
||||||
|
} else if (node.plainEnabled && !node.torEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.clear;
|
||||||
|
} else {
|
||||||
|
netOption = TorPlainNetworkOption.both;
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.coin is Epiccash) {
|
if (widget.coin is Epiccash) {
|
||||||
enableSSLCheckbox = !node.host.startsWith("http");
|
enableSSLCheckbox = !node.host.startsWith("http");
|
||||||
} else {
|
} else {
|
||||||
|
@ -716,6 +775,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
enableSSLCheckbox = true;
|
enableSSLCheckbox = true;
|
||||||
|
netOption = TorPlainNetworkOption.both;
|
||||||
// default to port 3413
|
// default to port 3413
|
||||||
// _portController.text = "3413";
|
// _portController.text = "3413";
|
||||||
}
|
}
|
||||||
|
@ -1168,7 +1228,139 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RadioTextButton(
|
||||||
|
label: "Only TOR traffic",
|
||||||
|
enabled: !widget.readOnly,
|
||||||
|
value: TorPlainNetworkOption.tor,
|
||||||
|
groupValue: netOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (!widget.readOnly) {
|
||||||
|
setState(
|
||||||
|
() => netOption = TorPlainNetworkOption.tor,
|
||||||
|
);
|
||||||
|
_updateState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RadioTextButton(
|
||||||
|
label: "Only non-TOR traffic",
|
||||||
|
enabled: !widget.readOnly,
|
||||||
|
value: TorPlainNetworkOption.clear,
|
||||||
|
groupValue: netOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (!widget.readOnly) {
|
||||||
|
setState(
|
||||||
|
() => netOption = TorPlainNetworkOption.clear,
|
||||||
|
);
|
||||||
|
_updateState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
RadioTextButton(
|
||||||
|
label: "Allow both",
|
||||||
|
enabled: !widget.readOnly,
|
||||||
|
value: TorPlainNetworkOption.both,
|
||||||
|
groupValue: netOption,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (!widget.readOnly) {
|
||||||
|
setState(
|
||||||
|
() => netOption = TorPlainNetworkOption.both,
|
||||||
|
);
|
||||||
|
_updateState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RadioTextButton<T> extends StatelessWidget {
|
||||||
|
const RadioTextButton({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.label,
|
||||||
|
required this.groupValue,
|
||||||
|
required this.onChanged,
|
||||||
|
this.enabled = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final T value;
|
||||||
|
final String label;
|
||||||
|
final T groupValue;
|
||||||
|
final bool enabled;
|
||||||
|
final void Function(T) onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ConditionalParent(
|
||||||
|
condition: Util.isDesktop,
|
||||||
|
builder: (child) => MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (value != groupValue) {
|
||||||
|
onChanged.call(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: Radio<T>(
|
||||||
|
activeColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.radioButtonIconEnabled,
|
||||||
|
value: value,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: !enabled
|
||||||
|
? null
|
||||||
|
: (_) {
|
||||||
|
if (value != groupValue) {
|
||||||
|
onChanged.call(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 14,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,15 +13,19 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../../../notifications/show_flush_bar.dart';
|
import '../../../../notifications/show_flush_bar.dart';
|
||||||
import 'add_edit_node_view.dart';
|
import '../../../../providers/global/active_wallet_provider.dart';
|
||||||
import '../../../../providers/global/secure_store_provider.dart';
|
import '../../../../providers/global/secure_store_provider.dart';
|
||||||
import '../../../../providers/providers.dart';
|
import '../../../../providers/providers.dart';
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
import '../../../../utilities/assets.dart';
|
import '../../../../utilities/assets.dart';
|
||||||
|
import '../../../../utilities/enums/sync_type_enum.dart';
|
||||||
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
||||||
import '../../../../utilities/test_node_connection.dart';
|
import '../../../../utilities/test_node_connection.dart';
|
||||||
import '../../../../utilities/text_styles.dart';
|
import '../../../../utilities/text_styles.dart';
|
||||||
|
import '../../../../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../../../../utilities/util.dart';
|
import '../../../../utilities/util.dart';
|
||||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import '../../../../widgets/background.dart';
|
import '../../../../widgets/background.dart';
|
||||||
|
@ -31,7 +35,7 @@ import '../../../../widgets/desktop/delete_button.dart';
|
||||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||||
import '../../../../widgets/desktop/primary_button.dart';
|
import '../../../../widgets/desktop/primary_button.dart';
|
||||||
import '../../../../widgets/desktop/secondary_button.dart';
|
import '../../../../widgets/desktop/secondary_button.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'add_edit_node_view.dart';
|
||||||
|
|
||||||
class NodeDetailsView extends ConsumerStatefulWidget {
|
class NodeDetailsView extends ConsumerStatefulWidget {
|
||||||
const NodeDetailsView({
|
const NodeDetailsView({
|
||||||
|
@ -59,6 +63,39 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
||||||
|
|
||||||
bool _desktopReadOnly = true;
|
bool _desktopReadOnly = true;
|
||||||
|
|
||||||
|
Future<void> _notifyWalletsOfUpdatedNode() async {
|
||||||
|
final wallets =
|
||||||
|
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
|
||||||
|
final prefs = ref.read(prefsChangeNotifierProvider);
|
||||||
|
|
||||||
|
switch (prefs.syncType) {
|
||||||
|
case SyncingType.currentWalletOnly:
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
} else {
|
||||||
|
unawaited(wallet.updateNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.selectedWalletsAtStartup:
|
||||||
|
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
if (walletIdsToSync.contains(wallet.walletId)) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
} else {
|
||||||
|
unawaited(wallet.updateNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.allWalletsOnStartup:
|
||||||
|
for (final wallet in wallets) {
|
||||||
|
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
secureStore = ref.read(secureStoreProvider);
|
secureStore = ref.read(secureStoreProvider);
|
||||||
|
@ -265,6 +302,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
||||||
.read(nodeServiceChangeNotifierProvider)
|
.read(nodeServiceChangeNotifierProvider)
|
||||||
.getNodeById(id: nodeId)!;
|
.getNodeById(id: nodeId)!;
|
||||||
|
|
||||||
|
final TorPlainNetworkOption netOption;
|
||||||
|
if (ref.read(nodeFormDataProvider).netOption != null) {
|
||||||
|
netOption = ref.read(nodeFormDataProvider).netOption!;
|
||||||
|
} else {
|
||||||
|
netOption = TorPlainNetworkOption.fromNodeData(
|
||||||
|
node.torEnabled,
|
||||||
|
node.plainEnabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final nodeFormData = NodeFormData()
|
final nodeFormData = NodeFormData()
|
||||||
..useSSL = node.useSSL
|
..useSSL = node.useSSL
|
||||||
..trusted = node.trusted
|
..trusted = node.trusted
|
||||||
|
@ -272,7 +319,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
||||||
..host = node.host
|
..host = node.host
|
||||||
..login = node.loginName
|
..login = node.loginName
|
||||||
..port = node.port
|
..port = node.port
|
||||||
..isFailover = node.isFailover;
|
..isFailover = node.isFailover
|
||||||
|
..netOption = netOption;
|
||||||
nodeFormData.password = await node.getPassword(
|
nodeFormData.password = await node.getPassword(
|
||||||
ref.read(secureStoreProvider),
|
ref.read(secureStoreProvider),
|
||||||
);
|
);
|
||||||
|
@ -338,6 +386,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
||||||
loginName: ref.read(nodeFormDataProvider).login,
|
loginName: ref.read(nodeFormDataProvider).login,
|
||||||
isFailover:
|
isFailover:
|
||||||
ref.read(nodeFormDataProvider).isFailover,
|
ref.read(nodeFormDataProvider).isFailover,
|
||||||
|
torEnabled:
|
||||||
|
ref.read(nodeFormDataProvider).netOption ==
|
||||||
|
TorPlainNetworkOption.tor ||
|
||||||
|
ref.read(nodeFormDataProvider).netOption ==
|
||||||
|
TorPlainNetworkOption.both,
|
||||||
|
plainEnabled:
|
||||||
|
ref.read(nodeFormDataProvider).netOption ==
|
||||||
|
TorPlainNetworkOption.clear ||
|
||||||
|
ref.read(nodeFormDataProvider).netOption ==
|
||||||
|
TorPlainNetworkOption.both,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ref
|
await ref
|
||||||
|
@ -347,6 +405,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
||||||
ref.read(nodeFormDataProvider).password,
|
ref.read(nodeFormDataProvider).password,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
await _notifyWalletsOfUpdatedNode();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,6 +59,8 @@ class NodeService extends ChangeNotifier {
|
||||||
enabled: savedNode.enabled,
|
enabled: savedNode.enabled,
|
||||||
isFailover: savedNode.isFailover,
|
isFailover: savedNode.isFailover,
|
||||||
trusted: savedNode.trusted,
|
trusted: savedNode.trusted,
|
||||||
|
torEnabled: savedNode.torEnabled,
|
||||||
|
plainEnabled: savedNode.plainEnabled,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,6 +77,8 @@ class NodeService extends ChangeNotifier {
|
||||||
enabled: primaryNode.enabled,
|
enabled: primaryNode.enabled,
|
||||||
isFailover: primaryNode.isFailover,
|
isFailover: primaryNode.isFailover,
|
||||||
trusted: primaryNode.trusted,
|
trusted: primaryNode.trusted,
|
||||||
|
torEnabled: primaryNode.torEnabled,
|
||||||
|
plainEnabled: primaryNode.plainEnabled,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,23 +11,23 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import '../app_config.dart';
|
import '../app_config.dart';
|
||||||
import '../db/hive/db.dart';
|
import '../db/hive/db.dart';
|
||||||
import '../electrumx_rpc/electrumx_client.dart';
|
import '../electrumx_rpc/electrumx_client.dart';
|
||||||
import '../exceptions/electrumx/no_such_transaction.dart';
|
import '../exceptions/electrumx/no_such_transaction.dart';
|
||||||
import '../models/exchange/response_objects/trade.dart';
|
import '../models/exchange/response_objects/trade.dart';
|
||||||
import '../models/notification_model.dart';
|
import '../models/notification_model.dart';
|
||||||
|
import '../utilities/logger.dart';
|
||||||
|
import '../utilities/prefs.dart';
|
||||||
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
import '../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
|
||||||
|
import 'exchange/exchange.dart';
|
||||||
import 'exchange/exchange_response.dart';
|
import 'exchange/exchange_response.dart';
|
||||||
import 'node_service.dart';
|
import 'node_service.dart';
|
||||||
import 'notifications_api.dart';
|
import 'notifications_api.dart';
|
||||||
import 'trade_service.dart';
|
import 'trade_service.dart';
|
||||||
import 'wallets.dart';
|
import 'wallets.dart';
|
||||||
import '../utilities/logger.dart';
|
|
||||||
import '../utilities/prefs.dart';
|
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
|
||||||
import '../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
|
|
||||||
|
|
||||||
import 'exchange/exchange.dart';
|
|
||||||
|
|
||||||
class NotificationsService extends ChangeNotifier {
|
class NotificationsService extends ChangeNotifier {
|
||||||
late NodeService nodeService;
|
late NodeService nodeService;
|
||||||
|
@ -136,12 +136,26 @@ class NotificationsService extends ChangeNotifier {
|
||||||
final node = nodeService.getPrimaryNodeFor(currency: coin);
|
final node = nodeService.getPrimaryNodeFor(currency: coin);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
if (wallet is ElectrumXInterface) {
|
if (wallet is ElectrumXInterface) {
|
||||||
|
if (prefs.useTor) {
|
||||||
|
if (node.plainEnabled && !node.torEnabled) {
|
||||||
|
// just ignore I guess??
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (node.torEnabled && !node.plainEnabled) {
|
||||||
|
// just ignore I guess??
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final eNode = ElectrumXNode(
|
final eNode = ElectrumXNode(
|
||||||
address: node.host,
|
address: node.host,
|
||||||
port: node.port,
|
port: node.port,
|
||||||
name: node.name,
|
name: node.name,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
useSSL: node.useSSL,
|
useSSL: node.useSSL,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.plainEnabled,
|
||||||
);
|
);
|
||||||
final failovers = nodeService
|
final failovers = nodeService
|
||||||
.failoverNodesFor(currency: coin)
|
.failoverNodesFor(currency: coin)
|
||||||
|
@ -152,6 +166,8 @@ class NotificationsService extends ChangeNotifier {
|
||||||
name: e.name,
|
name: e.name,
|
||||||
id: e.id,
|
id: e.id,
|
||||||
useSSL: e.useSSL,
|
useSSL: e.useSSL,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.plainEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -5,7 +5,6 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:on_chain/ada/ada.dart';
|
import 'package:on_chain/ada/ada.dart';
|
||||||
import 'package:on_chain/ada/src/provider/provider/provider.dart';
|
|
||||||
import 'package:socks5_proxy/socks.dart';
|
import 'package:socks5_proxy/socks.dart';
|
||||||
|
|
||||||
import '../networking/http.dart';
|
import '../networking/http.dart';
|
||||||
|
@ -25,6 +24,7 @@ import 'test_epic_box_connection.dart';
|
||||||
import 'test_eth_node_connection.dart';
|
import 'test_eth_node_connection.dart';
|
||||||
import 'test_monero_node_connection.dart';
|
import 'test_monero_node_connection.dart';
|
||||||
import 'test_stellar_node_connection.dart';
|
import 'test_stellar_node_connection.dart';
|
||||||
|
import 'tor_plain_net_option_enum.dart';
|
||||||
|
|
||||||
Future<bool> _xmrHelper(
|
Future<bool> _xmrHelper(
|
||||||
NodeFormData nodeFormData,
|
NodeFormData nodeFormData,
|
||||||
|
@ -45,7 +45,6 @@ Future<bool> _xmrHelper(
|
||||||
|
|
||||||
final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path";
|
final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path";
|
||||||
|
|
||||||
|
|
||||||
if (proxyInfo == null && uri.host.endsWith(".onion")) {
|
if (proxyInfo == null && uri.host.endsWith(".onion")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -93,6 +92,24 @@ Future<bool> testNodeConnection({
|
||||||
}) async {
|
}) async {
|
||||||
final formData = nodeFormData;
|
final formData = nodeFormData;
|
||||||
|
|
||||||
|
if (ref.read(prefsChangeNotifierProvider).useTor) {
|
||||||
|
if (formData.netOption! == TorPlainNetworkOption.clear) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"This node is configured for non-TOR only but TOR is enabled",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (formData.netOption! == TorPlainNetworkOption.tor) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"This node is configured for TOR only but TOR is disabled",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool testPassed = false;
|
bool testPassed = false;
|
||||||
|
|
||||||
switch (cryptoCurrency) {
|
switch (cryptoCurrency) {
|
||||||
|
@ -111,9 +128,7 @@ Future<bool> testNodeConnection({
|
||||||
|
|
||||||
case CryptonoteCurrency():
|
case CryptonoteCurrency():
|
||||||
try {
|
try {
|
||||||
final proxyInfo = ref
|
final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor
|
||||||
.read(prefsChangeNotifierProvider)
|
|
||||||
.useTor
|
|
||||||
? ref.read(pTorService).getProxyInfo()
|
? ref.read(pTorService).getProxyInfo()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@ -202,9 +217,7 @@ Future<bool> testNodeConnection({
|
||||||
"action": "version",
|
"action": "version",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
proxyInfo: ref
|
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
||||||
.read(prefsChangeNotifierProvider)
|
|
||||||
.useTor
|
|
||||||
? ref.read(pTorService).getProxyInfo()
|
? ref.read(pTorService).getProxyInfo()
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
@ -245,9 +258,7 @@ Future<bool> testNodeConnection({
|
||||||
case Cardano():
|
case Cardano():
|
||||||
try {
|
try {
|
||||||
final client = HttpClient();
|
final client = HttpClient();
|
||||||
if (ref
|
if (ref.read(prefsChangeNotifierProvider).useTor) {
|
||||||
.read(prefsChangeNotifierProvider)
|
|
||||||
.useTor) {
|
|
||||||
final proxyInfo = TorService.sharedInstance.getProxyInfo();
|
final proxyInfo = TorService.sharedInstance.getProxyInfo();
|
||||||
final proxySettings = ProxySettings(
|
final proxySettings = ProxySettings(
|
||||||
proxyInfo.host,
|
proxyInfo.host,
|
||||||
|
|
23
lib/utilities/tor_plain_net_option_enum.dart
Normal file
23
lib/utilities/tor_plain_net_option_enum.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
enum TorPlainNetworkOption {
|
||||||
|
tor,
|
||||||
|
clear,
|
||||||
|
both;
|
||||||
|
|
||||||
|
bool allowsTor() => this == tor || this == both;
|
||||||
|
bool allowsClear() => this == clear || this == both;
|
||||||
|
|
||||||
|
static TorPlainNetworkOption fromNodeData(
|
||||||
|
bool torEnabled,
|
||||||
|
bool clearEnabled,
|
||||||
|
) {
|
||||||
|
if (clearEnabled && torEnabled) {
|
||||||
|
return TorPlainNetworkOption.both;
|
||||||
|
} else if (torEnabled) {
|
||||||
|
return TorPlainNetworkOption.tor;
|
||||||
|
} else if (clearEnabled) {
|
||||||
|
return TorPlainNetworkOption.clear;
|
||||||
|
} else {
|
||||||
|
return TorPlainNetworkOption.both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1338,6 +1338,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
|
||||||
name: node.name,
|
name: node.name,
|
||||||
useSSL: node.useSSL,
|
useSSL: node.useSSL,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.plainEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1352,6 +1354,8 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T>
|
||||||
name: e.name,
|
name: e.name,
|
||||||
id: e.id,
|
id: e.id,
|
||||||
useSSL: e.useSSL,
|
useSSL: e.useSSL,
|
||||||
|
torEnabled: e.torEnabled,
|
||||||
|
clearEnabled: e.plainEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -467,7 +467,22 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
|
||||||
final host = Uri.parse(node.host).host;
|
final host = Uri.parse(node.host).host;
|
||||||
({InternetAddress host, int port})? proxy;
|
({InternetAddress host, int port})? proxy;
|
||||||
if (prefs.useTor) {
|
if (prefs.useTor) {
|
||||||
|
if (node.plainEnabled && !node.torEnabled) {
|
||||||
|
libMoneroWallet?.stopAutoSaving();
|
||||||
|
libMoneroWallet?.stopListeners();
|
||||||
|
libMoneroWallet?.stopSyncing();
|
||||||
|
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
|
||||||
|
throw Exception("TOR - clearnet mismatch");
|
||||||
|
}
|
||||||
proxy = TorService.sharedInstance.getProxyInfo();
|
proxy = TorService.sharedInstance.getProxyInfo();
|
||||||
|
} else {
|
||||||
|
if (!node.plainEnabled && node.torEnabled) {
|
||||||
|
libMoneroWallet?.stopAutoSaving();
|
||||||
|
libMoneroWallet?.stopListeners();
|
||||||
|
libMoneroWallet?.stopSyncing();
|
||||||
|
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
|
||||||
|
throw Exception("TOR - clearnet mismatch");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
|
_setSyncStatus(lib_monero_compat.ConnectingSyncStatus());
|
||||||
|
@ -495,6 +510,9 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
|
||||||
proxy == null ? null : "${proxy.host.address}:${proxy.port}",
|
proxy == null ? null : "${proxy.host.address}:${proxy.port}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
libMoneroWallet?.startSyncing();
|
||||||
|
libMoneroWallet?.startListeners();
|
||||||
|
libMoneroWallet?.startAutoSaving();
|
||||||
|
|
||||||
_setSyncStatus(lib_monero_compat.ConnectedSyncStatus());
|
_setSyncStatus(lib_monero_compat.ConnectedSyncStatus());
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -1020,6 +1038,26 @@ abstract class LibMoneroWallet<T extends CryptonoteCurrency>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final node = getCurrentNode();
|
||||||
|
|
||||||
|
if (prefs.useTor) {
|
||||||
|
if (node.plainEnabled && !node.torEnabled) {
|
||||||
|
libMoneroWallet?.stopAutoSaving();
|
||||||
|
libMoneroWallet?.stopListeners();
|
||||||
|
libMoneroWallet?.stopSyncing();
|
||||||
|
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
|
||||||
|
throw Exception("TOR - clearnet mismatch");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!node.plainEnabled && node.torEnabled) {
|
||||||
|
libMoneroWallet?.stopAutoSaving();
|
||||||
|
libMoneroWallet?.stopListeners();
|
||||||
|
libMoneroWallet?.stopSyncing();
|
||||||
|
_setSyncStatus(lib_monero_compat.FailedSyncStatus());
|
||||||
|
throw Exception("TOR - clearnet mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this acquire should be almost instant due to above check.
|
// this acquire should be almost instant due to above check.
|
||||||
// Slight possibility of race but should be irrelevant
|
// Slight possibility of race but should be irrelevant
|
||||||
await refreshMutex.acquire();
|
await refreshMutex.acquire();
|
||||||
|
|
|
@ -906,6 +906,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
name: node.name,
|
name: node.name,
|
||||||
useSSL: node.useSSL,
|
useSSL: node.useSSL,
|
||||||
id: node.id,
|
id: node.id,
|
||||||
|
torEnabled: node.torEnabled,
|
||||||
|
clearEnabled: node.plainEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,6 +921,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||||
name: e.name,
|
name: e.name,
|
||||||
id: e.id,
|
id: e.id,
|
||||||
useSSL: e.useSSL,
|
useSSL: e.useSSL,
|
||||||
|
torEnabled: e.torEnabled,
|
||||||
|
clearEnabled: e.plainEnabled,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -13,6 +13,8 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../notifications/show_flush_bar.dart';
|
import '../notifications/show_flush_bar.dart';
|
||||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
|
import '../pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
|
||||||
|
@ -26,6 +28,7 @@ import '../utilities/default_nodes.dart';
|
||||||
import '../utilities/enums/sync_type_enum.dart';
|
import '../utilities/enums/sync_type_enum.dart';
|
||||||
import '../utilities/test_node_connection.dart';
|
import '../utilities/test_node_connection.dart';
|
||||||
import '../utilities/text_styles.dart';
|
import '../utilities/text_styles.dart';
|
||||||
|
import '../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../utilities/util.dart';
|
import '../utilities/util.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import 'conditional_parent.dart';
|
import 'conditional_parent.dart';
|
||||||
|
@ -33,7 +36,6 @@ import 'custom_buttons/blue_text_button.dart';
|
||||||
import 'expandable.dart';
|
import 'expandable.dart';
|
||||||
import 'node_options_sheet.dart';
|
import 'node_options_sheet.dart';
|
||||||
import 'rounded_white_container.dart';
|
import 'rounded_white_container.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
class NodeCard extends ConsumerStatefulWidget {
|
class NodeCard extends ConsumerStatefulWidget {
|
||||||
const NodeCard({
|
const NodeCard({
|
||||||
|
@ -165,6 +167,15 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
text: "Connect",
|
text: "Connect",
|
||||||
enabled: _status == "Disconnected",
|
enabled: _status == "Disconnected",
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
final TorPlainNetworkOption netOption;
|
||||||
|
if (_node.torEnabled && !_node.plainEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.tor;
|
||||||
|
} else if (_node.plainEnabled && !_node.torEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.clear;
|
||||||
|
} else {
|
||||||
|
netOption = TorPlainNetworkOption.both;
|
||||||
|
}
|
||||||
|
|
||||||
final nodeFormData = NodeFormData()
|
final nodeFormData = NodeFormData()
|
||||||
..useSSL = _node.useSSL
|
..useSSL = _node.useSSL
|
||||||
..trusted = _node.trusted
|
..trusted = _node.trusted
|
||||||
|
@ -172,6 +183,7 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
..host = _node.host
|
..host = _node.host
|
||||||
..login = _node.loginName
|
..login = _node.loginName
|
||||||
..port = _node.port
|
..port = _node.port
|
||||||
|
..netOption = netOption
|
||||||
..isFailover = _node.isFailover;
|
..isFailover = _node.isFailover;
|
||||||
nodeFormData.password = await _node.getPassword(
|
nodeFormData.password = await _node.getPassword(
|
||||||
ref.read(secureStoreProvider),
|
ref.read(secureStoreProvider),
|
||||||
|
|
|
@ -27,6 +27,7 @@ import '../utilities/default_nodes.dart';
|
||||||
import '../utilities/enums/sync_type_enum.dart';
|
import '../utilities/enums/sync_type_enum.dart';
|
||||||
import '../utilities/test_node_connection.dart';
|
import '../utilities/test_node_connection.dart';
|
||||||
import '../utilities/text_styles.dart';
|
import '../utilities/text_styles.dart';
|
||||||
|
import '../utilities/tor_plain_net_option_enum.dart';
|
||||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||||
import 'rounded_white_container.dart';
|
import 'rounded_white_container.dart';
|
||||||
|
|
||||||
|
@ -256,6 +257,15 @@ class NodeOptionsSheet extends ConsumerWidget {
|
||||||
ref.read(secureStoreProvider),
|
ref.read(secureStoreProvider),
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
final TorPlainNetworkOption netOption;
|
||||||
|
if (node.torEnabled && !node.plainEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.tor;
|
||||||
|
} else if (node.plainEnabled &&
|
||||||
|
!node.torEnabled) {
|
||||||
|
netOption = TorPlainNetworkOption.clear;
|
||||||
|
} else {
|
||||||
|
netOption = TorPlainNetworkOption.both;
|
||||||
|
}
|
||||||
final canConnect = await testNodeConnection(
|
final canConnect = await testNodeConnection(
|
||||||
context: context,
|
context: context,
|
||||||
nodeFormData: NodeFormData()
|
nodeFormData: NodeFormData()
|
||||||
|
@ -266,6 +276,7 @@ class NodeOptionsSheet extends ConsumerWidget {
|
||||||
..port = node.port
|
..port = node.port
|
||||||
..useSSL = node.useSSL
|
..useSSL = node.useSSL
|
||||||
..isFailover = node.isFailover
|
..isFailover = node.isFailover
|
||||||
|
..netOption = netOption
|
||||||
..trusted = node.trusted,
|
..trusted = node.trusted,
|
||||||
cryptoCurrency: coin,
|
cryptoCurrency: coin,
|
||||||
ref: ref,
|
ref: ref,
|
||||||
|
|
|
@ -168,6 +168,8 @@ void main() {
|
||||||
name: "some name",
|
name: "some name",
|
||||||
id: "some ID",
|
id: "some ID",
|
||||||
useSSL: true,
|
useSSL: true,
|
||||||
|
torEnabled: true,
|
||||||
|
clearEnabled: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final client =
|
final client =
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hive_test/hive_test.dart';
|
import 'package:hive_test/hive_test.dart';
|
||||||
|
import 'package:stackwallet/app_config.dart';
|
||||||
import 'package:stackwallet/db/hive/db.dart';
|
import 'package:stackwallet/db/hive/db.dart';
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/services/node_service.dart';
|
import 'package:stackwallet/services/node_service.dart';
|
||||||
import 'package:stackwallet/app_config.dart';
|
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
|
||||||
|
@ -48,6 +48,8 @@ void main() {
|
||||||
coinName: "bitcoin",
|
coinName: "bitcoin",
|
||||||
isFailover: true,
|
isFailover: true,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
);
|
);
|
||||||
await service.setPrimaryNodeFor(
|
await service.setPrimaryNodeFor(
|
||||||
coin: Bitcoin(CryptoCurrencyNetwork.main),
|
coin: Bitcoin(CryptoCurrencyNetwork.main),
|
||||||
|
@ -129,6 +131,8 @@ void main() {
|
||||||
coinName: "bitcoin",
|
coinName: "bitcoin",
|
||||||
isFailover: true,
|
isFailover: true,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
);
|
);
|
||||||
final nodeB = NodeModel(
|
final nodeB = NodeModel(
|
||||||
host: "host2",
|
host: "host2",
|
||||||
|
@ -140,6 +144,8 @@ void main() {
|
||||||
coinName: "monero",
|
coinName: "monero",
|
||||||
isFailover: true,
|
isFailover: true,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
);
|
);
|
||||||
final nodeC = NodeModel(
|
final nodeC = NodeModel(
|
||||||
host: "host3",
|
host: "host3",
|
||||||
|
@ -151,6 +157,8 @@ void main() {
|
||||||
coinName: "epicCash",
|
coinName: "epicCash",
|
||||||
isFailover: true,
|
isFailover: true,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
|
|
|
@ -37,6 +37,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -51,6 +53,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -112,6 +116,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -126,6 +132,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -188,6 +196,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -202,6 +212,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ void main() {
|
||||||
final mockPrefs = MockPrefs();
|
final mockPrefs = MockPrefs();
|
||||||
final mockNodeService = MockNodeService();
|
final mockNodeService = MockNodeService();
|
||||||
|
|
||||||
when(mockNodeService.getNodeById(id: "node id")).thenAnswer(
|
when(mockNodeService.getNodeById(id: "node id"))
|
||||||
(realInvocation) => NodeModel(
|
.thenAnswer((realInvocation) => NodeModel(
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
port: 2000,
|
port: 2000,
|
||||||
name: "Some other name",
|
name: "Some other name",
|
||||||
|
@ -35,7 +35,10 @@ void main() {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false));
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
|
));
|
||||||
|
|
||||||
when(mockNodeService.getPrimaryNodeFor(
|
when(mockNodeService.getPrimaryNodeFor(
|
||||||
currency: Bitcoin(CryptoCurrencyNetwork.main)))
|
currency: Bitcoin(CryptoCurrencyNetwork.main)))
|
||||||
|
@ -48,6 +51,8 @@ void main() {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
isDown: false));
|
isDown: false));
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -109,6 +114,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -125,6 +132,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -186,6 +195,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -202,6 +213,8 @@ void main() {
|
||||||
coinName: "Bitcoin",
|
coinName: "Bitcoin",
|
||||||
isFailover: false,
|
isFailover: false,
|
||||||
isDown: false,
|
isDown: false,
|
||||||
|
torEnabled: true,
|
||||||
|
plainEnabled: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue