Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Adaptive default token #4

Merged
merged 1 commit into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/blocs/generic/network_module/network_module_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:miro/shared/models/network/status/a_network_status_model.dart';
import 'package:miro/shared/models/network/status/network_empty_model.dart';
import 'package:miro/shared/models/network/status/network_unknown_model.dart';
import 'package:miro/shared/models/network/status/online/a_network_online_model.dart';
import 'package:miro/shared/models/tokens/token_default_denom_model.dart';
import 'package:miro/shared/utils/network_utils.dart';

class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
Expand All @@ -31,6 +32,7 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {

late Timer _timer;
bool _refreshingBool = false;
TokenDefaultDenomModel? tokenDefaultDenomModel;

NetworkModuleBloc() : super(NetworkModuleState.disconnected()) {
on<NetworkModuleInitEvent>(_mapInitEventToState);
Expand Down Expand Up @@ -79,8 +81,10 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
if (networkUnchangedBool) {
await _networkCustomSectionCubit.updateNetworks(networkStatusModel);
emit(NetworkModuleState.connected(networkStatusModel));
_refreshTokenDefaultDenomModel(networkStatusModel);
}
}

await _networkCustomSectionCubit.refreshNetworks();

_refreshingBool = false;
Expand All @@ -102,6 +106,7 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
_rpcBrowserUrlController.setRpcAddress(networkStatusModel);
await _networkCustomSectionCubit.updateNetworks(networkStatusModel);
emit(NetworkModuleState.connected(networkStatusModel));
_refreshTokenDefaultDenomModel(networkStatusModel);
}
}

Expand All @@ -110,6 +115,7 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
_rpcBrowserUrlController.setRpcAddress(networkOnlineModel);
await _networkCustomSectionCubit.updateNetworks(networkOnlineModel);
emit(NetworkModuleState.connected(networkOnlineModel));
_switchTokenDefaultDenomModel(networkOnlineModel);
}

Future<void> _mapDisconnectEventToState(
Expand All @@ -135,4 +141,16 @@ class NetworkModuleBloc extends Bloc<ANetworkModuleEvent, NetworkModuleState> {
ANetworkStatusModel networkStatusModel = await _networkModuleService.getNetworkStatusModel(networkUnknownModel);
_networkListCubit.setNetworkStatusModel(networkStatusModel: networkStatusModel);
}

void _refreshTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
if (networkStatusModel is ANetworkOnlineModel) {
tokenDefaultDenomModel ??= networkStatusModel.tokenDefaultDenomModel;
}
}

