From cbebe69ad8ae711112ed0eaca082d3a68e0f91d7 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 29 Jan 2023 11:21:35 -0600 Subject: [PATCH 01/11] WIP initial majestic bank api work --- .../majestic_bank/majestic_bank_api.dart | 240 ++++++++++++++++++ .../majestic_bank/majestic_bank_exchange.dart | 82 ++++++ 2 files changed, 322 insertions(+) create mode 100644 lib/services/exchange/majestic_bank/majestic_bank_api.dart create mode 100644 lib/services/exchange/majestic_bank/majestic_bank_exchange.dart diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart new file mode 100644 index 000000000..58333b807 --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -0,0 +1,240 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:uuid/uuid.dart'; + +class MajesticBankAPI { + static const String scheme = "https"; + static const String authority = "majesticbank.sc"; + static const String version = "v1"; + static const String refCode = ""; + + 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? params}) { + return Uri.https(authority, "/api/$version/$endpoint", params); + } + + String getPrettyJSONString(jsonObject) { + var encoder = const JsonEncoder.withIndent(" "); + return encoder.convert(jsonObject); + } + + Future _makeGetRequest(Uri uri) async { + final client = this.client ?? http.Client(); + int code = -1; + try { + final response = await client.get( + uri, + ); + + code = response.statusCode; + print(response.body); + + 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 getRates() async { + final uri = _buildUri( + endpoint: "rates", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future calculateOrder() async { + final uri = _buildUri( + endpoint: "calculate", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance + .log("calculateOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> createOrder({ + required String amount, + required String fromCurrency, + required String receiveCurrency, + required String receiveAddress, + }) async { + final params = { + "from_amount": amount, + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + "receive_address": receiveAddress, + "referral_code": refCode, + }; + + final uri = _buildUri(endpoint: "create", params: params); + + try { + final now = DateTime.now(); + final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); + + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["trx"] as String, + rateType: "floating-rate", + direction: "direct", + timestamp: now, + updatedAt: now, + payInCurrency: json["from_currency"] as String, + payInAmount: json["from_amount"] as String, + payInAddress: json["address"] as String, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: json["receive_currency"] as String, + payOutAmount: json["receive_amount"] as String, + payOutAddress: json["receive_address"] as String, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: "", + refundExtraId: "", + status: "Waiting", + exchangeName: MajesticBankExchange.exchangeName, + ); + return ExchangeResponse(value: trade); + } catch (e, s) { + Logging.instance + .log("createOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> createFixedRateOrder({ + 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.from(jsonObject as Map); + + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["trx"] as String, + rateType: "fixed-rate", + direction: reversed ? "reversed" : "direct", + timestamp: now, + updatedAt: now, + payInCurrency: json["from_currency"] as String, + payInAmount: json["from_amount"] as String, + payInAddress: json["address"] as String, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: json["receive_currency"] as String, + payOutAmount: json["receive_amount"] as String, + payOutAddress: json["receive_address"] as String, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: "", + refundExtraId: "", + status: "Waiting", + exchangeName: MajesticBankExchange.exchangeName, + ); + return ExchangeResponse(value: trade); + } catch (e, s) { + Logging.instance + .log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future trackOrder({required String orderId}) async { + final uri = _buildUri( + endpoint: "track", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance + .log("createOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart new file mode 100644 index 000000000..978ffb914 --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -0,0 +1,82 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; + +class MajesticBankExchange extends Exchange { + static const exchangeName = "MajesticBank"; + + @override + Future> createTrade( + {required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + String? rateId, + required bool reversed}) { + // TODO: implement createTrade + throw UnimplementedError(); + } + + @override + Future>> getAllCurrencies(bool fixedRate) { + // TODO: implement getAllCurrencies + throw UnimplementedError(); + } + + @override + Future>> getAllPairs(bool fixedRate) { + // TODO: implement getAllPairs + throw UnimplementedError(); + } + + @override + Future> getEstimate( + String from, String to, Decimal amount, bool fixedRate, bool reversed) { + // TODO: implement getEstimate + throw UnimplementedError(); + } + + @override + Future>> getPairsFor( + String currency, bool fixedRate) { + // TODO: implement getPairsFor + throw UnimplementedError(); + } + + @override + Future> getRange( + String from, String to, bool fixedRate) { + // TODO: implement getRange + throw UnimplementedError(); + } + + @override + Future> getTrade(String tradeId) { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() { + // TODO: implement getTrades + throw UnimplementedError(); + } + + @override + String get name => exchangeName; + + @override + Future> updateTrade(Trade trade) { + // TODO: implement updateTrade + throw UnimplementedError(); + } +} From 9ab087587b72c51eb8c4d207e7317cb57e0173d2 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 30 Jan 2023 07:58:11 -0600 Subject: [PATCH 02/11] helper --- .../global_settings_view/hidden_settings.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index d92b166d7..bc86ecdb0 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -128,6 +128,27 @@ class HiddenSettings extends StatelessWidget { ), ); }), + // const SizedBox( + // height: 12, + // ), + // Consumer(builder: (_, ref, __) { + // return GestureDetector( + // onTap: () async { + // final x = + // await MajesticBankAPI.instance.getRates(); + // print(x); + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Click me", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context) + // .extension()! + // .accentColorDark), + // ), + // ), + // ); + // }), const SizedBox( height: 12, ), From e666928d63294613779bf85dfc7d54e8e4851f92 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 1 Feb 2023 18:24:41 -0600 Subject: [PATCH 03/11] majestic bank limits and rates api calls --- .../exchange/majestic_bank/mb_limit.dart | 19 +++++++ .../exchange/majestic_bank/mb_object.dart | 1 + .../exchange/majestic_bank/mb_rate.dart | 15 ++++++ .../majestic_bank/majestic_bank_api.dart | 53 ++++++++++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 lib/models/exchange/majestic_bank/mb_limit.dart create mode 100644 lib/models/exchange/majestic_bank/mb_object.dart create mode 100644 lib/models/exchange/majestic_bank/mb_rate.dart diff --git a/lib/models/exchange/majestic_bank/mb_limit.dart b/lib/models/exchange/majestic_bank/mb_limit.dart new file mode 100644 index 000000000..baa002d56 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_limit.dart @@ -0,0 +1,19 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBLimit extends MBObject { + MBLimit({ + required this.currency, + required this.min, + required this.max, + }); + + final String currency; + final Decimal min; + final Decimal max; + + @override + String toString() { + return "MBLimit: { $currency: { min: $min, max: $max } }"; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_object.dart b/lib/models/exchange/majestic_bank/mb_object.dart new file mode 100644 index 000000000..e3810131c --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_object.dart @@ -0,0 +1 @@ +abstract class MBObject {} diff --git a/lib/models/exchange/majestic_bank/mb_rate.dart b/lib/models/exchange/majestic_bank/mb_rate.dart new file mode 100644 index 000000000..60d71cdf0 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_rate.dart @@ -0,0 +1,15 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBRate extends MBObject { + MBRate({required this.fromCurrency, required this.toCurrency, required this.rate,}); + + final String fromCurrency; + final String toCurrency; + final Decimal rate; + + @override + String toString() { + return "MBRate: { $fromCurrency-$toCurrency: $rate }"; + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 58333b807..00d4e2e20 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -1,6 +1,9 @@ import 'dart:convert'; +import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; @@ -54,7 +57,7 @@ class MajesticBankAPI { } } - Future getRates() async { + Future>> getRates() async { final uri = _buildUri( endpoint: "rates", ); @@ -62,7 +65,20 @@ class MajesticBankAPI { try { final jsonObject = await _makeGetRequest(uri); - return getPrettyJSONString(jsonObject); + final map = Map.from(jsonObject as Map); + final List 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); } catch (e, s) { Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); return ExchangeResponse( @@ -74,6 +90,39 @@ class MajesticBankAPI { } } + Future>> 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.from(jsonObject as Map)["limits"] as Map; + final List 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, + ), + ); + } + } + Future calculateOrder() async { final uri = _buildUri( endpoint: "calculate", From b5040597355f1a32502581e12741afff6a4c59d0 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 29 Jan 2023 11:21:35 -0600 Subject: [PATCH 04/11] WIP initial majestic bank api work --- .../majestic_bank/majestic_bank_api.dart | 240 ++++++++++++++++++ .../majestic_bank/majestic_bank_exchange.dart | 82 ++++++ 2 files changed, 322 insertions(+) create mode 100644 lib/services/exchange/majestic_bank/majestic_bank_api.dart create mode 100644 lib/services/exchange/majestic_bank/majestic_bank_exchange.dart diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart new file mode 100644 index 000000000..58333b807 --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -0,0 +1,240 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:uuid/uuid.dart'; + +class MajesticBankAPI { + static const String scheme = "https"; + static const String authority = "majesticbank.sc"; + static const String version = "v1"; + static const String refCode = ""; + + 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? params}) { + return Uri.https(authority, "/api/$version/$endpoint", params); + } + + String getPrettyJSONString(jsonObject) { + var encoder = const JsonEncoder.withIndent(" "); + return encoder.convert(jsonObject); + } + + Future _makeGetRequest(Uri uri) async { + final client = this.client ?? http.Client(); + int code = -1; + try { + final response = await client.get( + uri, + ); + + code = response.statusCode; + print(response.body); + + 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 getRates() async { + final uri = _buildUri( + endpoint: "rates", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future calculateOrder() async { + final uri = _buildUri( + endpoint: "calculate", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance + .log("calculateOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> createOrder({ + required String amount, + required String fromCurrency, + required String receiveCurrency, + required String receiveAddress, + }) async { + final params = { + "from_amount": amount, + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + "receive_address": receiveAddress, + "referral_code": refCode, + }; + + final uri = _buildUri(endpoint: "create", params: params); + + try { + final now = DateTime.now(); + final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); + + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["trx"] as String, + rateType: "floating-rate", + direction: "direct", + timestamp: now, + updatedAt: now, + payInCurrency: json["from_currency"] as String, + payInAmount: json["from_amount"] as String, + payInAddress: json["address"] as String, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: json["receive_currency"] as String, + payOutAmount: json["receive_amount"] as String, + payOutAddress: json["receive_address"] as String, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: "", + refundExtraId: "", + status: "Waiting", + exchangeName: MajesticBankExchange.exchangeName, + ); + return ExchangeResponse(value: trade); + } catch (e, s) { + Logging.instance + .log("createOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> createFixedRateOrder({ + 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.from(jsonObject as Map); + + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["trx"] as String, + rateType: "fixed-rate", + direction: reversed ? "reversed" : "direct", + timestamp: now, + updatedAt: now, + payInCurrency: json["from_currency"] as String, + payInAmount: json["from_amount"] as String, + payInAddress: json["address"] as String, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: json["receive_currency"] as String, + payOutAmount: json["receive_amount"] as String, + payOutAddress: json["receive_address"] as String, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: "", + refundExtraId: "", + status: "Waiting", + exchangeName: MajesticBankExchange.exchangeName, + ); + return ExchangeResponse(value: trade); + } catch (e, s) { + Logging.instance + .log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future trackOrder({required String orderId}) async { + final uri = _buildUri( + endpoint: "track", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return getPrettyJSONString(jsonObject); + } catch (e, s) { + Logging.instance + .log("createOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart new file mode 100644 index 000000000..978ffb914 --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -0,0 +1,82 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; + +class MajesticBankExchange extends Exchange { + static const exchangeName = "MajesticBank"; + + @override + Future> createTrade( + {required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + String? rateId, + required bool reversed}) { + // TODO: implement createTrade + throw UnimplementedError(); + } + + @override + Future>> getAllCurrencies(bool fixedRate) { + // TODO: implement getAllCurrencies + throw UnimplementedError(); + } + + @override + Future>> getAllPairs(bool fixedRate) { + // TODO: implement getAllPairs + throw UnimplementedError(); + } + + @override + Future> getEstimate( + String from, String to, Decimal amount, bool fixedRate, bool reversed) { + // TODO: implement getEstimate + throw UnimplementedError(); + } + + @override + Future>> getPairsFor( + String currency, bool fixedRate) { + // TODO: implement getPairsFor + throw UnimplementedError(); + } + + @override + Future> getRange( + String from, String to, bool fixedRate) { + // TODO: implement getRange + throw UnimplementedError(); + } + + @override + Future> getTrade(String tradeId) { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() { + // TODO: implement getTrades + throw UnimplementedError(); + } + + @override + String get name => exchangeName; + + @override + Future> updateTrade(Trade trade) { + // TODO: implement updateTrade + throw UnimplementedError(); + } +} From fca09fbe02dc3ea0e7a5ff54206c82bda076d242 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 30 Jan 2023 07:58:11 -0600 Subject: [PATCH 05/11] helper --- .../global_settings_view/hidden_settings.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index d92b166d7..bc86ecdb0 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -128,6 +128,27 @@ class HiddenSettings extends StatelessWidget { ), ); }), + // const SizedBox( + // height: 12, + // ), + // Consumer(builder: (_, ref, __) { + // return GestureDetector( + // onTap: () async { + // final x = + // await MajesticBankAPI.instance.getRates(); + // print(x); + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Click me", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context) + // .extension()! + // .accentColorDark), + // ), + // ), + // ); + // }), const SizedBox( height: 12, ), From 8bdf0413bd1b707d2cc966ca966fa7b76a7353e3 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 1 Feb 2023 18:24:41 -0600 Subject: [PATCH 06/11] majestic bank limits and rates api calls --- .../exchange/majestic_bank/mb_limit.dart | 19 +++++++ .../exchange/majestic_bank/mb_object.dart | 1 + .../exchange/majestic_bank/mb_rate.dart | 15 ++++++ .../majestic_bank/majestic_bank_api.dart | 53 ++++++++++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 lib/models/exchange/majestic_bank/mb_limit.dart create mode 100644 lib/models/exchange/majestic_bank/mb_object.dart create mode 100644 lib/models/exchange/majestic_bank/mb_rate.dart diff --git a/lib/models/exchange/majestic_bank/mb_limit.dart b/lib/models/exchange/majestic_bank/mb_limit.dart new file mode 100644 index 000000000..baa002d56 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_limit.dart @@ -0,0 +1,19 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBLimit extends MBObject { + MBLimit({ + required this.currency, + required this.min, + required this.max, + }); + + final String currency; + final Decimal min; + final Decimal max; + + @override + String toString() { + return "MBLimit: { $currency: { min: $min, max: $max } }"; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_object.dart b/lib/models/exchange/majestic_bank/mb_object.dart new file mode 100644 index 000000000..e3810131c --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_object.dart @@ -0,0 +1 @@ +abstract class MBObject {} diff --git a/lib/models/exchange/majestic_bank/mb_rate.dart b/lib/models/exchange/majestic_bank/mb_rate.dart new file mode 100644 index 000000000..60d71cdf0 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_rate.dart @@ -0,0 +1,15 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBRate extends MBObject { + MBRate({required this.fromCurrency, required this.toCurrency, required this.rate,}); + + final String fromCurrency; + final String toCurrency; + final Decimal rate; + + @override + String toString() { + return "MBRate: { $fromCurrency-$toCurrency: $rate }"; + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 58333b807..00d4e2e20 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -1,6 +1,9 @@ import 'dart:convert'; +import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; @@ -54,7 +57,7 @@ class MajesticBankAPI { } } - Future getRates() async { + Future>> getRates() async { final uri = _buildUri( endpoint: "rates", ); @@ -62,7 +65,20 @@ class MajesticBankAPI { try { final jsonObject = await _makeGetRequest(uri); - return getPrettyJSONString(jsonObject); + final map = Map.from(jsonObject as Map); + final List 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); } catch (e, s) { Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); return ExchangeResponse( @@ -74,6 +90,39 @@ class MajesticBankAPI { } } + Future>> 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.from(jsonObject as Map)["limits"] as Map; + final List 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, + ), + ); + } + } + Future calculateOrder() async { final uri = _buildUri( endpoint: "calculate", From 991f12841660519c1638d6da7367a6bdcc4dfc1a Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 2 Feb 2023 14:07:03 -0600 Subject: [PATCH 07/11] buy warning popup refactor for desktop style/size --- .../sub_widgets/buy_warning_popup.dart | 391 +++++++++++------- 1 file changed, 235 insertions(+), 156 deletions(-) diff --git a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart index c98d53580..dc9935a03 100644 --- a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart +++ b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart @@ -18,16 +18,28 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -class BuyWarningPopup extends StatelessWidget { - BuyWarningPopup({ +class BuyWarningPopup extends StatefulWidget { + const BuyWarningPopup({ Key? key, required this.quote, this.order, }) : super(key: key); - final SimplexQuote quote; + final SimplexOrder? order; + @override + State createState() => _BuyWarningPopupState(); +} + +class _BuyWarningPopupState extends State { + late final bool isDesktop; SimplexOrder? order; + String get title => "Buy ${widget.quote.crypto.ticker}"; + String get message => + "This purchase is provided and fulfilled by Simplex by nuvei " + "(a third party). You will be taken to their website. Please follow " + "their instructions."; + Future> newOrder(SimplexQuote quote) async { final orderResponse = await SimplexAPI.instance.newOrder(quote); @@ -38,174 +50,241 @@ class BuyWarningPopup extends StatelessWidget { return SimplexAPI.instance.redirect(order); } - @override - Widget build(BuildContext context) { - final isDesktop = Util.isDesktop; - - Future _buyInvoice() async { - await showDialog( - context: context, - // useRootNavigator: isDesktop, - builder: (context) { - return isDesktop - ? DesktopDialog( - maxHeight: 700, - maxWidth: 580, - child: Column( + Future _buyInvoice() async { + await showDialog( + context: context, + // useRootNavigator: isDesktop, + builder: (context) { + return isDesktop + ? DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Order details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Order details", - style: STextStyles.desktopH3(context), + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: BuyOrderDetailsView( + order: order as SimplexOrder, + ), ), ), - const DesktopDialogCloseButton(), ], ), + ), + ), + ], + ), + ) + : BuyOrderDetailsView( + order: order as SimplexOrder, + ); + }, + ); + } + + Future onContinue() async { + BuyResponse orderResponse = await newOrder(widget.quote); + if (orderResponse.exception == null) { + await redirect(orderResponse.value as SimplexOrder) + .then((_response) async { + order = orderResponse.value as SimplexOrder; + Navigator.of(context, rootNavigator: isDesktop).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); + await _buyInvoice(); + }); + } else { + await showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Simplex API error", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + "${orderResponse.exception?.errorMessage}", + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Row( - children: [ - Expanded( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(16), - borderColor: Theme.of(context) - .extension()! - .background, - child: BuyOrderDetailsView( - order: order as SimplexOrder, - ), - ), - ), - ], - ), + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, ), ), ], - ), - ) - : BuyOrderDetailsView( - order: order as SimplexOrder, - ); - }); - } - - return StackDialog( - title: "Buy ${quote.crypto.ticker}", - message: "This purchase is provided and fulfilled by Simplex by nuvei " - "(a third party). You will be taken to their website. Please follow " - "their instructions.", - leftButton: SecondaryButton( - label: "Cancel", - onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, - ), - rightButton: PrimaryButton( - label: "Continue", - onPressed: () async { - BuyResponse orderResponse = await newOrder(quote); - if (orderResponse.exception == null) { - await redirect(orderResponse.value as SimplexOrder) - .then((_response) async { - this.order = orderResponse.value as SimplexOrder; - Navigator.of(context, rootNavigator: isDesktop).pop(); - Navigator.of(context, rootNavigator: isDesktop).pop(); - await _buyInvoice(); - }); + ) + ], + ), + ), + ); } else { - await showDialog( - context: context, - barrierDismissible: true, - builder: (context) { - if (isDesktop) { - return DesktopDialog( - maxWidth: 450, - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Simplex API error", - style: STextStyles.desktopH3(context), - ), - const SizedBox( - height: 24, - ), - Text( - "${orderResponse.exception?.errorMessage}", - style: STextStyles.smallMed14(context), - ), - const SizedBox( - height: 56, - ), - Row( - children: [ - const Spacer(), - Expanded( - child: PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Ok", - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - Navigator.of(context).pop(); // weee - }, - ), - ), - ], - ) - ], - ), - ), - ); - } else { - return StackDialog( - title: "Simplex API error", - message: "${orderResponse.exception?.errorMessage}", - // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", - rightButton: TextButton( - style: Theme.of(context) + return StackDialog( + title: "Simplex API error", + message: "${orderResponse.exception?.errorMessage}", + // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) .extension()! - .getSecondaryEnabledButtonStyle(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - Navigator.of(context).pop(); // weee - }, - ), - ); - } - }, + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, + ), ); } }, - ), - icon: SizedBox( - width: 64, - height: 32, - child: SvgPicture.asset( - Assets.buy.simplexLogo(context), + ); + } + } + + @override + void initState() { + order = widget.order; + isDesktop = Util.isDesktop; + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 350, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ], + ), + const Spacer(), + Text( + message, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: isDesktop).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: onContinue, + ), + ), + ], + ) + ], + ), ), - ), - ); + ); + } else { + return StackDialog( + title: title, + message: message, + leftButton: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, + ), + rightButton: PrimaryButton( + label: "Continue", + onPressed: onContinue, + ), + icon: SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ); + } } } From 3ba9f7d61b1dfd71804ac66502886148e2b1b005 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 2 Feb 2023 14:18:27 -0600 Subject: [PATCH 08/11] WIP majestic bank order calculate api call --- .../majestic_bank/mb_order_calculation.dart | 21 +++++++++ .../global_settings_view/hidden_settings.dart | 44 ++++++++++--------- .../majestic_bank/majestic_bank_api.dart | 24 +++++++++- 3 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 lib/models/exchange/majestic_bank/mb_order_calculation.dart diff --git a/lib/models/exchange/majestic_bank/mb_order_calculation.dart b/lib/models/exchange/majestic_bank/mb_order_calculation.dart new file mode 100644 index 000000000..931ca440f --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order_calculation.dart @@ -0,0 +1,21 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBOrderCalculation extends MBObject { + MBOrderCalculation({ + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + }); + + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final Decimal receiveAmount; + + @override + String toString() { + return "MBOrderCalculation: { $fromCurrency: $fromAmount, $receiveCurrency: $receiveAmount }"; + } +} diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index bc86ecdb0..d5c4864ae 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -11,6 +11,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import '../../../services/exchange/majestic_bank/majestic_bank_api.dart'; + class HiddenSettings extends StatelessWidget { const HiddenSettings({Key? key}) : super(key: key); @@ -128,27 +130,27 @@ class HiddenSettings extends StatelessWidget { ), ); }), - // const SizedBox( - // height: 12, - // ), - // Consumer(builder: (_, ref, __) { - // return GestureDetector( - // onTap: () async { - // final x = - // await MajesticBankAPI.instance.getRates(); - // print(x); - // }, - // child: RoundedWhiteContainer( - // child: Text( - // "Click me", - // style: STextStyles.button(context).copyWith( - // color: Theme.of(context) - // .extension()! - // .accentColorDark), - // ), - // ), - // ); - // }), + const SizedBox( + height: 12, + ), + Consumer(builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + final x = + await MajesticBankAPI.instance.getLimits(); + print(x); + }, + child: RoundedWhiteContainer( + child: Text( + "Click me", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ); + }), const SizedBox( height: 12, ), diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 00d4e2e20..3b1f9218b 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; @@ -123,15 +124,34 @@ class MajesticBankAPI { } } - Future calculateOrder() async { + /// If [reversed] then the amount is the expected receive_amount, otherwise + /// the amount is assumed to be the from_amount. + Future> calculateOrder({ + required String amount, + required bool reversed, + required String fromCurrency, + required String receiveCurrency, + }) async { final uri = _buildUri( endpoint: "calculate", ); + final params = { + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + }; + + if (reversed) { + params["receive_amount"] = amount; + } else { + params["from_amount"] = amount; + } + try { final jsonObject = await _makeGetRequest(uri); - return getPrettyJSONString(jsonObject); + // return getPrettyJSONString(jsonObject); + return ExchangeResponse(); } catch (e, s) { Logging.instance .log("calculateOrder exception: $e\n$s", level: LogLevel.Error); From a3b5ba5b0497fcb709fc2390aadff134874a33bb Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 4 Feb 2023 09:16:05 -0600 Subject: [PATCH 09/11] majestic bank dart api impl --- .../exchange/majestic_bank/mb_order.dart | 43 ++++++ .../majestic_bank/mb_order_status.dart | 32 ++++ .../majestic_bank/majestic_bank_api.dart | 140 +++++++++--------- 3 files changed, 142 insertions(+), 73 deletions(-) create mode 100644 lib/models/exchange/majestic_bank/mb_order.dart create mode 100644 lib/models/exchange/majestic_bank/mb_order_status.dart diff --git a/lib/models/exchange/majestic_bank/mb_order.dart b/lib/models/exchange/majestic_bank/mb_order.dart new file mode 100644 index 000000000..f5dde038e --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order.dart @@ -0,0 +1,43 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +enum MBOrderType { + fixed, + floating, +} + +class MBOrder extends MBObject { + MBOrder({ + required this.orderId, + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + required this.address, + required this.orderType, + required this.expiration, + required this.createdAt, + }); + + final String orderId; + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final String address; + final Decimal receiveAmount; + final MBOrderType orderType; + + /// minutes + final int expiration; + + final DateTime createdAt; + + bool isExpired() => + (DateTime.now().difference(createdAt) >= Duration(minutes: expiration)); + + @override + String toString() { + // todo: full toString + return orderId; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_order_status.dart b/lib/models/exchange/majestic_bank/mb_order_status.dart new file mode 100644 index 000000000..5496aa5e2 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order_status.dart @@ -0,0 +1,32 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBOrderStatus extends MBObject { + MBOrderStatus({ + required this.orderId, + required this.status, + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + required this.address, + required this.received, + required this.confirmed, + }); + + final String orderId; + final String status; + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final Decimal receiveAmount; + final String address; + final Decimal received; + final Decimal confirmed; + + @override + String toString() { + // todo: full toString + return status; + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index 3b1f9218b..da68db574 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -3,19 +3,18 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order_status.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart'; -import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; -import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; -import 'package:uuid/uuid.dart'; class MajesticBankAPI { static const String scheme = "https"; static const String authority = "majesticbank.sc"; static const String version = "v1"; - static const String refCode = ""; + static const String refCode = "fixme"; MajesticBankAPI._(); @@ -30,11 +29,6 @@ class MajesticBankAPI { return Uri.https(authority, "/api/$version/$endpoint", params); } - String getPrettyJSONString(jsonObject) { - var encoder = const JsonEncoder.withIndent(" "); - return encoder.convert(jsonObject); - } - Future _makeGetRequest(Uri uri) async { final client = this.client ?? http.Client(); int code = -1; @@ -132,10 +126,6 @@ class MajesticBankAPI { required String fromCurrency, required String receiveCurrency, }) async { - final uri = _buildUri( - endpoint: "calculate", - ); - final params = { "from_currency": fromCurrency, "receive_currency": receiveCurrency, @@ -147,11 +137,22 @@ class MajesticBankAPI { params["from_amount"] = amount; } + final uri = _buildUri( + endpoint: "calculate", + params: params, + ); + try { final jsonObject = await _makeGetRequest(uri); + final map = Map.from(jsonObject as Map); + 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()), + ); - // return getPrettyJSONString(jsonObject); - return ExchangeResponse(); + return ExchangeResponse(value: result); } catch (e, s) { Logging.instance .log("calculateOrder exception: $e\n$s", level: LogLevel.Error); @@ -164,52 +165,40 @@ class MajesticBankAPI { } } - Future> createOrder({ - required String amount, + Future> createOrder({ + required String fromAmount, required String fromCurrency, required String receiveCurrency, required String receiveAddress, }) async { final params = { - "from_amount": amount, + "from_amount": fromAmount, "from_currency": fromCurrency, "receive_currency": receiveCurrency, "receive_address": receiveAddress, "referral_code": refCode, }; - final uri = _buildUri(endpoint: "create", params: params); + final uri = _buildUri(endpoint: "exchange", params: params); try { final now = DateTime.now(); final jsonObject = await _makeGetRequest(uri); final json = Map.from(jsonObject as Map); - final trade = Trade( - uuid: const Uuid().v1(), - tradeId: json["trx"] as String, - rateType: "floating-rate", - direction: "direct", - timestamp: now, - updatedAt: now, - payInCurrency: json["from_currency"] as String, - payInAmount: json["from_amount"] as String, - payInAddress: json["address"] as String, - payInNetwork: "", - payInExtraId: "", - payInTxid: "", - payOutCurrency: json["receive_currency"] as String, - payOutAmount: json["receive_amount"] as String, - payOutAddress: json["receive_address"] as String, - payOutNetwork: "", - payOutExtraId: "", - payOutTxid: "", - refundAddress: "", - refundExtraId: "", - status: "Waiting", - exchangeName: MajesticBankExchange.exchangeName, + 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, ); - return ExchangeResponse(value: trade); + + return ExchangeResponse(value: order); } catch (e, s) { Logging.instance .log("createOrder exception: $e\n$s", level: LogLevel.Error); @@ -222,7 +211,10 @@ class MajesticBankAPI { } } - Future> createFixedRateOrder({ + /// 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> createFixedRateOrder({ required String amount, required String fromCurrency, required String receiveCurrency, @@ -249,31 +241,19 @@ class MajesticBankAPI { final jsonObject = await _makeGetRequest(uri); final json = Map.from(jsonObject as Map); - final trade = Trade( - uuid: const Uuid().v1(), - tradeId: json["trx"] as String, - rateType: "fixed-rate", - direction: reversed ? "reversed" : "direct", - timestamp: now, - updatedAt: now, - payInCurrency: json["from_currency"] as String, - payInAmount: json["from_amount"] as String, - payInAddress: json["address"] as String, - payInNetwork: "", - payInExtraId: "", - payInTxid: "", - payOutCurrency: json["receive_currency"] as String, - payOutAmount: json["receive_amount"] as String, - payOutAddress: json["receive_address"] as String, - payOutNetwork: "", - payOutExtraId: "", - payOutTxid: "", - refundAddress: "", - refundExtraId: "", - status: "Waiting", - exchangeName: MajesticBankExchange.exchangeName, + 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, ); - return ExchangeResponse(value: trade); + + return ExchangeResponse(value: order); } catch (e, s) { Logging.instance .log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error); @@ -286,15 +266,29 @@ class MajesticBankAPI { } } - Future trackOrder({required String orderId}) async { - final uri = _buildUri( - endpoint: "track", - ); + Future> trackOrder( + {required String orderId}) async { + final uri = _buildUri(endpoint: "track", params: { + "trx": orderId, + }); try { final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); - return getPrettyJSONString(jsonObject); + 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()), + ); + + return ExchangeResponse(value: status); } catch (e, s) { Logging.instance .log("createOrder exception: $e\n$s", level: LogLevel.Error); From 4cbf8b8cd95aff24165e1c6acaebe9d3f8902ca1 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 4 Feb 2023 11:15:42 -0600 Subject: [PATCH 10/11] refactor exchange exception --- lib/exceptions/exchange/exchange_exception.dart | 13 +++++++++++++ .../exchange/majestic_bank/mb_exception.dart | 5 +++++ .../exchange/change_now/change_now_api.dart | 1 + .../exchange/exchange_data_loading_service.dart | 6 +++--- lib/services/exchange/exchange_response.dart | 13 +------------ .../exchange/majestic_bank/majestic_bank_api.dart | 1 + .../exchange/simpleswap/simpleswap_api.dart | 1 + 7 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 lib/exceptions/exchange/exchange_exception.dart create mode 100644 lib/exceptions/exchange/majestic_bank/mb_exception.dart diff --git a/lib/exceptions/exchange/exchange_exception.dart b/lib/exceptions/exchange/exchange_exception.dart new file mode 100644 index 000000000..af7aa8f65 --- /dev/null +++ b/lib/exceptions/exchange/exchange_exception.dart @@ -0,0 +1,13 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +enum ExchangeExceptionType { generic, serializeResponseError } + +class ExchangeException extends SWException { + ExchangeExceptionType type; + ExchangeException(super.message, this.type); + + @override + String toString() { + return message; + } +} diff --git a/lib/exceptions/exchange/majestic_bank/mb_exception.dart b/lib/exceptions/exchange/majestic_bank/mb_exception.dart new file mode 100644 index 000000000..d3130d874 --- /dev/null +++ b/lib/exceptions/exchange/majestic_bank/mb_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; + +class MBException extends ExchangeException { + MBException(super.message, super.type); +} diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index d957eaf1a..a9920bfb8 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index d06f8b726..8d232e957 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -55,7 +55,7 @@ class ExchangeDataLoadingService { } } else { Logging.instance.log( - "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", + "Failed to load changeNOW fixed rate markets: ${response3.exception?.message}", level: LogLevel.Error); ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = @@ -122,7 +122,7 @@ class ExchangeDataLoadingService { } } else { Logging.instance.log( - "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}", + "Failed to load changeNOW available floating rate pairs: ${response2.exception?.message}", level: LogLevel.Error); ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = ChangeNowLoadStatus.failed; @@ -130,7 +130,7 @@ class ExchangeDataLoadingService { } } else { Logging.instance.log( - "Failed to load changeNOW currencies: ${response.exception?.errorMessage}", + "Failed to load changeNOW currencies: ${response.exception?.message}", level: LogLevel.Error); await Future.delayed(const Duration(seconds: 3)); ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = diff --git a/lib/services/exchange/exchange_response.dart b/lib/services/exchange/exchange_response.dart index 59441fb9b..79339e5f7 100644 --- a/lib/services/exchange/exchange_response.dart +++ b/lib/services/exchange/exchange_response.dart @@ -1,15 +1,4 @@ -enum ExchangeExceptionType { generic, serializeResponseError } - -class ExchangeException implements Exception { - String errorMessage; - ExchangeExceptionType type; - ExchangeException(this.errorMessage, this.type); - - @override - String toString() { - return errorMessage; - } -} +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; class ExchangeResponse { late final T? value; diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index da68db574..e140e5308 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart'; import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart'; diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 3d626b2eb..bd6e933fe 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; From 585a684ecc00d510fb4f5a2339c96fd765c19d28 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 4 Feb 2023 11:48:07 -0600 Subject: [PATCH 11/11] apply majestic bank api to an exchange class --- .../majestic_bank/majestic_bank_api.dart | 39 ++- .../majestic_bank/majestic_bank_exchange.dart | 247 +++++++++++++++--- 2 files changed, 251 insertions(+), 35 deletions(-) diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart index e140e5308..6d7e2ffe7 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_api.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -86,6 +86,40 @@ class MajesticBankAPI { } } + Future> getLimit({ + required String fromCurrency, + }) async { + final uri = _buildUri( + endpoint: "limits", + params: { + "from_currency": fromCurrency, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final map = Map.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>> getLimits() async { final uri = _buildUri( endpoint: @@ -267,8 +301,9 @@ class MajesticBankAPI { } } - Future> trackOrder( - {required String orderId}) async { + Future> trackOrder({ + required String orderId, + }) async { final uri = _buildUri(endpoint: "track", params: { "trx": orderId, }); diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart index 978ffb914..4610f6c3d 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -1,4 +1,7 @@ import 'package:decimal/decimal.dart'; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/exceptions/exchange/majestic_bank/mb_exception.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; @@ -6,67 +9,212 @@ import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_api.dart'; +import 'package:uuid/uuid.dart'; class MajesticBankExchange extends Exchange { static const exchangeName = "MajesticBank"; @override - Future> createTrade( - {required String from, - required String to, - required bool fixedRate, - required Decimal amount, - required String addressTo, - String? extraId, - required String addressRefund, - required String refundExtraId, - String? rateId, - required bool reversed}) { - // TODO: implement createTrade - throw UnimplementedError(); + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + String? rateId, + required bool reversed, + }) async { + ExchangeResponse? response; + + if (fixedRate) { + response = await MajesticBankAPI.instance.createFixedRateOrder( + amount: amount.toString(), + fromCurrency: from, + receiveCurrency: to, + receiveAddress: addressTo, + reversed: reversed, + ); + } else { + if (reversed) { + return ExchangeResponse( + exception: MBException( + "Reversed trade not available", + ExchangeExceptionType.generic, + ), + ); + } + response = await MajesticBankAPI.instance.createOrder( + fromAmount: amount.toString(), + fromCurrency: from, + receiveCurrency: to, + receiveAddress: addressTo, + ); + } + + if (response.value != null) { + final order = response.value!; + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: order.orderId, + rateType: fixedRate ? "fixed" : "floating", + direction: reversed ? "reversed" : "direct", + timestamp: order.createdAt, + updatedAt: order.createdAt, + payInCurrency: order.fromCurrency, + payInAmount: order.fromAmount.toString(), + payInAddress: order.address, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: order.receiveCurrency, + payOutAmount: order.receiveAmount.toString(), + payOutAddress: addressTo, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: addressRefund, + refundExtraId: refundExtraId, + status: "Waiting", + exchangeName: exchangeName, + ); + + return ExchangeResponse(value: trade); + } else { + return ExchangeResponse(exception: response.exception!); + } } @override - Future>> getAllCurrencies(bool fixedRate) { - // TODO: implement getAllCurrencies - throw UnimplementedError(); + Future>> getAllCurrencies( + bool fixedRate, + ) async { + final response = await MajesticBankAPI.instance.getLimits(); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List currencies = []; + final limits = response.value!; + + for (final limit in limits) { + final currency = Currency( + ticker: limit.currency, + name: limit.currency, + network: "", + image: "", + hasExternalId: false, + isFiat: false, + featured: false, + isStable: false, + supportsFixedRate: true, + ); + currencies.add(currency); + } + + return ExchangeResponse(value: currencies); } @override - Future>> getAllPairs(bool fixedRate) { - // TODO: implement getAllPairs - throw UnimplementedError(); + Future>> getAllPairs(bool fixedRate) async { + final response = await MajesticBankAPI.instance.getRates(); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List pairs = []; + final rates = response.value!; + + for (final rate in rates) { + final pair = Pair( + from: rate.fromCurrency, + fromNetwork: "", + to: rate.toCurrency, + toNetwork: "", + fixedRate: true, + floatingRate: true, + ); + pairs.add(pair); + } + + return ExchangeResponse(value: pairs); } @override Future> getEstimate( - String from, String to, Decimal amount, bool fixedRate, bool reversed) { - // TODO: implement getEstimate - throw UnimplementedError(); + String from, + String to, + Decimal amount, + bool fixedRate, + bool reversed, + ) async { + final response = await MajesticBankAPI.instance.calculateOrder( + amount: amount.toString(), + reversed: reversed, + fromCurrency: from, + receiveCurrency: to, + ); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final calc = response.value!; + final estimate = Estimate( + estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount, + fixedRate: fixedRate, + reversed: reversed, + ); + return ExchangeResponse(value: estimate); } @override Future>> getPairsFor( - String currency, bool fixedRate) { - // TODO: implement getPairsFor - throw UnimplementedError(); + String currency, + bool fixedRate, + ) async { + final response = await getAllPairs(fixedRate); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final pairs = response.value!.where( + (e) => + e.from.toUpperCase() == currency.toUpperCase() || + e.to.toUpperCase() == currency.toUpperCase(), + ); + + return ExchangeResponse(value: pairs.toList()); } @override Future> getRange( - String from, String to, bool fixedRate) { - // TODO: implement getRange - throw UnimplementedError(); + String from, + String to, + bool fixedRate, + ) async { + final response = + await MajesticBankAPI.instance.getLimit(fromCurrency: from); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final limit = response.value!; + final range = Range(min: limit.min, max: limit.max); + + return ExchangeResponse(value: range); } @override - Future> getTrade(String tradeId) { + Future> getTrade(String tradeId) async { // TODO: implement getTrade throw UnimplementedError(); } @override - Future>> getTrades() { + Future>> getTrades() async { // TODO: implement getTrades throw UnimplementedError(); } @@ -75,8 +223,41 @@ class MajesticBankExchange extends Exchange { String get name => exchangeName; @override - Future> updateTrade(Trade trade) { - // TODO: implement updateTrade - throw UnimplementedError(); + Future> updateTrade(Trade trade) async { + final response = await MajesticBankAPI.instance.trackOrder( + orderId: trade.tradeId, + ); + + if (response.value != null) { + final status = response.value!; + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: status.orderId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: status.fromCurrency, + payInAmount: status.fromAmount.toString(), + payInAddress: status.address, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: status.receiveCurrency, + payOutAmount: status.receiveAmount.toString(), + payOutAddress: trade.payOutAddress, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: status.status, + exchangeName: exchangeName, + ); + + return ExchangeResponse(value: updatedTrade); + } else { + return ExchangeResponse(exception: response.exception); + } } }