Skip to content

Commit

Permalink
Feature: Adaptive default token (#4)
Browse files Browse the repository at this point in the history
This branch introduces adaptive default token queried from the network, and thus specific to each network. Until now, default token was hardcoded, which was removed in this branch. It also prepares a model for the default bech32 prefix, which has analogous purpose and was initially intended to implement together. However, due to work organization purposes, the Adaptive Bech32 Address Prefix feature will be introduced separately.

List of changes:
- deleted hardcoded "defaultFeeTokenAliasModel" from app_config.dart in order to replace it with queried values
- created query_kira_token_aliases_req.dart to enable using query parameters introduced in interx v0.4.43 - pagination and querying by denomination name
- updated query_kira_token_aliases_resp.dart with new parameters "defaultDenom" and "bech32Prefix", added in interx v0.4.43
- created token_default_denom_model.dart which is a business model representing default token alias. It also contains default bech32 prefix, which will be used later in the analogous feature Adaptive Bech32 Address Prefix.
- created "getTokenDefaultDenomModel()" method in query_kira_token_aliases_service.dart, which is called with every network change and conditions its status - if TokenDefaultDenomModel is missing, network is unhealthy
- added "tokenDefaultDenomModel" field to classes extending ANetworkOnlineModel to enable passing the new parameters to the network
- replaced every usage of hardcoded default token with the queried values, accessed via NetworkModuleBloc with the variable "tokenDefaultDenomModel"
- unrelated with the branch's specific domain: modified token_avatar.dart to handle both .svg and .png logo images
  • Loading branch information
nemoforte authored Mar 6, 2024
1 parent 5fcb08a commit 59b0045
Show file tree
Hide file tree
Showing 33 changed files with 357 additions and 99 deletions.
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

0 comments on commit 59b0045

Please sign in to comment.