Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cw 613 quantex #1377

Merged
merged 23 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
119a5d8
save progress
fossephate Apr 11, 2024
7731ee0
[skip ci]
fossephate Apr 11, 2024
1cb6edc
forgot to add [skip ci]
fossephate Apr 11, 2024
d7adf21
not sure what exactly I changed but it just works now! ¯\_(ツ)_/¯
fossephate Apr 12, 2024
3320950
status updates
fossephate Apr 12, 2024
3fd9608
minor cleanup
fossephate Apr 15, 2024
3b218db
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate Apr 15, 2024
7ded8d4
minor fix (toUppercase needed)
fossephate Apr 16, 2024
255d7d4
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate Apr 23, 2024
5a5a711
remove unnecessary apikey + keep original raw values
fossephate Apr 25, 2024
d0d3261
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate Apr 25, 2024
1e823a7
Merge branch 'main' into CW-613-quantex
OmarHatem28 Apr 29, 2024
9e5ce0c
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate May 1, 2024
e26020a
Merge branch 'CW-613-quantex' of https://github.com/cake-tech/cake_wa…
fossephate May 1, 2024
91f4dcb
fix track url for quantex
fossephate May 1, 2024
749b063
only increment raw values
fossephate May 6, 2024
aa39cff
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate May 6, 2024
5330350
[skip ci] Merge branch 'main' of https://github.com/cake-tech/cake_wa…
fossephate May 7, 2024
6311138
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate May 8, 2024
127897f
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate May 10, 2024
fa8d311
Merge branch 'main' into CW-613-quantex
OmarHatem28 May 11, 2024
5e216e2
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into …
fossephate May 13, 2024
79e6bc1
Merge branch 'CW-613-quantex' of https://github.com/cake-tech/cake_wa…
fossephate May 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart

- name: Rename app
run: |
Expand Down
Binary file added assets/images/quantex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 7 additions & 3 deletions lib/exchange/exchange_provider_description.dart
fossephate marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
static const quantex =
ExchangeProviderDescription(title: 'Quantex', raw: 7, image: 'assets/images/quantex.png');
static const thorChain =
ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png');
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');

static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 9, image: '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet fixed, everything should return to as it was before, just add quantex as raw 9


static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) {
Expand All @@ -43,9 +45,11 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
return trocador;
case 6:
return exolix;
case 7:
return quantex;
case 8:
return thorChain;
case 7:
case 9:
return all;
default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
Expand Down
252 changes: 252 additions & 0 deletions lib/exchange/provider/quantex_exchange_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import 'dart:convert';

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_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 QuantexExchangeProvider extends ExchangeProvider {
QuantexExchangeProvider() : super(pairList: supportedPairs(_notSupported));

static final List<CryptoCurrency> _notSupported = [
...(CryptoCurrency.all
.where((element) => ![
CryptoCurrency.btc,
CryptoCurrency.sol,
CryptoCurrency.eth,
CryptoCurrency.ltc,
CryptoCurrency.ada,
CryptoCurrency.bch,
CryptoCurrency.usdt,
CryptoCurrency.bnb,
CryptoCurrency.xmr,
].contains(element))
.toList())
];

static final markup = secrets.quantexExchangeMarkup;

static const apiAuthority = 'api.myquantex.com';
static const getRate = '/api/swap/get-rate';
static const getCoins = '/api/swap/get-coins';
static const createOrder = '/api/swap/create-order';

@override
String get title => 'Quantex';

@override
bool get isAvailable => true;

@override
bool get isEnabled => true;

@override
bool get supportsFixedRate => false;

@override
ExchangeProviderDescription get description => ExchangeProviderDescription.quantex;

@override
Future<bool> checkIsAvailable() async => true;

@override
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode,
}) async {
try {
final uri = Uri.https(apiAuthority, getCoins);
final response = await get(uri);

final responseJSON = json.decode(response.body) as Map<String, dynamic>;

if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');

final coinsInfo = responseJSON['data'] as List<dynamic>;

for (var coin in coinsInfo) {
if (coin['id'].toString().toUpperCase() == _normalizeCurrency(from)) {
return Limits(
min: double.parse(coin['min'].toString()),
max: double.parse(coin['max'].toString()),
);
}
}

// coin not found:
return Limits(min: 0, max: 0);
} catch (e) {
print(e.toString());
return Limits(min: 0, max: 0);
}
}

@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 = <String, String>{};
final params = <String, dynamic>{};
final body = <String, String>{
'coin_send': _normalizeCurrency(from),
'coin_receive': _normalizeCurrency(to),
'ref': 'cake',
};

final uri = Uri.https(apiAuthority, getRate, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;

if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');

final data = responseBody['data'] as Map<String, dynamic>;
double rate = double.parse(data['price'].toString());
return rate;
} catch (e) {
print("error fetching rate: ${e.toString()}");
return 0.0;
}
}

