Merge pull request #765 from cake-tech/CW-282-add-trocador-exchange

CW-282 add trocador exchange
This commit is contained in:
Omar Hatem 2023-03-02 21:42:30 +02:00 committed by GitHub
commit 3128d458f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 680 additions and 188 deletions

View file

@ -111,6 +111,8 @@ jobs:
echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart
echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
- name: Rename app - name: Rename app
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties

BIN
assets/images/trocador.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -117,7 +117,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29, name: 'xusd'); static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29, name: 'xusd');
static const ape = CryptoCurrency(title: 'APE', tag: 'ETH', fullName: 'ApeCoin', raw: 30, name: 'ape', iconPath: 'assets/images/ape_icon.png'); static const ape = CryptoCurrency(title: 'APE', tag: 'ETH', fullName: 'ApeCoin', raw: 30, name: 'ape', iconPath: 'assets/images/ape_icon.png');
static const avaxc = CryptoCurrency(title: 'AVAX', tag: 'C-CHAIN', raw: 31, name: 'avaxc', iconPath: 'assets/images/avaxc_icon.png'); static const avaxc = CryptoCurrency(title: 'AVAX', tag: 'AVAXC', raw: 31, name: 'avaxc', iconPath: 'assets/images/avaxc_icon.png');
static const btt = CryptoCurrency(title: 'BTT', tag: 'ETH', fullName: 'BitTorrent', raw: 32, name: 'btt', iconPath: 'assets/images/btt_icon.png'); static const btt = CryptoCurrency(title: 'BTT', tag: 'ETH', fullName: 'BitTorrent', raw: 32, name: 'btt', iconPath: 'assets/images/btt_icon.png');
static const bttc = CryptoCurrency(title: 'BTTC', tag: 'TRX', fullName: 'BitTorrent-NEW', raw: 33, name: 'bttc', iconPath: 'assets/images/bttbsc_icon.png'); static const bttc = CryptoCurrency(title: 'BTTC', tag: 'TRX', fullName: 'BitTorrent-NEW', raw: 33, name: 'bttc', iconPath: 'assets/images/bttbsc_icon.png');
static const doge = CryptoCurrency(title: 'DOGE', fullName: 'Dogecoin', raw: 34, name: 'doge', iconPath: 'assets/images/doge_icon.png'); static const doge = CryptoCurrency(title: 'DOGE', fullName: 'Dogecoin', raw: 34, name: 'doge', iconPath: 'assets/images/doge_icon.png');

View file

@ -13,6 +13,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
http: ^0.13.4 http: ^0.13.4
file: ^6.1.4
path_provider: ^2.0.11 path_provider: ^2.0.11
mobx: ^2.0.7+4 mobx: ^2.0.7+4
flutter_mobx: ^2.0.6+1 flutter_mobx: ^2.0.6+1

View file

@ -217,7 +217,7 @@ class BackupService {
final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?; final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?;
final currentPinLength = data[PreferencesKey.currentPinLength] as int?; final currentPinLength = data[PreferencesKey.currentPinLength] as int?;
final currentTheme = data[PreferencesKey.currentTheme] as int?; final currentTheme = data[PreferencesKey.currentTheme] as int?;
final disableExchange = data[PreferencesKey.disableExchangeKey] as bool?; final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?;
final currentDefaultSettingsMigrationVersion = data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; final currentDefaultSettingsMigrationVersion = data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?;
final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?; final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?;
final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?; final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?;
@ -280,9 +280,9 @@ class BackupService {
await _sharedPreferences.setInt( await _sharedPreferences.setInt(
PreferencesKey.currentTheme, currentTheme); PreferencesKey.currentTheme, currentTheme);
if (disableExchange != null) if (exchangeStatus != null)
await _sharedPreferences.setBool( await _sharedPreferences.setInt(
PreferencesKey.disableExchangeKey, disableExchange); PreferencesKey.exchangeStatusKey, exchangeStatus);
if (currentDefaultSettingsMigrationVersion != null) if (currentDefaultSettingsMigrationVersion != null)
await _sharedPreferences.setInt( await _sharedPreferences.setInt(
@ -431,8 +431,8 @@ class BackupService {
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
PreferencesKey.currentTheme: PreferencesKey.currentTheme:
_sharedPreferences.getInt(PreferencesKey.currentTheme), _sharedPreferences.getInt(PreferencesKey.currentTheme),
PreferencesKey.disableExchangeKey: PreferencesKey.exchangeStatusKey:
_sharedPreferences.getBool(PreferencesKey.disableExchangeKey), _sharedPreferences.getInt(PreferencesKey.exchangeStatusKey),
PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion),
PreferencesKey.bitcoinTransactionPriority: PreferencesKey.bitcoinTransactionPriority:

View file

@ -1,10 +1,12 @@
import 'dart:io' show File, Platform; import 'dart:io' show File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:share_plus/share_plus.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -142,7 +144,9 @@ Future defaultSettingsMigration(
case 19: case 19:
await validateBitcoinSavedTransactionPriority(sharedPreferences); await validateBitcoinSavedTransactionPriority(sharedPreferences);
break; break;
case 20:
await migrateExchangeStatus(sharedPreferences);
break;
default: default:
break; break;
} }
@ -501,3 +505,15 @@ Future<void> changeDefaultHavenNode(
await node.save(); await node.save();
}); });
} }
Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
final isExchangeDisabled = sharedPreferences.getBool(PreferencesKey.disableExchangeKey);
if (isExchangeDisabled == null) {
return;
}
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
}

