mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-27 05:49:21 +00:00
504 lines
15 KiB
Dart
504 lines
15 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:decimal/decimal.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:http/http.dart' as http;
|
|
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';
|
|
import 'package:stackwallet/models/exchange/response_objects/range.dart';
|
|
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
|
import 'package:stackwallet/models/exchange/simpleswap/sp_currency.dart';
|
|
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
|
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
|
|
import 'package:stackwallet/utilities/logger.dart';
|
|
import 'package:tuple/tuple.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
class SimpleSwapAPI {
|
|
static const String scheme = "https";
|
|
static const String authority = "api.simpleswap.io";
|
|
|
|
SimpleSwapAPI._();
|
|
static final SimpleSwapAPI _instance = SimpleSwapAPI._();
|
|
static SimpleSwapAPI get instance => _instance;
|
|
|
|
/// set this to override using standard http client. Useful for testing
|
|
http.Client? client;
|
|
|
|
Uri _buildUri(String path, Map<String, String>? params) {
|
|
return Uri.https(authority, path, params);
|
|
}
|
|
|
|
Future<dynamic> _makeGetRequest(Uri uri) async {
|
|
final client = this.client ?? http.Client();
|
|
int code = -1;
|
|
try {
|
|
final response = await client.get(
|
|
uri,
|
|
);
|
|
|
|
code = response.statusCode;
|
|
|
|
final parsed = jsonDecode(response.body);
|
|
|
|
return parsed;
|
|
} catch (e, s) {
|
|
Logging.instance.log(
|
|
"_makeRequest($uri) HTTP:$code threw: $e\n$s",
|
|
level: LogLevel.Error,
|
|
);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<dynamic> _makePostRequest(
|
|
Uri uri,
|
|
Map<String, dynamic> body,
|
|
) async {
|
|
final client = this.client ?? http.Client();
|
|
try {
|
|
final response = await client.post(
|
|
uri,
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final parsed = jsonDecode(response.body);
|
|
return parsed;
|
|
}
|
|
|
|
throw Exception("response: ${response.body}");
|
|
} catch (e, s) {
|
|
Logging.instance
|
|
.log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<ExchangeResponse<Trade>> createNewExchange({
|
|
required bool isFixedRate,
|
|
required String currencyFrom,
|
|
required String currencyTo,
|
|
required String addressTo,
|
|
required String userRefundAddress,
|
|
required String userRefundExtraId,
|
|
required String amount,
|
|
String? extraIdTo,
|
|
String? apiKey,
|
|
}) async {
|
|
Map<String, dynamic> body = {
|
|
"fixed": isFixedRate,
|
|
"currency_from": currencyFrom,
|
|
"currency_to": currencyTo,
|
|
"addressTo": addressTo,
|
|
"userRefundAddress": userRefundAddress,
|
|
"userRefundExtraId": userRefundExtraId,
|
|
"amount": double.parse(amount),
|
|
"extraIdTo": extraIdTo,
|
|
};
|
|
|
|
final uri =
|
|
_buildUri("/create_exchange", {"api_key": apiKey ?? kSimpleSwapApiKey});
|
|
|
|
try {
|
|
final jsonObject = await _makePostRequest(uri, body);
|
|
|
|
final json = Map<String, dynamic>.from(jsonObject as Map);
|
|
final trade = Trade(
|
|
uuid: const Uuid().v1(),
|
|
tradeId: json["id"] as String,
|
|
rateType: json["type"] as String,
|
|
direction: "direct",
|
|
timestamp: DateTime.parse(json["timestamp"] as String),
|
|
updatedAt: DateTime.parse(json["updated_at"] as String),
|
|
payInCurrency: json["currency_from"] as String,
|
|
payInAmount: json["amount_from"] as String,
|
|
payInAddress: json["address_from"] as String,
|
|
payInNetwork: "",
|
|
payInExtraId: json["extra_id_from"] as String? ?? "",
|
|
payInTxid: json["tx_from"] as String? ?? "",
|
|
payOutCurrency: json["currency_to"] as String,
|
|
payOutAmount: json["amount_to"] as String,
|
|
payOutAddress: json["address_to"] as String,
|
|
payOutNetwork: "",
|
|
payOutExtraId: json["extra_id_to"] as String? ?? "",
|
|
payOutTxid: json["tx_to"] as String? ?? "",
|
|
refundAddress: json["user_refund_address"] as String,
|
|
refundExtraId: json["user_refund_extra_id"] as String,
|
|
status: json["status"] as String,
|
|
exchangeName: SimpleSwapExchange.exchangeName,
|
|
);
|
|
return ExchangeResponse(value: trade, exception: null);
|
|
} catch (e, s) {
|
|
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
value: null,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<ExchangeResponse<List<SPCurrency>>> getAllCurrencies({
|
|
String? apiKey,
|
|
required bool fixedRate,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_all_currencies", {"api_key": apiKey ?? kSimpleSwapApiKey});
|
|
|
|
try {
|
|
final jsonArray = await _makeGetRequest(uri);
|
|
|
|
return await compute(_parseAvailableCurrenciesJson, jsonArray as List);
|
|
} catch (e, s) {
|
|
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
ExchangeResponse<List<SPCurrency>> _parseAvailableCurrenciesJson(
|
|
List<dynamic> jsonArray) {
|
|
try {
|
|
List<SPCurrency> currencies = [];
|
|
|
|
for (final json in jsonArray) {
|
|
try {
|
|
currencies
|
|
.add(SPCurrency.fromJson(Map<String, dynamic>.from(json as Map)));
|
|
} catch (_) {
|
|
return ExchangeResponse(
|
|
exception: ExchangeException("Failed to serialize $json",
|
|
ExchangeExceptionType.serializeResponseError));
|
|
}
|
|
}
|
|
|
|
return ExchangeResponse(value: currencies);
|
|
} catch (e, s) {
|
|
Logging.instance.log("_parseAvailableCurrenciesJson exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<ExchangeResponse<SPCurrency>> getCurrency({
|
|
required String symbol,
|
|
String? apiKey,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_currency",
|
|
{
|
|
"api_key": apiKey ?? kSimpleSwapApiKey,
|
|
"symbol": symbol,
|
|
},
|
|
);
|
|
|
|
try {
|
|
final jsonObject = await _makeGetRequest(uri);
|
|
|
|
return ExchangeResponse(
|
|
value: SPCurrency.fromJson(
|
|
Map<String, dynamic>.from(jsonObject as Map)));
|
|
} catch (e, s) {
|
|
Logging.instance
|
|
.log("getCurrency exception: $e\n$s", level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// returns a map where the key currency symbol is a valid pair with any of
|
|
/// the symbols in its value list
|
|
Future<ExchangeResponse<List<Pair>>> getAllPairs({
|
|
required bool isFixedRate,
|
|
String? apiKey,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_all_pairs",
|
|
{
|
|
"api_key": apiKey ?? kSimpleSwapApiKey,
|
|
"fixed": isFixedRate.toString(),
|
|
},
|
|
);
|
|
|
|
try {
|
|
final jsonObject = await _makeGetRequest(uri);
|
|
final result = await compute(
|
|
_parseAvailablePairsJson,
|
|
Tuple2(jsonObject as Map, isFixedRate),
|
|
);
|
|
return result;
|
|
} catch (e, s) {
|
|
Logging.instance
|
|
.log("getAllPairs exception: $e\n$s", level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
ExchangeResponse<List<Pair>> _parseAvailablePairsJson(
|
|
Tuple2<Map<dynamic, dynamic>, bool> args,
|
|
) {
|
|
try {
|
|
List<Pair> pairs = [];
|
|
|
|
for (final entry in args.item1.entries) {
|
|
try {
|
|
final from = entry.key as String;
|
|
for (final to in entry.value as List) {
|
|
pairs.add(
|
|
Pair(
|
|
from: from,
|
|
fromNetwork: "",
|
|
to: to as String,
|
|
toNetwork: "",
|
|
fixedRate: args.item2,
|
|
floatingRate: !args.item2,
|
|
),
|
|
);
|
|
}
|
|
} catch (_) {
|
|
return ExchangeResponse(
|
|
exception: ExchangeException("Failed to serialize $json",
|
|
ExchangeExceptionType.serializeResponseError));
|
|
}
|
|
}
|
|
|
|
return ExchangeResponse(value: pairs);
|
|
} catch (e, s) {
|
|
Logging.instance.log("_parseAvailableCurrenciesJson exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// returns the estimated amount as a string
|
|
Future<ExchangeResponse<String>> getEstimated({
|
|
required bool isFixedRate,
|
|
required String currencyFrom,
|
|
required String currencyTo,
|
|
required String amount,
|
|
String? apiKey,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_estimated",
|
|
{
|
|
"api_key": apiKey ?? kSimpleSwapApiKey,
|
|
"fixed": isFixedRate.toString(),
|
|
"currency_from": currencyFrom,
|
|
"currency_to": currencyTo,
|
|
"amount": amount,
|
|
},
|
|
);
|
|
|
|
try {
|
|
final jsonObject = await _makeGetRequest(uri);
|
|
|
|
return ExchangeResponse(value: jsonObject as String);
|
|
} catch (e, s) {
|
|
Logging.instance
|
|
.log("getEstimated exception: $e\n$s", level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// returns the exchange for the given id
|
|
Future<ExchangeResponse<Trade>> getExchange({
|
|
required String exchangeId,
|
|
String? apiKey,
|
|
Trade? oldTrade,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_exchange",
|
|
{
|
|
"api_key": apiKey ?? kSimpleSwapApiKey,
|
|
"id": exchangeId,
|
|
},
|
|
);
|
|
|
|
try {
|
|
final jsonObject = await _makeGetRequest(uri);
|
|
|
|
final json = Map<String, dynamic>.from(jsonObject as Map);
|
|
final ts = DateTime.parse(json["timestamp"] as String);
|
|
final trade = Trade(
|
|
uuid: oldTrade?.uuid ?? const Uuid().v1(),
|
|
tradeId: json["id"] as String,
|
|
rateType: json["type"] as String,
|
|
direction: "direct",
|
|
timestamp: ts,
|
|
updatedAt: DateTime.tryParse(json["updated_at"] as String? ?? "") ?? ts,
|
|
payInCurrency: json["currency_from"] as String,
|
|
payInAmount: json["amount_from"] as String,
|
|
payInAddress: json["address_from"] as String,
|
|
payInNetwork: "",
|
|
payInExtraId: json["extra_id_from"] as String? ?? "",
|
|
payInTxid: json["tx_from"] as String? ?? "",
|
|
payOutCurrency: json["currency_to"] as String,
|
|
payOutAmount: json["amount_to"] as String,
|
|
payOutAddress: json["address_to"] as String,
|
|
payOutNetwork: "",
|
|
payOutExtraId: json["extra_id_to"] as String? ?? "",
|
|
payOutTxid: json["tx_to"] as String? ?? "",
|
|
refundAddress: json["user_refund_address"] as String,
|
|
refundExtraId: json["user_refund_extra_id"] as String,
|
|
status: json["status"] as String,
|
|
exchangeName: SimpleSwapExchange.exchangeName,
|
|
);
|
|
|
|
return ExchangeResponse(value: trade);
|
|
} catch (e, s) {
|
|
Logging.instance
|
|
.log("getExchange exception: $e\n$s", level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// returns the minimal exchange amount
|
|
Future<ExchangeResponse<Range>> getRange({
|
|
required bool isFixedRate,
|
|
required String currencyFrom,
|
|
required String currencyTo,
|
|
String? apiKey,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_ranges",
|
|
{
|
|
"api_key": apiKey ?? kSimpleSwapApiKey,
|
|
"fixed": isFixedRate.toString(),
|
|
"currency_from": currencyFrom,
|
|
"currency_to": currencyTo,
|
|
},
|
|
);
|
|
|
|
try {
|
|
final jsonObject = await _makeGetRequest(uri);
|
|
|
|
final json = Map<String, dynamic>.from(jsonObject as Map);
|
|
return ExchangeResponse(
|
|
value: Range(
|
|
max: Decimal.tryParse(json["max"] as String? ?? ""),
|
|
min: Decimal.tryParse(json["min"] as String? ?? ""),
|
|
),
|
|
);
|
|
} catch (e, s) {
|
|
Logging.instance.log("getRange exception: $e\n$s", level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<ExchangeResponse<List<FixedRateMarket>>> getFixedRateMarketInfo({
|
|
String? apiKey,
|
|
}) async {
|
|
final uri = _buildUri(
|
|
"/get_market_info",
|
|
null,
|
|
// {
|
|
// "api_key": apiKey ?? kSimpleSwapApiKey,
|
|
// "fixed": isFixedRate.toString(),
|
|
// "currency_from": currencyFrom,
|
|
// "currency_to": currencyTo,
|
|
// },
|
|
);
|
|
|
|
try {
|
|
final jsonArray = await _makeGetRequest(uri);
|
|
|
|
try {
|
|
final result = await compute(
|
|
_parseFixedRateMarketsJson,
|
|
jsonArray as List,
|
|
);
|
|
return result;
|
|
} catch (e, s) {
|
|
Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
"Error: $jsonArray",
|
|
ExchangeExceptionType.serializeResponseError,
|
|
),
|
|
);
|
|
}
|
|
} catch (e, s) {
|
|
Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s",
|
|
level: LogLevel.Error);
|
|
return ExchangeResponse(
|
|
exception: ExchangeException(
|
|
e.toString(),
|
|
ExchangeExceptionType.generic,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
ExchangeResponse<List<FixedRateMarket>> _parseFixedRateMarketsJson(
|
|
List<dynamic> jsonArray) {
|
|
try {
|
|
final List<FixedRateMarket> markets = [];
|
|
for (final json in jsonArray) {
|
|
try {
|
|
final map = Map<String, dynamic>.from(json as Map);
|
|
markets.add(FixedRateMarket(
|
|
from: map["currency_from"] as String,
|
|
to: map["currency_to"] as String,
|
|
min: Decimal.parse(map["min"] as String),
|
|
max: Decimal.parse(map["max"] as String),
|
|
rate: Decimal.parse(map["rate"] as String),
|
|
minerFee: null,
|
|
));
|
|
} catch (_) {
|
|
return ExchangeResponse(
|
|
exception: ExchangeException("Failed to serialize $json",
|
|
ExchangeExceptionType.serializeResponseError));
|
|
}
|
|
}
|
|
return ExchangeResponse(value: markets);
|
|
} catch (_) {
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|