pretty big refactor for pow, still some bugs

This commit is contained in:
fosse 2023-08-16 17:09:29 -04:00
parent 33cb419fcc
commit 74f6f44a4b
21 changed files with 814 additions and 62 deletions

View file

@ -62,8 +62,6 @@ class Node extends HiveObject with Keyable {
@HiveField(6)
String? socksProxyAddress;
bool isPowNode = false;
bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;

218
cw_core/lib/pow_node.dart Normal file
View file

@ -0,0 +1,218 @@
import 'dart:io';
import 'package:cw_core/keyable.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;
part 'pow_node.g.dart';
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
@HiveType(typeId: PowNode.typeId)
class PowNode extends HiveObject with Keyable {
PowNode({
this.login,
this.password,
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
String? uri,
WalletType? type,
}) {
if (uri != null) {
uriRaw = uri;
}
if (type != null) {
this.type = type;
}
}
PowNode.fromMap(Map<String, Object?> map)
: uriRaw = map['uri'] as String? ?? '',
login = map['login'] as String?,
password = map['password'] as String?,
useSSL = map['useSSL'] as bool?,
trusted = map['trusted'] as bool? ?? false,
socksProxyAddress = map['socksProxyPort'] as String?;
static const typeId = NODE_TYPE_ID;
static const boxName = 'Nodes';
@HiveField(0, defaultValue: '')
late String uriRaw;
@HiveField(1)
String? login;
@HiveField(2)
String? password;
@HiveField(3, defaultValue: 0)
late int typeRaw;
@HiveField(4)
bool? useSSL;
@HiveField(5, defaultValue: false)
bool trusted;
@HiveField(6)
String? socksProxyAddress;
bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
Uri get uri {
switch (type) {
case WalletType.monero:
return Uri.http(uriRaw, '');
case WalletType.bitcoin:
return createUriFromElectrumAddress(uriRaw);
case WalletType.litecoin:
return createUriFromElectrumAddress(uriRaw);
case WalletType.haven:
return Uri.http(uriRaw, '');
case WalletType.ethereum:
return Uri.https(uriRaw, '');
case WalletType.nano:
case WalletType.banano:
if (uriRaw.contains("https") || uriRaw.endsWith("443") || isSSL) {
return Uri.https(uriRaw, '');
} else {
return Uri.http(uriRaw, '');
}
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
}
@override
bool operator ==(other) =>
other is PowNode &&
(other.uriRaw == uriRaw &&
other.login == login &&
other.password == password &&
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
@override
int get hashCode =>
uriRaw.hashCode ^
login.hashCode ^
password.hashCode ^
typeRaw.hashCode ^
useSSL.hashCode ^
trusted.hashCode ^
socksProxyAddress.hashCode;
@override
dynamic get keyIndex {
_keyIndex ??= key;
return _keyIndex;
}
WalletType get type => deserializeFromInt(typeRaw);
set type(WalletType type) => typeRaw = serializeToInt(type);
dynamic _keyIndex;
Future<bool> requestNode() async {
try {
switch (type) {
case WalletType.monero:
return useSocksProxy
? requestNodeWithProxy(socksProxyAddress ?? '')
: requestMoneroNode();
case WalletType.bitcoin:
return requestElectrumServer();
case WalletType.litecoin:
return requestElectrumServer();
case WalletType.haven:
return requestMoneroNode();
case WalletType.ethereum:
return requestElectrumServer();
default:
return false;
}
} catch (_) {
return false;
}
}
Future<bool> requestMoneroNode() async {
final path = '/json_rpc';
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
final realm = 'monero-rpc';
final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
try {
final authenticatingClient = HttpClient();
authenticatingClient.addCredentials(
rpcUri,
realm,
HttpClientDigestCredentials(login ?? '', password ?? ''),
);
final http.Client client = ioc.IOClient(authenticatingClient);
final response = await client.post(
rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
client.close();
final resBody = json.decode(response.body) as Map<String, dynamic>;
return !(resBody['result']['offline'] as bool);
} catch (_) {
return false;
}
}
Future<bool> requestNodeWithProxy(String proxy) async {
if (proxy.isEmpty || !proxy.contains(':')) {
return false;
}
final proxyAddress = proxy.split(':')[0];
final proxyPort = int.parse(proxy.split(':')[1]);
try {
final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5));
socket.destroy();
return true;
} catch (_) {
return false;
}
}
Future<bool> requestElectrumServer() async {
try {
await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
return true;
} catch (_) {
return false;
}
}
Future<bool> requestEthereumServer() async {
try {
final response = await http.get(
uri,
headers: {'Content-Type': 'application/json'},
);
return response.statusCode >= 200 && response.statusCode < 300;
} catch (_) {
return false;
}
}
}

View file

@ -1,3 +1,4 @@
import 'package:cw_core/pow_node.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart';
@ -53,7 +54,7 @@ abstract class WalletBase<
Future<void> connectToNode({required Node node});
// there is a default definition here because only coins with a pow node (nano based) need to override this
Future<void> connectToPowNode({required Node node}) async {}
Future<void> connectToPowNode({required PowNode node}) async {}
Future<void> startSync();

View file

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/pow_node.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.dart';
import 'package:cw_nano/nano_util.dart';
@ -20,7 +21,7 @@ class NanoClient {
StreamSubscription<Transfer>? subscription;
Node? _node;
Node? _powNode;
PowNode? _powNode;
bool connect(Node node) {
try {
@ -31,7 +32,7 @@ class NanoClient {
}
}
bool connectPow(Node node) {
bool connectPow(PowNode node) {
try {
_powNode = node;
return true;

View file

@ -6,6 +6,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/pow_node.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
@ -152,7 +153,7 @@ abstract class NanoWalletBase
}
@override
Future<void> connectToPowNode({required Node node}) async {
Future<void> connectToPowNode({required PowNode node}) async {
_client.connectPow(node);
}

View file

@ -25,6 +25,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
@ -91,6 +92,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/pow_node.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart';
@ -225,6 +227,7 @@ final getIt = GetIt.instance;
var _isSetupFinished = false;
late Box<WalletInfo> _walletInfoSource;
late Box<Node> _nodeSource;
late Box<PowNode> _powNodeSource;
late Box<Contact> _contactSource;
late Box<Trade> _tradesSource;
late Box<Template> _templates;
@ -237,6 +240,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future setup({
required Box<WalletInfo> walletInfoSource,
required Box<Node> nodeSource,
required Box<PowNode> powNodeSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Template> templates,
@ -248,6 +252,7 @@ Future setup({
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
@ -271,7 +276,7 @@ Future setup({
final settingsStore = await SettingsStoreBase.load(
nodeSource: _nodeSource,
powNodeSource: _nodeSource,
powNodeSource: _powNodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@ -284,6 +289,7 @@ Future setup({
}
getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerFactory<Box<PowNode>>(() => _powNodeSource);
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
@ -742,7 +748,7 @@ Future setup({
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return PowNodeListViewModel(_nodeSource, appStore);
return PowNodeListViewModel(_powNodeSource, appStore);
});
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
@ -764,7 +770,7 @@ Future setup({
getIt.registerFactoryParam<PowNodeCreateOrEditViewModel, WalletType?, void>(
(WalletType? type, _) => PowNodeCreateOrEditViewModel(
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
_powNodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
@ -772,6 +778,12 @@ Future setup({
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactoryParam<PowNodeCreateOrEditPage, PowNode?, bool?>(
(PowNode? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<PowNodeCreateOrEditViewModel>(),
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
@ -35,6 +36,7 @@ Future defaultSettingsMigration(
required SharedPreferences sharedPreferences,
required FlutterSecureStorage secureStorage,
required Box<Node> nodes,
required Box<PowNode> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Trade> tradeSource,
required Box<Contact> contactSource}) async {
@ -43,7 +45,7 @@ Future defaultSettingsMigration(
}
// check current nodes for nullability regardless of the version
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
final isNewInstall =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
@ -118,7 +120,7 @@ Future defaultSettingsMigration(
break;
case 12:
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 13:
@ -129,13 +131,13 @@ Future defaultSettingsMigration(
await addLitecoinElectrumServerList(nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 16:
await addHavenNodeList(nodes: nodes);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 17:
@ -160,7 +162,9 @@ Future defaultSettingsMigration(
case 22:
await addNanoNodeList(nodes: nodes);
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeNanoCurrentPowNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes);
await resetPowToDefault(powNodes);
break;
default:
@ -254,9 +258,10 @@ Node? getNanoDefaultNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
}
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano && node.isPowNode == true));
PowNode? getNanoDefaultPowNode({required Box<PowNode> nodes}) {
return nodes.values.firstWhereOrNull((PowNode node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values
.firstWhereOrNull((node) => (node.type == WalletType.nano));
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
@ -428,7 +433,7 @@ Future<void> changeDefaultMoneroNode(
}
}
Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
Future<void> checkCurrentNodes(Box<Node> nodeSource, Box<PowNode> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
@ -492,9 +497,8 @@ Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPre
}
if (currentNanoPowNodeServer == null) {
final node = Node(uri: nanoDefaultPowNodeUri, type: WalletType.nano);
node.isPowNode = true;
await nodeSource.add(node);
final node = PowNode(uri: nanoDefaultPowNodeUri, type: WalletType.nano);
await powNodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
}
}
@ -577,7 +581,7 @@ Future<void> changeNanoCurrentNodeToDefault(
}
Future<void> changeNanoCurrentPowNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<PowNode> nodes}) async {
final node = getNanoDefaultPowNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);

View file

@ -1,3 +1,4 @@
import 'package:cw_core/pow_node.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import "package:yaml/yaml.dart";
@ -97,23 +98,22 @@ Future<List<Node>> loadDefaultNanoNodes() async {
}
}
return nodes;
}
Future<List<PowNode>> loadDefaultNanoPowNodes() async {
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
print(powNodesRaw);
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
final nodes = <PowNode>[];
for (final raw in loadedPowNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
final node = PowNode.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.nano;
node.isPowNode = true;
nodes.add(node);
}
}
for (var node in nodes) {
print(node.uriRaw + " " + node.isPowNode.toString());
}
return nodes;
}
@ -135,3 +135,10 @@ Future resetToDefault(Box<Node> nodeSource) async {
await nodeSource.clear();
await nodeSource.addAll(nodes);
}
Future resetPowToDefault(Box<PowNode> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes;
await powNodeSource.clear();
await powNodeSource.addAll(nodes);
}

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -81,6 +82,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(NodeAdapter());
}
if (!CakeHive.isAdapterRegistered(PowNode.typeId)) {
CakeHive.registerAdapter(PowNodeAdapter());
}
if (!CakeHive.isAdapterRegistered(TransactionDescription.typeId)) {
CakeHive.registerAdapter(TransactionDescriptionAdapter());
}
@ -128,6 +133,7 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName);
final powNodes = await CakeHive.openBox<PowNode>(PowNode.boxName);
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
@ -146,6 +152,7 @@ Future<void> initializeAppConfigs() async {
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
@ -163,6 +170,7 @@ Future<void> initializeAppConfigs() async {
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
required Box<PowNode> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
@ -183,10 +191,12 @@ Future<void> initialSetup(
walletInfoSource: walletInfoSource,
contactSource: contactSource,
tradeSource: tradesSource,
nodes: nodes);
nodes: nodes,
powNodes: powNodes);
await setup(
walletInfoSource: walletInfoSource,
nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource,
tradesSource: tradesSource,
templates: templates,

View file

@ -14,6 +14,7 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
@ -56,6 +57,7 @@ import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@ -330,6 +332,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.newPowNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
param1: args?['editingNode'] as PowNode?, param2: args?['isSelected'] as bool?));
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(

View file

@ -20,6 +20,7 @@ class Routes {
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
static const newNode = '/new_node_list';
static const newPowNode = '/new_pow_node_list';
static const login = '/login';
static const splash = '/splash';
static const accountCreation = '/account_new';

View file

@ -1,10 +1,13 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';

View file

@ -0,0 +1,202 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
class PowNodeCreateOrEditPage extends BasePage {
PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
if (nodeCreateOrEditViewModel.hasAuthCredentials) {
reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
if (login != _loginController.text) {
_loginController.text = login;
}
});
reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
if (password != _passwordController.text) {
_passwordController.text = password;
}
});
}
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
() => nodeCreateOrEditViewModel.login = _loginController.text);
_passwordController.addListener(
() => nodeCreateOrEditViewModel.password = _passwordController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@override
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () async {
await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
),
);
final PowNodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final PowNode? editingNode;
final bool? isSelected;
@override
Widget body(BuildContext context) {
reaction((_) => nodeCreateOrEditViewModel.connectionState,
(ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) =>
AlertWithOneAction(
alertTitle: S.of(context).new_node_testing,
alertContent: state.payload as bool
? S.of(context).node_connection_successful
: S.of(context).node_connection_failed,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()));
});
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: PowNodeForm(
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).remove_node,
alertContent: S
.of(context)
.remove_node_message,
rightButtonText:
S.of(context).remove,
leftButtonText:
S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await editingNode!.delete();
Navigator.of(context).pop();
}
},
text: S.of(context).delete,
isDisabled: !nodeCreateOrEditViewModel.isReady ||
(isSelected ?? false),
color: Palette.red,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nodeCreateOrEditViewModel.save(
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
Navigator.of(context).pop();
},
text: S.of(context).save,
color: Theme.of(context)
.accentTextTheme!
.bodyLarge!
.color!,
textColor: Colors.white,
isDisabled: (!nodeCreateOrEditViewModel.isReady)||
(nodeCreateOrEditViewModel
.connectionState is IsExecutingState),
),
)),
],
)),
));
}
}

View file

@ -0,0 +1,201 @@
import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/core/node_port_validator.dart';
import 'package:cake_wallet/core/socks_proxy_node_address_validator.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class PowNodeForm extends StatelessWidget {
PowNodeForm({
required this.nodeViewModel,
required this.formKey,
this.editingNode,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
_loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password),
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
if (editingNode != null) {
nodeViewModel
..setAddress((editingNode!.uri.host.toString()))
..setPort((editingNode!.uri.port.toString()))
..setPassword((editingNode!.password ?? ''))
..setLogin((editingNode!.login ?? ''))
..setSSL((editingNode!.isSSL))
..setTrusted((editingNode!.trusted))
..setSocksProxy((editingNode!.useSocksProxy))
..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
}
if (nodeViewModel.hasAuthCredentials) {
reaction((_) => nodeViewModel.login, (String login) {
if (login != _loginController.text) {
_loginController.text = login;
}
});
reaction((_) => nodeViewModel.password, (String password) {
if (password != _passwordController.text) {
_passwordController.text = password;
}
});
}
reaction((_) => nodeViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
_portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
}
final PowNodeCreateOrEditViewModel nodeViewModel;
final GlobalKey<FormState> formKey;
final PowNode? editingNode;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
final TextEditingController _socksAddressController;
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: NodeAddressValidator(),
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _portController,
hintText: S.of(context).node_port,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
validator: NodePortValidator(),
))
],
),
SizedBox(height: 10.0),
if (nodeViewModel.hasAuthCredentials) ...[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _loginController,
hintText: S.of(context).login,
))
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _passwordController,
hintText: S.of(context).password,
))
],
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.trusted,
onChanged: (value) => nodeViewModel.trusted = value,
caption: S.of(context).trusted,
),
),
],
),
),
Observer(
builder: (_) => Column(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _socksAddressController,
hintText: '[<ip>:]<port>',
validator: SocksProxyNodeAddressValidator(),
))
],
),
]
],
)),
]
],
),
);
}
}