View file

@ -0,0 +1,39 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/enumerable_item.dart';
class ExchangeApiMode extends EnumerableItem<int> with Serializable<int> {
const ExchangeApiMode({required String title, required int raw}) : super(title: title, raw: raw);
static const all = [ExchangeApiMode.enabled, ExchangeApiMode.torOnly, ExchangeApiMode.disabled];
static const enabled = ExchangeApiMode(raw: 0, title: 'Enabled');
static const torOnly = ExchangeApiMode(raw: 1, title: 'Tor only');
static const disabled = ExchangeApiMode(raw: 2, title: 'Disabled');
static ExchangeApiMode deserialize({required int raw}) {
switch (raw) {
case 0:
return enabled;
case 1:
return torOnly;
case 2:
return disabled;
default:
throw Exception('Unexpected token: $raw for ExchangeApiMode deserialize');
}
}
@override
String toString() {
switch (this) {
case ExchangeApiMode.enabled:
return S.current.enabled;
case ExchangeApiMode.torOnly:
return S.current.tor_only;
case ExchangeApiMode.disabled:
return S.current.disabled;
default:
return '';
}
}
}

View file

@ -13,6 +13,7 @@ class PreferencesKey {
static const allowBiometricalAuthenticationKey = static const allowBiometricalAuthenticationKey =
'allow_biometrical_authentication'; 'allow_biometrical_authentication';
static const disableExchangeKey = 'disable_exchange'; static const disableExchangeKey = 'disable_exchange';
static const exchangeStatusKey = 'exchange_status';
static const currentTheme = 'current_theme'; static const currentTheme = 'current_theme';
static const isDarkThemeLegacy = 'dark_theme'; static const isDarkThemeLegacy = 'dark_theme';
static const displayActionListModeKey = 'display_list_mode'; static const displayActionListModeKey = 'display_list_mode';

View file

@ -269,6 +269,7 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
: currency.title.toLowerCase(); : currency.title.toLowerCase();
} }
} }
} }
String normalizeCryptoCurrency(CryptoCurrency currency) { String normalizeCryptoCurrency(CryptoCurrency currency) {

View file

@ -14,6 +14,7 @@ abstract class ExchangeProvider {
bool get isAvailable; bool get isAvailable;
bool get isEnabled; bool get isEnabled;
bool get supportsFixedRate; bool get supportsFixedRate;
bool get supportsOnionAddress => false;
@override @override
String toString() => title; String toString() => title;

View file

@ -1,18 +1,15 @@
import 'package:cw_core/enumerable_item.dart'; import 'package:cw_core/enumerable_item.dart';
class ExchangeProviderDescription extends EnumerableItem<int> class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<int> {
with Serializable<int> { const ExchangeProviderDescription(
const ExchangeProviderDescription({ {required String title, required int raw, required this.image, this.horizontalLogo = false})
required String title,
required int raw,
required this.image,
this.horizontalLogo = false})
: super(title: title, raw: raw); : super(title: title, raw: raw);
final bool horizontalLogo; final bool horizontalLogo;
final String image; final String image;
static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png'); static const xmrto =
ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png');
static const changeNow = static const changeNow =
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png'); ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
static const morphToken = static const morphToken =
@ -21,11 +18,13 @@ class ExchangeProviderDescription extends EnumerableItem<int>
static const sideShift = static const sideShift =
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png'); ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
static const simpleSwap = static const simpleSwap = ExchangeProviderDescription(
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png'); title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
static const all = static const trocador =
ExchangeProviderDescription(title: 'All trades', raw: 5, image:''); ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: '');
static ExchangeProviderDescription deserialize({required int raw}) { static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) { switch (raw) {
@ -40,6 +39,8 @@ class ExchangeProviderDescription extends EnumerableItem<int>
case 4: case 4:
return simpleSwap; return simpleSwap;
case 5: case 5:
return trocador;
case 6:
return all; return all;
default: default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');

View file

@ -8,8 +8,8 @@ part 'trade.g.dart';
@HiveType(typeId: Trade.typeId) @HiveType(typeId: Trade.typeId)
class Trade extends HiveObject { class Trade extends HiveObject {
Trade( Trade({
{required this.id, required this.id,
required this.amount, required this.amount,
ExchangeProviderDescription? provider, ExchangeProviderDescription? provider,
CryptoCurrency? from, CryptoCurrency? from,
@ -22,7 +22,11 @@ class Trade extends HiveObject {
this.outputTransaction, this.outputTransaction,
this.refundAddress, this.refundAddress,
this.walletId, this.walletId,
this.payoutAddress}) { this.payoutAddress,
this.password,
this.providerId,
this.providerName,
}) {
if (provider != null) { if (provider != null) {
providerRaw = provider.raw; providerRaw = provider.raw;
} }
@ -92,16 +96,23 @@ class Trade extends HiveObject {
@HiveField(13) @HiveField(13)
String? payoutAddress; String? payoutAddress;
@HiveField(14)
String? password;
@HiveField(15)
String? providerId;
@HiveField(16)
String? providerName;
static Trade fromMap(Map<String, Object?> map) { static Trade fromMap(Map<String, Object?> map) {
return Trade( return Trade(
id: map['id'] as String, id: map['id'] as String,
provider: ExchangeProviderDescription.deserialize( provider: ExchangeProviderDescription.deserialize(raw: map['provider'] as int),
raw: map['provider'] as int),
from: CryptoCurrency.deserialize(raw: map['input'] as int), from: CryptoCurrency.deserialize(raw: map['input'] as int),
to: CryptoCurrency.deserialize(raw: map['output'] as int), to: CryptoCurrency.deserialize(raw: map['output'] as int),
createdAt: map['date'] != null createdAt:
? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
: null,
amount: map['amount'] as String, amount: map['amount'] as String,
walletId: map['wallet_id'] as String); walletId: map['wallet_id'] as String);
} }

View file

@ -0,0 +1,312 @@
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:http/http.dart';
class TrocadorExchangeProvider extends ExchangeProvider {
TrocadorExchangeProvider({this.useTorOnly = false})
: _lastUsedRateId = '',
super(pairList: _supportedPairs());
bool useTorOnly;
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.scrt,
CryptoCurrency.stx,
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 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;
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) {
final _request = request as TrocadorRequest;
return _createTrade(request: _request, isFixedRateMode: isFixedRateMode);
}
Future<Trade> _createTrade({
required TrocadorRequest request,
required bool isFixedRateMode,
}) async {
final params = <String, String>{
'api_key': apiKey,
'ticker_from': request.from.title.toLowerCase(),
'ticker_to': request.to.title.toLowerCase(),
'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
};
if (isFixedRateMode) {
await fetchRate(
from: request.from,
to: request.to,
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 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
ExchangeProviderDescription get description => ExchangeProviderDescription.trocador;
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, String>{
'api_key': apiKey,
'ticker': from.title.toLowerCase(),
'name': from.name,
};
final uri = await _getUri(coinPath, params);
final response = await get(uri);
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');
}
final coinJson = responseJSON.first as Map<String, dynamic>;
return Limits(
min: coinJson['minimum'] as double,
max: coinJson['maximum'] 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 params = <String, String>{
'api_key': apiKey,
'ticker_from': from.title.toLowerCase(),
'ticker_to': to.title.toLowerCase(),
'network_from': _networkFor(from),
'network_to': _networkFor(to),
if (!isFixedRateMode) 'amount_from': amount.toString(),
if (isFixedRateMode) 'amount_to': amount.toString(),
'payment': isFixedRateMode ? 'True' : 'False',
'min_kycrating': 'C',
'markup': markup,
'best_only': 'True',
};
final uri = await _getUri(newRatePath, params);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromAmount = double.parse(responseJSON['amount_from'].toString());
final toAmount = double.parse(responseJSON['amount_to'].toString());
final rateId = responseJSON['trade_id'] as String? ?? '';
if (rateId.isNotEmpty) {
_lastUsedRateId = rateId;
}
return isReceiveAmount ? (amount / fromAmount) : (toAmount / amount);
} catch (e) {
print(e.toString());
return 0.0;
}
}
@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) {
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 inputAddress = responseJSON['address_user'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final payoutAddress = 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,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
createdAt: date,
amount: fromAmount,
state: state,
payoutAddress: payoutAddress,
password: password,
providerId: providerId,
providerName: providerName,
);
});
}
@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:
return 'ERC20';
case CryptoCurrency.maticpoly:
return 'Mainnet';
case CryptoCurrency.usdcpoly:
return 'MATIC';
case CryptoCurrency.zec:
return 'Mainnet';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'ETH':
return 'ERC20';
case 'TRX':
return 'TRC20';
default:
return tag.toLowerCase();
}
}
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;
}
try {
await get(uri);
return uri;
} catch (e) {
return Uri.https(clearNetAuthority, path, queryParams);
}
}
}

View file

@ -0,0 +1,21 @@
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;
}

View file

@ -9,7 +9,8 @@ class TradeRow extends StatelessWidget {
required this.to, required this.to,
required this.createdAtFormattedDate, required this.createdAtFormattedDate,
this.onTap, this.onTap,
this.formattedAmount,}); this.formattedAmount,
});
final VoidCallback? onTap; final VoidCallback? onTap;
final ExchangeProviderDescription provider; final ExchangeProviderDescription provider;
@ -37,45 +38,38 @@ class TradeRow extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('${from.toString()}${to.toString()}', Text('${from.toString()}${to.toString()}',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor! color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
)),
formattedAmount != null formattedAmount != null
? Text(formattedAmount! + ' ' + amountCrypto, ? Text(formattedAmount! + ' ' + amountCrypto,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor! color:
)) Theme.of(context).accentTextTheme!.headline2!.backgroundColor!))
: Container() : Container()
]), ]),
SizedBox(height: 5), SizedBox(height: 5),
Row( Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
if (createdAtFormattedDate != null) if (createdAtFormattedDate != null)
Text(createdAtFormattedDate!, Text(createdAtFormattedDate!,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Theme.of(context).textTheme! color: Theme.of(context).textTheme!.overline!.backgroundColor!))
.overline!.backgroundColor!))
]) ])
], ],
) ))
)
], ],
), ),
)); ));
} }
Image? _getPoweredImage(ExchangeProviderDescription provider) { Widget? _getPoweredImage(ExchangeProviderDescription provider) {
Image? image; Widget? image;
switch (provider) { switch (provider) {
case ExchangeProviderDescription.xmrto: case ExchangeProviderDescription.xmrto:
@ -93,6 +87,11 @@ class TradeRow extends StatelessWidget {
case ExchangeProviderDescription.simpleSwap: case ExchangeProviderDescription.simpleSwap:
image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36); image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36);
break; break;
case ExchangeProviderDescription.trocador:
image = ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
break;
default: default:
image = null; image = null;
} }

View file

@ -1,7 +1,11 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart'; import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -48,29 +52,51 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
...widget.privacySettingsViewModel.settings.map( Observer(
(item) => Observer( builder: (_) {
builder: (_) => SettingsSwitcherCell( return SettingsSwitcherCell(
title: item.title, title: S.current.disable_fiat,
value: item.value(), value: widget.privacySettingsViewModel.fiatApi == FiatApiMode.disabled,
onValueChange: item.onValueChange, onValueChange: (BuildContext context, bool value) {
), widget.privacySettingsViewModel.setFiatMode(value);
), });
}
), ),
Observer( Observer(
builder: (_) { builder: (_) {
if (widget.privacySettingsViewModel.addCustomNode) { return SettingsChoicesCell(
return Padding( ChoicesListItem<ExchangeApiMode>(
title: S.current.exchange,
items: ExchangeApiMode.all,
selectedItem: widget.privacySettingsViewModel.exchangeStatus,
onItemSelected: (ExchangeApiMode mode) =>
widget.privacySettingsViewModel.setExchangeApiMode(mode),
),
);
}
),
Observer(
builder: (_) {
return Column(
children: [
SettingsSwitcherCell(
title: S.current.add_custom_node,
value: widget.privacySettingsViewModel.addCustomNode,
onValueChange: (_, __) => widget.privacySettingsViewModel.toggleAddCustomNode(),
),
if (widget.privacySettingsViewModel.addCustomNode)
Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 24), padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: NodeForm( child: NodeForm(
formKey: _formKey, formKey: _formKey,
nodeViewModel: widget.nodeViewModel, nodeViewModel: widget.nodeViewModel,
), ),
)
],
); );
} }
return const SizedBox();
},
), ),
], ],
), ),
bottomSectionPadding: EdgeInsets.all(24), bottomSectionPadding: EdgeInsets.all(24),

