Cw 473 exchange refactoring (#1126)

* CW-473 Remove deprecated Exchanges

* CW-473 Clean up Exchange Code

* CW-473 Add Decimals to Crypto Enums

* CW-473 Fix minor merge error

* CW-473 Fix minor merge error

* CW-473 Fix FAQ Locals

* CW-473 Apply requested changes

* CW-473 Show Error if Exchange Provider is not supported anymore

* CW-473 Implement Requested Changes
This commit is contained in:
Konstantin Ullrich
2023-10-25 23:58:25 +03:00
committed by GitHub
parent db7f025b71
commit cef3029f6f
74 changed files with 941 additions and 1783 deletions
@@ -1,22 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class ChangeNowRequest extends TradeRequest {
ChangeNowRequest(
{required this.from,
required this.to,
required this.address,
required this.fromAmount,
required this.toAmount,
required this.refundAddress,
required this.isReverse});
CryptoCurrency from;
CryptoCurrency to;
String address;
String fromAmount;
String toAmount;
String refundAddress;
bool isReverse;
}
+1 -4
View File
@@ -1,10 +1,7 @@
import 'package:cw_core/crypto_currency.dart';
class ExchangePair {
ExchangePair({
required this.from,
required this.to,
this.reverse = true});
ExchangePair({required this.from, required this.to, this.reverse = true});
final CryptoCurrency from;
final CryptoCurrency to;
@@ -14,16 +14,12 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
static const morphToken =
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
static const sideShift =
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
static const simpleSwap = ExchangeProviderDescription(
title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
static const trocador =
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
+1 -2
View File
@@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/exchange/trade.dart';
abstract class ExchangeTradeState {}
@@ -18,4 +17,4 @@ class TradeIsCreatedFailure extends ExchangeTradeState {
final String title;
final String error;
}
}
-20
View File
@@ -1,20 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class ExolixRequest extends TradeRequest {
ExolixRequest(
{required this.from,
required this.to,
required this.address,
required this.fromAmount,
required this.toAmount,
required this.refundAddress});
CryptoCurrency from;
CryptoCurrency to;
String address;
String fromAmount;
String toAmount;
String refundAddress;
}
+2 -2
View File
@@ -1,6 +1,6 @@
class Limits {
const Limits({this.min, this.max});
final double? min;
final double? max;
}
}
-1
View File
@@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/exchange/limits.dart';
abstract class LimitsState {}
@@ -1,225 +0,0 @@
import 'dart:convert';
import 'package:cw_core/amount_converter.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_request.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_not_created_exeption.dart';
class MorphTokenExchangeProvider extends ExchangeProvider {
MorphTokenExchangeProvider({required this.trades})
: super(pairList: [
ExchangePair(from: CryptoCurrency.xmr, to: CryptoCurrency.eth),
ExchangePair(from: CryptoCurrency.xmr, to: CryptoCurrency.bch),
ExchangePair(from: CryptoCurrency.xmr, to: CryptoCurrency.ltc),
ExchangePair(from: CryptoCurrency.xmr, to: CryptoCurrency.dash),
ExchangePair(from: CryptoCurrency.dash, to: CryptoCurrency.btc),
ExchangePair(from: CryptoCurrency.dash, to: CryptoCurrency.eth),
ExchangePair(from: CryptoCurrency.dash, to: CryptoCurrency.bch),
ExchangePair(from: CryptoCurrency.dash, to: CryptoCurrency.ltc),
ExchangePair(from: CryptoCurrency.dash, to: CryptoCurrency.xmr),
ExchangePair(from: CryptoCurrency.ltc, to: CryptoCurrency.btc),
ExchangePair(from: CryptoCurrency.ltc, to: CryptoCurrency.eth),
ExchangePair(from: CryptoCurrency.ltc, to: CryptoCurrency.bch),
ExchangePair(from: CryptoCurrency.ltc, to: CryptoCurrency.dash),
ExchangePair(from: CryptoCurrency.ltc, to: CryptoCurrency.xmr),
ExchangePair(from: CryptoCurrency.bch, to: CryptoCurrency.btc),
ExchangePair(from: CryptoCurrency.bch, to: CryptoCurrency.eth),
ExchangePair(from: CryptoCurrency.bch, to: CryptoCurrency.ltc),
ExchangePair(from: CryptoCurrency.bch, to: CryptoCurrency.dash),
ExchangePair(from: CryptoCurrency.bch, to: CryptoCurrency.xmr),
ExchangePair(from: CryptoCurrency.eth, to: CryptoCurrency.btc),
ExchangePair(from: CryptoCurrency.eth, to: CryptoCurrency.bch),
ExchangePair(from: CryptoCurrency.eth, to: CryptoCurrency.ltc),
ExchangePair(from: CryptoCurrency.eth, to: CryptoCurrency.dash),
ExchangePair(from: CryptoCurrency.eth, to: CryptoCurrency.xmr),
ExchangePair(from: CryptoCurrency.btc, to: CryptoCurrency.eth),
ExchangePair(from: CryptoCurrency.btc, to: CryptoCurrency.bch),
ExchangePair(from: CryptoCurrency.btc, to: CryptoCurrency.ltc),
ExchangePair(from: CryptoCurrency.btc, to: CryptoCurrency.dash),
ExchangePair(from: CryptoCurrency.btc, to: CryptoCurrency.xmr)
]);
Box<Trade> trades;
static const apiUri = 'https://api.morphtoken.com';
static const _morphURISuffix = '/morph';
static const _limitsURISuffix = '/limits';
static const _ratesURISuffix = '/rates';
static const weight = 10000;
@override
String get title => 'MorphToken';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => false;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.morphToken;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final url = apiUri + _limitsURISuffix;
final uri = Uri.parse(url);
final headers = {'Content-type': 'application/json'};
final body = json.encode({
"input": {"asset": from.toString()},
"output": [
{"asset": to.toString(), "weight": weight}
]
});
final response = await post(uri, headers: headers, body: body);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = responseJSON['input']['limits']['min'] as int;
int max = 0;
double ethMax;
if (from == CryptoCurrency.eth) {
ethMax = responseJSON['input']['limits']['max'] as double;
} else {
max = responseJSON['input']['limits']['max'] as int;
}
final minFormatted = AmountConverter.amountIntToDouble(from, min);
final maxFormatted = AmountConverter.amountIntToDouble(from, max);
return Limits(min: minFormatted, max: maxFormatted);
}
@override
Future<Trade> createTrade({
required TradeRequest request,
required bool isFixedRateMode}) async {
const url = apiUri + _morphURISuffix;
final _request = request as MorphTokenRequest;
final body = {
"input": {
"asset": _request.from.toString(),
"refund": _request.refundAddress
},
"output": [
{
"asset": _request.to.toString(),
"weight": weight,
"address": _request.address
}
],
"tag": "cakewallet"
};
final uri = Uri.parse(url);
final response = await post(uri,
headers: {'Content-Type': 'application/json'}, body: json.encode(body));
if (response.statusCode != 200) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['description'] as String;
throw TradeNotCreatedException(description, description: error);
}
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
return Trade(
id: id,
provider: description,
from: _request.from,
to: _request.to,
state: TradeState.created,
amount: _request.amount,
createdAt: DateTime.now());
}
@override
Future<Trade> findTradeById({required String id}) async {
final url = apiUri + _morphURISuffix + '/' + id;
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode != 200) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['description'] as String;
throw TradeNotFoundException(id,
provider: description, description: error);
}
throw TradeNotFoundException(id, provider: description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromCurrency = responseJSON['input']['asset'] as String;
final from = CryptoCurrency.fromString(fromCurrency.toLowerCase());
final toCurrency = responseJSON['output'][0]['asset'] as String;
final to = CryptoCurrency.fromString(toCurrency.toLowerCase());
final inputAddress = responseJSON['input']['deposit_address'] as String;
final status = responseJSON['state'] as String;
final state = TradeState.deserialize(raw: status.toLowerCase());
String amount = "";
for (final trade in trades.values) {
if (trade.id == id) {
amount = trade.amount;
break;
}
}
return Trade(
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
state: state);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
final url = apiUri + _ratesURISuffix;
final uri = Uri.parse(url);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final rate = responseJSON['data'][from.toString()][to.toString()] as String;
try {
final estimatedAmount = double.parse(rate) * amount;
return estimatedAmount;
} catch (_) {
return 0.0;
}
}
}
@@ -1,18 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class MorphTokenRequest extends TradeRequest {
MorphTokenRequest(
{required this.from,
required this.to,
required this.address,
required this.amount,
required this.refundAddress});
CryptoCurrency from;
CryptoCurrency to;
String address;
String amount;
String refundAddress;
}
@@ -1,33 +1,32 @@
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/distribution_info.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/changenow/changenow_request.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:http/http.dart';
class ChangeNowExchangeProvider extends ExchangeProvider {
ChangeNowExchangeProvider({required this.settingsStore})
: _lastUsedRateId = '',
super(
pairList: CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList());
ChangeNowExchangeProvider({required SettingsStore settingsStore})
: _settingsStore = settingsStore,
_lastUsedRateId = '',
super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.zaddr,
CryptoCurrency.xhv,
];
static final apiKey =
DeviceInfo.instance.isMobile ? secrets.changeNowApiKey : secrets.changeNowApiKeyDesktop;
@@ -38,6 +37,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
static const rangePath = '/v2/exchange/range';
static const apiHeaderKey = 'x-changenow-api-key';
final SettingsStore _settingsStore;
String _lastUsedRateId;
@override
String get title => 'ChangeNOW';
@@ -56,25 +58,18 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
@override
Future<bool> checkIsAvailable() async => true;
final SettingsStore settingsStore;
String _lastUsedRateId;
static String getFlow(bool isFixedRate) => isFixedRate ? 'fixed-rate' : 'standard';
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final headers = {apiHeaderKey: apiKey};
final flow = getFlow(isFixedRateMode);
final params = <String, String>{
'fromCurrency': _normalizeCurrency(from),
'toCurrency': _normalizeCurrency(to),
'fromNetwork': _networkFor(from),
'toNetwork': _networkFor(to),
'flow': flow
'flow': _getFlow(isFixedRateMode)
};
final uri = Uri.https(apiAuthority, rangePath, params);
final response = await get(uri, headers: headers);
@@ -86,20 +81,61 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
throw Exception('${error}\n$message');
}
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return Limits(
min: responseJSON['minAmount'] as double?, max: responseJSON['maxAmount'] as double?);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) return 0.0;
final headers = {apiHeaderKey: apiKey};
final isReverse = isReceiveAmount;
final type = isReverse ? 'reverse' : 'direct';
final params = <String, String>{
'fromCurrency': _normalizeCurrency(from),
'toCurrency': _normalizeCurrency(to),
'fromNetwork': _networkFor(from),
'toNetwork': _networkFor(to),
'type': type,
'flow': _getFlow(isFixedRateMode)
};
if (isReverse)
params['toAmount'] = amount.toString();
else
params['fromAmount'] = amount.toString();
final uri = Uri.https(apiAuthority, estimatedAmountPath, params);
final response = await get(uri, headers: headers);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromAmount = double.parse(responseJSON['fromAmount'].toString());
final toAmount = double.parse(responseJSON['toAmount'].toString());
final rateId = responseJSON['rateId'] as String? ?? '';
if (rateId.isNotEmpty) _lastUsedRateId = rateId;
return isReverse ? (amount / fromAmount) : (toAmount / amount);
} catch (e) {
print(e.toString());
return 0.0;
}
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as ChangeNowRequest;
final distributionPath = await DistributionInfo.instance.getDistributionPath();
final formattedAppVersion = int.tryParse(settingsStore.appVersion.replaceAll('.', '')) ?? 0;
final formattedAppVersion = int.tryParse(_settingsStore.appVersion.replaceAll('.', '')) ?? 0;
final payload = {
'app': isMoneroOnly ? 'monerocom' : 'cakewallet',
'device': Platform.operatingSystem,
@@ -107,19 +143,18 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
'version': formattedAppVersion
};
final headers = {apiHeaderKey: apiKey, 'Content-Type': 'application/json'};
final flow = getFlow(isFixedRateMode);
final type = isFixedRateMode ? 'reverse' : 'direct';
final body = <String, dynamic>{
'fromCurrency': _normalizeCurrency(_request.from),
'toCurrency': _normalizeCurrency(_request.to),
'fromNetwork': _networkFor(_request.from),
'toNetwork': _networkFor(_request.to),
if (!isFixedRateMode) 'fromAmount': _request.fromAmount,
if (isFixedRateMode) 'toAmount': _request.toAmount,
'address': _request.address,
'flow': flow,
'fromCurrency': _normalizeCurrency(request.fromCurrency),
'toCurrency': _normalizeCurrency(request.toCurrency),
'fromNetwork': _networkFor(request.fromCurrency),
'toNetwork': _networkFor(request.toCurrency),
if (!isFixedRateMode) 'fromAmount': request.fromAmount,
if (isFixedRateMode) 'toAmount': request.toAmount,
'address': request.toAddress,
'flow': _getFlow(isFixedRateMode),
'type': type,
'refundAddress': _request.refundAddress,
'refundAddress': request.refundAddress,
'payload': payload,
};
@@ -127,9 +162,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
// since we schedule to calculate the rate every 5 seconds we need to ensure that
// we have the latest rate id with the given inputs before creating the trade
await fetchRate(
from: _request.from,
to: _request.to,
amount: double.tryParse(_request.toAmount) ?? 0,
from: request.fromCurrency,
to: request.toCurrency,
amount: double.tryParse(request.toAmount) ?? 0,
isFixedRateMode: true,
isReceiveAmount: true,
);
@@ -146,9 +181,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
throw Exception('${error}\n$message');
}
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
@@ -159,14 +193,14 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
return Trade(
id: id,
from: _request.from,
to: _request.to,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
extraId: extraId,
createdAt: DateTime.now(),
amount: responseJSON['fromAmount']?.toString() ?? _request.fromAmount,
amount: responseJSON['fromAmount']?.toString() ?? request.fromAmount,
state: TradeState.created,
payoutAddress: payoutAddress);
}
@@ -178,9 +212,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
final uri = Uri.https(apiAuthority, findTradeByIdPath, params);
final response = await get(uri, headers: headers);
if (response.statusCode == 404) {
throw TradeNotFoundException(id, provider: description);
}
if (response.statusCode == 404) throw TradeNotFoundException(id, provider: description);
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@@ -189,9 +221,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
throw TradeNotFoundException(id, provider: description, description: error);
}
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromCurrency = responseJSON['fromCurrency'] as String;
@@ -222,54 +253,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final headers = {apiHeaderKey: apiKey};
final isReverse = isReceiveAmount;
final type = isReverse ? 'reverse' : 'direct';
final flow = getFlow(isFixedRateMode);
final params = <String, String>{
'fromCurrency': _normalizeCurrency(from),
'toCurrency': _normalizeCurrency(to),
'fromNetwork': _networkFor(from),
'toNetwork': _networkFor(to),
'type': type,
'flow': flow
};
if (isReverse) {
params['toAmount'] = amount.toString();
} else {
params['fromAmount'] = amount.toString();
}
final uri = Uri.https(apiAuthority, estimatedAmountPath, params);
final response = await get(uri, headers: headers);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromAmount = double.parse(responseJSON['fromAmount'].toString());
final toAmount = double.parse(responseJSON['toAmount'].toString());
final rateId = responseJSON['rateId'] as String? ?? '';
if (rateId.isNotEmpty) {
_lastUsedRateId = rateId;
}
return isReverse ? (amount / fromAmount) : (toAmount / amount);
} catch (e) {
print(e.toString());
return 0.0;
}
}
String _getFlow(bool isFixedRate) => isFixedRate ? 'fixed-rate' : 'standard';
String _networkFor(CryptoCurrency currency) {
switch (currency) {
@@ -301,4 +285,4 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
return tag.toLowerCase();
}
}
}
}
@@ -1,37 +1,43 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cw_core/crypto_currency.dart';
abstract class ExchangeProvider {
ExchangeProvider({required this.pairList});
String get title;
List<ExchangePair> pairList;
ExchangeProviderDescription get description;
bool get isAvailable;
bool get isEnabled;
bool get supportsFixedRate;
bool get supportsOnionAddress => false;
@override
String toString() => title;
Future<Limits> fetchLimits(
{required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode});
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode});
Future<Trade> findTradeById({required String id});
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode});
Future<Trade> createTrade({
required TradeRequest request,
required bool isFixedRateMode});
Future<Trade> findTradeById({required String id});
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount});
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount});
Future<bool> checkIsAvailable();
}
@@ -1,19 +1,19 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart';
class ExolixExchangeProvider extends ExchangeProvider {
ExolixExchangeProvider() : super(pairList: _supportedPairs());
ExolixExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static final apiKey = secrets.exolixApiKey;
static const apiBaseUrl = 'exolix.com';
@@ -40,16 +40,6 @@ class ExolixExchangeProvider extends ExchangeProvider {
CryptoCurrency.weth,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
@override
String get title => 'Exolix';
@@ -68,15 +58,13 @@ class ExolixExchangeProvider extends ExchangeProvider {
@override
Future<bool> checkIsAvailable() async => true;
static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, String>{
'rateType': getRateType(isFixedRateMode),
'rateType': _getRateType(isFixedRateMode),
'amount': '1',
};
if (isFixedRateMode) {
@@ -93,35 +81,71 @@ class ExolixExchangeProvider extends ExchangeProvider {
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return Limits(min: responseJSON['minAmount'] as double?);
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as ExolixRequest;
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) return 0.0;
final params = {
'coinFrom': _normalizeCurrency(from),
'coinTo': _normalizeCurrency(to),
'networkFrom': _networkFor(from),
'networkTo': _networkFor(to),
'rateType': _getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isReceiveAmount)
params['withdrawalAmount'] = amount.toString();
else
params['amount'] = amount.toString();
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200) {
final message = responseJSON['message'] as String?;
throw Exception(message);
}
return responseJSON['rate'] as double;
} catch (e) {
print(e.toString());
return 0.0;
}
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final headers = {'Content-Type': 'application/json'};
final body = <String, dynamic>{
'coinFrom': _normalizeCurrency(_request.from),
'coinTo': _normalizeCurrency(_request.to),
'networkFrom': _networkFor(_request.from),
'networkTo': _networkFor(_request.to),
'withdrawalAddress': _request.address,
'refundAddress': _request.refundAddress,
'rateType': getRateType(isFixedRateMode),
final body = {
'coinFrom': _normalizeCurrency(request.fromCurrency),
'coinTo': _normalizeCurrency(request.toCurrency),
'networkFrom': _networkFor(request.fromCurrency),
'networkTo': _networkFor(request.toCurrency),
'withdrawalAddress': request.toAddress,
'refundAddress': request.refundAddress,
'rateType': _getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isFixedRateMode) {
body['withdrawalAmount'] = _request.toAmount;
} else {
body['amount'] = _request.fromAmount;
}
if (isFixedRateMode)
body['withdrawalAmount'] = request.toAmount;
else
body['amount'] = request.fromAmount;
final uri = Uri.https(apiBaseUrl, transactionsPath);
final response = await post(uri, headers: headers, body: json.encode(body));
@@ -133,9 +157,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
throw Exception(errorMessage);
}
if (response.statusCode != 200 && response.statusCode != 201) {
if (response.statusCode != 200 && response.statusCode != 201)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
@@ -147,8 +170,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
return Trade(
id: id,
from: _request.from,
to: _request.to,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
@@ -161,13 +184,11 @@ class ExolixExchangeProvider extends ExchangeProvider {
@override
Future<Trade> findTradeById({required String id}) async {
final findTradeByIdPath = transactionsPath + '/$id';
final findTradeByIdPath = '$transactionsPath/$id';
final uri = Uri.https(apiBaseUrl, findTradeByIdPath);
final response = await get(uri);
if (response.statusCode == 404) {
throw TradeNotFoundException(id, provider: description);
}
if (response.statusCode == 404) throw TradeNotFoundException(id, provider: description);
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@@ -177,80 +198,33 @@ class ExolixExchangeProvider extends ExchangeProvider {
throw TradeNotFoundException(id, provider: description, description: errorMessage);
}
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
final from = CryptoCurrency.fromString(coinFrom);
final coinTo = responseJSON['coinTo']['coinCode'] as String;
final to = CryptoCurrency.fromString(coinTo);
final inputAddress = responseJSON['depositAddress'] as String;
final amount = responseJSON['amount'].toString();
final status = responseJSON['status'] as String;
final state = TradeState.deserialize(raw: _prepareStatus(status));
final extraId = responseJSON['depositExtraId'] as String?;
final outputTransaction = responseJSON['hashOut']['hash'] as String?;
final payoutAddress = responseJSON['withdrawalAddress'] as String;
return Trade(
id: id,
from: from,
to: to,
from: CryptoCurrency.fromString(coinFrom),
to: CryptoCurrency.fromString(coinTo),
provider: description,
inputAddress: inputAddress,
amount: amount,
state: state,
state: TradeState.deserialize(raw: _prepareStatus(status)),
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final params = <String, String>{
'coinFrom': _normalizeCurrency(from),
'coinTo': _normalizeCurrency(to),
'networkFrom': _networkFor(from),
'networkTo': _networkFor(to),
'rateType': getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isReceiveAmount) {
params['withdrawalAmount'] = amount.toString();
} else {
params['amount'] = amount.toString();
}
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200) {
final message = responseJSON['message'] as String?;
throw Exception(message);
}
final rate = responseJSON['rate'] as double;
return rate;
} catch (e) {
print(e.toString());
return 0.0;
}
}
String _getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
String _prepareStatus(String status) {
switch (status) {
@@ -1,28 +1,20 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/trade_not_created_exeption.dart';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart';
class SideShiftExchangeProvider extends ExchangeProvider {
SideShiftExchangeProvider() : super(pairList: _supportedPairs());
static const affiliateId = secrets.sideShiftAffiliateId;
static const apiBaseUrl = 'https://sideshift.ai/api';
static const rangePath = '/v2/pair';
static const orderPath = '/v2/shifts';
static const quotePath = '/v2/quotes';
static const permissionPath = '/v2/permissions';
SideShiftExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.xhv,
@@ -39,49 +31,28 @@ class SideShiftExchangeProvider extends ExchangeProvider {
CryptoCurrency.eos,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
static const affiliateId = secrets.sideShiftAffiliateId;
static const apiBaseUrl = 'https://sideshift.ai/api';
static const rangePath = '/v2/pair';
static const orderPath = '/v2/shifts';
static const quotePath = '/v2/quotes';
static const permissionPath = '/v2/permissions';
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
@override
String get title => 'SideShift';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.sideShift;
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final fromCurrency = from.title.toLowerCase();
final toCurrency = to.title.toLowerCase();
final depositNetwork = _networkFor(from);
final settleNetwork = _networkFor(to);
final url = "$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork?amount=$amount";
final uri = Uri.parse(url);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final rate = double.parse(responseJSON['rate'] as String);
return rate;
} catch (_) {
return 0.00;
}
}
@override
Future<bool> checkIsAvailable() async {
const url = apiBaseUrl + permissionPath;
@@ -95,110 +66,10 @@ class SideShiftExchangeProvider extends ExchangeProvider {
throw Exception('$error');
}
if (response.statusCode != 200) {
return false;
}
if (response.statusCode != 200) return false;
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final cancreateShift = responseJSON['createShift'] as bool;
return cancreateShift;
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as SideShiftRequest;
String url = '';
final depositCoin = request.depositMethod.title.toLowerCase();
final settleCoin = request.settleMethod.title.toLowerCase();
final body = {
'affiliateId': affiliateId,
'settleAddress': _request.settleAddress,
'refundAddress': _request.refundAddress,
};
if (isFixedRateMode) {
final quoteId = await _createQuote(_request);
body['quoteId'] = quoteId;
url = apiBaseUrl + orderPath + '/fixed';
} else {
url = apiBaseUrl + orderPath + '/variable';
final depositNetwork = _networkFor(request.depositMethod);
final settleNetwork = _networkFor(request.settleMethod);
body["depositCoin"] = depositCoin;
body["settleCoin"] = settleCoin;
body["settleNetwork"] = settleNetwork;
body["depositNetwork"] = depositNetwork;
}
final headers = {'Content-Type': 'application/json'};
final uri = Uri.parse(url);
final response = await post(uri, headers: headers, body: json.encode(body));
if (response.statusCode != 201) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error']['message'] as String;
throw TradeNotCreatedException(description, description: error);
}
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final inputAddress = responseJSON['depositAddress'] as String;
final settleAddress = responseJSON['settleAddress'] as String;
final depositAmount = responseJSON['depositAmount'] as String?;
return Trade(
id: id,
provider: description,
from: _request.depositMethod,
to: _request.settleMethod,
inputAddress: inputAddress,
refundAddress: settleAddress,
state: TradeState.created,
amount: depositAmount ?? _request.depositAmount,
payoutAddress: settleAddress,
createdAt: DateTime.now(),
);
}
Future<String> _createQuote(SideShiftRequest request) async {
final url = apiBaseUrl + quotePath;
final headers = {'Content-Type': 'application/json'};
final depositMethod = request.depositMethod.title.toLowerCase();
final settleMethod = request.settleMethod.title.toLowerCase();
final depositNetwork = _networkFor(request.depositMethod);
final settleNetwork = _networkFor(request.settleMethod);
final body = {
'depositCoin': depositMethod,
'settleCoin': settleMethod,
'affiliateId': affiliateId,
'settleAmount': request.depositAmount,
'settleNetwork': settleNetwork,
'depositNetwork': depositNetwork,
};
final uri = Uri.parse(url);
final response = await post(uri, headers: headers, body: json.encode(body));
if (response.statusCode != 201) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error']['message'] as String;
throw TradeNotCreatedException(description, description: error);
}
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final quoteId = responseJSON['id'] as String;
return quoteId;
return responseJSON['createShift'] as bool;
}
@override
@@ -244,6 +115,91 @@ class SideShiftExchangeProvider extends ExchangeProvider {
return Limits(min: min, max: max);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) return 0.0;
final fromCurrency = from.title.toLowerCase();
final toCurrency = to.title.toLowerCase();
final depositNetwork = _networkFor(from);
final settleNetwork = _networkFor(to);
final url =
"$apiBaseUrl$rangePath/$fromCurrency-$depositNetwork/$toCurrency-$settleNetwork?amount=$amount";
final uri = Uri.parse(url);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return double.parse(responseJSON['rate'] as String);
} catch (_) {
return 0.00;
}
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
String url = '';
final body = {
'affiliateId': affiliateId,
'settleAddress': request.toAddress,
'refundAddress': request.refundAddress,
};
if (isFixedRateMode) {
final quoteId = await _createQuote(request);
body['quoteId'] = quoteId;
url = apiBaseUrl + orderPath + '/fixed';
} else {
url = apiBaseUrl + orderPath + '/variable';
body["depositCoin"] = request.fromCurrency.title.toLowerCase();
body["settleCoin"] = request.toCurrency.title.toLowerCase();
body["settleNetwork"] = _networkFor(request.toCurrency);
body["depositNetwork"] = _networkFor(request.fromCurrency);
}
final headers = {'Content-Type': 'application/json'};
final uri = Uri.parse(url);
final response = await post(uri, headers: headers, body: json.encode(body));
if (response.statusCode != 201) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error']['message'] as String;
throw TradeNotCreatedException(description, description: error);
}
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final inputAddress = responseJSON['depositAddress'] as String;
final settleAddress = responseJSON['settleAddress'] as String;
final depositAmount = responseJSON['depositAmount'] as String?;
return Trade(
id: id,
provider: description,
from: request.fromCurrency,
to: request.toCurrency,
inputAddress: inputAddress,
refundAddress: settleAddress,
state: TradeState.created,
amount: depositAmount ?? request.fromAmount,
payoutAddress: settleAddress,
createdAt: DateTime.now(),
);
}
@override
Future<Trade> findTradeById({required String id}) async {
final url = apiBaseUrl + orderPath + '/' + id;
@@ -267,44 +223,56 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromCurrency = responseJSON['depositCoin'] as String;
final from = CryptoCurrency.fromString(fromCurrency);
final toCurrency = responseJSON['settleCoin'] as String;
final to = CryptoCurrency.fromString(toCurrency);
final inputAddress = responseJSON['depositAddress'] as String;
final expectedSendAmount = responseJSON['depositAmount'] as String?;
final status = responseJSON['status'] as String?;
final settleAddress = responseJSON['settleAddress'] as String;
TradeState? state;
state = TradeState.deserialize(raw: status ?? 'created');
final isVariable = (responseJSON['type'] as String) == 'variable';
final expiredAtRaw = responseJSON['expiresAt'] as String;
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
return Trade(
id: id,
from: from,
to: to,
from: CryptoCurrency.fromString(fromCurrency),
to: CryptoCurrency.fromString(toCurrency),
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount ?? '',
state: state,
state: TradeState.deserialize(raw: status ?? 'created'),
expiredAt: expiredAt,
payoutAddress: settleAddress);
}
@override
bool get isAvailable => true;
Future<String> _createQuote(TradeRequest request) async {
final url = apiBaseUrl + quotePath;
final headers = {'Content-Type': 'application/json'};
final body = {
'depositCoin': request.fromCurrency.title.toLowerCase(),
'settleCoin': request.toCurrency.title.toLowerCase(),
'affiliateId': affiliateId,
'settleAmount': request.toAmount,
'settleNetwork': _networkFor(request.toCurrency),
'depositNetwork': _networkFor(request.fromCurrency),
};
final uri = Uri.parse(url);
final response = await post(uri, headers: headers, body: json.encode(body));
@override
bool get isEnabled => true;
if (response.statusCode != 201) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error']['message'] as String;
@override
bool get supportsFixedRate => true;
throw TradeNotCreatedException(description, description: error);
}
@override
String get title => 'SideShift';
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return responseJSON['id'] as String;
}
String _networkFor(CryptoCurrency currency) =>
currency.tag != null ? _normalizeTag(currency.tag!) : 'mainnet';
@@ -1,71 +1,49 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
import 'package:cake_wallet/exchange/trade_not_created_exeption.dart';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:http/http.dart';
class SimpleSwapExchangeProvider extends ExchangeProvider {
SimpleSwapExchangeProvider()
: super(
pairList: CryptoCurrency.all
.where((i) => i != CryptoCurrency.zaddr)
.map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.zaddr)
.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList());
SimpleSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.zaddr,
CryptoCurrency.xhv,
];
static final apiKey =
DeviceInfo.instance.isMobile ? secrets.simpleSwapApiKey : secrets.simpleSwapApiKeyDesktop;
static const apiAuthority = 'api.simpleswap.io';
static const getEstimatePath = '/v1/get_estimated';
static const rangePath = '/v1/get_ranges';
static const getExchangePath = '/v1/get_exchange';
static const createExchangePath = '/v1/create_exchange';
static final apiKey = DeviceInfo.instance.isMobile ? secrets.simpleSwapApiKey : secrets.simpleSwapApiKeyDesktop;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.simpleSwap;
String get title => 'SimpleSwap';
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final fromCurrency = _normalizeCryptoCurrency(from);
final toCurrency = _normalizeCryptoCurrency(to);
final params = <String, String>{
'api_key': apiKey,
'currency_from': fromCurrency,
'currency_to': toCurrency,
'amount': amount.toString(),
'fixed': isFixedRateMode.toString()
};
final uri = Uri.https(apiAuthority, getEstimatePath, params);
final response = await get(uri);
bool get isAvailable => true;
if (response.body == "null") return 0.00;
final data = json.decode(response.body) as String;
return double.parse(data) / amount;
} catch (_) {
return 0.00;
}
}
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => false;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.simpleSwap;
@override
Future<bool> checkIsAvailable() async {
@@ -76,20 +54,79 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as SimpleSwapRequest;
final headers = {
'Content-Type': 'application/json'};
final params = <String, String>{
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, dynamic>{
'api_key': apiKey,
'fixed': isFixedRateMode.toString(),
'currency_from': _normalizeCurrency(from),
'currency_to': _normalizeCurrency(to),
};
final uri = Uri.https(apiAuthority, rangePath, params);
final response = await get(uri);
if (response.statusCode == 500) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['message'] as String;
throw Exception('$error');
}
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = double.tryParse(responseJSON['min'] as String? ?? '');
final max = double.tryParse(responseJSON['max'] as String? ?? '');
return Limits(min: min, max: max);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) return 0.0;
final params = {
'api_key': apiKey,
'currency_from': _normalizeCurrency(from),
'currency_to': _normalizeCurrency(to),
'amount': amount.toString(),
'fixed': isFixedRateMode.toString()
};
final uri = Uri.https(apiAuthority, getEstimatePath, params);
final response = await get(uri);
if (response.body == "null") return 0.00;
final data = json.decode(response.body) as String;
return double.parse(data) / amount;
} catch (_) {
return 0.00;
}
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final headers = {'Content-Type': 'application/json'};
final params = {'api_key': apiKey};
final body = <String, dynamic>{
"currency_from": _normalizeCryptoCurrency(_request.from),
"currency_to": _normalizeCryptoCurrency(_request.to),
"amount": _request.amount,
"currency_from": _normalizeCurrency(request.fromCurrency),
"currency_to": _normalizeCurrency(request.toCurrency),
"amount": request.fromAmount,
"fixed": isFixedRateMode,
"user_refund_address": _request.refundAddress,
"address_to": _request.address
"user_refund_address": request.refundAddress,
"address_to": request.toAddress
};
final uri = Uri.https(apiAuthority, createExchangePath, params);
@@ -112,56 +149,22 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final payoutAddress = responseJSON['address_to'] as String;
final settleAddress = responseJSON['user_refund_address'] as String;
final extraId = responseJSON['extra_id_from'] as String?;
return Trade(
id: id,
provider: description,
from: _request.from,
to: _request.to,
from: request.fromCurrency,
to: request.toCurrency,
inputAddress: inputAddress,
refundAddress: settleAddress,
extraId: extraId,
state: TradeState.created,
amount: _request.amount,
amount: request.fromAmount,
payoutAddress: payoutAddress,
createdAt: DateTime.now(),
);
}
@override
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final fromCurrency = _normalizeCryptoCurrency(from);
final toCurrency = _normalizeCryptoCurrency(to);
final params = <String, dynamic>{
'api_key': apiKey,
'fixed': isFixedRateMode.toString(),
'currency_from': fromCurrency,
'currency_to': toCurrency,
};
final uri = Uri.https(apiAuthority, rangePath, params);
final response = await get(uri);
if (response.statusCode == 500) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['message'] as String;
throw Exception('$error');
}
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = double.tryParse(responseJSON['min'] as String? ?? '');
final max = double.tryParse(responseJSON['max'] as String? ?? '');
return Limits(min: min, max: max);
}
@override
Future<Trade> findTradeById({required String id}) async {
final params = {'api_key': apiKey, 'id': id};
@@ -185,42 +188,27 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromCurrency = responseJSON['currency_from'] as String;
final from = CryptoCurrency.fromString(fromCurrency);
final toCurrency = responseJSON['currency_to'] as String;
final to = CryptoCurrency.fromString(toCurrency);
final inputAddress = responseJSON['address_from'] as String;
final expectedSendAmount = responseJSON['expected_amount'].toString();
final extraId = responseJSON['extra_id_from'] as String?;
final status = responseJSON['status'] as String;
final payoutAddress = responseJSON['address_to'] as String;
final state = TradeState.deserialize(raw: status);
return Trade(
id: id,
from: from,
to: to,
from: CryptoCurrency.fromString(fromCurrency),
to: CryptoCurrency.fromString(toCurrency),
extraId: extraId,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount,
state: state,
state: TradeState.deserialize(raw: status),
payoutAddress: payoutAddress,
);
}
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => false;
@override
String get title => 'SimpleSwap';
static String _normalizeCryptoCurrency(CryptoCurrency currency) {
static String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.zaddr:
return 'zec';
@@ -1,21 +1,20 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart';
class TrocadorExchangeProvider extends ExchangeProvider {
TrocadorExchangeProvider({this.useTorOnly = false})
: _lastUsedRateId = '',
super(pairList: _supportedPairs());
super(pairList: supportedPairs(_notSupported));
bool useTorOnly;
@@ -24,135 +23,58 @@ class TrocadorExchangeProvider extends ExchangeProvider {
CryptoCurrency.zaddr,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
static const apiKey = secrets.trocadorApiKey;
static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
static const clearNetAuthority = 'trocador.app';
static const apiKey = secrets.trocadorApiKey;
static const markup = secrets.trocadorExchangeMarkup;
static const newRatePath = '/api/new_rate';
static const createTradePath = 'api/new_trade';
static const tradePath = 'api/trade';
static const coinPath = 'api/coin';
String _lastUsedRateId;
@override
Future<bool> checkIsAvailable() async => true;
String get title => 'Trocador';
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) {
final _request = request as TrocadorRequest;
return _createTrade(request: _request, isFixedRateMode: isFixedRateMode);
}
bool get isAvailable => true;
Future<Trade> _createTrade({
required TrocadorRequest request,
required bool isFixedRateMode,
}) async {
final params = <String, String>{
'api_key': apiKey,
'ticker_from': _normalizeCurrency(request.from),
'ticker_to': _normalizeCurrency(request.to),
'network_from': _networkFor(request.from),
'network_to': _networkFor(request.to),
'payment': isFixedRateMode ? 'True' : 'False',
'min_kycrating': 'C',
'markup': markup,
'best_only': 'True',
if (!isFixedRateMode) 'amount_from': request.fromAmount,
if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.address,
'refund': request.refundAddress
};
@override
bool get isEnabled => true;
if (isFixedRateMode) {
await fetchRate(
from: request.from,
to: request.to,
amount: double.tryParse(request.toAmount) ?? 0,
isFixedRateMode: true,
isReceiveAmount: true,
);
params['id'] = _lastUsedRateId;
}
@override
bool get supportsFixedRate => true;
final uri = await _getUri(createTradePath, params);
final response = await get(uri);
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error'] as String;
final message = responseJSON['message'] as String;
throw Exception('${error}\n$message');
}
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['trade_id'] as String;
final inputAddress = responseJSON['address_provider'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final status = responseJSON['status'] as String;
final state = TradeState.deserialize(raw: status);
final payoutAddress = responseJSON['address_user'] as String;
final date = responseJSON['date'] as String;
final password = responseJSON['password'] as String;
final providerId = responseJSON['id_provider'] as String;
final providerName = responseJSON['provider'] as String;
return Trade(
id: id,
from: request.from,
to: request.to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
state: state,
password: password,
providerId: providerId,
providerName: providerName,
createdAt: DateTime.tryParse(date)?.toLocal(),
amount: responseJSON['amount_from']?.toString() ?? request.fromAmount,
payoutAddress: payoutAddress);
}
@override
bool get supportsOnionAddress => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.trocador;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, String>{
final params = {
'api_key': apiKey,
'ticker': _normalizeCurrency(from),
'name': from.name,
};
final uri = await _getUri(coinPath, params);
final response = await get(uri);
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as List<dynamic>;
if (responseJSON.isEmpty) {
throw Exception('No data');
}
if (responseJSON.isEmpty) throw Exception('No data');
final coinJson = responseJSON.first as Map<String, dynamic>;
@@ -170,9 +92,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
if (amount == 0) return 0.0;
final params = <String, String>{
'api_key': apiKey,
@@ -195,9 +115,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final toAmount = double.parse(responseJSON['amount_to'].toString());
final rateId = responseJSON['trade_id'] as String? ?? '';
if (rateId.isNotEmpty) {
_lastUsedRateId = rateId;
}
if (rateId.isNotEmpty) _lastUsedRateId = rateId;
return isReceiveAmount ? (amount / fromAmount) : (toAmount / amount);
} catch (e) {
@@ -206,40 +124,103 @@ class TrocadorExchangeProvider extends ExchangeProvider {
}
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final params = {
'api_key': apiKey,
'ticker_from': _normalizeCurrency(request.fromCurrency),
'ticker_to': _normalizeCurrency(request.toCurrency),
'network_from': _networkFor(request.fromCurrency),
'network_to': _networkFor(request.toCurrency),
'payment': isFixedRateMode ? 'True' : 'False',
'min_kycrating': 'C',
'markup': markup,
'best_only': 'True',
if (!isFixedRateMode) 'amount_from': request.fromAmount,
if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.toAddress,
'refund': request.refundAddress
};
if (isFixedRateMode) {
await fetchRate(
from: request.fromCurrency,
to: request.toCurrency,
amount: double.tryParse(request.toAmount) ?? 0,
isFixedRateMode: true,
isReceiveAmount: true,
);
params['id'] = _lastUsedRateId;
}
final uri = await _getUri(createTradePath, params);
final response = await get(uri);
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error'] as String;
final message = responseJSON['message'] as String;
throw Exception('${error}\n$message');
}
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['trade_id'] as String;
final inputAddress = responseJSON['address_provider'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final status = responseJSON['status'] as String;
final payoutAddress = responseJSON['address_user'] as String;
final date = responseJSON['date'] as String;
final password = responseJSON['password'] as String;
final providerId = responseJSON['id_provider'] as String;
final providerName = responseJSON['provider'] as String;
return Trade(
id: id,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
state: TradeState.deserialize(raw: status),
password: password,
providerId: providerId,
providerName: providerName,
createdAt: DateTime.tryParse(date)?.toLocal(),
amount: responseJSON['amount_from']?.toString() ?? request.fromAmount,
payoutAddress: payoutAddress);
}
@override
Future<Trade> findTradeById({required String id}) async {
final uri = await _getUri(tradePath, {'api_key': apiKey, 'id': id});
return get(uri).then((response) {
if (response.statusCode != 200) {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseListJson = json.decode(response.body) as List;
final responseJSON = responseListJson.first;
final id = responseJSON['trade_id'] as String;
final payoutAddress = responseJSON['address_user'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final inputAddress = responseJSON['address_provider'] as String;
final fromAmount = responseJSON['amount_from']?.toString() ?? '0';
final from = CryptoCurrency.fromString(responseJSON['ticker_from'] as String);
final to = CryptoCurrency.fromString(responseJSON['ticker_to'] as String);
final state = TradeState.deserialize(raw: responseJSON['status'] as String);
final date = DateTime.parse(responseJSON['date'] as String);
final password = responseJSON['password'] as String;
final providerId = responseJSON['id_provider'] as String;
final providerName = responseJSON['provider'] as String;
return Trade(
id: id,
from: from,
to: to,
from: CryptoCurrency.fromString(responseJSON['ticker_from'] as String),
to: CryptoCurrency.fromString(responseJSON['ticker_to'] as String),
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
createdAt: date,
createdAt: DateTime.parse(responseJSON['date'] as String),
amount: fromAmount,
state: state,
state: TradeState.deserialize(raw: responseJSON['status'] as String),
payoutAddress: payoutAddress,
password: password,
providerId: providerId,
@@ -248,21 +229,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
});
}
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
bool get supportsOnionAddress => true;
@override
String get title => 'Trocador';
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.eth:
@@ -301,15 +267,9 @@ class TrocadorExchangeProvider extends ExchangeProvider {
}
Future<Uri> _getUri(String path, Map<String, String> queryParams) async {
if (!supportsOnionAddress) {
return Uri.https(clearNetAuthority, path, queryParams);
}
final uri = Uri.http(onionApiAuthority, path, queryParams);
if (useTorOnly) {
return uri;
}
if (useTorOnly) return uri;
try {
await get(uri);
@@ -1,17 +0,0 @@
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cw_core/crypto_currency.dart';
class SideShiftRequest extends TradeRequest {
SideShiftRequest(
{required this.depositMethod,
required this.settleMethod,
required this.depositAmount,
required this.settleAddress,
required this.refundAddress});
final CryptoCurrency depositMethod;
final CryptoCurrency settleMethod;
final String depositAmount;
final String settleAddress;
final String refundAddress;
}
@@ -1,21 +0,0 @@
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class SimpleSwapRequest extends TradeRequest {
SimpleSwapRequest({
required this.from,
required this.to,
required this.address,
required this.amount,
required this.refundAddress,
this.toAmount = ''
});
CryptoCurrency from;
CryptoCurrency to;
String address;
String amount;
String toAmount;
String refundAddress;
}
+8 -13
View File
@@ -1,6 +1,6 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
@@ -28,18 +28,13 @@ class Trade extends HiveObject {
this.providerId,
this.providerName,
}) {
if (provider != null) {
providerRaw = provider.raw;
}
if (from != null) {
fromRaw = from.raw;
}
if (to != null) {
toRaw = to.raw;
}
if (state != null) {
stateRaw = state.raw;
}
if (provider != null) providerRaw = provider.raw;
if (from != null) fromRaw = from.raw;
if (to != null) toRaw = to.raw;
if (state != null) stateRaw = state.raw;
}
static const typeId = TRADE_TYPE_ID;
@@ -8,12 +8,5 @@ class TradeNotCreatedException implements Exception {
String description;
@override
String toString() {
var text = provider != null
? S.current.trade_for_not_created(provider.title)
: S.current.trade_not_created;
text += ' $description';
return text;
}
String toString() => '${S.current.trade_for_not_created(provider.title)} $description';
}
@@ -0,0 +1,13 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
class TradeNotFoundException implements Exception {
TradeNotFoundException(this.tradeId, {required this.provider, this.description = ''});
String tradeId;
ExchangeProviderDescription provider;
String description;
@override
String toString() => '${S.current.trade_id_not_found(tradeId, provider.title)} $description';
}
@@ -1,20 +0,0 @@
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
class TradeNotFoundException implements Exception {
TradeNotFoundException(this.tradeId, {this.provider, this.description = ''});
String? tradeId;
ExchangeProviderDescription? provider;
String description;
@override
String toString() {
var text = tradeId != null && provider != null
? S.current.trade_id_not_found(tradeId!, provider!.title)
: S.current.trade_not_found;
text += ' $description';
return text;
}
}
+20 -1
View File
@@ -1 +1,20 @@
abstract class TradeRequest {}
import 'package:cw_core/crypto_currency.dart';
class TradeRequest {
TradeRequest(
{required this.fromCurrency,
required this.toCurrency,
required this.toAddress,
required this.refundAddress,
required this.fromAmount,
this.toAmount = '',
this.isFixedRate = false});
final CryptoCurrency fromCurrency;
final CryptoCurrency toCurrency;
final String toAddress;
final String refundAddress;
final String fromAmount;
final String toAmount;
final bool isFixedRate;
}
+4 -9
View File
@@ -1,9 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/enumerable_item.dart';
class TradeState extends EnumerableItem<String> with Serializable<String> {
const TradeState({required String raw, required String title})
: super(raw: raw, title: title);
const TradeState({required String raw, required String title}) : super(raw: raw, title: title);
@override
bool operator ==(Object other) => other is TradeState && other.raw == raw;
@@ -13,12 +11,10 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const trading = TradeState(raw: 'trading', title: 'Trading');
static const traded = TradeState(raw: 'traded', title: 'Traded');
static const complete = TradeState(raw: 'complete', title: 'Complete');
static const toBeCreated =
TradeState(raw: 'TO_BE_CREATED', title: 'To be created');
static const toBeCreated = TradeState(raw: 'TO_BE_CREATED', title: 'To be created');
static const unpaid = TradeState(raw: 'UNPAID', title: 'Unpaid');
static const underpaid = TradeState(raw: 'UNDERPAID', title: 'Underpaid');
static const paidUnconfirmed =
TradeState(raw: 'PAID_UNCONFIRMED', title: 'Paid unconfirmed');
static const paidUnconfirmed = TradeState(raw: 'PAID_UNCONFIRMED', title: 'Paid unconfirmed');
static const paid = TradeState(raw: 'PAID', title: 'Paid');
static const btcSent = TradeState(raw: 'BTC_SENT', title: 'Btc sent');
static const timeout = TradeState(raw: 'TIMED_OUT', title: 'Timeout');
@@ -27,8 +23,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const finished = TradeState(raw: 'finished', title: 'Finished');
static const waiting = TradeState(raw: 'waiting', title: 'Waiting');
static const processing = TradeState(raw: 'processing', title: 'Processing');
static const waitingPayment =
TradeState(raw: 'waitingPayment', title: 'Waiting payment');
static const waitingPayment = TradeState(raw: 'waitingPayment', title: 'Waiting payment');
static const waitingAuthorization =
TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization');
static const failed = TradeState(raw: 'failed', title: 'Failed');
@@ -1,21 +0,0 @@
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cw_core/crypto_currency.dart';
class TrocadorRequest extends TradeRequest {
TrocadorRequest(
{required this.from,
required this.to,
required this.address,
required this.fromAmount,
required this.toAmount,
required this.refundAddress,
required this.isReverse});
CryptoCurrency from;
CryptoCurrency to;
String address;
String fromAmount;
String toAmount;
String refundAddress;
bool isReverse;
}
@@ -0,0 +1,12 @@
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cw_core/crypto_currency.dart';
List<ExchangePair> supportedPairs(List<CryptoCurrency> notSupported) {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !notSupported.contains(element)).toList();
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
@@ -1,236 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_trade_request.dart';
import 'package:cake_wallet/exchange/trade_not_created_exeption.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:cake_wallet/generated/i18n.dart';
class XMRTOExchangeProvider extends ExchangeProvider {
XMRTOExchangeProvider()
: _isAvailable = false,
super(pairList: [
ExchangePair(
from: CryptoCurrency.xmr, to: CryptoCurrency.btc, reverse: false)
]);
static const userAgent = 'CakeWallet/XMR iOS';
static const originalApiUri = 'https://xmr.to/api/v3/xmr2btc';
static const _orderParameterUriSuffix = '/order_parameter_query';
static const _orderStatusUriSuffix = '/order_status_query/';
static const _orderCreateUriSuffix = '/order_create/';
static const _headers = {
'Content-Type': 'application/json',
'User-Agent': userAgent
};
static Future<bool> _checkIsAvailable() async {
const url = originalApiUri + _orderParameterUriSuffix;
final uri = Uri.parse(url);
final response = await get(uri, headers: _headers);
return !(response.statusCode == 403);
}
@override
String get title => 'XMR.TO';
@override
bool get isAvailable => _isAvailable;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => false;
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.xmrto;
double _rate = 0;
bool _isAvailable;
@override
Future<bool> checkIsAvailable() async {
_isAvailable = await _checkIsAvailable();
return isAvailable;
}
@override
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final url = originalApiUri + _orderParameterUriSuffix;
final uri = Uri.parse(url);
final response = await get(uri);
final correction = 0.001;
if (response.statusCode != 200) {
return Limits(min: 0, max: 0);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
double min = double.parse(responseJSON['lower_limit'] as String);
double max = double.parse(responseJSON['upper_limit'] as String);
final price = double.parse(responseJSON['price'] as String);
if (price > 0) {
try {
min = min / price + correction;
min = _limitsFormat(min);
max = max / price - correction;
max = _limitsFormat(max);
} catch (e) {
min = 0;
max = 0;
}
} else {
min = 0;
max = 0;
}
return Limits(min: min, max: max);
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as XMRTOTradeRequest;
final url = originalApiUri + _orderCreateUriSuffix;
final _amount =
_request.isBTCRequest ? _request.receiveAmount : _request.amount;
final _amountCurrency = _request.isBTCRequest
? _request.to.toString()
: _request.from.toString();
final pattern = '^([0-9]+([.\,][0-9]{0,8})?|[.\,][0-9]{1,8})\$';
final isValid = RegExp(pattern).hasMatch(_amount);
if (!isValid) {
throw TradeNotCreatedException(description,
description: S.current.xmr_to_error_description);
}
final body = {
'amount': _amount,
'amount_currency': _amountCurrency,
'btc_dest_address': _request.address};
final uri = Uri.parse(url);
final response =
await post(uri, headers: _headers, body: json.encode(body));
if (response.statusCode != 201) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error_msg'] as String;
throw TradeNotCreatedException(description, description: error);
}
throw TradeNotCreatedException(description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final uuid = responseJSON["uuid"] as String;
return Trade(
id: uuid,
provider: description,
from: _request.from,
to: _request.to,
state: TradeState.created,
amount: _request.amount,
createdAt: DateTime.now());
}
@override
Future<Trade> findTradeById({required String id}) async {
final url = originalApiUri + _orderStatusUriSuffix;
final uri = Uri.parse(url);
final body = {'uuid': id};
final response =
await post(uri, headers: _headers, body: json.encode(body));
if (response.statusCode != 200) {
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error_msg'] as String;
throw TradeNotFoundException(id,
provider: description, description: error);
}
throw TradeNotFoundException(id, provider: description);
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final address = responseJSON['receiving_subaddress'] as String;
final paymentId = responseJSON['xmr_required_payment_id_short'] as String;
final amount = responseJSON['incoming_amount_total'].toString();
final stateRaw = responseJSON['state'] as String;
final expiredAtRaw = responseJSON['expires_at'] as String;
final expiredAt = DateTime.parse(expiredAtRaw).toLocal();
final outputTransaction = responseJSON['btc_transaction_id'] as String;
final state = TradeState.deserialize(raw: stateRaw);
return Trade(
id: id,
provider: description,
from: CryptoCurrency.xmr,
to: CryptoCurrency.btc,
inputAddress: address,
extraId: paymentId,
expiredAt: expiredAt,
amount: amount,
state: state,
outputTransaction: outputTransaction);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
if (from != CryptoCurrency.xmr && to != CryptoCurrency.btc) {
return 0;
}
if (_rate == 0) {
_rate = await _fetchRates();
}
final double result = isReceiveAmount
? _rate == 0
? 0
: amount / _rate
: _rate * amount;
return double.parse(result.toStringAsFixed(12));
}
Future<double> _fetchRates() async {
try {
final url = originalApiUri + _orderParameterUriSuffix;
final uri = Uri.parse(url);
final response = await get(uri, headers: _headers);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final price = double.parse(responseJSON['price'] as String);
return price;
} catch (e) {
print(e.toString());
return 0.0;
}
}
double _limitsFormat(double limit) => double.parse(limit.toStringAsFixed(3));
}
@@ -1,22 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class XMRTOTradeRequest extends TradeRequest {
XMRTOTradeRequest(
{required this.from,
required this.to,
required this.amount,
required this.receiveAmount,
required this.address,
required this.refundAddress,
required this.isBTCRequest});
final CryptoCurrency from;
final CryptoCurrency to;
final String amount;
final String receiveAmount;
final String address;
final String refundAddress;
final bool isBTCRequest;
}
@@ -1,6 +1,6 @@
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:flutter/material.dart';
@@ -9,9 +9,6 @@ import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
// import 'package:cake_wallet/exchange/exchange_trade_state.dart';
// import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
@@ -176,9 +173,7 @@ class ExchangeTemplatePage extends BasePage {
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable:
exchangeViewModel.provider is
XMRTOExchangeProvider ? true : false,
initialIsAmountEditable: false,
initialIsAddressEditable:
exchangeViewModel.isReceiveAddressEnabled,
isAmountEstimated: true,
@@ -205,26 +200,25 @@ class ExchangeTemplatePage extends BasePage {
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(builder: (_) {
final description =
exchangeViewModel.provider is XMRTOExchangeProvider
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).extension<ExchangePageTheme>()!.receiveAmountColor,
fontWeight: FontWeight.w500,
fontSize: 12),
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(
builder: (_) => Center(
child: Text(
S.of(context).amount_is_estimate,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.receiveAmountColor,
fontWeight: FontWeight.w500,
fontSize: 12,
),
),
),
),
);
}),
),
PrimaryButton(
),
PrimaryButton(
onPressed: () {
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
exchangeViewModel.addTemplate(
@@ -340,9 +334,7 @@ class ExchangeTemplatePage extends BasePage {
});
reaction((_) => exchangeViewModel.provider, (ExchangeProvider? provider) {
provider is XMRTOExchangeProvider
? receiveKey.currentState!.isAmountEditable(isEditable: true)
: receiveKey.currentState!.isAmountEditable(isEditable: false);
receiveKey.currentState!.isAmountEditable(isEditable: false);
});
/*reaction((_) => exchangeViewModel.limitsState, (LimitsState state) {
@@ -1,18 +1,19 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_provider_unsupported_item.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/standard_list_card.dart';
import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
class TradeDetailsPage extends BasePage {
TradeDetailsPage(this.tradeDetailsViewModel);
@@ -23,8 +24,7 @@ class TradeDetailsPage extends BasePage {
final TradeDetailsViewModel tradeDetailsViewModel;
@override
Widget body(BuildContext context) =>
TradeDetailsPageBody(tradeDetailsViewModel);
Widget body(BuildContext context) => TradeDetailsPageBody(tradeDetailsViewModel);
}
class TradeDetailsPageBody extends StatefulWidget {
@@ -33,8 +33,7 @@ class TradeDetailsPageBody extends StatefulWidget {
final TradeDetailsViewModel tradeDetailsViewModel;
@override
TradeDetailsPageBodyState createState() =>
TradeDetailsPageBodyState(tradeDetailsViewModel);
TradeDetailsPageBodyState createState() => TradeDetailsPageBodyState(tradeDetailsViewModel);
}
class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
@@ -51,7 +50,7 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
@override
Widget build(BuildContext context) {
return Observer(builder: (_) {
int itemsCount = tradeDetailsViewModel.items.length;
final itemsCount = tradeDetailsViewModel.items.length;
return SectionStandardList(
sectionCount: 1,
@@ -59,37 +58,39 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
itemBuilder: (__, index) {
final item = tradeDetailsViewModel.items[index];
if (item is TrackTradeListItem) {
if (item is TrackTradeListItem)
return GestureDetector(
onTap: item.onTap,
child: ListRow(
title: '${item.title}', value: '${item.value}'));
}
child: ListRow(title: '${item.title}', value: '${item.value}'));
if (item is DetailsListStatusItem) {
return StandardListStatusRow(
title: item.title,
value: item.value);
}
if (item is DetailsListStatusItem)
return StandardListStatusRow(title: item.title, value: item.value);
if (item is TradeDetailsListCardItem) {
if (item is TradeDetailsListCardItem)
return TradeDetailsStandardListCard(
id: item.id,
create: item.createdAt,
pair: item.pair,
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,
onTap: item.onTap,);
}
id: item.id,
create: item.createdAt,
pair: item.pair,
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,
onTap: item.onTap,
);
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: ListRow(
title: '${item.title}', value: '${item.value}'));
if (item is TradeProviderUnsupportedItem)
return AutoSizeText(item.value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.red,
));
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: ListRow(title: '${item.title}', value: '${item.value}'));
});
});
}
}
@@ -0,0 +1,5 @@
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
class TradeProviderUnsupportedItem extends StandartListItem {
TradeProviderUnsupportedItem({required String error}) : super(title: '', value: error);
}
@@ -1,22 +1,21 @@
import 'dart:async';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'exchange_trade_view_model.g.dart';
@@ -35,16 +34,10 @@ abstract class ExchangeTradeViewModelBase with Store {
tradesStore.trade!.from.tag == CryptoCurrency.eth.title),
items = ObservableList<ExchangeTradeItem>() {
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
_provider = XMRTOExchangeProvider();
break;
case ExchangeProviderDescription.changeNow:
_provider =
ChangeNowExchangeProvider(settingsStore: sendViewModel.balanceViewModel.settingsStore);
break;
case ExchangeProviderDescription.morphToken:
_provider = MorphTokenExchangeProvider(trades: trades);
break;
case ExchangeProviderDescription.sideShift:
_provider = SideShiftExchangeProvider();
break;
@@ -60,8 +53,11 @@ abstract class ExchangeTradeViewModelBase with Store {
}
_updateItems();
_updateTrade();
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
if (_provider != null) {
_updateTrade();
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
}
final WalletBase wallet;
@@ -100,10 +96,8 @@ abstract class ExchangeTradeViewModelBase with Store {
Timer? timer;
@action
Future confirmSending() async {
if (!isSendable) {
return;
}
Future<void> confirmSending() async {
if (!isSendable) return;
sendViewModel.clearOutputs();
final output = sendViewModel.outputs.first;
@@ -118,13 +112,10 @@ abstract class ExchangeTradeViewModelBase with Store {
try {
final updatedTrade = await _provider!.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
if (updatedTrade.createdAt == null && trade.createdAt != null)
updatedTrade.createdAt = trade.createdAt;
}
if (updatedTrade.amount.isEmpty) {
updatedTrade.amount = trade.amount;
}
if (updatedTrade.amount.isEmpty) updatedTrade.amount = trade.amount;
trade = updatedTrade;
+59 -165
View File
@@ -3,48 +3,39 @@ import 'dart:collection';
import 'dart:convert';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/changenow/changenow_request.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_trade_request.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_request.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'exchange_view_model.g.dart';
@@ -144,6 +135,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
_calculateBestRate();
});
}
bool _useTorOnly;
final Box<Trade> trades;
final ExchangeTemplateStore _exchangeTemplateStore;
@@ -384,13 +376,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
double minLimit = limits.min ?? 0;
double? maxLimit = limits.max;
if (_enteredAmount < minLimit) {
return false;
}
if (_enteredAmount < minLimit) return false;
if (maxLimit != null && _enteredAmount > maxLimit) {
return false;
}
if (maxLimit != null && _enteredAmount > maxLimit) return false;
return true;
}
@@ -422,16 +410,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
}
}
if (_sortedAvailableProviders.isNotEmpty) {
_bestRate = _sortedAvailableProviders.keys.first;
}
if (_sortedAvailableProviders.isNotEmpty) _bestRate = _sortedAvailableProviders.keys.first;
}
@action
Future<void> loadLimits() async {
if (selectedProviders.isEmpty) {
return;
}
if (selectedProviders.isEmpty) return;
limitsState = LimitsIsLoading();
@@ -444,20 +428,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
try {
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
if (!providersForCurrentPair().contains(provider)) continue;
try {
final tempLimits =
await provider.fetchLimits(from: from, to: to, isFixedRateMode: isFixedRateMode);
if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) {
lowestMin = tempLimits.min;
}
if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) {
if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) lowestMin = tempLimits.min;
if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax)
highestMax = tempLimits.max;
}
} catch (e) {
continue;
}
@@ -482,111 +462,34 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
Future<void> createTrade() async {
TradeRequest? request;
String amount = '';
try {
for (var provider in _sortedAvailableProviders.values) {
if (!(await provider.checkIsAvailable())) {
continue;
}
if (!(await provider.checkIsAvailable())) continue;
if (provider is SideShiftExchangeProvider) {
request = SideShiftRequest(
depositMethod: depositCurrency,
settleMethod: receiveCurrency,
depositAmount: isFixedRateMode
? receiveAmount.replaceAll(',', '.')
: depositAmount.replaceAll(',', '.'),
settleAddress: receiveAddress,
final request = TradeRequest(
fromCurrency: depositCurrency,
toCurrency: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is SimpleSwapExchangeProvider) {
request = SimpleSwapRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is XMRTOExchangeProvider) {
request = XMRTOTradeRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
receiveAmount: receiveAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
isBTCRequest: isReceiveAmountEntered);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is ChangeNowExchangeProvider) {
request = ChangeNowRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress,
isReverse: isFixedRateMode);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is MorphTokenExchangeProvider) {
request = MorphTokenRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is TrocadorExchangeProvider) {
request = TrocadorRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress,
isReverse: isFixedRateMode);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is ExolixExchangeProvider) {
request = ExolixRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
toAddress: receiveAddress,
isFixedRate: isFixedRateMode);
var amount = isFixedRateMode ? receiveAmount : depositAmount;
amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) {
if (double.tryParse(amount) == null) {
if (double.tryParse(amount) == null) continue;
if (limits.max != null && double.parse(amount) < limits.min!)
continue;
}
if (limits.max != null && double.parse(amount) < limits.min!) {
else if (limits.max != null && double.parse(amount) > limits.max!)
continue;
} else if (limits.max != null && double.parse(amount) > limits.max!) {
continue;
} else {
else {
try {
tradeState = TradeIsCreating();
final trade =
await provider.createTrade(request: request!, isFixedRateMode: isFixedRateMode);
await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode);
trade.walletId = wallet.id;
tradesStore.setTrade(trade);
await trades.add(trade);
@@ -636,9 +539,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final priority = _settingsStore.priority[wallet.type]!;
final fee = wallet.calculateEstimatedFee(priority, null);
if (availableBalance < fee || availableBalance == 0) {
return;
}
if (availableBalance < fee || availableBalance == 0) return;
final amount = availableBalance - fee;
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
@@ -669,19 +570,15 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
void removeTemplate({required ExchangeTemplate template}) =>
_exchangeTemplateStore.remove(template: template);
List<ExchangeProvider> providersForCurrentPair() {
return _providersForPair(from: depositCurrency, to: receiveCurrency);
}
List<ExchangeProvider> providersForCurrentPair() =>
_providersForPair(from: depositCurrency, to: receiveCurrency);
List<ExchangeProvider> _providersForPair(
{required CryptoCurrency from, required CryptoCurrency to}) {
final providers = providerList
.where((provider) =>
provider.pairList.where((pair) => pair.from == from && pair.to == to).isNotEmpty)
.toList();
return providers;
}
{required CryptoCurrency from, required CryptoCurrency to}) =>
providerList
.where((provider) =>
provider.pairList.where((pair) => pair.from == from && pair.to == to).isNotEmpty)
.toList();
void _onPairChange() {
depositAmount = '';
@@ -744,9 +641,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
void addExchangeProvider(ExchangeProvider provider) {
selectedProviders.add(provider);
if (providersForCurrentPair().contains(provider)) {
_tradeAvailableProviders.add(provider);
}
if (providersForCurrentPair().contains(provider)) _tradeAvailableProviders.add(provider);
}
@action
@@ -817,14 +712,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
void _setProviders() {
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly) {
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly)
providerList = _allProviders.where((provider) => provider.supportsOnionAddress).toList();
} else {
else
providerList = _allProviders;
}
}
int get depositMaxDigits => depositCurrency == CryptoCurrency.btc ? 8 : 12;
int get depositMaxDigits => depositCurrency.decimals;
int get receiveMaxDigits => receiveCurrency == CryptoCurrency.btc ? 8 : 12;
int get receiveMaxDigits => receiveCurrency.decimals;
}
+25 -26
View File
@@ -1,28 +1,28 @@
import 'dart:async';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_provider_unsupported_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:collection/collection.dart';
part 'trade_details_view_model.g.dart';
@@ -37,15 +37,9 @@ abstract class TradeDetailsViewModelBase with Store {
trade = trades.values.firstWhereOrNull((element) => element.id == tradeForDetails.id) ??
tradeForDetails {
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
_provider = XMRTOExchangeProvider();
break;
case ExchangeProviderDescription.changeNow:
_provider = ChangeNowExchangeProvider(settingsStore: settingsStore);
break;
case ExchangeProviderDescription.morphToken:
_provider = MorphTokenExchangeProvider(trades: trades);
break;
case ExchangeProviderDescription.sideShift:
_provider = SideShiftExchangeProvider();
break;
@@ -62,9 +56,10 @@ abstract class TradeDetailsViewModelBase with Store {
_updateItems();
_updateTrade();
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
if (_provider != null) {
_updateTrade();
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
}
final Box<Trade> trades;
@@ -86,9 +81,9 @@ abstract class TradeDetailsViewModelBase with Store {
try {
final updatedTrade = await _provider!.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
if (updatedTrade.createdAt == null && trade.createdAt != null)
updatedTrade.createdAt = trade.createdAt;
}
Trade? foundElement = trades.values.firstWhereOrNull((element) => element.id == trade.id);
if (foundElement != null) {
final editedTrade = trades.get(foundElement.key);
@@ -109,6 +104,10 @@ abstract class TradeDetailsViewModelBase with Store {
items.clear();
if (_provider == null)
items.add(TradeProviderUnsupportedItem(
error: S.current.exchange_provider_unsupported(trade.provider.title)));
items.add(
DetailsListStatusItem(title: S.current.trade_details_state, value: trade.state.toString()));
@@ -165,7 +164,7 @@ abstract class TradeDetailsViewModelBase with Store {
if (trade.provider == ExchangeProviderDescription.exolix) {
final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
items.add(
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
}
}