CakeWallet/lib/exchange/provider/xoswap_exchange_provider.dart

326 lines
11 KiB
Dart
Raw Normal View History

import 'dart:convert';
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_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:cw_core/utils/print_verbose.dart';
CW-519 Enable built-in Tor (#1950) * tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 21:56:18 +02:00
import 'package:cw_core/utils/proxy_wrapper.dart';
class XOSwapExchangeProvider extends ExchangeProvider {
XOSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [];
static const _apiAuthority = 'exchange.exodus.io';
static const _apiPath = '/v3';
static const _pairsPath = '/pairs';
static const _ratePath = '/rates';
static const _orders = '/orders';
static const _assets = '/assets';
static const _headers = {'Content-Type': 'application/json', 'App-Name': 'cake-labs'};
final _networks = <String, String>{
'POL': 'matic',
'ETH': 'ethereum',
'BTC': 'bitcoin',
'BSC': 'bsc',
'SOL': 'solana',
'TRX': 'tronmainnet',
'ZEC': 'zcash',
'ADA': 'cardano',
'DOGE': 'dogecoin',
'XMR': 'monero',
'BCH': 'bcash',
'BSV': 'bitcoinsv',
'XRP': 'ripple',
'LTC': 'litecoin',
'EOS': 'eosio',
'XLM': 'stellar',
};
@override
String get title => 'XOSwap';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.xoSwap;
@override
Future<bool> checkIsAvailable() async => true;
Future<String?> _getAssets(CryptoCurrency currency) async {
if (currency.tag == null) return currency.title;
try {
final normalizedNetwork = _networks[currency.tag];
if (normalizedNetwork == null) return null;
final uri = Uri.https(_apiAuthority, _apiPath + _assets,
{'networks': normalizedNetwork, 'query': currency.title});
CW-519 Enable built-in Tor (#1950) * tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 21:56:18 +02:00
final response = await ProxyWrapper().get(clearnetUri: uri);
if (response.statusCode != 200) {
throw Exception('Failed to fetch assets for ${currency.title} on ${currency.tag}');
}
final assets = json.decode(response.body) as List<dynamic>;
final asset = assets.firstWhere(
(asset) {
final assetSymbol = (asset['symbol'] as String).toUpperCase();
return assetSymbol == currency.title.toUpperCase();
},
orElse: () => null,
);
return asset != null ? asset['id'] as String : null;
} catch (e) {
printV(e.toString());
return null;
}
}
Future<List<dynamic>> getRatesForPair({
required CryptoCurrency from,
required CryptoCurrency to,
}) async {
try {
final curFrom = await _getAssets(from);
final curTo = await _getAssets(to);
if (curFrom == null || curTo == null) return [];
final pairId = curFrom + '_' + curTo;
final uri = Uri.https(_apiAuthority, '$_apiPath$_pairsPath/$pairId$_ratePath');
CW-519 Enable built-in Tor (#1950) * tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 21:56:18 +02:00
final response = await ProxyWrapper().get(clearnetUri: uri);
if (response.statusCode != 200) return [];
return json.decode(response.body) as List<dynamic>;
} catch (e) {
printV(e.toString());
return [];
}
}
Future<Limits> fetchLimits({
required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode,
}) async {
final rates = await getRatesForPair(from: from, to: to);
if (rates.isEmpty) return Limits(min: 0, max: 0);
double minLimit = double.infinity;
double maxLimit = 0;
for (var rate in rates) {
final double currentMin = double.parse(rate['min']['value'].toString());
final double currentMax = double.parse(rate['max']['value'].toString());
if (currentMin < minLimit) minLimit = currentMin;
if (currentMax > maxLimit) maxLimit = currentMax;
}
return Limits(min: minLimit, max: maxLimit);
}
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount,
}) async {
try {
final rates = await getRatesForPair(from: from, to: to);
if (rates.isEmpty) return 0;
if (!isFixedRateMode) {
double bestOutput = 0.0;
for (var rate in rates) {
final double minVal = double.parse(rate['min']['value'].toString());
final double maxVal = double.parse(rate['max']['value'].toString());
if (amount >= minVal && amount <= maxVal) {
final double rateMultiplier = double.parse(rate['amount']['value'].toString());
final double minerFee = double.parse(rate['minerFee']['value'].toString());
final double outputAmount = (amount * rateMultiplier) - minerFee;
if (outputAmount > bestOutput) {
bestOutput = outputAmount;
}
}
}
return bestOutput > 0 ? (bestOutput / amount) : 0;
} else {
double bestInput = double.infinity;
for (var rate in rates) {
final double rateMultiplier = double.parse(rate['amount']['value'].toString());
final double minerFee = double.parse(rate['minerFee']['value'].toString());
final double minVal = double.parse(rate['min']['value'].toString());
final double maxVal = double.parse(rate['max']['value'].toString());
final double requiredSend = (amount + minerFee) / rateMultiplier;
if (requiredSend >= minVal && requiredSend <= maxVal) {
if (requiredSend < bestInput) {
bestInput = requiredSend;
}
}
}
return bestInput < double.infinity ? amount / bestInput : 0;
}
} catch (e) {
printV(e.toString());
return 0;
}
}
@override
Future<Trade> createTrade({
required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll,
}) async {
try {
final uri = Uri.https(_apiAuthority, '$_apiPath$_orders');
2025-04-01 16:33:49 +03:00
final curFrom = await _getAssets(request.fromCurrency);
final curTo = await _getAssets(request.toCurrency);
if (curFrom == null || curTo == null) {
throw TradeNotCreatedException(description);
}
final pairId = curFrom + '_' + curTo;
final payload = {
'fromAmount': request.fromAmount,
'fromAddress': request.refundAddress,
'toAmount': request.toAmount,
'toAddress': request.toAddress,
2025-04-01 16:33:49 +03:00
'pairId': pairId,
};
CW-519 Enable built-in Tor (#1950) * tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 21:56:18 +02:00
final response = await ProxyWrapper().post(
clearnetUri: uri,
headers: _headers,
body: json.encode(payload),
);
if (response.statusCode != 201) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error'] ?? 'Unknown error';
final message = responseJSON['message'] ?? '';
throw Exception('$error\n$message');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final amount = responseJSON['amount'] as Map<String, dynamic>;
final toAmount = responseJSON['toAmount'] as Map<String, dynamic>;
final orderId = responseJSON['id'] as String;
final from = request.fromCurrency;
final to = request.toCurrency;
final payoutAddress = responseJSON['toAddress'] as String;
final depositAddress = responseJSON['payInAddress'] as String;
final refundAddress = responseJSON['fromAddress'] as String;
final depositAmount = _toDouble(amount['value']);
final receiveAmount = toAmount['value'] as String;
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['createdAt'] as String;
final extraId = responseJSON['payInAddressTag'] as String?;
final createdAt = DateTime.parse(createdAtString).toLocal();
return Trade(
id: orderId,
from: from,
to: to,
provider: description,
inputAddress: depositAddress,
refundAddress: refundAddress,
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
amount: depositAmount.toString(),
receiveAmount: receiveAmount.toString(),
payoutAddress: payoutAddress,
extraId: extraId,
);
} catch (e) {
printV(e.toString());
throw TradeNotCreatedException(description);
}
}
@override
Future<Trade> findTradeById({required String id}) async {
try {
final uri = Uri.https(_apiAuthority, '$_apiPath$_orders/$id');
CW-519 Enable built-in Tor (#1950) * tor wip * Enable tor on iOS * Prevent app lag when node is exceptionally slow (usually over tor) * fix: logic in daemonBlockchainHeight refresh fix: storing tor state * Pin ledger_flutter_plus dependency to fix builds * bump arti version * wip * add single httpclient * route everything I was able to catch trough the built-in tor node * Enable proxy for http.Client [run tests] * add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests] * remove log pollution, cleanup [skip slack] * fix tests not working in latest main [skip slack] [run tests] * remove cw_wownero import * fix build issues * migrate all remaining calls to use ProxyWrapper add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks * fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top) * wip [skip ci] * relicense to GPLv3 add socks5 license, build fixes * use ProxyWrapper instead of http in robinhood * Revert "relicense to GPLv3" * feat(cw_bitcoin): support socks proxy and CakeTor * fix(tor): migrate OCP and EVM over to ProxyWrapper() * chore: cleanup fix: show tor loading screen when app is starting * fix: tor switch properly dismisses fullscreen loading dialog fix: connectToNode after tor startup on app start * fix(tor): status check for xmr/wow/zano * fix(tor): onramper request fix * fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt fix(tor): do not fallback to clearnet when tor failed. fix(tor): do not leak connections during app startup chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline fix(cw_bitcoin): migrate payjoin to use ProxyWrapper * [skip ci] remove print * address comments from review * fix: derusting tor implementation Instead of rust-based Arti I've moved back to the OG C++ tor implementation. This fixed all issues we had with Tor. - onion services now work - all requests are going through without random errors - we don't have to navigate a maze of multiple forks of multiple packages - fully working `torrc` config file (probably will be needed for Tari). - logging for Tor client - and so on. feat: network logging tab feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though) * fix conflicts with main bump https to fix build issue relax store() call * fix(cw_wownero): tor connection fix(tor): connection issues * fix(cw_evm): add missing chainId fix(cw_core): solana rpc fix * feat: mark tor as experimental fix: drop anonpay onion authority fix: drop fiatapi onion authority fix: drop trocador onion authority fix: disable networkimage when tor is enabled fix: handle cakepay errors gracefully * fix re-formatting [skip ci] * changes from review * Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive * fix missing imports * Update pubspec_base.yaml --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 21:56:18 +02:00
final response = await ProxyWrapper().get(clearnetUri: uri);
if (response.statusCode != 200) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (responseJSON.containsKey('code') && responseJSON['code'] == 'NOT_FOUND') {
throw Exception('Trade not found');
}
final error = responseJSON['error'] ?? 'Unknown error';
final message = responseJSON['message'] ?? responseJSON['details'] ?? '';
throw Exception('$error\n$message');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final pairId = responseJSON['pairId'] as String;
final pairParts = pairId.split('_');
final CryptoCurrency fromCurrency =
CryptoCurrency.fromString(pairParts.isNotEmpty ? pairParts[0] : "");
final CryptoCurrency toCurrency =
CryptoCurrency.fromString(pairParts.length > 1 ? pairParts[1] : "");
final amount = responseJSON['amount'] as Map<String, dynamic>;
final toAmount = responseJSON['toAmount'] as Map<String, dynamic>;
final orderId = responseJSON['id'] as String;
final depositAmount = amount['value'] as String;
final receiveAmount = toAmount['value'] as String;
final depositAddress = responseJSON['payInAddress'] as String;
final payoutAddress = responseJSON['toAddress'] as String;
final refundAddress = responseJSON['fromAddress'] as String;
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['createdAt'] as String;
final createdAt = DateTime.parse(createdAtString).toLocal();
final extraId = responseJSON['payInAddressTag'] as String?;
return Trade(
id: orderId,
from: fromCurrency,
to: toCurrency,
provider: description,
inputAddress: depositAddress,
refundAddress: refundAddress,
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
amount: depositAmount,
receiveAmount: receiveAmount,
payoutAddress: payoutAddress,
extraId: extraId,
);
} catch (e) {
printV(e.toString());
throw TradeNotCreatedException(description);
}
}
double _toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
} else {
return 0.0;
}
}
}