void _switchTokenDefaultDenomModel(ANetworkStatusModel networkStatusModel) {
if (networkStatusModel is ANetworkOnlineModel) {
tokenDefaultDenomModel = networkStatusModel.tokenDefaultDenomModel;
}
}
}
11 changes: 1 addition & 10 deletions lib/config/app_config.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import 'package:miro/shared/controllers/browser/rpc_browser_url_controller.dart';
import 'package:miro/shared/models/network/data/connection_status_type.dart';
import 'package:miro/shared/models/network/status/network_unknown_model.dart';
import 'package:miro/shared/models/tokens/token_alias_model.dart';
import 'package:miro/shared/models/tokens/token_denomination_model.dart';
import 'package:miro/shared/utils/logger/app_logger.dart';
import 'package:miro/shared/utils/logger/log_level.dart';
import 'package:miro/shared/utils/network_utils.dart';
Expand All @@ -14,7 +12,6 @@ class AppConfig {
final Duration loadingPageTimerDuration;
final List<String> supportedInterxVersions;
final RpcBrowserUrlController rpcBrowserUrlController;
final TokenAliasModel defaultFeeTokenAliasModel;

final int _defaultRefreshIntervalSeconds;
final NetworkUnknownModel _defaultNetworkUnknownModel;
Expand All @@ -30,7 +27,6 @@ class AppConfig {
required this.loadingPageTimerDuration,
required this.supportedInterxVersions,
required this.rpcBrowserUrlController,
required this.defaultFeeTokenAliasModel,
required int defaultRefreshIntervalSeconds,
required NetworkUnknownModel defaultNetworkUnknownModel,
}) : _defaultRefreshIntervalSeconds = defaultRefreshIntervalSeconds,
Expand All @@ -42,13 +38,8 @@ class AppConfig {
defaultApiCacheMaxAge: const Duration(seconds: 60),
outdatedBlockDuration: const Duration(minutes: 5),
loadingPageTimerDuration: const Duration(seconds: 4),
supportedInterxVersions: <String>['v0.4.41'],
supportedInterxVersions: <String>['v0.4.46'],
rpcBrowserUrlController: RpcBrowserUrlController(),
defaultFeeTokenAliasModel: const TokenAliasModel(
name: 'Kira',
defaultTokenDenominationModel: TokenDenominationModel(name: 'ukex', decimals: 0),
networkTokenDenominationModel: TokenDenominationModel(name: 'KEX', decimals: 6),
),
defaultRefreshIntervalSeconds: 60,
defaultNetworkUnknownModel: NetworkUnknownModel(
connectionStatusType: ConnectionStatusType.disconnected,
Expand Down
2 changes: 2 additions & 0 deletions lib/generated/intl/messages_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Enable custom address"),
"networkWarningIncompatible": MessageLookupByLibrary.simpleMessage(
"The application is incompatible with this server. Some views may not work correctly."),
"networkWarningMissingInfo": MessageLookupByLibrary.simpleMessage(
"Connecting a wallet unavailable due to missing essential data from network."),
"networkWarningWhenLastBlock": m5,
"or": MessageLookupByLibrary.simpleMessage("or "),
"paginatedListPageSize":
Expand Down
10 changes: 10 additions & 0 deletions lib/generated/l10n.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:equatable/equatable.dart';

class QueryKiraTokensAliasesReq extends Equatable {
final List<String>? tokens;
final int? limit;
final int? offset;

const QueryKiraTokensAliasesReq({
this.tokens,
this.limit,
this.offset,
});

Map<String, dynamic> get queryParameters => <String, dynamic>{
'tokens': tokens?.join(','),
'limit': limit,
'offset': offset,
};

@override
List<Object?> get props => <Object?>[tokens, limit, offset];
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import 'package:miro/infra/dto/api_kira/query_kira_tokens_aliases/response/token

class QueryKiraTokensAliasesResp extends Equatable {
final List<TokenAlias> tokenAliases;
final String defaultDenom;
final String bech32Prefix;

const QueryKiraTokensAliasesResp({
required this.tokenAliases,
required this.defaultDenom,
required this.bech32Prefix,
});

factory QueryKiraTokensAliasesResp.fromJsonList(List<dynamic> jsonList) {
factory QueryKiraTokensAliasesResp.fromJson(Map<String, dynamic> json) {
List<dynamic> jsonList = json['token_aliases_data'] as List<dynamic>;
return QueryKiraTokensAliasesResp(
tokenAliases: jsonList
.map(
(dynamic e) => TokenAlias.fromJson(e as Map<String, dynamic>),
)
.toList(),
tokenAliases: jsonList.map((dynamic e) => TokenAlias.fromJson(e as Map<String, dynamic>)).toList(),
defaultDenom: json['default_denom'] as String,
bech32Prefix: json['bech32_prefix'] as String,
);
}

@override
List<Object?> get props => <Object>[tokenAliases.hashCode];
List<Object?> get props => <Object>[tokenAliases.hashCode, defaultDenom, bech32Prefix];
}
6 changes: 4 additions & 2 deletions lib/infra/repositories/api/api_kira_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:miro/infra/dto/api_kira/query_delegations/request/query_delegati
import 'package:miro/infra/dto/api_kira/query_execution_fee/request/query_execution_fee_request.dart';
import 'package:miro/infra/dto/api_kira/query_identity_record_verify_requests/request/query_identity_record_verify_requests_by_approver_req.dart';
import 'package:miro/infra/dto/api_kira/query_identity_record_verify_requests/request/query_identity_record_verify_requests_by_requester_req.dart';
import 'package:miro/infra/dto/api_kira/query_kira_tokens_aliases/request/query_kira_tokens_aliases_req.dart';
import 'package:miro/infra/dto/api_kira/query_staking_pool/request/query_staking_pool_req.dart';
import 'package:miro/infra/dto/api_kira/query_undelegations/request/query_undelegations_req.dart';
import 'package:miro/infra/exceptions/dio_connect_exception.dart';
Expand Down Expand Up @@ -33,7 +34,7 @@ abstract class IApiKiraRepository {

Future<Response<T>> fetchQueryIdentityRecordVerifyRequestsByRequester<T>(ApiRequestModel<QueryIdentityRecordVerifyRequestsByRequesterReq> apiRequestModel);

Future<Response<T>> fetchQueryKiraTokensAliases<T>(ApiRequestModel<void> apiRequestModel);
Future<Response<T>> fetchQueryKiraTokensAliases<T>(ApiRequestModel<QueryKiraTokensAliasesReq> apiRequestModel);

Future<Response<T>> fetchQueryKiraTokensRates<T>(ApiRequestModel<void> apiRequestModel);

Expand Down Expand Up @@ -195,11 +196,12 @@ class RemoteApiKiraRepository implements IApiKiraRepository {
}

@override
Future<Response<T>> fetchQueryKiraTokensAliases<T>(ApiRequestModel<void> apiRequestModel) async {
Future<Response<T>> fetchQueryKiraTokensAliases<T>(ApiRequestModel<QueryKiraTokensAliasesReq> apiRequestModel) async {
try {
final Response<T> response = await _httpClientManager.get<T>(
networkUri: apiRequestModel.networkUri,
path: '/api/kira/tokens/aliases',
queryParameters: apiRequestModel.requestData.queryParameters,
apiCacheConfigModel: ApiCacheConfigModel(forceRequestBool: apiRequestModel.forceRequestBool),
);
return response;
Expand Down
5 changes: 1 addition & 4 deletions lib/infra/services/api_kira/query_execution_fee_service.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:decimal/decimal.dart';
import 'package:dio/dio.dart';
import 'package:miro/blocs/generic/network_module/network_module_bloc.dart';
import 'package:miro/config/app_config.dart';
import 'package:miro/config/locator.dart';
import 'package:miro/infra/dto/api_kira/query_execution_fee/request/query_execution_fee_request.dart';
import 'package:miro/infra/dto/api_kira/query_execution_fee/response/query_execution_fee_response.dart';
Expand All @@ -16,7 +15,6 @@ abstract class _IQueryExecutionFeeService {
}

class QueryExecutionFeeService implements _IQueryExecutionFeeService {
final AppConfig _appConfig = globalLocator<AppConfig>();
final IApiKiraRepository _apiKiraRepository = globalLocator<IApiKiraRepository>();
final QueryNetworkPropertiesService _queryNetworkPropertiesService = globalLocator<QueryNetworkPropertiesService>();

Expand All @@ -33,8 +31,7 @@ class QueryExecutionFeeService implements _IQueryExecutionFeeService {
QueryExecutionFeeResponse queryExecutionFeeResponse = QueryExecutionFeeResponse.fromJson(response.data as Map<String, dynamic>);
TokenAmountModel feeTokenAmountModel = TokenAmountModel(
defaultDenominationAmount: Decimal.parse(queryExecutionFeeResponse.fee.executionFee),
// tokenAliasModel - interx doesn't return denomination used in QueryExecutionFee endpoint, so we assumed that it's always represented in "ukex"
tokenAliasModel: _appConfig.defaultFeeTokenAliasModel,
tokenAliasModel: globalLocator<NetworkModuleBloc>().tokenDefaultDenomModel!.defaultTokenAliasModel,
);
return feeTokenAmountModel;
} catch (_) {
Expand Down
60 changes: 57 additions & 3 deletions lib/infra/services/api_kira/query_kira_tokens_aliases_service.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import 'package:dio/dio.dart';
import 'package:miro/blocs/generic/network_module/network_module_bloc.dart';
import 'package:miro/config/locator.dart';
import 'package:miro/infra/dto/api_kira/query_kira_tokens_aliases/request/query_kira_tokens_aliases_req.dart';
import 'package:miro/infra/dto/api_kira/query_kira_tokens_aliases/response/query_kira_tokens_aliases_resp.dart';
import 'package:miro/infra/exceptions/dio_parse_exception.dart';
import 'package:miro/infra/models/api_request_model.dart';
import 'package:miro/infra/repositories/api/api_kira_repository.dart';
import 'package:miro/shared/models/tokens/token_alias_model.dart';
import 'package:miro/shared/models/tokens/token_default_denom_model.dart';
import 'package:miro/shared/utils/logger/app_logger.dart';
import 'package:miro/shared/utils/logger/log_level.dart';

abstract class _IQueryKiraTokensAliasesService {
Future<List<TokenAliasModel>> getTokenAliasModels();

Future<TokenDefaultDenomModel> getTokenDefaultDenomModel(Uri networkUri);
}

class QueryKiraTokensAliasesService implements _IQueryKiraTokensAliasesService {
Expand All @@ -19,17 +23,67 @@ class QueryKiraTokensAliasesService implements _IQueryKiraTokensAliasesService {
@override
Future<List<TokenAliasModel>> getTokenAliasModels() async {
Uri networkUri = globalLocator<NetworkModuleBloc>().state.networkUri;
Response<dynamic> response = await _apiKiraRepository.fetchQueryKiraTokensAliases<dynamic>(ApiRequestModel<void>(
Response<dynamic> response = await _apiKiraRepository.fetchQueryKiraTokensAliases<dynamic>(ApiRequestModel<QueryKiraTokensAliasesReq>(
networkUri: networkUri,
requestData: null,
requestData: const QueryKiraTokensAliasesReq(),
));

try {
QueryKiraTokensAliasesResp queryKiraTokensAliasesResp = QueryKiraTokensAliasesResp.fromJsonList(response.data as List<dynamic>);
QueryKiraTokensAliasesResp queryKiraTokensAliasesResp = QueryKiraTokensAliasesResp.fromJson(response.data as Map<String, dynamic>);
return queryKiraTokensAliasesResp.tokenAliases.map(TokenAliasModel.fromDto).toList();
} catch (e) {
AppLogger().log(message: 'QueryKiraTokensAliasesService: Cannot parse getTokenAliasModels() for URI $networkUri ${e}', logLevel: LogLevel.error);
throw DioParseException(response: response, error: e);
}
}

@override
Future<TokenDefaultDenomModel> getTokenDefaultDenomModel(Uri networkUri, {bool forceRequestBool = false}) async {
TokenDefaultDenomModel initialTokenDefaultDenomModel = await _getTokenDefaultDenom(networkUri);
try {
TokenAliasModel defaultTokenAliasModel = await _getAliasByTokenName(
initialTokenDefaultDenomModel.defaultTokenAliasModel.name,
networkUri: networkUri,
forceRequestBool: forceRequestBool,
);
return TokenDefaultDenomModel(
bech32AddressPrefix: initialTokenDefaultDenomModel.bech32AddressPrefix,
defaultTokenAliasModel: defaultTokenAliasModel,
);
} catch (e) {
return initialTokenDefaultDenomModel;
}
}

Future<TokenAliasModel> _getAliasByTokenName(String tokenName, {Uri? networkUri, bool forceRequestBool = false}) async {
networkUri ??= globalLocator<NetworkModuleBloc>().state.networkUri;
Response<dynamic> response = await _apiKiraRepository.fetchQueryKiraTokensAliases<dynamic>(ApiRequestModel<QueryKiraTokensAliasesReq>(
networkUri: networkUri,
requestData: QueryKiraTokensAliasesReq(tokens: <String>[tokenName]),
forceRequestBool: forceRequestBool,
));

try {
QueryKiraTokensAliasesResp queryKiraTokensAliasesResp = QueryKiraTokensAliasesResp.fromJson(response.data as Map<String, dynamic>);
return TokenAliasModel.fromDto(queryKiraTokensAliasesResp.tokenAliases.first);
} catch (e) {
AppLogger().log(message: 'QueryKiraTokensAliasesService: Cannot parse getAliasByTokenName() for URI $networkUri ${e}', logLevel: LogLevel.error);
throw DioParseException(response: response, error: e);
}
}

Future<TokenDefaultDenomModel> _getTokenDefaultDenom(Uri networkUri) async {
Response<dynamic> response = await _apiKiraRepository.fetchQueryKiraTokensAliases<dynamic>(ApiRequestModel<QueryKiraTokensAliasesReq>(
networkUri: networkUri,
requestData: const QueryKiraTokensAliasesReq(offset: 0, limit: 0),
));

try {
QueryKiraTokensAliasesResp queryKiraTokensAliasesResp = QueryKiraTokensAliasesResp.fromJson(response.data as Map<String, dynamic>);
return TokenDefaultDenomModel.fromDto(queryKiraTokensAliasesResp);
} catch (e) {
AppLogger().log(message: 'QueryKiraTokensAliasesService: Cannot parse getTokenDefaultDenom() for URI $networkUri ${e}', logLevel: LogLevel.error);
throw DioParseException(response: response, error: e);
}
}
}
15 changes: 15 additions & 0 deletions lib/infra/services/network_module_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import 'package:miro/infra/dto/api/query_interx_status/query_interx_status_resp.
import 'package:miro/infra/dto/api/query_validators/response/status.dart';
import 'package:miro/infra/services/api/query_interx_status_service.dart';
import 'package:miro/infra/services/api/query_validators_service.dart';
import 'package:miro/infra/services/api_kira/query_kira_tokens_aliases_service.dart';
import 'package:miro/shared/models/network/data/connection_status_type.dart';
import 'package:miro/shared/models/network/data/network_info_model.dart';
import 'package:miro/shared/models/network/status/a_network_status_model.dart';
import 'package:miro/shared/models/network/status/network_offline_model.dart';
import 'package:miro/shared/models/network/status/network_unknown_model.dart';
import 'package:miro/shared/models/network/status/online/a_network_online_model.dart';
import 'package:miro/shared/models/tokens/token_default_denom_model.dart';
import 'package:miro/shared/utils/logger/app_logger.dart';

abstract class _INetworkModuleService {
Expand All @@ -17,14 +19,17 @@ abstract class _INetworkModuleService {

class NetworkModuleService implements _INetworkModuleService {
final QueryInterxStatusService _queryInterxStatusService = globalLocator<QueryInterxStatusService>();
final QueryKiraTokensAliasesService _queryKiraTokensAliasesService = globalLocator<QueryKiraTokensAliasesService>();
final QueryValidatorsService _queryValidatorsService = globalLocator<QueryValidatorsService>();

@override
Future<ANetworkStatusModel> getNetworkStatusModel(NetworkUnknownModel networkUnknownModel, {NetworkUnknownModel? previousNetworkUnknownModel}) async {
try {
NetworkInfoModel networkInfoModel = await _getNetworkInfoModel(networkUnknownModel);
TokenDefaultDenomModel? tokenDefaultDenomModel = await _getTokenDefaultDenomModel(networkUnknownModel);
return ANetworkOnlineModel.build(
networkInfoModel: networkInfoModel,
tokenDefaultDenomModel: tokenDefaultDenomModel,
connectionStatusType: ConnectionStatusType.disconnected,
uri: networkUnknownModel.uri,
name: networkUnknownModel.name,
Expand All @@ -45,6 +50,16 @@ class NetworkModuleService implements _INetworkModuleService {
}
}

Future<TokenDefaultDenomModel?> _getTokenDefaultDenomModel(NetworkUnknownModel networkUnknownModel) async {
TokenDefaultDenomModel? tokenDefaultDenomModel;
try {
tokenDefaultDenomModel = await _queryKiraTokensAliasesService.getTokenDefaultDenomModel(networkUnknownModel.uri, forceRequestBool: true);
} catch (e) {
AppLogger().log(message: 'NetworkModuleService: Cannot fetch getTokenDefaultDenomModel() for URI ${networkUnknownModel.uri} $e');
}
return tokenDefaultDenomModel;
}

Future<NetworkInfoModel> _getNetworkInfoModel(NetworkUnknownModel networkUnknownModel) async {
Status? status;

Expand Down
1 change: 1 addition & 0 deletions lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
"networkErrorAddressEmpty": "Field can't be empty",
"networkErrorAddressInvalid": "Invalid network address",
"networkHintCustomAddress": "Custom address",
"networkWarningMissingInfo": "Connecting a wallet unavailable due to missing essential data from network.",
"networkWarningIncompatible": "The application is incompatible with this server. Some views may not work correctly.",
"networkWarningWhenLastBlock": "The last available block on this interx was created long time ago {latestBlockTime}. The displayed contents may be out of date.",
"@networkWarningWhenLastBlock": {
Expand Down
Loading