View file

@ -1,6 +1,10 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -27,12 +31,14 @@ class PrivacyPage extends BasePage {
onValueChange: (BuildContext context, bool value) { onValueChange: (BuildContext context, bool value) {
_privacySettingsViewModel.setFiatMode(value); _privacySettingsViewModel.setFiatMode(value);
}), }),
SettingsSwitcherCell( SettingsChoicesCell(
title: S.current.disable_exchange, ChoicesListItem<ExchangeApiMode>(
value: _privacySettingsViewModel.disableExchange, title: S.current.exchange,
onValueChange: (BuildContext context, bool value) { items: ExchangeApiMode.all,
_privacySettingsViewModel.setEnableExchange(value); selectedItem: _privacySettingsViewModel.exchangeStatus,
}), onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
),
),
SettingsSwitcherCell( SettingsSwitcherCell(
title: S.current.settings_save_recipient_address, title: S.current.settings_save_recipient_address,
value: _privacySettingsViewModel.shouldSaveRecipientAddress, value: _privacySettingsViewModel.shouldSaveRecipientAddress,

View file

@ -38,7 +38,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
if (widget.title?.isNotEmpty ?? false) if (widget.title.isNotEmpty)
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 24), padding: EdgeInsets.symmetric(horizontal: 24),
child: Text( child: Text(
@ -58,7 +58,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)), borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container( child: Container(
color: Theme.of(context).accentTextTheme!.headline6!.color!, color: Theme.of(context).accentTextTheme.headline6!.color!,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.65, maxHeight: MediaQuery.of(context).size.height * 0.65,
@ -70,7 +70,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ children: <Widget>[
(items?.length ?? 0) > 3 (items.length) > 3
? Scrollbar( ? Scrollbar(
controller: controller, controller: controller,
child: itemsList(), child: itemsList(),
@ -95,14 +95,14 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
Widget itemsList() { Widget itemsList() {
return Container( return Container(
color: Theme.of(context).accentTextTheme!.headline6!.backgroundColor!, color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
child: ListView.separated( child: ListView.separated(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
controller: controller, controller: controller,
shrinkWrap: true, shrinkWrap: true,
separatorBuilder: (context, index) => widget.isSeparated separatorBuilder: (context, index) => widget.isSeparated
? Divider( ? Divider(
color: Theme.of(context).accentTextTheme!.headline6!.backgroundColor!, color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
height: 1, height: 1,
) )
: const SizedBox(), : const SizedBox(),
@ -121,13 +121,13 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
}, },
child: Container( child: Container(
height: 55, height: 55,
color: Theme.of(context).accentTextTheme!.headline6!.color!, color: Theme.of(context).accentTextTheme.headline6!.color!,
padding: EdgeInsets.only(left: 24, right: 24), padding: EdgeInsets.only(left: 24, right: 24),
child: CheckboxListTile( child: CheckboxListTile(
value: item.value, value: item.value,
activeColor: item.value activeColor: item.value
? Palette.blueCraiola ? Palette.blueCraiola
: Theme.of(context).accentTextTheme!.subtitle1!.decorationColor!, : Theme.of(context).accentTextTheme.subtitle1!.decorationColor!,
checkColor: Colors.white, checkColor: Colors.white,
title: widget.displayItem?.call(item) ?? title: widget.displayItem?.call(item) ??
Text( Text(
@ -138,7 +138,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: item.isDisabled color: item.isDisabled
? Colors.grey.withOpacity(0.5) ? Colors.grey.withOpacity(0.5)
: Theme.of(context).primaryTextTheme!.headline6!.color!, : Theme.of(context).primaryTextTheme.headline6!.color!,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
), ),

View file

@ -12,7 +12,8 @@ abstract class TradeFilterStoreBase with Store {
displayChangeNow = true, displayChangeNow = true,
displaySideShift = true, displaySideShift = true,
displayMorphToken = true, displayMorphToken = true,
displaySimpleSwap = true; displaySimpleSwap = true,
displayTrocador = true;
@observable @observable
bool displayXMRTO; bool displayXMRTO;
@ -29,8 +30,11 @@ abstract class TradeFilterStoreBase with Store {
@observable @observable
bool displaySimpleSwap; bool displaySimpleSwap;
@observable
bool displayTrocador;
@computed @computed
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap; bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador;
@action @action
void toggleDisplayExchange(ExchangeProviderDescription provider) { void toggleDisplayExchange(ExchangeProviderDescription provider) {
@ -50,6 +54,9 @@ abstract class TradeFilterStoreBase with Store {
case ExchangeProviderDescription.morphToken: case ExchangeProviderDescription.morphToken:
displayMorphToken = !displayMorphToken; displayMorphToken = !displayMorphToken;
break; break;
case ExchangeProviderDescription.trocador:
displayTrocador = !displayTrocador;
break;
case ExchangeProviderDescription.all: case ExchangeProviderDescription.all:
if (displayAllTrades) { if (displayAllTrades) {
displayChangeNow = false; displayChangeNow = false;
@ -57,12 +64,14 @@ abstract class TradeFilterStoreBase with Store {
displayXMRTO = false; displayXMRTO = false;
displayMorphToken = false; displayMorphToken = false;
displaySimpleSwap = false; displaySimpleSwap = false;
displayTrocador = false;
} else { } else {
displayChangeNow = true; displayChangeNow = true;
displaySideShift = true; displaySideShift = true;
displayXMRTO = true; displayXMRTO = true;
displayMorphToken = true; displayMorphToken = true;
displaySimpleSwap = true; displaySimpleSwap = true;
displayTrocador = true;
} }
break; break;
} }
@ -88,7 +97,8 @@ abstract class TradeFilterStoreBase with Store {
ExchangeProviderDescription.morphToken) ExchangeProviderDescription.morphToken)
||(displaySimpleSwap && ||(displaySimpleSwap &&
item.trade.provider == item.trade.provider ==
ExchangeProviderDescription.simpleSwap)) ExchangeProviderDescription.simpleSwap)
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador))
.toList() .toList()
: _trades; : _trades;
} }

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
@ -31,7 +32,7 @@ abstract class SettingsStoreBase with Store {
required bool initialSaveRecipientAddress, required bool initialSaveRecipientAddress,
required FiatApiMode initialFiatMode, required FiatApiMode initialFiatMode,
required bool initialAllowBiometricalAuthentication, required bool initialAllowBiometricalAuthentication,
required bool initialExchangeEnabled, required ExchangeApiMode initialExchangeStatus,
required ThemeBase initialTheme, required ThemeBase initialTheme,
required int initialPinLength, required int initialPinLength,
required String initialLanguageCode, required String initialLanguageCode,
@ -53,7 +54,7 @@ abstract class SettingsStoreBase with Store {
shouldSaveRecipientAddress = initialSaveRecipientAddress, shouldSaveRecipientAddress = initialSaveRecipientAddress,
fiatApiMode = initialFiatMode, fiatApiMode = initialFiatMode,
allowBiometricalAuthentication = initialAllowBiometricalAuthentication, allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
disableExchange = initialExchangeEnabled, exchangeStatus = initialExchangeStatus,
currentTheme = initialTheme, currentTheme = initialTheme,
pinCodeLength = initialPinLength, pinCodeLength = initialPinLength,
languageCode = initialLanguageCode, languageCode = initialLanguageCode,
@ -153,9 +154,9 @@ abstract class SettingsStoreBase with Store {
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize())); PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
reaction( reaction(
(_) => disableExchange, (_) => exchangeStatus,
(bool disableExchange) => sharedPreferences.setBool( (ExchangeApiMode mode) => sharedPreferences.setInt(
PreferencesKey.disableExchangeKey, disableExchange)); PreferencesKey.exchangeStatusKey, mode.serialize()));
this this
.nodes .nodes
@ -192,7 +193,7 @@ abstract class SettingsStoreBase with Store {
bool allowBiometricalAuthentication; bool allowBiometricalAuthentication;
@observable @observable
bool disableExchange; ExchangeApiMode exchangeStatus;
@observable @observable
ThemeBase currentTheme; ThemeBase currentTheme;
@ -284,8 +285,9 @@ abstract class SettingsStoreBase with Store {
final allowBiometricalAuthentication = sharedPreferences final allowBiometricalAuthentication = sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false; false;
final disableExchange = sharedPreferences final exchangeStatus = ExchangeApiMode.deserialize(
.getBool(PreferencesKey.disableExchangeKey) ?? false; raw: sharedPreferences
.getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw);
final legacyTheme = final legacyTheme =
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false) (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index ? ThemeType.dark.index
@ -354,7 +356,7 @@ abstract class SettingsStoreBase with Store {
initialSaveRecipientAddress: shouldSaveRecipientAddress, initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialFiatMode: currentFiatApiMode, initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialExchangeEnabled: disableExchange, initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme, initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode, actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength, initialPinLength: pinLength,
@ -400,7 +402,9 @@ abstract class SettingsStoreBase with Store {
allowBiometricalAuthentication = sharedPreferences allowBiometricalAuthentication = sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication; allowBiometricalAuthentication;
disableExchange = sharedPreferences.getBool(PreferencesKey.disableExchangeKey) ?? disableExchange; exchangeStatus = ExchangeApiMode.deserialize(
raw: sharedPreferences
.getInt(PreferencesKey.exchangeStatusKey) ?? ExchangeApiMode.enabled.raw);
final legacyTheme = final legacyTheme =
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false) (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index ? ThemeType.dark.index

View file

@ -1,9 +1,8 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'advanced_privacy_settings_view_model.g.dart'; part 'advanced_privacy_settings_view_model.g.dart';
@ -11,35 +10,19 @@ class AdvancedPrivacySettingsViewModel = AdvancedPrivacySettingsViewModelBase
with _$AdvancedPrivacySettingsViewModel; with _$AdvancedPrivacySettingsViewModel;
abstract class AdvancedPrivacySettingsViewModelBase with Store { abstract class AdvancedPrivacySettingsViewModelBase with Store {
AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) : _addCustomNode = false;
: _addCustomNode = false {
settings = [
SwitcherListItem(
title: S.current.disable_fiat,
value: () => _settingsStore.fiatApiMode == FiatApiMode.disabled,
onValueChange: (_, bool value) => setFiatMode(value),
),
SwitcherListItem(
title: S.current.disable_exchange,
value: () => _settingsStore.disableExchange,
onValueChange: (_, bool value) {
_settingsStore.disableExchange = value;
},
),
SwitcherListItem(
title: S.current.add_custom_node,
value: () => _addCustomNode,
onValueChange: (_, bool value) => _addCustomNode = value,
),
];
}
late List<SwitcherListItem> settings; @computed
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
@computed
FiatApiMode get fiatApi => _settingsStore.fiatApiMode;
@observable @observable
bool _addCustomNode = false; bool _addCustomNode = false;
final WalletType type; final WalletType type;
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
@computed @computed
@ -53,4 +36,14 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
} }
_settingsStore.fiatApiMode = FiatApiMode.enabled; _settingsStore.fiatApiMode = FiatApiMode.enabled;
} }
@action
void setExchangeApiMode(ExchangeApiMode value) {
_settingsStore.exchangeStatus = value;
}
@action
void toggleAddCustomNode() {
_addCustomNode = !_addCustomNode;
}
} }

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
@ -96,6 +98,11 @@ abstract class DashboardViewModelBase with Store {
caption: ExchangeProviderDescription.simpleSwap.title, caption: ExchangeProviderDescription.simpleSwap.title,
onChanged: () => tradeFilterStore onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)), .toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
FilterItem(
value: () => tradeFilterStore.displayTrocador,
caption: ExchangeProviderDescription.trocador.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
] ]
}, },
subname = '', subname = '',
@ -268,7 +275,7 @@ abstract class DashboardViewModelBase with Store {
settingsStore.shouldShowYatPopup = shouldShow; settingsStore.shouldShowYatPopup = shouldShow;
@computed @computed
bool get isEnabledExchangeAction => !settingsStore.disableExchange; bool get isEnabledExchangeAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled;
@observable @observable
bool hasExchangeAction; bool hasExchangeAction;

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/exchange/sideshift/sideshift_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/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/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
@ -46,6 +47,9 @@ abstract class ExchangeTradeViewModelBase with Store {
case ExchangeProviderDescription.simpleSwap: case ExchangeProviderDescription.simpleSwap:
_provider = SimpleSwapExchangeProvider(); _provider = SimpleSwapExchangeProvider();
break; break;
case ExchangeProviderDescription.trocador:
_provider = TrocadorExchangeProvider();
break;
} }
_updateItems(); _updateItems();

View file

@ -2,11 +2,15 @@ import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.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/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.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/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:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
@ -53,6 +57,7 @@ abstract class ExchangeViewModelBase with Store {
isDepositAddressEnabled = false, isDepositAddressEnabled = false,
isReceiveAddressEnabled = false, isReceiveAddressEnabled = false,
isReceiveAmountEditable = false, isReceiveAmountEditable = false,
_useTorOnly = false,
receiveCurrencies = <CryptoCurrency>[], receiveCurrencies = <CryptoCurrency>[],
depositCurrencies = <CryptoCurrency>[], depositCurrencies = <CryptoCurrency>[],
limits = Limits(min: 0, max: 0), limits = Limits(min: 0, max: 0),
@ -60,8 +65,10 @@ abstract class ExchangeViewModelBase with Store {
limitsState = LimitsInitialState(), limitsState = LimitsInitialState(),
receiveCurrency = wallet.currency, receiveCurrency = wallet.currency,
depositCurrency = wallet.currency, depositCurrency = wallet.currency,
providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()], providerList = [],
selectedProviders = ObservableList<ExchangeProvider>() { selectedProviders = ObservableList<ExchangeProvider>() {
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
_setProviders();
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano]; const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp,
CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano]; CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano];
@ -117,13 +124,20 @@ abstract class ExchangeViewModelBase with Store {
_calculateBestRate(); _calculateBestRate();
}); });
} }
bool _useTorOnly;
final WalletBase wallet; final WalletBase wallet;
final Box<Trade> trades; final Box<Trade> trades;
final ExchangeTemplateStore _exchangeTemplateStore; final ExchangeTemplateStore _exchangeTemplateStore;
final TradesStore tradesStore; final TradesStore tradesStore;
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
List<ExchangeProvider> get _allProviders => [
ChangeNowExchangeProvider(),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
];
@observable @observable
ExchangeProvider? provider; ExchangeProvider? provider;
@ -455,6 +469,18 @@ abstract class ExchangeViewModelBase with Store {
amount = isFixedRateMode ? receiveAmount : depositAmount; 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;
}
amount = amount.replaceAll(',', '.'); amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) { if (limitsState is LimitsLoadedSuccessfully) {
@ -675,4 +701,12 @@ abstract class ExchangeViewModelBase with Store {
break; break;
} }
} }
void _setProviders(){
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly) {
providerList = _allProviders.where((provider) => provider.supportsOnionAddress).toList();
} else {
providerList = _allProviders;
}
}
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart';
@ -12,7 +13,7 @@ abstract class PrivacySettingsViewModelBase with Store {
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
@computed @computed
bool get disableExchange => _settingsStore.disableExchange; ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
@computed @computed
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress; bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
@ -24,7 +25,7 @@ abstract class PrivacySettingsViewModelBase with Store {
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
@action @action
void setEnableExchange(bool value) => _settingsStore.disableExchange = value; void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
@action @action
void setFiatMode(bool value) { void setFiatMode(bool value) {

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dar
import 'package:cake_wallet/exchange/sideshift/sideshift_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/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.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/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/date_formatter.dart'; import 'package:cake_wallet/utils/date_formatter.dart';
@ -22,15 +23,14 @@ import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
part 'trade_details_view_model.g.dart'; part 'trade_details_view_model.g.dart';
class TradeDetailsViewModel = TradeDetailsViewModelBase class TradeDetailsViewModel = TradeDetailsViewModelBase with _$TradeDetailsViewModel;
with _$TradeDetailsViewModel;
abstract class TradeDetailsViewModelBase with Store { abstract class TradeDetailsViewModelBase with Store {
TradeDetailsViewModelBase({ TradeDetailsViewModelBase({
required Trade tradeForDetails, required Trade tradeForDetails,
required this.trades, required this.trades,
required this.settingsStore}) required this.settingsStore,
: items = ObservableList<StandartListItem>(), }) : items = ObservableList<StandartListItem>(),
trade = tradeForDetails { trade = tradeForDetails {
switch (trade.provider) { switch (trade.provider) {
case ExchangeProviderDescription.xmrto: case ExchangeProviderDescription.xmrto:
@ -48,6 +48,9 @@ abstract class TradeDetailsViewModelBase with Store {
case ExchangeProviderDescription.simpleSwap: case ExchangeProviderDescription.simpleSwap:
_provider = SimpleSwapExchangeProvider(); _provider = SimpleSwapExchangeProvider();
break; break;
case ExchangeProviderDescription.trocador:
_provider = TrocadorExchangeProvider();
break;
} }
items = ObservableList<StandartListItem>(); items = ObservableList<StandartListItem>();
@ -96,12 +99,7 @@ abstract class TradeDetailsViewModelBase with Store {
items.clear(); items.clear();
items.add( items.add(
DetailsListStatusItem( DetailsListStatusItem(title: S.current.trade_details_state, value: trade.state.toString()));
title: S.current.trade_details_state,
value: trade.state != null
? trade.state.toString()
: S.current.trade_details_fetching)
);
items.add(TradeDetailsListCardItem.tradeDetails( items.add(TradeDetailsListCardItem.tradeDetails(
id: trade.id, id: trade.id,
@ -114,15 +112,11 @@ abstract class TradeDetailsViewModelBase with Store {
}, },
)); ));
if (trade.provider != null) {
items.add(StandartListItem( items.add(StandartListItem(
title: S.current.trade_details_provider, title: S.current.trade_details_provider, value: trade.provider.toString()));
value: trade.provider.toString()));
}
if (trade.provider == ExchangeProviderDescription.changeNow) { if (trade.provider == ExchangeProviderDescription.changeNow) {
final buildURL = final buildURL = 'https://changenow.io/exchange/txs/${trade.id.toString()}';
'https://changenow.io/exchange/txs/${trade.id.toString()}';
items.add(TrackTradeListItem( items.add(TrackTradeListItem(
title: 'Track', title: 'Track',
value: buildURL, value: buildURL,
@ -133,14 +127,25 @@ abstract class TradeDetailsViewModelBase with Store {
if (trade.provider == ExchangeProviderDescription.sideShift) { if (trade.provider == ExchangeProviderDescription.sideShift) {
final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}'; final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}';
items.add(TrackTradeListItem( items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
} }
if (trade.provider == ExchangeProviderDescription.simpleSwap) { if (trade.provider == ExchangeProviderDescription.simpleSwap) {
final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}'; final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}';
items.add(TrackTradeListItem( items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
title: 'Track', value: buildURL, onTap: () => launch(buildURL))); }
if (trade.provider == ExchangeProviderDescription.trocador) {
final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}';
items.add(TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => launch(buildURL)));
items.add(StandartListItem(
title: '${trade.providerName} ${S.current.id.toUpperCase()}',
value: trade.providerId ?? ''));
if (trade.password != null && trade.password!.isNotEmpty)
items.add(StandartListItem(
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
} }
} }
} }

View file

@ -6,17 +6,12 @@ class SecretKey {
static final base = [ static final base = [
SecretKey('salt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), SecretKey('salt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)),
SecretKey('keychainSalt', SecretKey('keychainSalt', () => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)),
() => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)),
SecretKey('key', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), SecretKey('key', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)),
SecretKey( SecretKey('walletSalt', () => hex.encode(encrypt.Key.fromSecureRandom(4).bytes)),
'walletSalt', () => hex.encode(encrypt.Key.fromSecureRandom(4).bytes)), SecretKey('shortKey', () => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)),
SecretKey( SecretKey('backupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(8).bytes)),
'shortKey', () => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)), SecretKey('backupKeychainSalt', () => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)),
SecretKey(
'backupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(8).bytes)),
SecretKey('backupKeychainSalt',
() => hex.encode(encrypt.Key.fromSecureRandom(12).bytes)),
SecretKey('changeNowApiKey', () => ''), SecretKey('changeNowApiKey', () => ''),
SecretKey('wyreSecretKey', () => ''), SecretKey('wyreSecretKey', () => ''),
SecretKey('wyreApiKey', () => ''), SecretKey('wyreApiKey', () => ''),
@ -29,6 +24,8 @@ class SecretKey {
SecretKey('anypayToken', () => ''), SecretKey('anypayToken', () => ''),
SecretKey('onramperApiKey', () => ''), SecretKey('onramperApiKey', () => ''),
SecretKey('ioniaClientId', () => ''), SecretKey('ioniaClientId', () => ''),
SecretKey('trocadorApiKey', () => ''),
SecretKey('trocadorExchangeMarkup', () => ''),
SecretKey('twitterBearerToken', () => ''), SecretKey('twitterBearerToken', () => ''),
]; ];