View file

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
class NodeIndicator extends StatelessWidget {
NodeIndicator({this.isLive = false});
final bool isLive;
@override
Widget build(BuildContext context) {
return Container(
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle, color: isLive ? Palette.green : Palette.red),
);
}
}

View file

@ -0,0 +1,62 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cw_core/pow_node.dart';
import 'package:flutter/material.dart';
class PowNodeListRow extends StandardListRow {
PowNodeListRow(
{required String title,
required this.node,
required void Function(BuildContext context) onTap,
required bool isSelected})
: super(title: title, onTap: onTap, isSelected: isSelected);
final PowNode node;
@override
Widget buildLeading(BuildContext context) {
return FutureBuilder(
future: node.requestNode(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return NodeIndicator(isLive: (snapshot.data as bool?) ?? false);
default:
return NodeIndicator(isLive: false);
}
});
}
@override
Widget buildTrailing(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.newPowNode,
arguments: {'editingNode': node, 'isSelected': isSelected}),
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.textTheme
.headlineMedium!
.decorationColor!),
child: Icon(Icons.edit,
size: 14,
color: Theme.of(context).textTheme.headlineMedium!.color!)));
}
}
class PowNodeHeaderListRow extends StandardListRow {
PowNodeHeaderListRow({required String title, required void Function(BuildContext context) onTap})
: super(title: title, onTap: onTap, isSelected: false);
@override
Widget buildTrailing(BuildContext context) {
return SizedBox(
width: 20,
child: Icon(Icons.add,
color: Theme.of(context).accentTextTheme!.titleMedium!.color,size: 24.0),
);
}
}

