mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 13:14:32 +00:00
Merge branch 'majestic_bank' into paynyms
This commit is contained in:
commit
62f7ebbc3c
15 changed files with 801 additions and 15 deletions
13
lib/exceptions/exchange/exchange_exception.dart
Normal file
13
lib/exceptions/exchange/exchange_exception.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
5
lib/exceptions/exchange/majestic_bank/mb_exception.dart
Normal file
5
lib/exceptions/exchange/majestic_bank/mb_exception.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
||||||
|
|
||||||
|
class MBException extends ExchangeException {
|
||||||
|
MBException(super.message, super.type);
|
||||||
|
}
|
19
lib/models/exchange/majestic_bank/mb_limit.dart
Normal file
19
lib/models/exchange/majestic_bank/mb_limit.dart
Normal file
|
@ -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 } }";
|
||||||
|
}
|
||||||
|
}
|
1
lib/models/exchange/majestic_bank/mb_object.dart
Normal file
1
lib/models/exchange/majestic_bank/mb_object.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
abstract class MBObject {}
|
43
lib/models/exchange/majestic_bank/mb_order.dart
Normal file
43
lib/models/exchange/majestic_bank/mb_order.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
21
lib/models/exchange/majestic_bank/mb_order_calculation.dart
Normal file
21
lib/models/exchange/majestic_bank/mb_order_calculation.dart
Normal file
|
@ -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 }";
|
||||||
|
}
|
||||||
|
}
|
32
lib/models/exchange/majestic_bank/mb_order_status.dart
Normal file
32
lib/models/exchange/majestic_bank/mb_order_status.dart
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
15
lib/models/exchange/majestic_bank/mb_rate.dart
Normal file
15
lib/models/exchange/majestic_bank/mb_rate.dart
Normal file
|
@ -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 }";
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
import '../../../services/exchange/majestic_bank/majestic_bank_api.dart';
|
||||||
|
|
||||||
class HiddenSettings extends StatelessWidget {
|
class HiddenSettings extends StatelessWidget {
|
||||||
const HiddenSettings({Key? key}) : super(key: key);
|
const HiddenSettings({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@ -128,6 +130,48 @@ 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<StackColors>()!
|
||||||
|
// .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<StackColors>()!
|
||||||
|
.accentColorDark),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
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/external_api_keys.dart';
|
||||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
||||||
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
|
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
|
||||||
|
|
|
@ -55,7 +55,7 @@ class ExchangeDataLoadingService {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logging.instance.log(
|
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);
|
level: LogLevel.Error);
|
||||||
|
|
||||||
ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
|
ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
|
||||||
|
@ -122,7 +122,7 @@ class ExchangeDataLoadingService {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logging.instance.log(
|
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);
|
level: LogLevel.Error);
|
||||||
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
|
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
|
||||||
ChangeNowLoadStatus.failed;
|
ChangeNowLoadStatus.failed;
|
||||||
|
@ -130,7 +130,7 @@ class ExchangeDataLoadingService {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Failed to load changeNOW currencies: ${response.exception?.errorMessage}",
|
"Failed to load changeNOW currencies: ${response.exception?.message}",
|
||||||
level: LogLevel.Error);
|
level: LogLevel.Error);
|
||||||
await Future<void>.delayed(const Duration(seconds: 3));
|
await Future<void>.delayed(const Duration(seconds: 3));
|
||||||
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
|
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
|
||||||
|
|
|
@ -1,15 +1,4 @@
|
||||||
enum ExchangeExceptionType { generic, serializeResponseError }
|
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
||||||
|
|
||||||
class ExchangeException implements Exception {
|
|
||||||
String errorMessage;
|
|
||||||
ExchangeExceptionType type;
|
|
||||||
ExchangeException(this.errorMessage, this.type);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExchangeResponse<T> {
|
class ExchangeResponse<T> {
|
||||||
late final T? value;
|
late final T? value;
|
||||||
|
|
339
lib/services/exchange/majestic_bank/majestic_bank_api.dart
Normal file
339
lib/services/exchange/majestic_bank/majestic_bank_api.dart
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
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';
|
||||||
|
import 'package:stackwallet/models/exchange/majestic_bank/mb_order_status.dart';
|
||||||
|
import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart';
|
||||||
|
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";
|
||||||
|
static const String refCode = "fixme";
|
||||||
|
|
||||||
|
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;
|
||||||
|
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<ExchangeResponse<List<MBRate>>> getRates() async {
|
||||||
|
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);
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = _buildUri(
|
||||||
|
endpoint: "calculate",
|
||||||
|
params: params,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final jsonObject = await _makeGetRequest(uri);
|
||||||
|
final map = Map<String, dynamic>.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 ExchangeResponse(value: result);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance
|
||||||
|
.log("calculateOrder exception: $e\n$s", level: LogLevel.Error);
|
||||||
|
return ExchangeResponse(
|
||||||
|
exception: ExchangeException(
|
||||||
|
e.toString(),
|
||||||
|
ExchangeExceptionType.generic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ExchangeResponse<MBOrder>> createOrder({
|
||||||
|
required String fromAmount,
|
||||||
|
required String fromCurrency,
|
||||||
|
required String receiveCurrency,
|
||||||
|
required String receiveAddress,
|
||||||
|
}) async {
|
||||||
|
final params = {
|
||||||
|
"from_amount": fromAmount,
|
||||||
|
"from_currency": fromCurrency,
|
||||||
|
"receive_currency": receiveCurrency,
|
||||||
|
"receive_address": receiveAddress,
|
||||||
|
"referral_code": refCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
final uri = _buildUri(endpoint: "exchange", params: params);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final jsonObject = await _makeGetRequest(uri);
|
||||||
|
final json = Map<String, dynamic>.from(jsonObject as Map);
|
||||||
|
|
||||||
|
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: order);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance
|
||||||
|
.log("createOrder exception: $e\n$s", level: LogLevel.Error);
|
||||||
|
return ExchangeResponse(
|
||||||
|
exception: ExchangeException(
|
||||||
|
e.toString(),
|
||||||
|
ExchangeExceptionType.generic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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({
|
||||||
|
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);
|
||||||
|
|
||||||
|
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: order);
|
||||||
|
} 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 {
|
||||||
|
final uri = _buildUri(endpoint: "track", params: {
|
||||||
|
"trx": orderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final jsonObject = await _makeGetRequest(uri);
|
||||||
|
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()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ExchangeResponse(value: status);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance
|
||||||
|
.log("createOrder exception: $e\n$s", level: LogLevel.Error);
|
||||||
|
return ExchangeResponse(
|
||||||
|
exception: ExchangeException(
|
||||||
|
e.toString(),
|
||||||
|
ExchangeExceptionType.generic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
Normal file
263
lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
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';
|
||||||
|
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<ExchangeResponse<Trade>> 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<MBOrder>? 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<ExchangeResponse<List<Currency>>> getAllCurrencies(
|
||||||
|
bool fixedRate,
|
||||||
|
) async {
|
||||||
|
final response = await MajesticBankAPI.instance.getLimits();
|
||||||
|
if (response.value == null) {
|
||||||
|
return ExchangeResponse(exception: response.exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Currency> 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<ExchangeResponse<List<Pair>>> getAllPairs(bool fixedRate) async {
|
||||||
|
final response = await MajesticBankAPI.instance.getRates();
|
||||||
|
if (response.value == null) {
|
||||||
|
return ExchangeResponse(exception: response.exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Pair> 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<ExchangeResponse<Estimate>> getEstimate(
|
||||||
|
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<ExchangeResponse<List<Pair>>> getPairsFor(
|
||||||
|
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<ExchangeResponse<Range>> getRange(
|
||||||
|
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<ExchangeResponse<Trade>> getTrade(String tradeId) async {
|
||||||
|
// TODO: implement getTrade
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ExchangeResponse<List<Trade>>> getTrades() async {
|
||||||
|
// TODO: implement getTrades
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => exchangeName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ExchangeResponse<Trade>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
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/external_api_keys.dart';
|
||||||
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
|
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
|
||||||
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
|
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
|
||||||
|
|
Loading…
Reference in a new issue