stack_wallet/lib/services/exchange/change_now/change_now_api.dart

1016 lines
30 KiB
Dart
Raw Normal View History

2022-08-26 08:11:35 +00:00
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
2023-02-04 17:15:42 +00:00
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
2023-02-21 19:29:32 +00:00
import 'package:stackwallet/exceptions/exchange/unsupported_currency_exception.dart';
2022-08-26 08:11:35 +00:00
import 'package:stackwallet/external_api_keys.dart';
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
2022-08-26 08:11:35 +00:00
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
2022-10-03 16:30:50 +00:00
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
2022-10-02 21:48:43 +00:00
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
2022-10-02 20:53:53 +00:00
import 'package:stackwallet/services/exchange/exchange_response.dart';
2022-08-26 08:11:35 +00:00
import 'package:stackwallet/utilities/logger.dart';
import 'package:tuple/tuple.dart';
2022-08-26 08:11:35 +00:00
2022-10-02 20:53:53 +00:00
class ChangeNowAPI {
2022-08-26 08:11:35 +00:00
static const String scheme = "https";
static const String authority = "api.changenow.io";
static const String apiVersion = "/v1";
static const String apiVersionV2 = "/v2";
2022-08-26 08:11:35 +00:00
2022-10-02 20:53:53 +00:00
ChangeNowAPI._();
static final ChangeNowAPI _instance = ChangeNowAPI._();
static ChangeNowAPI get instance => _instance;
/// set this to override using standard http client. Useful for testing
http.Client? client;
Uri _buildUri(String path, Map<String, dynamic>? params) {
2022-08-26 08:11:35 +00:00
return Uri.https(authority, apiVersion + path, params);
}
Uri _buildUriV2(String path, Map<String, dynamic>? params) {
return Uri.https(authority, apiVersionV2 + path, params);
}
Future<dynamic> _makeGetRequest(Uri uri) async {
final client = this.client ?? http.Client();
2022-08-26 08:11:35 +00:00
try {
final response = await client.get(
uri,
headers: {'Content-Type': 'application/json'},
);
2023-02-21 19:29:32 +00:00
try {
final parsed = jsonDecode(response.body);
2022-08-26 08:11:35 +00:00
2023-02-21 19:29:32 +00:00
return parsed;
} on FormatException catch (e) {
return {
"error": "Dart format exception",
"message": response.body,
};
}
2022-08-26 08:11:35 +00:00
} catch (e, s) {
Logging.instance
.log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error);
rethrow;
}
}
Future<dynamic> _makeGetRequestV2(Uri uri, String apiKey) async {
final client = this.client ?? http.Client();
try {
final response = await client.get(
uri,
headers: {
// 'Content-Type': 'application/json',
'x-changenow-api-key': apiKey,
},
);
final parsed = jsonDecode(response.body);
return parsed;
} catch (e, s) {
Logging.instance
.log("_makeRequestV2($uri) threw: $e\n$s", level: LogLevel.Error);
rethrow;
}
}
Future<dynamic> _makePostRequest(
2022-08-26 08:11:35 +00:00
Uri uri,
Map<String, String> body,
) async {
final client = this.client ?? http.Client();
2022-08-26 08:11:35 +00:00
try {
final response = await client.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
);
try {
final parsed = jsonDecode(response.body);
2022-08-26 08:11:35 +00:00
return parsed;
} catch (_) {
Logging.instance.log("ChangeNOW api failed to parse: ${response.body}",
level: LogLevel.Error);
rethrow;
}
2022-08-26 08:11:35 +00:00
} catch (e, s) {
Logging.instance
.log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error);
2022-08-26 08:11:35 +00:00
rethrow;
}
}
/// This API endpoint returns the list of available currencies.
///
/// Set [active] to true to return only active currencies.
/// Set [fixedRate] to true to return only currencies available on a fixed-rate flow.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<List<Currency>>> getAvailableCurrencies({
2022-08-26 08:11:35 +00:00
bool? fixedRate,
bool? active,
}) async {
Map<String, dynamic>? params;
if (active != null || fixedRate != null) {
params = {};
if (fixedRate != null) {
params.addAll({"fixedRate": fixedRate.toString()});
}
if (active != null) {
params.addAll({"active": active.toString()});
}
}
final uri = _buildUri("/currencies", params);
try {
// json array is expected here
final jsonArray = await _makeGetRequest(uri);
try {
final result = await compute(
_parseAvailableCurrenciesJson,
Tuple2(jsonArray as List<dynamic>, fixedRate == true),
);
2022-08-26 08:11:35 +00:00
return result;
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Error: $jsonArray",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-02 20:53:53 +00:00
ExchangeResponse<List<Currency>> _parseAvailableCurrenciesJson(
Tuple2<List<dynamic>, bool> args,
) {
2022-08-26 08:11:35 +00:00
try {
List<Currency> currencies = [];
for (final json in args.item1) {
2022-08-26 08:11:35 +00:00
try {
final map = Map<String, dynamic>.from(json as Map);
currencies.add(
Currency.fromJson(
map,
rateType: (map["supportsFixedRate"] as bool)
? SupportedRateType.both
: SupportedRateType.estimated,
exchangeName: ChangeNowExchange.exchangeName,
),
);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException("Failed to serialize $json",
ExchangeExceptionType.serializeResponseError));
2022-08-26 08:11:35 +00:00
}
}
return ExchangeResponse(value: currencies);
} catch (_) {
rethrow;
}
}
Future<ExchangeResponse<List<Currency>>> getCurrenciesV2(
// {
// bool? fixedRate,
// bool? active,
// }
) async {
Map<String, dynamic>? params;
// if (active != null || fixedRate != null) {
// params = {};
// if (fixedRate != null) {
// params.addAll({"fixedRate": fixedRate.toString()});
// }
// if (active != null) {
// params.addAll({"active": active.toString()});
// }
// }
final uri = _buildUriV2("/exchange/currencies", params);
try {
// json array is expected here
final jsonArray = await _makeGetRequest(uri);
try {
final result = await compute(
_parseV2CurrenciesJson,
jsonArray as List<dynamic>,
);
return result;
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
"Error: $jsonArray",
ExchangeExceptionType.serializeResponseError,
),
);
}
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
ExchangeResponse<List<Currency>> _parseV2CurrenciesJson(
List<dynamic> args,
) {
try {
List<Currency> currencies = [];
for (final json in args) {
try {
final map = Map<String, dynamic>.from(json as Map);
currencies.add(
Currency.fromJson(
map,
rateType: (map["supportsFixedRate"] as bool)
? SupportedRateType.both
: SupportedRateType.estimated,
exchangeName: ChangeNowExchange.exchangeName,
),
);
} catch (_) {
return ExchangeResponse(
exception: ExchangeException("Failed to serialize $json",
ExchangeExceptionType.serializeResponseError));
}
}
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: currencies);
2022-08-26 08:11:35 +00:00
} catch (_) {
rethrow;
}
}
/// This API endpoint returns the array of markets available for the specified currency be default.
/// The availability of a particular pair is determined by the 'isAvailable' field.
///
/// Required [ticker] to fetch paired currencies for.
/// Set [fixedRate] to true to return only currencies available on a fixed-rate flow.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<List<Currency>>> getPairedCurrencies({
2022-08-26 08:11:35 +00:00
required String ticker,
bool? fixedRate,
}) async {
Map<String, dynamic>? params;
if (fixedRate != null) {
params = {};
params.addAll({"fixedRate": fixedRate.toString()});
}
final uri = _buildUri("/currencies-to/$ticker", params);
try {
// json array is expected here
2023-02-21 19:29:32 +00:00
final response = await _makeGetRequest(uri);
if (response is Map && response["error"] != null) {
return ExchangeResponse(
exception: UnsupportedCurrencyException(
response["message"] as String? ?? response["error"].toString(),
ExchangeExceptionType.generic,
ticker,
),
);
}
final jsonArray = response as List;
2022-08-26 08:11:35 +00:00
List<Currency> currencies = [];
try {
for (final json in jsonArray) {
try {
final map = Map<String, dynamic>.from(json as Map);
currencies.add(
Currency.fromJson(
map,
rateType: (map["supportsFixedRate"] as bool)
? SupportedRateType.both
: SupportedRateType.estimated,
exchangeName: ChangeNowExchange.exchangeName,
),
);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
}
} catch (e, s) {
Logging.instance.log("getPairedCurrencies exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException("Error: $jsonArray",
ExchangeExceptionType.serializeResponseError));
2022-08-26 08:11:35 +00:00
}
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: currencies);
2022-08-26 08:11:35 +00:00
} catch (e, s) {
Logging.instance
.log("getPairedCurrencies exception: $e\n$s", level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
/// The API endpoint returns minimal payment amount required to make
/// an exchange of [fromTicker] to [toTicker].
/// If you try to exchange less, the transaction will most likely fail.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<Decimal>> getMinimalExchangeAmount({
2022-08-26 08:11:35 +00:00
required String fromTicker,
required String toTicker,
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
2022-08-29 21:20:27 +00:00
Map<String, dynamic>? params = {"api_key": apiKey ?? kChangeNowApiKey};
2022-08-26 08:11:35 +00:00
final uri = _buildUri("/min-amount/${fromTicker}_$toTicker", params);
try {
// simple json object is expected here
final json = await _makeGetRequest(uri);
try {
final value = Decimal.parse(json["minAmount"].toString());
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: value);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getMinimalExchangeAmount exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-02 21:48:43 +00:00
/// The API endpoint returns minimal payment amount and maximum payment amount
/// required to make an exchange. If you try to exchange less than minimum or
/// more than maximum, the transaction will most likely fail. Any pair of
/// assets has minimum amount and some of pairs have maximum amount.
Future<ExchangeResponse<Range>> getRange({
required String fromTicker,
required String toTicker,
required bool isFixedRate,
String? apiKey,
}) async {
Map<String, dynamic>? params = {"api_key": apiKey ?? kChangeNowApiKey};
final uri = _buildUri(
"/exchange-range${isFixedRate ? "/fixed-rate" : ""}/${fromTicker}_$toTicker",
params);
try {
final jsonObject = await _makeGetRequest(uri);
final json = Map<String, dynamic>.from(jsonObject as Map);
return ExchangeResponse(
value: Range(
2022-10-03 23:04:59 +00:00
max: Decimal.tryParse(json["maxAmount"]?.toString() ?? ""),
min: Decimal.tryParse(json["minAmount"]?.toString() ?? ""),
2022-10-02 21:48:43 +00:00
),
);
} catch (e, s) {
Logging.instance.log(
"getRange exception: $e\n$s",
level: LogLevel.Error,
);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
2022-08-26 08:11:35 +00:00
/// Get estimated amount of [toTicker] cryptocurrency to receive
/// for [fromAmount] of [fromTicker]
2022-10-03 16:30:50 +00:00
Future<ExchangeResponse<Estimate>> getEstimatedExchangeAmount({
2022-08-26 08:11:35 +00:00
required String fromTicker,
required String toTicker,
required Decimal fromAmount,
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
2022-08-29 21:20:27 +00:00
Map<String, dynamic> params = {"api_key": apiKey ?? kChangeNowApiKey};
2022-08-26 08:11:35 +00:00
final uri = _buildUri(
"/exchange-amount/${fromAmount.toString()}/${fromTicker}_$toTicker",
params,
);
try {
// simple json object is expected here
2022-10-02 21:48:43 +00:00
final json = await _makeGetRequest(uri);
try {
final map = Map<String, dynamic>.from(json as Map);
if (map["error"] != null) {
if (map["error"] == "pair_is_inactive") {
return ExchangeResponse(
exception: PairUnavailableException(
map["message"] as String,
ExchangeExceptionType.generic,
),
);
} else {
return ExchangeResponse(
exception: ExchangeException(
map["message"] as String,
ExchangeExceptionType.generic,
),
);
}
}
final value = EstimatedExchangeAmount.fromJson(map);
2022-10-03 16:30:50 +00:00
return ExchangeResponse(
value: Estimate(
estimatedAmount: value.estimatedAmount,
fixedRate: false,
reversed: false,
rateId: value.rateId,
warningMessage: value.warningMessage,
exchangeProvider: ChangeNowExchange.exchangeName,
2022-10-03 16:30:50 +00:00
),
);
2022-10-02 21:48:43 +00:00
} catch (_) {
return ExchangeResponse(
exception: ExchangeException(
"Failed to serialize $json",
ExchangeExceptionType.serializeResponseError,
),
);
}
} catch (e, s) {
Logging.instance.log("getEstimatedExchangeAmount exception: $e\n$s",
level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
/// Get estimated amount of [toTicker] cryptocurrency to receive
/// for [fromAmount] of [fromTicker]
2022-10-03 16:30:50 +00:00
Future<ExchangeResponse<Estimate>> getEstimatedExchangeAmountFixedRate({
2022-10-02 21:48:43 +00:00
required String fromTicker,
required String toTicker,
required Decimal fromAmount,
required bool reversed,
2022-10-03 23:04:59 +00:00
bool useRateId = true,
2022-10-02 21:48:43 +00:00
String? apiKey,
}) async {
2022-10-03 23:04:59 +00:00
Map<String, dynamic> params = {
"api_key": apiKey ?? kChangeNowApiKey,
"useRateId": useRateId.toString(),
};
2022-10-02 21:48:43 +00:00
late final Uri uri;
if (reversed) {
uri = _buildUri(
"/exchange-deposit/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker",
params,
);
} else {
uri = _buildUri(
"/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker",
params,
);
}
try {
// simple json object is expected here
2022-08-26 08:11:35 +00:00
final json = await _makeGetRequest(uri);
try {
2023-02-08 19:04:55 +00:00
final map = Map<String, dynamic>.from(json as Map);
if (map["error"] != null) {
if (map["error"] == "not_valid_fixed_rate_pair") {
return ExchangeResponse(
exception: PairUnavailableException(
map["message"] as String? ?? "Unsupported fixed rate pair",
ExchangeExceptionType.generic,
),
);
} else {
return ExchangeResponse(
exception: ExchangeException(
map["message"] as String? ?? map["error"].toString(),
ExchangeExceptionType.generic,
),
);
}
}
final value = EstimatedExchangeAmount.fromJson(map);
2022-10-03 16:30:50 +00:00
return ExchangeResponse(
value: Estimate(
estimatedAmount: value.estimatedAmount,
fixedRate: true,
reversed: reversed,
rateId: value.rateId,
warningMessage: value.warningMessage,
exchangeProvider: ChangeNowExchange.exchangeName,
2022-10-03 16:30:50 +00:00
),
);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getEstimatedExchangeAmount exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
// old v1 version
2022-08-26 08:11:35 +00:00
/// This API endpoint returns fixed-rate estimated exchange amount of
/// [toTicker] cryptocurrency to receive for [fromAmount] of [fromTicker]
2022-10-02 20:53:53 +00:00
// Future<ExchangeResponse<EstimatedExchangeAmount>>
// getEstimatedFixedRateExchangeAmount({
// required String fromTicker,
// required String toTicker,
// required Decimal fromAmount,
// // (Optional) Use rateId for fixed-rate flow. If this field is true, you
// // could use returned field "rateId" in next method for creating transaction
// // to freeze estimated amount that you got in this method. Current estimated
// // amount would be valid until time in field "validUntil"
// bool useRateId = true,
// String? apiKey,
// }) async {
// Map<String, dynamic> params = {
// "api_key": apiKey ?? kChangeNowApiKey,
// "useRateId": useRateId.toString(),
// };
//
// final uri = _buildUri(
// "/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker",
// params,
// );
//
// try {
// // simple json object is expected here
// final json = await _makeGetRequest(uri);
//
// try {
// final value = EstimatedExchangeAmount.fromJson(
// Map<String, dynamic>.from(json as Map));
2022-10-02 20:53:53 +00:00
// return ExchangeResponse(value: value);
// } catch (_) {
2022-10-02 20:53:53 +00:00
// return ExchangeResponse(
// exception: ExchangeException(
// "Failed to serialize $json",
2022-10-02 20:53:53 +00:00
// ExchangeExceptionType.serializeResponseError,
// ),
// );
// }
// } catch (e, s) {
// Logging.instance.log(
// "getEstimatedFixedRateExchangeAmount exception: $e\n$s",
// level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
// return ExchangeResponse(
// exception: ExchangeException(
// e.toString(),
2022-10-02 20:53:53 +00:00
// ExchangeExceptionType.generic,
// ),
// );
// }
// }
/// Get estimated amount of [toTicker] cryptocurrency to receive
/// for [fromAmount] of [fromTicker]
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<CNExchangeEstimate>> getEstimatedExchangeAmountV2({
2022-08-26 08:11:35 +00:00
required String fromTicker,
required String toTicker,
required CNEstimateType fromOrTo,
required Decimal amount,
String? fromNetwork,
String? toNetwork,
CNFlowType flow = CNFlowType.standard,
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
Map<String, dynamic>? params = {
"fromCurrency": fromTicker,
"toCurrency": toTicker,
"flow": flow.value,
"type": fromOrTo.name,
2022-08-26 08:11:35 +00:00
};
switch (fromOrTo) {
case CNEstimateType.direct:
params["fromAmount"] = amount.toString();
break;
case CNEstimateType.reverse:
params["toAmount"] = amount.toString();
break;
}
if (fromNetwork != null) {
params["fromNetwork"] = fromNetwork;
}
if (toNetwork != null) {
params["toNetwork"] = toNetwork;
}
if (flow == CNFlowType.fixedRate) {
params["useRateId"] = "true";
}
final uri = _buildUriV2("/exchange/estimated-amount", params);
2022-08-26 08:11:35 +00:00
try {
// simple json object is expected here
final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey);
2022-08-26 08:11:35 +00:00
try {
final value =
CNExchangeEstimate.fromJson(Map<String, dynamic>.from(json as Map));
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: value);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getEstimatedExchangeAmountV2 exception: $e\n$s",
2022-08-26 08:11:35 +00:00
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
/// This API endpoint returns the list of all the pairs available on a
/// fixed-rate flow. Some currencies get enabled or disabled from time to
/// time and the market info gets updates, so make sure to refresh the list
/// occasionally. One time per minute is sufficient.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<List<FixedRateMarket>>> getAvailableFixedRateMarkets({
2022-08-29 21:20:27 +00:00
String? apiKey,
}) async {
2022-08-29 21:20:27 +00:00
final uri = _buildUri(
"/market-info/fixed-rate/${apiKey ?? kChangeNowApiKey}", null);
2022-08-26 08:11:35 +00:00
try {
// json array is expected here
final jsonArray = await _makeGetRequest(uri);
try {
final result =
await compute(_parseFixedRateMarketsJson, jsonArray as List);
return result;
} catch (e, s) {
Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Error: $jsonArray",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-02 20:53:53 +00:00
ExchangeResponse<List<FixedRateMarket>> _parseFixedRateMarketsJson(
2022-08-26 08:11:35 +00:00
List<dynamic> jsonArray) {
try {
List<FixedRateMarket> markets = [];
for (final json in jsonArray) {
try {
markets.add(
2022-10-03 16:30:50 +00:00
FixedRateMarket.fromMap(Map<String, dynamic>.from(json as Map)));
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException("Failed to serialize $json",
ExchangeExceptionType.serializeResponseError));
2022-08-26 08:11:35 +00:00
}
}
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: markets);
2022-08-26 08:11:35 +00:00
} catch (_) {
rethrow;
}
}
/// The API endpoint creates a transaction, generates an address for
/// sending funds and returns transaction attributes.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<ExchangeTransaction>>
2022-08-26 08:11:35 +00:00
createStandardExchangeTransaction({
required String fromTicker,
required String toTicker,
required String receivingAddress,
required Decimal amount,
String extraId = "",
String userId = "",
String contactEmail = "",
String refundAddress = "",
String refundExtraId = "",
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
final Map<String, String> map = {
"from": fromTicker,
"to": toTicker,
"address": receivingAddress,
"amount": amount.toString(),
"flow": "standard",
"extraId": extraId,
"userId": userId,
"contactEmail": contactEmail,
"refundAddress": refundAddress,
"refundExtraId": refundExtraId,
};
2022-08-29 21:20:27 +00:00
final uri = _buildUri("/transactions/${apiKey ?? kChangeNowApiKey}", null);
2022-08-26 08:11:35 +00:00
try {
// simple json object is expected here
final json = await _makePostRequest(uri, map);
// pass in date to prevent using default 1970 date
json["date"] = DateTime.now().toString();
2022-08-26 08:11:35 +00:00
try {
final value = ExchangeTransaction.fromJson(
Map<String, dynamic>.from(json as Map));
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: value);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log(
"createStandardExchangeTransaction exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
/// The API endpoint creates a transaction, generates an address for
/// sending funds and returns transaction attributes.
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<ExchangeTransaction>>
2022-08-26 08:11:35 +00:00
createFixedRateExchangeTransaction({
required String fromTicker,
required String toTicker,
required String receivingAddress,
required Decimal amount,
required String rateId,
required bool reversed,
2022-08-26 08:11:35 +00:00
String extraId = "",
String userId = "",
String contactEmail = "",
String refundAddress = "",
String refundExtraId = "",
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
final Map<String, String> map = {
"from": fromTicker,
"to": toTicker,
"address": receivingAddress,
"flow": "fixed-rate",
"extraId": extraId,
"userId": userId,
"contactEmail": contactEmail,
"refundAddress": refundAddress,
"refundExtraId": refundExtraId,
"rateId": rateId,
};
if (reversed) {
map["result"] = amount.toString();
} else {
map["amount"] = amount.toString();
}
2022-08-29 21:20:27 +00:00
final uri = _buildUri(
"/transactions/fixed-rate${reversed ? "/from-result" : ""}/${apiKey ?? kChangeNowApiKey}",
null,
);
2022-08-26 08:11:35 +00:00
try {
// simple json object is expected here
final json = await _makePostRequest(uri, map);
// pass in date to prevent using default 1970 date
json["date"] = DateTime.now().toString();
2022-08-26 08:11:35 +00:00
try {
final value = ExchangeTransaction.fromJson(
Map<String, dynamic>.from(json as Map));
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: value);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log(
"createFixedRateExchangeTransaction exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-02 20:53:53 +00:00
Future<ExchangeResponse<ExchangeTransactionStatus>> getTransactionStatus({
2022-08-26 08:11:35 +00:00
required String id,
2022-08-29 21:20:27 +00:00
String? apiKey,
2022-08-26 08:11:35 +00:00
}) async {
2022-08-29 21:20:27 +00:00
final uri =
_buildUri("/transactions/$id/${apiKey ?? kChangeNowApiKey}", null);
2022-08-26 08:11:35 +00:00
try {
// simple json object is expected here
final json = await _makeGetRequest(uri);
try {
final value = ExchangeTransactionStatus.fromJson(
Map<String, dynamic>.from(json as Map));
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: value);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Failed to serialize $json",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance
.log("getTransactionStatus exception: $e\n$s", level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-03 15:41:53 +00:00
Future<ExchangeResponse<List<Pair>>> getAvailableFloatingRatePairs({
2022-08-26 08:11:35 +00:00
bool includePartners = false,
}) async {
final uri = _buildUri("/market-info/available-pairs",
{"includePartners": includePartners.toString()});
try {
// json array is expected here
final jsonArray = await _makeGetRequest(uri);
try {
final result = await compute(
_parseAvailableFloatingRatePairsJson, jsonArray as List);
return result;
} catch (e, s) {
Logging.instance.log("getAvailableFloatingRatePairs exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
"Error: $jsonArray",
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.serializeResponseError,
2022-08-26 08:11:35 +00:00
),
);
}
} catch (e, s) {
Logging.instance.log("getAvailableFloatingRatePairs exception: $e\n$s",
level: LogLevel.Error);
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException(
2022-08-26 08:11:35 +00:00
e.toString(),
2022-10-02 20:53:53 +00:00
ExchangeExceptionType.generic,
2022-08-26 08:11:35 +00:00
),
);
}
}
2022-10-03 15:41:53 +00:00
ExchangeResponse<List<Pair>> _parseAvailableFloatingRatePairsJson(
List<dynamic> jsonArray) {
2022-08-26 08:11:35 +00:00
try {
2022-10-03 15:41:53 +00:00
List<Pair> pairs = [];
2022-08-26 08:11:35 +00:00
for (final json in jsonArray) {
try {
final List<String> stringPair = (json as String).split("_");
2022-10-03 15:41:53 +00:00
pairs.add(
Pair(
exchangeName: ChangeNowExchange.exchangeName,
2022-10-03 15:41:53 +00:00
from: stringPair[0],
to: stringPair[1],
2023-02-06 14:43:16 +00:00
rateType: SupportedRateType.estimated,
2022-10-03 15:41:53 +00:00
),
);
2022-08-26 08:11:35 +00:00
} catch (_) {
2022-10-02 20:53:53 +00:00
return ExchangeResponse(
exception: ExchangeException("Failed to serialize $json",
ExchangeExceptionType.serializeResponseError));
2022-08-26 08:11:35 +00:00
}
}
2022-10-02 20:53:53 +00:00
return ExchangeResponse(value: pairs);
2022-08-26 08:11:35 +00:00
} catch (_) {
rethrow;
}
}
}