View file

@ -2,11 +2,13 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_list_row.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -26,9 +28,9 @@ class ManagePowNodesPage extends BasePage {
children: [
Semantics(
button: true,
child: NodeHeaderListRow(
child: PowNodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newPowNode),
),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
@ -45,12 +47,8 @@ class ManagePowNodesPage extends BasePage {
},
itemBuilder: (_, sectionIndex, index) {
final node = nodeListViewModel.nodes[index];
// technically not correct but the node doesn't
// have any potentially unique attributes (keyIndex -> hashCode)
// and it fixes the bug where the default (pow) node is not highlighted until
// after making a selection
final isSelected = node.hashCode == nodeListViewModel.currentNode.hashCode;
final nodeListRow = NodeListRow(
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = PowNodeListRow(
title: node.uriRaw,
node: node,
isSelected: isSelected,

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_core/pow_node.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/themes/theme_list.dart';
@ -60,6 +61,7 @@ abstract class SettingsStoreBase with Store {
required this.appVersion,
required this.deviceName,
required Map<WalletType, Node> nodes,
required Map<WalletType, PowNode> powNodes,
required this.shouldShowYatPopup,
required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode,
@ -82,7 +84,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, PowNode>.of(powNodes),
_sharedPreferences = sharedPreferences,
_backgroundTasks = backgroundTasks,
fiatCurrency = initialFiatCurrency,
@ -457,7 +459,7 @@ abstract class SettingsStoreBase with Store {
final BackgroundTasks _backgroundTasks;
ObservableMap<WalletType, Node> nodes;
ObservableMap<WalletType, Node> powNodes;
ObservableMap<WalletType, PowNode> powNodes;
Node getCurrentNode(WalletType walletType) {
final node = nodes[walletType];
@ -469,7 +471,7 @@ abstract class SettingsStoreBase with Store {
return node;
}
Node getCurrentPowNode(WalletType walletType) {
PowNode getCurrentPowNode(WalletType walletType) {
final node = powNodes[walletType];
if (node == null) {
@ -489,7 +491,7 @@ abstract class SettingsStoreBase with Store {
static Future<SettingsStore> load(
{required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<PowNode> powNodeSource,
required bool isBitcoinBuyEnabled,
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
@ -620,7 +622,7 @@ abstract class SettingsStoreBase with Store {
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final nodes = <WalletType, Node>{};
final powNodes = <WalletType, Node>{};
final powNodes = <WalletType, PowNode>{};
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
@ -648,6 +650,8 @@ abstract class SettingsStoreBase with Store {
if (nanoPowNode != null) {
powNodes[WalletType.nano] = nanoPowNode;
}
print(nanoPowNode);
print("@@@@@@@@@@@@");
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
@ -658,6 +662,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,

View file

@ -86,7 +86,7 @@ abstract class NodeListViewModelBase with Store {
nodes.clear();
_nodeSource.bindToList(
nodes,
filter: (val) => (val.type == _appStore.wallet!.type && val.isPowNode == false),
filter: (val) => val.type == _appStore.wallet!.type,
initialFire: true,
);
}

View file

@ -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/pow_node.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/node.dart';
@ -13,7 +14,8 @@ class PowNodeCreateOrEditViewModel = PowNodeCreateOrEditViewModelBase
with _$PowNodeCreateOrEditViewModel;
abstract class PowNodeCreateOrEditViewModelBase with Store {
PowNodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
PowNodeCreateOrEditViewModelBase(
this._nodeSource, this._walletType, this._settingsStore)
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
useSSL = false,
@ -72,7 +74,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
}
final WalletType _walletType;
final Box<Node> _nodeSource;
final Box<PowNode> _nodeSource;
final SettingsStore _settingsStore;
@action
@ -112,8 +114,8 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
void setSocksProxyAddress(String val) => socksProxyAddress = val;
@action
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node(
Future<void> save({PowNode? editingNode, bool saveAsCurrent = false}) async {
final node = PowNode(
uri: uri,
type: _walletType,
login: login,
@ -121,7 +123,6 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
useSSL: useSSL,
trusted: trusted,
socksProxyAddress: socksProxyAddress);
node.isPowNode = true;
try {
state = IsExecutingState();
if (editingNode != null) {
@ -144,7 +145,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
@action
Future<void> connect() async {
final node = Node(
final node = PowNode(
uri: uri,
type: _walletType,
login: login,
@ -161,7 +162,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
}
}
Node? _existingNode(Node node) {
PowNode? _existingNode(PowNode node) {
final nodes = _nodeSource.values.toList();
nodes.forEach((item) {
item.login ??= '';
@ -172,7 +173,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
}
@action
void setAsCurrent(Node node) => _settingsStore.powNodes[_walletType] = node;
void setAsCurrent(PowNode node) => _settingsStore.powNodes[_walletType] = node;
@action
Future<void> scanQRCodeForNewNode() async {
@ -190,7 +191,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
}
final userInfo = uri.userInfo.split(':');
if (userInfo.length < 2) {
throw Exception('Unexpected scan QR code value: Value is invalid');
}

View file

@ -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/pow_node.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
@ -16,7 +17,7 @@ class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewMode
abstract class PowNodeListViewModelBase with Store {
PowNodeListViewModelBase(this._nodeSource, this._appStore)
: nodes = ObservableList<Node>(),
: nodes = ObservableList<PowNode>(),
settingsStore = _appStore.settingsStore {
_bindNodes();
@ -26,7 +27,7 @@ abstract class PowNodeListViewModelBase with Store {
}
@computed
Node get currentNode {
PowNode get currentNode {
final node = settingsStore.powNodes[_appStore.wallet!.type];
if (node == null) {
@ -40,15 +41,15 @@ abstract class PowNodeListViewModelBase with Store {
S.current.change_current_node(uri) +
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}';
final ObservableList<Node> nodes;
final ObservableList<PowNode> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
final Box<PowNode> _nodeSource;
final AppStore _appStore;
Future<void> reset() async {
await resetToDefault(_nodeSource);
await resetPowToDefault(_nodeSource);
Node node;
PowNode node;
switch (_appStore.wallet!.type) {
case WalletType.nano:
@ -62,9 +63,9 @@ abstract class PowNodeListViewModelBase with Store {
}
@action
Future<void> delete(Node node) async => node.delete();
Future<void> delete(PowNode node) async => node.delete();
Future<void> setAsCurrent(Node node) async =>
Future<void> setAsCurrent(PowNode node) async =>
settingsStore.powNodes[_appStore.wallet!.type] = node;
@action
@ -74,7 +75,7 @@ abstract class PowNodeListViewModelBase with Store {
});
_nodeSource.bindToList(
nodes,
filter: (val) => (val.type == _appStore.wallet!.type && val.isPowNode == true),
filter: (val) => val.type == _appStore.wallet!.type,
initialFire: true,
);
}