2023-05-26 21:21:16 +00:00
|
|
|
/*
|
|
|
|
* This file is part of Stack Wallet.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2023 Cypher Stack
|
|
|
|
* All Rights Reserved.
|
|
|
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
|
|
* Generated by Cypher Stack on 2023-05-26
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2023-04-28 16:30:31 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2023-04-28 21:27:38 +00:00
|
|
|
import 'package:flutter/foundation.dart';
|
2023-04-28 16:30:31 +00:00
|
|
|
import 'package:flutter_native_splash/cli_commands.dart';
|
|
|
|
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
|
2023-09-08 21:09:59 +00:00
|
|
|
import 'package:stackwallet/networking/http.dart';
|
2023-04-28 16:30:31 +00:00
|
|
|
import 'package:stackwallet/services/exchange/exchange_response.dart';
|
|
|
|
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart';
|
|
|
|
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_rate.dart';
|
|
|
|
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade.dart';
|
|
|
|
import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade_new.dart';
|
2023-09-08 21:09:59 +00:00
|
|
|
import 'package:stackwallet/services/tor_service.dart';
|
2023-04-28 16:30:31 +00:00
|
|
|
import 'package:stackwallet/utilities/logger.dart';
|
2023-09-08 21:09:59 +00:00
|
|
|
import 'package:stackwallet/utilities/prefs.dart';
|
2023-04-28 16:30:31 +00:00
|
|
|
|
|
|
|
const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n";
|
|
|
|
const kTrocadorRefCode = "9eHm9BkQfS";
|
|
|
|
|
|
|
|
abstract class TrocadorAPI {
|
|
|
|
static const String authority = "trocador.app";
|
|
|
|
static const String onionAuthority =
|
|
|
|
"trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion";
|
|
|
|
|
|
|
|
static const String markup = "1";
|
|
|
|
static const String minKYCRating = "C";
|
2023-09-08 21:09:59 +00:00
|
|
|
static HTTP client = HTTP();
|
2023-04-28 16:30:31 +00:00
|
|
|
|
|
|
|
static Uri _buildUri({
|
|
|
|
required String method,
|
|
|
|
required bool isOnion,
|
|
|
|
Map<String, String>? params,
|
|
|
|
}) {
|
|
|
|
return isOnion
|
|
|
|
? Uri.http(onionAuthority, "api/$method", params)
|
|
|
|
: Uri.https(authority, "api/$method", params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<dynamic> _makeGetRequest(Uri uri) async {
|
|
|
|
int code = -1;
|
|
|
|
try {
|
2023-04-28 21:27:38 +00:00
|
|
|
debugPrint("URI: $uri");
|
2023-09-08 21:09:59 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: uri,
|
2023-04-28 16:30:31 +00:00
|
|
|
headers: {'Content-Type': 'application/json'},
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-04-28 16:30:31 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 21:09:59 +00:00
|
|
|
code = response.code;
|
2023-04-28 16:30:31 +00:00
|
|
|
|
2023-11-06 17:23:16 +00:00
|
|
|
// debugPrint("CODE: $code");
|
|
|
|
// debugPrint("BODY: ${response.body}");
|
2023-04-28 16:30:31 +00:00
|
|
|
|
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
|
|
|
|
return json;
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"_makeRequest($uri) HTTP:$code threw: $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// fetch all supported coins
|
|
|
|
static Future<ExchangeResponse<List<TrocadorCoin>>> getCoins({
|
|
|
|
required bool isOnion,
|
|
|
|
}) async {
|
|
|
|
final uri = _buildUri(
|
|
|
|
isOnion: isOnion,
|
|
|
|
method: "coins",
|
|
|
|
params: {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
final json = await _makeGetRequest(uri);
|
|
|
|
|
|
|
|
if (json is List) {
|
|
|
|
final list = List<Map<String, dynamic>>.from(json);
|
|
|
|
final List<TrocadorCoin> coins = list
|
|
|
|
.map(
|
|
|
|
(e) => TrocadorCoin.fromMap(e),
|
|
|
|
)
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
return ExchangeResponse(value: coins);
|
|
|
|
} else {
|
|
|
|
throw Exception("unexpected json: $json");
|
|
|
|
}
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log("getCoins exception: $e\n$s", level: LogLevel.Error);
|
|
|
|
return ExchangeResponse(
|
|
|
|
exception: ExchangeException(
|
|
|
|
e.toString(),
|
|
|
|
ExchangeExceptionType.generic,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// get trade info
|
|
|
|
static Future<ExchangeResponse<TrocadorTrade>> getTrade({
|
|
|
|
required bool isOnion,
|
|
|
|
required String tradeId,
|
|
|
|
}) async {
|
|
|
|
final uri = _buildUri(
|
|
|
|
isOnion: isOnion,
|
|
|
|
method: "trade",
|
|
|
|
params: {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
|
|
|
"id": tradeId,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
final json = await _makeGetRequest(uri);
|
2023-04-28 20:26:51 +00:00
|
|
|
final map = Map<String, dynamic>.from((json as List).first as Map);
|
2023-04-28 16:30:31 +00:00
|
|
|
|
|
|
|
return ExchangeResponse(value: TrocadorTrade.fromMap(map));
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log("getTrade exception: $e\n$s", level: LogLevel.Error);
|
|
|
|
return ExchangeResponse(
|
|
|
|
exception: ExchangeException(
|
|
|
|
e.toString(),
|
|
|
|
ExchangeExceptionType.generic,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// get standard/floating rate
|
2023-04-28 20:26:51 +00:00
|
|
|
static Future<ExchangeResponse<TrocadorRate>> getNewStandardRate({
|
2023-04-28 16:30:31 +00:00
|
|
|
required bool isOnion,
|
|
|
|
required String fromTicker,
|
|
|
|
required String fromNetwork,
|
|
|
|
required String toTicker,
|
|
|
|
required String toNetwork,
|
|
|
|
required String fromAmount,
|
|
|
|
}) async {
|
|
|
|
final params = {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_from": fromTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_from": fromNetwork,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_to": toTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_to": toNetwork,
|
|
|
|
"amount_from": fromAmount,
|
2023-04-28 20:26:51 +00:00
|
|
|
"payment": "false",
|
2023-04-28 16:30:31 +00:00
|
|
|
"min_kycrating": minKYCRating,
|
|
|
|
"markup": markup,
|
|
|
|
};
|
|
|
|
|
|
|
|
return await _getNewRate(isOnion: isOnion, params: params);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// get fixed rate/payment rate
|
2023-04-28 20:26:51 +00:00
|
|
|
static Future<ExchangeResponse<TrocadorRate>> getNewPaymentRate({
|
2023-04-28 16:30:31 +00:00
|
|
|
required bool isOnion,
|
|
|
|
required String fromTicker,
|
|
|
|
required String fromNetwork,
|
|
|
|
required String toTicker,
|
|
|
|
required String toNetwork,
|
|
|
|
required String toAmount,
|
|
|
|
}) async {
|
|
|
|
final params = {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_from": fromTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_from": fromNetwork,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_to": toTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_to": toNetwork,
|
|
|
|
"amount_to": toAmount,
|
2023-04-28 20:26:51 +00:00
|
|
|
"payment": "true",
|
2023-04-28 16:30:31 +00:00
|
|
|
"min_kycrating": minKYCRating,
|
|
|
|
"markup": markup,
|
|
|
|
};
|
|
|
|
|
|
|
|
return await _getNewRate(isOnion: isOnion, params: params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<ExchangeResponse<TrocadorRate>> _getNewRate({
|
|
|
|
required bool isOnion,
|
|
|
|
required Map<String, String> params,
|
|
|
|
}) async {
|
|
|
|
final uri = _buildUri(
|
|
|
|
isOnion: isOnion,
|
|
|
|
method: "new_rate",
|
|
|
|
params: params,
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
final json = await _makeGetRequest(uri);
|
|
|
|
final map = Map<String, dynamic>.from(json as Map);
|
|
|
|
|
|
|
|
return ExchangeResponse(value: TrocadorRate.fromMap(map));
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance
|
|
|
|
.log("getNewRate exception: $e\n$s", level: LogLevel.Error);
|
|
|
|
return ExchangeResponse(
|
|
|
|
exception: ExchangeException(
|
|
|
|
e.toString(),
|
|
|
|
ExchangeExceptionType.generic,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// create new floating rate/standard trade
|
|
|
|
static Future<ExchangeResponse<TrocadorTradeNew>> createNewStandardRateTrade({
|
|
|
|
required bool isOnion,
|
|
|
|
required String? rateId,
|
|
|
|
required String fromTicker,
|
|
|
|
required String fromNetwork,
|
|
|
|
required String toTicker,
|
|
|
|
required String toNetwork,
|
|
|
|
required String fromAmount,
|
|
|
|
required String receivingAddress,
|
|
|
|
required String? receivingMemo,
|
|
|
|
required String refundAddress,
|
|
|
|
required String? refundMemo,
|
|
|
|
required String exchangeProvider,
|
|
|
|
required bool isFixedRate,
|
|
|
|
}) async {
|
|
|
|
final Map<String, String> params = {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_from": fromTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_from": fromNetwork,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_to": toTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_to": toNetwork,
|
|
|
|
"amount_from": fromAmount,
|
|
|
|
"address": receivingAddress,
|
|
|
|
"address_memo": receivingMemo ?? "0",
|
|
|
|
"refund": refundAddress,
|
|
|
|
"refund_memo": refundMemo ?? "0",
|
|
|
|
"provider": exchangeProvider,
|
|
|
|
"fixed": isFixedRate.toString().capitalize(),
|
|
|
|
"payment": "False",
|
|
|
|
"min_kycrating": minKYCRating,
|
|
|
|
"markup": markup,
|
|
|
|
};
|
|
|
|
|
2023-04-28 20:26:51 +00:00
|
|
|
if (rateId != null) {
|
|
|
|
params["id"] = rateId;
|
|
|
|
}
|
|
|
|
|
2023-04-28 16:30:31 +00:00
|
|
|
return await _getNewTrade(isOnion: isOnion, params: params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<ExchangeResponse<TrocadorTradeNew>> createNewPaymentRateTrade({
|
|
|
|
required bool isOnion,
|
2023-04-28 20:26:51 +00:00
|
|
|
required String? rateId,
|
2023-04-28 16:30:31 +00:00
|
|
|
required String fromTicker,
|
|
|
|
required String fromNetwork,
|
|
|
|
required String toTicker,
|
|
|
|
required String toNetwork,
|
|
|
|
required String toAmount,
|
|
|
|
required String receivingAddress,
|
|
|
|
required String? receivingMemo,
|
|
|
|
required String refundAddress,
|
|
|
|
required String? refundMemo,
|
|
|
|
required String exchangeProvider,
|
|
|
|
required bool isFixedRate,
|
|
|
|
}) async {
|
|
|
|
final params = {
|
|
|
|
"api_key": kTrocadorApiKey,
|
|
|
|
"ref": kTrocadorRefCode,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_from": fromTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_from": fromNetwork,
|
2023-04-28 20:26:51 +00:00
|
|
|
"ticker_to": toTicker.toLowerCase(),
|
2023-04-28 16:30:31 +00:00
|
|
|
"network_to": toNetwork,
|
|
|
|
"amount_to": toAmount,
|
|
|
|
"address": receivingAddress,
|
|
|
|
"address_memo": receivingMemo ?? "0",
|
|
|
|
"refund": refundAddress,
|
|
|
|
"refund_memo": refundMemo ?? "0",
|
|
|
|
"provider": exchangeProvider,
|
|
|
|
"fixed": isFixedRate.toString().capitalize(),
|
|
|
|
"payment": "True",
|
|
|
|
"min_kycrating": minKYCRating,
|
|
|
|
"markup": markup,
|
|
|
|
};
|
|
|
|
|
2023-04-28 20:26:51 +00:00
|
|
|
if (rateId != null) {
|
|
|
|
params["id"] = rateId;
|
|
|
|
}
|
|
|
|
|
2023-04-28 16:30:31 +00:00
|
|
|
return await _getNewTrade(isOnion: isOnion, params: params);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<ExchangeResponse<TrocadorTradeNew>> _getNewTrade({
|
|
|
|
required bool isOnion,
|
|
|
|
required Map<String, String> params,
|
|
|
|
}) async {
|
|
|
|
final uri = _buildUri(
|
|
|
|
isOnion: isOnion,
|
|
|
|
method: "new_trade",
|
|
|
|
params: params,
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
final json = await _makeGetRequest(uri);
|
|
|
|
final map = Map<String, dynamic>.from(json as Map);
|
|
|
|
|
2023-05-05 16:17:20 +00:00
|
|
|
try {
|
|
|
|
return ExchangeResponse(value: TrocadorTradeNew.fromMap(map));
|
|
|
|
} catch (e, s) {
|
|
|
|
String error = map["error"] as String? ?? json.toString();
|
|
|
|
if (error ==
|
|
|
|
"trade could not be generated, some unknown error happened") {
|
|
|
|
error =
|
|
|
|
"This trade couldn't be completed. Please select another provider.";
|
|
|
|
}
|
|
|
|
|
|
|
|
Logging.instance.log(
|
|
|
|
"_getNewTrade failed to parse response: $json\n$e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return ExchangeResponse(
|
|
|
|
exception: ExchangeException(
|
|
|
|
error,
|
|
|
|
ExchangeExceptionType.serializeResponseError,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2023-04-28 16:30:31 +00:00
|
|
|
} catch (e, s) {
|
2023-05-05 16:17:20 +00:00
|
|
|
Logging.instance.log(
|
|
|
|
"_getNewTrade exception: $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
2023-04-28 16:30:31 +00:00
|
|
|
return ExchangeResponse(
|
|
|
|
exception: ExchangeException(
|
|
|
|
e.toString(),
|
|
|
|
ExchangeExceptionType.generic,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|