stack_wallet/lib/services/exchange/majestic_bank/majestic_bank_api.dart

363 lines
11 KiB
Dart
Raw Normal View History

2023-01-29 17:21:35 +00:00
import 'dart:convert';
import 'package:decimal/decimal.dart';
2023-01-29 17:21:35 +00:00
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';
import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart';
2023-02-04 15:16:05 +00:00
import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart';
2023-02-04 15:16:05 +00:00
import 'package:stackwallet/models/exchange/majestic_bank/mb_order_status.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart';
2023-01-29 17:21:35 +00:00
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/utilities/logger.dart';
class MajesticBankAPI {
static const String scheme = "https";
static const String authority = "majesticbank.sc";
static const String version = "v1";
2023-02-04 15:16:05 +00:00
static const String refCode = "fixme";
2023-01-29 17:21:35 +00:00
MajesticBankAPI._();
static final MajesticBankAPI _instance = MajesticBankAPI._();
static MajesticBankAPI get instance => _instance;
/// set this to override using standard http client. Useful for testing
http.Client? client;
Uri _buildUri({required String endpoint, Map<String, String>? params}) {
return Uri.https(authority, "/api/$version/$endpoint", params);
}
Future<dynamic> _makeGetRequest(Uri uri) async {
final client = this.client ?? http.Client();
int code = -1;
try {
final response = await client.get(
uri,
);
code = response.statusCode;
final parsed = jsonDecode(response.body);
return parsed;
} catch (e, s) {
Logging.instance.log(
"_makeRequest($uri) HTTP:$code threw: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
Future<ExchangeResponse<List<MBRate>>> getRates() async {
2023-01-29 17:21:35 +00:00
final uri = _buildUri(
endpoint: "rates",
);
try {
final jsonObject = await _makeGetRequest(uri);
final map = Map<String, dynamic>.from(jsonObject as Map);
final List<MBRate> rates = [];
for (final key in map.keys) {
final currencies = key.split("-");
if (currencies.length == 2) {
final rate = MBRate(
fromCurrency: currencies.first,
toCurrency: currencies.last,
rate: Decimal.parse(map[key].toString()),
);
rates.add(rate);
}
}
return ExchangeResponse(value: rates);
2023-01-29 17:21:35 +00:00
} catch (e, s) {
Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
Future<ExchangeResponse<MBLimit>> getLimit({
required String fromCurrency,
}) async {
final uri = _buildUri(
endpoint: "limits",
params: {
"from_currency": fromCurrency,
},
);
try {
final jsonObject = await _makeGetRequest(uri);
final map = Map<String, dynamic>.from(jsonObject as Map);
final limit = MBLimit(
currency: fromCurrency,
min: Decimal.parse(map["min"].toString()),
max: Decimal.parse(map["max"].toString()),
);
return ExchangeResponse(value: limit);
} catch (e, s) {
Logging.instance
.log("getLimits exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
Future<ExchangeResponse<List<MBLimit>>> getLimits() async {
final uri = _buildUri(
endpoint:
"rates", // limits are included in the rates call for some reason???
);
try {
final jsonObject = await _makeGetRequest(uri);
final map = Map<String, dynamic>.from(jsonObject as Map)["limits"] as Map;
final List<MBLimit> limits = [];
for (final key in map.keys) {
final limit = MBLimit(
currency: key as String,
min: Decimal.parse(map[key]["min"].toString()),
max: Decimal.parse(map[key]["max"].toString()),
);
limits.add(limit);
}
return ExchangeResponse(value: limits);
} catch (e, s) {
Logging.instance
.log("getLimits exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
/// If [reversed] then the amount is the expected receive_amount, otherwise
/// the amount is assumed to be the from_amount.
Future<ExchangeResponse<MBOrderCalculation>> calculateOrder({
required String amount,
required bool reversed,
required String fromCurrency,
required String receiveCurrency,
}) async {
final params = {
"from_currency": fromCurrency,
"receive_currency": receiveCurrency,
};
if (reversed) {
params["receive_amount"] = amount;
} else {
params["from_amount"] = amount;
}
2023-02-04 15:16:05 +00:00
final uri = _buildUri(
endpoint: "calculate",
params: params,
);
2023-01-29 17:21:35 +00:00
try {
final jsonObject = await _makeGetRequest(uri);
2023-02-04 15:16:05 +00:00
final map = Map<String, dynamic>.from(jsonObject as Map);
if (map["error"] != null) {
final errorMessage = map["extra"] as String?;
if (errorMessage != null &&
errorMessage.startsWith("Bad") &&
errorMessage.endsWith("currency symbol")) {
return ExchangeResponse(
exception: PairUnavailableException(
errorMessage,
ExchangeExceptionType.generic,
),
);
} else {
return ExchangeResponse(
exception: ExchangeException(
errorMessage ?? "Error: ${map["error"]}",
ExchangeExceptionType.generic,
),
);
}
}
2023-02-04 15:16:05 +00:00
final result = MBOrderCalculation(
fromCurrency: map["from_currency"] as String,
fromAmount: Decimal.parse(map["from_amount"].toString()),
receiveCurrency: map["receive_currency"] as String,
receiveAmount: Decimal.parse(map["receive_amount"].toString()),
);
2023-01-29 17:21:35 +00:00
2023-02-04 15:16:05 +00:00
return ExchangeResponse(value: result);
2023-01-29 17:21:35 +00:00
} catch (e, s) {
2023-02-05 23:50:40 +00:00
Logging.instance.log(
"calculateOrder $fromCurrency-$receiveCurrency exception: $e\n$s",
level: LogLevel.Error);
2023-01-29 17:21:35 +00:00
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
2023-02-04 15:16:05 +00:00
Future<ExchangeResponse<MBOrder>> createOrder({
required String fromAmount,
2023-01-29 17:21:35 +00:00
required String fromCurrency,
required String receiveCurrency,
required String receiveAddress,
}) async {
final params = {
2023-02-04 15:16:05 +00:00
"from_amount": fromAmount,
2023-01-29 17:21:35 +00:00
"from_currency": fromCurrency,
"receive_currency": receiveCurrency,
"receive_address": receiveAddress,
"referral_code": refCode,
};
2023-02-04 15:16:05 +00:00
final uri = _buildUri(endpoint: "exchange", params: params);
2023-01-29 17:21:35 +00:00
try {
final now = DateTime.now();
final jsonObject = await _makeGetRequest(uri);
final json = Map<String, dynamic>.from(jsonObject as Map);
2023-02-04 15:16:05 +00:00
final order = MBOrder(
orderId: json["trx"] as String,
fromCurrency: json["from_currency"] as String,
fromAmount: Decimal.parse(json["from_amount"].toString()),
receiveCurrency: json["receive_currency"] as String,
receiveAmount: Decimal.parse(json["receive_amount"].toString()),
address: json["address"] as String,
orderType: MBOrderType.floating,
expiration: json["expiration"] as int,
createdAt: now,
2023-01-29 17:21:35 +00:00
);
2023-02-04 15:16:05 +00:00
return ExchangeResponse(value: order);
2023-01-29 17:21:35 +00:00
} catch (e, s) {
Logging.instance
.log("createOrder exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
2023-02-04 15:16:05 +00:00
/// Fixed rate for 10 minutes, useful for payments.
/// If [reversed] then the amount is the expected receive_amount, otherwise
/// the amount is assumed to be the from_amount.
Future<ExchangeResponse<MBOrder>> createFixedRateOrder({
2023-01-29 17:21:35 +00:00
required String amount,
required String fromCurrency,
required String receiveCurrency,
required String receiveAddress,
required bool reversed,
}) async {
final params = {
"from_currency": fromCurrency,
"receive_currency": receiveCurrency,
"receive_address": receiveAddress,
"referral_code": refCode,
};
if (reversed) {
params["receive_amount"] = amount;
} else {
params["from_amount"] = amount;
}
final uri = _buildUri(endpoint: "pay", params: params);
try {
final now = DateTime.now();
final jsonObject = await _makeGetRequest(uri);
final json = Map<String, dynamic>.from(jsonObject as Map);
2023-02-04 15:16:05 +00:00
final order = MBOrder(
orderId: json["trx"] as String,
fromCurrency: json["from_currency"] as String,
fromAmount: Decimal.parse(json["from_amount"].toString()),
receiveCurrency: json["receive_currency"] as String,
receiveAmount: Decimal.parse(json["receive_amount"].toString()),
address: json["address"] as String,
orderType: MBOrderType.fixed,
expiration: json["expiration"] as int,
createdAt: now,
2023-01-29 17:21:35 +00:00
);
2023-02-04 15:16:05 +00:00
return ExchangeResponse(value: order);
2023-01-29 17:21:35 +00:00
} catch (e, s) {
Logging.instance
.log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
Future<ExchangeResponse<MBOrderStatus>> trackOrder({
required String orderId,
}) async {
2023-02-04 15:16:05 +00:00
final uri = _buildUri(endpoint: "track", params: {
"trx": orderId,
});
2023-01-29 17:21:35 +00:00
try {
final jsonObject = await _makeGetRequest(uri);
2023-02-04 15:16:05 +00:00
final json = Map<String, dynamic>.from(jsonObject as Map);
final status = MBOrderStatus(
orderId: json["trx"] as String,
status: json["status"] as String,
fromCurrency: json["from_currency"] as String,
fromAmount: Decimal.parse(json["from_amount"].toString()),
receiveCurrency: json["receive_currency"] as String,
receiveAmount: Decimal.parse(json["receive_amount"].toString()),
address: json["address"] as String,
received: Decimal.parse(json["received"].toString()),
confirmed: Decimal.parse(json["confirmed"].toString()),
);
2023-01-29 17:21:35 +00:00
2023-02-04 15:16:05 +00:00
return ExchangeResponse(value: status);
2023-01-29 17:21:35 +00:00
} catch (e, s) {
Logging.instance
.log("createOrder exception: $e\n$s", level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
ExchangeExceptionType.generic,
),
);
}
}
}