diff --git a/lib/models/epicbox_config_model.dart b/lib/models/epicbox_config_model.dart new file mode 100644 index 000000000..b7605d806 --- /dev/null +++ b/lib/models/epicbox_config_model.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; + +import 'package:hive/hive.dart'; +import 'package:stackwallet/models/epicbox_server_model.dart'; + +part 'type_adaptors/epicbox_config_model.g.dart'; + +// @HiveType(typeId: 72) +class EpicBoxConfigModel { + // @HiveField(1) + final String host; + // @HiveField(2) + final int? port; + // @HiveField(3) + final bool? protocolInsecure; + // @HiveField(4) + final int? addressIndex; + // // @HiveField(5) + // final String? id; + // // @HiveField(6) + // final String? name; + + EpicBoxConfigModel({ + required this.host, + this.port, + this.protocolInsecure, + this.addressIndex, + // this.id, + // this.name, + }); + + EpicBoxConfigModel copyWith({ + int? port, + bool? protocolInsecure, + int? addressIndex, + // String? id, + // String? name, + }) { + return EpicBoxConfigModel( + host: host, + port: this.port ?? 443, + protocolInsecure: this.protocolInsecure ?? false, + addressIndex: this.addressIndex ?? 0, + // id: id ?? this.id, + // name: name ?? this.name, + ); + } + + Map toMap() { + Map map = {}; + map['epicbox_domain'] = host; + map['epicbox_port'] = port; + map['epicbox_protocol_insecure'] = protocolInsecure; + map['epicbox_address_index'] = addressIndex; + // map['id'] = id; + // map['name'] = name; + return map; + } + + Map toJson() { + return { + 'epicbox_domain': host, + 'epicbox_port': port, + 'epicbox_protocol_insecure': protocolInsecure, + 'epicbox_address_index': addressIndex, + // 'id': id, + // 'name': name, + }; + } + + @override + String toString() { + return json.encode(toJson()); + } + + static EpicBoxConfigModel fromString(String epicBoxConfigString) { + dynamic _epicBox = json.decode(epicBoxConfigString); + + // handle old epicbox config formats + final oldDomain = _epicBox["domain"] ?? "empty"; + if (oldDomain != "empty") { + _epicBox['epicbox_domain'] = _epicBox['domain']; + } + final oldPort = _epicBox["port"] ?? "empty"; + if (oldPort != "empty") { + _epicBox['epicbox_port'] = _epicBox['port']; + } + final oldProtocolInsecure = _epicBox["protocol_insecur"] ?? "empty"; + if (oldProtocolInsecure != "empty") { + _epicBox['epicbox_protocol_insecure'] = _epicBox['protocol_insecur']; + } + final oldAddressIndex = _epicBox["address_index"] ?? "empty"; + if (oldAddressIndex != "empty") { + _epicBox['epicbox_address_index'] = _epicBox['address_index']; + } + + return EpicBoxConfigModel( + host: _epicBox['epicbox_domain'] as String, + port: _epicBox['epicbox_port'] as int, + protocolInsecure: _epicBox['epicbox_protocol_insecure'] as bool, + addressIndex: _epicBox['epicbox_address_index'] as int, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } + + static EpicBoxConfigModel fromServer(EpicBoxServerModel server, + {bool? protocolInsecure, int? addressIndex}) { + return EpicBoxConfigModel( + host: server.host, + port: server.port ?? 443, + protocolInsecure: protocolInsecure ?? false, + addressIndex: addressIndex ?? 0, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } +} diff --git a/lib/models/epicbox_server_model.dart b/lib/models/epicbox_server_model.dart new file mode 100644 index 000000000..8bb431348 --- /dev/null +++ b/lib/models/epicbox_server_model.dart @@ -0,0 +1,83 @@ +import 'package:hive/hive.dart'; + +part 'type_adaptors/epicbox_server_model.g.dart'; + +// @HiveType(typeId: 71) +class EpicBoxServerModel { + // @HiveField(0) + final String id; + // @HiveField(1) + final String host; + // @HiveField(2) + final int? port; + // @HiveField(3) + final String name; + // @HiveField(4) + final bool? useSSL; + // @HiveField(5) + final bool? enabled; + // @HiveField(6) + final bool? isFailover; + // @HiveField(7) + final bool? isDown; + + EpicBoxServerModel({ + required this.id, + required this.host, + this.port, + required this.name, + this.useSSL, + this.enabled, + this.isFailover, + this.isDown, + }); + + EpicBoxServerModel copyWith({ + String? host, + int? port, + String? name, + bool? useSSL, + bool? enabled, + bool? isFailover, + bool? isDown, + }) { + return EpicBoxServerModel( + id: id, + host: host ?? this.host, + port: port ?? this.port, + name: name ?? this.name, + useSSL: useSSL ?? this.useSSL, + enabled: enabled ?? this.enabled, + isFailover: isFailover ?? this.isFailover, + isDown: isDown ?? this.isDown, + ); + } + + Map toMap() { + Map map = {}; + map['id'] = id; + map['host'] = host; + map['port'] = port; + map['name'] = name; + map['useSSL'] = useSSL; + map['enabled'] = enabled; + map['isFailover'] = isFailover; + map['isDown'] = isDown; + return map; + } + + bool get isDefault => id.startsWith("default_"); + + Map toJson() { + return { + 'id': id, + 'host': host, + 'port': port, + 'name': name, + 'useSSL': useSSL, + 'enabled': enabled, + 'isFailover': isFailover, + 'isDown': isDown, + }; + } +} diff --git a/lib/models/type_adaptors/epicbox_config_model.g.dart b/lib/models/type_adaptors/epicbox_config_model.g.dart new file mode 100644 index 000000000..70d066370 --- /dev/null +++ b/lib/models/type_adaptors/epicbox_config_model.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../epicbox_config_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class EpicBoxConfigModelAdapter extends TypeAdapter { + @override + final int typeId = 72; + + @override + EpicBoxConfigModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return EpicBoxConfigModel( + host: fields[1] as String, + port: fields[2] as int, + protocolInsecure: fields[3] as bool, + addressIndex: fields[4] as int, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } + + @override + void write(BinaryWriter writer, EpicBoxConfigModel obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.host) + ..writeByte(1) + ..write(obj.port) + ..writeByte(2) + ..write(obj.protocolInsecure) + ..writeByte(3) + ..write(obj.addressIndex); + // ..writeByte(4) + // ..write(obj.id) + // ..writeByte(5) + // ..write(obj.name) + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is EpicBoxConfigModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/epicbox_server_model.g.dart b/lib/models/type_adaptors/epicbox_server_model.g.dart new file mode 100644 index 000000000..cc741bf83 --- /dev/null +++ b/lib/models/type_adaptors/epicbox_server_model.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../epicbox_server_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class EpicBoxServerModelAdapter extends TypeAdapter { + @override + final int typeId = 71; + + @override + EpicBoxServerModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return EpicBoxServerModel( + host: fields[1] as String, + port: fields[2] as int, + name: fields[3] as String, + id: fields[0] as String, + useSSL: fields[4] as bool, + enabled: fields[5] as bool, + isFailover: fields[6] as bool, + isDown: fields[7] as bool, + ); + } + + @override + void write(BinaryWriter writer, EpicBoxServerModel obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.name) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.enabled) + ..writeByte(6) + ..write(obj.isFailover) + ..writeByte(7) + ..write(obj.isDown); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is EpicBoxServerModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index e96258f3f..5d78e5de6 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:convert'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/epicbox_config_model.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; @@ -356,10 +356,9 @@ class _EpiBoxInfoFormState extends ConsumerState { .getManager(widget.walletId) .wallet as EpicCashWallet; - wallet.getEpicBoxConfig().then((value) { - final config = jsonDecode(value); - hostController.text = config["domain"] as String; - portController.text = (config["port"] as int).toString(); + wallet.getEpicBoxConfig().then((EpicBoxConfigModel epicBoxConfig) { + hostController.text = epicBoxConfig.host; + portController.text = "${epicBoxConfig.port ?? 443}"; }); super.initState(); } diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index ce83fe2ab..b938c596f 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -11,6 +11,7 @@ import 'package:mutex/mutex.dart'; import 'package:stack_wallet_backup/generate_password.dart'; import 'package:stackwallet/db/main_db.dart'; import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/epicbox_config_model.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; @@ -27,6 +28,7 @@ import 'package:stackwallet/services/mixins/wallet_cache.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_epicboxes.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; @@ -456,7 +458,7 @@ class EpicCashWallet extends CoinServiceAPI Future confirmSend({required Map txData}) async { try { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // TODO determine whether it is worth sending change to a change address. dynamic message; @@ -465,10 +467,8 @@ class EpicCashWallet extends CoinServiceAPI if (!receiverAddress.startsWith("http://") || !receiverAddress.startsWith("https://")) { - final decoded = json.decode(epicboxConfig); bool isEpicboxConnected = await testEpicboxServer( - decoded["epicbox_domain"] as String, - decoded["epicbox_port"] as int); + epicboxConfig.host, epicboxConfig.port ?? 443); if (!isEpicboxConnected) { throw Exception("Failed to send TX : Unable to reach epicbox server"); } @@ -505,7 +505,7 @@ class EpicCashWallet extends CoinServiceAPI "amount": txData['recipientAmt'], "address": txData['addresss'], "secretKeyIndex": 0, - "epicboxConfig": epicboxConfig!, + "epicboxConfig": epicboxConfig, "minimumConfirmations": MINIMUM_CONFIRMATIONS, }, name: walletName); @@ -567,13 +567,13 @@ class EpicCashWallet extends CoinServiceAPI if (address == null) { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); String? walletAddress; await m.protect(() async { walletAddress = await compute( _initGetAddressInfoWrapper, - Tuple3(wallet!, index, epicboxConfig!), + Tuple3(wallet!, index, epicboxConfig.toString()), ); }); Logging.instance @@ -727,12 +727,13 @@ class EpicCashWallet extends CoinServiceAPI int index = 0; Logging.instance.log("This index is $index", level: LogLevel.Info); - final epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + String? walletAddress; await m.protect(() async { walletAddress = await compute( _initGetAddressInfoWrapper, - Tuple3(wallet!, index, epicboxConfig!), + Tuple3(wallet!, index, epicboxConfig.toString()), ); }); Logging.instance @@ -770,14 +771,14 @@ class EpicCashWallet extends CoinServiceAPI final String password = generatePassword(); String stringConfig = await getConfig(); - String epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonicString); await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_password', value: password); await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig); + key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); String name = _walletId; @@ -993,6 +994,7 @@ class EpicCashWallet extends CoinServiceAPI } Future testEpicboxServer(String host, int port) async { + // TODO use an EpicBoxServerModel as the only param final websocketConnectionUri = 'wss://$host:$port'; const connectionOptions = SocketConnectionOptions( pingIntervalMs: 3000, @@ -1035,36 +1037,49 @@ class EpicCashWallet extends CoinServiceAPI return isConnected; } - Future getEpicBoxConfig() async { + Future getEpicBoxConfig() async { + EpicBoxConfigModel? _epicBoxConfig; + // read epicbox config from secure store String? storedConfig = await _secureStore.read(key: '${_walletId}_epicboxConfig'); + // we should move to storing the primary server model like we do with nodes, and build the config from that (see epic-mobile) + // EpicBoxServerModel? _epicBox = epicBox ?? + // DB.instance.get( + // boxName: DB.boxNamePrimaryEpicBox, key: 'primary'); + // Logging.instance.log( + // "Read primary Epic Box config: ${jsonEncode(_epicBox)}", + // level: LogLevel.Info); + if (storedConfig == null) { - storedConfig = json.encode(DefaultNodes.defaultEpicBoxConfig); + // if no config stored, use the default epicbox server as config + _epicBoxConfig = + EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer); } else { - dynamic decoded = json.decode(storedConfig!); - if (decoded is String) { // we should make a model instead - decoded = json.decode(storedConfig!); - } - final domain = decoded["domain"] ?? "empty"; - if (domain != "empty") { - //If we have the old invalid config, use the new default one - // new storage format stores domain under "epicbox_domain", old storage format used "domain" - storedConfig = json.encode(DefaultNodes.defaultEpicBoxConfig); - } + // if a config is stored, test it + + _epicBoxConfig = EpicBoxConfigModel.fromString( + storedConfig); // fromString handles checking old config formats } - final decoded = json.decode(storedConfig); - //Check Epicbox is up before returning it + bool isEpicboxConnected = await testEpicboxServer( - decoded["epicbox_domain"] as String, decoded["epicbox_port"] as int); + _epicBoxConfig.host, _epicBoxConfig.port ?? 443); if (!isEpicboxConnected) { - //Default Epicbox is not connected, Defaulting to Europe - storedConfig = json.encode(DefaultNodes.epicBoxConfigEUR); - // TODO test this connection before returning it, iterating through the list of default Epic Box servers + // default Epicbox is not connected, default to Europe + _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + + // example of selecting another random server from the default list + // alternative servers: copy list of all default EB servers but remove the default default + // List alternativeServers = DefaultEpicBoxes.all; + // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // alternativeServers.shuffle(); // randomize which server is used + // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + + // TODO test this connection before returning it } - return storedConfig; + return _epicBoxConfig; } Future getRealConfig() async { @@ -1174,14 +1189,14 @@ class EpicCashWallet extends CoinServiceAPI final String password = generatePassword(); String stringConfig = await getConfig(); - String epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); final String name = _walletName.trim(); await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_password', value: password); await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig); + key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); await compute( _recoverWrapper, @@ -1382,14 +1397,14 @@ class EpicCashWallet extends CoinServiceAPI Future listenForSlates() async { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); await m.protect(() async { Logging.instance.log("CALLING LISTEN FOR SLATES", level: LogLevel.Info); ReceivePort receivePort = await getIsolate({ "function": "listenForSlates", "wallet": wallet, - "epicboxConfig": epicboxConfig, + "epicboxConfig": epicboxConfig.toString(), }, name: walletName); var result = await receivePort.first; diff --git a/lib/utilities/default_epicboxes.dart b/lib/utilities/default_epicboxes.dart new file mode 100644 index 000000000..ecbd4524a --- /dev/null +++ b/lib/utilities/default_epicboxes.dart @@ -0,0 +1,43 @@ +import 'package:stackwallet/models/epicbox_server_model.dart'; + +abstract class DefaultEpicBoxes { + static const String defaultName = "Default"; + + static List get all => [americas, asia, europe]; + static List get defaultIds => ['americas', 'asia', 'europe']; + + static EpicBoxServerModel get americas => EpicBoxServerModel( + host: 'epicbox.epic.tech', + port: 443, + name: 'Americas', + id: 'americas', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static EpicBoxServerModel get asia => EpicBoxServerModel( + host: 'epicbox.hyperbig.com', + port: 443, + name: 'Asia', + id: 'asia', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static EpicBoxServerModel get europe => EpicBoxServerModel( + host: 'epicbox.fastepic.eu', + port: 443, + name: 'Europe', + id: 'europe', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static final defaultEpicBoxServer = americas; +} diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 3348089a3..fc25fee24 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -255,18 +253,4 @@ abstract class DefaultNodes { return dogecoinTestnet; } } - - static final String defaultEpicBoxConfig = jsonEncode({ - "epicbox_domain": "epicbox.epic.tech", - "epicbox_port": 443, - "epicbox_protocol_unsecure": false, - "epicbox_address_index": 0, - }); - - static final String epicBoxConfigEUR = jsonEncode({ - "epicbox_domain": "epicbox.fastepic.eu", - "epicbox_port": 443, - "epicbox_protocol_unsecure": false, - "epicbox_address_index": 0, - }); }