@override
Future<Trade> createTrade({
required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll,
}) async {
try {
final headers = <String, String>{};
final params = <String, dynamic>{};
var body = <String, dynamic>{
'coin_send': _normalizeCurrency(request.fromCurrency),
'coin_receive': _normalizeCurrency(request.toCurrency),
'amount_send': request.fromAmount,
'recipient': request.toAddress,
'ref': 'cake',
'markup': markup,
};

String? fromNetwork = _networkFor(request.fromCurrency);
String? toNetwork = _networkFor(request.toCurrency);
if (fromNetwork != null) body['coin_send_network'] = fromNetwork;
if (toNetwork != null) body['coin_receive_network'] = toNetwork;

final uri = Uri.https(apiAuthority, createOrder, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;

if (response.statusCode == 400 || responseBody["success"] == false) {
final error = responseBody['errors'][0]['msg'] as String;
throw TradeNotCreatedException(description, description: error);
}

if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');

final responseData = responseBody['data'] as Map<String, dynamic>;

return Trade(
id: responseData["order_id"] as String,
inputAddress: responseData["server_address"] as String,
amount: request.fromAmount,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
createdAt: DateTime.now(),
state: TradeState.created,
payoutAddress: request.toAddress,
isSendAll: isSendAll,
);
} catch (e) {
print("error creating trade: ${e.toString()}");
throw TradeNotCreatedException(description, description: e.toString());
}
}

@override
Future<Trade> findTradeById({required String id}) async {
try {
final headers = <String, String>{};
final params = <String, dynamic>{};
var body = <String, dynamic>{
'order_id': id,
};

final uri = Uri.https(apiAuthority, createOrder, params);
final response = await post(uri, body: body, headers: headers);
final responseBody = json.decode(response.body) as Map<String, dynamic>;

if (response.statusCode == 400 || responseBody["success"] == false) {
final error = responseBody['errors'][0]['msg'] as String;
throw TradeNotCreatedException(description, description: error);
}

if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');

final responseData = responseBody['data'] as Map<String, dynamic>;
final fromCurrency = responseData['coin_send'] as String;
final from = CryptoCurrency.fromString(fromCurrency);
final toCurrency = responseData['coin_receive'] as String;
final to = CryptoCurrency.fromString(toCurrency);
final inputAddress = responseData['server_address'] as String;
final status = responseData['status'] as String;
final state = TradeState.deserialize(raw: status);
final response_id = responseData['order_id'] as String;
final expectedSendAmount = responseData['amount_send'] as String;

return Trade(
id: response_id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount,
state: state,
);
} catch (e) {
print("error getting trade: ${e.toString()}");
throw TradeNotFoundException(
id,
provider: description,
description: e.toString(),
);
}
}

String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
default:
return currency.title.toUpperCase();
}
}

String? _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.usdt:
return "USDT_ERC20";
case CryptoCurrency.bnb:
return "BNB_BSC";
default:
return null;
}
}
}
27 changes: 27 additions & 0 deletions lib/exchange/trade_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization');
static const failed = TradeState(raw: 'failed', title: 'Failed');
static const completed = TradeState(raw: 'completed', title: 'Completed');
static const expired = TradeState(raw: 'expired', title: 'Expired');
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
static const wait = TradeState(raw: 'wait', title: 'Waiting');
Expand All @@ -39,7 +40,33 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success');

static TradeState deserialize({required String raw}) {

switch (raw) {
case '1':
return unpaid;
case '2':
return paidUnconfirmed;
case '3':
return sending;
case '4':
return confirmed;
case '5':
case '6':
return exchanging;
case '7':
return sending;
case '8':
return complete;
case '9':
return expired;
case '10':
return underpaid;
case '11':
return failed;
}

switch (raw) {
case 'NOT_FOUND':
return notFound;
Expand Down
4 changes: 4 additions & 0 deletions lib/view_model/exchange/exchange_trade_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:cake_wallet/exchange/exchange_provider_description.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/quantex_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/thorchain_exchange.provider.dart';
Expand Down Expand Up @@ -48,6 +49,9 @@ abstract class ExchangeTradeViewModelBase with Store {
case ExchangeProviderDescription.exolix:
_provider = ExolixExchangeProvider();
break;
case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider();
break;
case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades);
break;
Expand Down
2 changes: 2 additions & 0 deletions lib/view_model/exchange/exchange_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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/quantex_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/thorchain_exchange.provider.dart';
Expand Down Expand Up @@ -157,6 +158,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
ThorChainExchangeProvider(tradesStore: trades),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
QuantexExchangeProvider(),
];

@observable
Expand Down
6 changes: 6 additions & 0 deletions lib/view_model/trade_details_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:cake_wallet/exchange/exchange_provider_description.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/quantex_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/thorchain_exchange.provider.dart';
Expand Down Expand Up @@ -56,6 +57,9 @@ abstract class TradeDetailsViewModelBase with Store {
case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades);
break;
case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider();
break;
}

_updateItems();
Expand All @@ -80,6 +84,8 @@ abstract class TradeDetailsViewModelBase with Store {
return 'https://exolix.com/transaction/${trade.id}';
case ExchangeProviderDescription.thorChain:
return 'https://track.ninerealms.com/${trade.id}';
case ExchangeProviderDescription.quantex:
return 'https://myquantex.com/send/${trade.id}';
}
return null;
}
Expand Down
1 change: 1 addition & 0 deletions tool/utils/secret_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class SecretKey {
SecretKey('walletConnectProjectId', () => ''),
SecretKey('moralisApiKey', () => ''),
SecretKey('ankrApiKey', () => ''),
SecretKey('quantexExchangeMarkup', () => ''),
];

static final evmChainsSecrets = [
Expand Down
Loading