Add trocador exchange provider

This commit is contained in:
Godwin Asuquo 2023-02-06 21:20:43 +02:00
parent 61050ac715
commit 032a8c8c33
19 changed files with 453 additions and 108 deletions

BIN
assets/images/trocador.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

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 shouldUseOnionAddress => false;
@override @override
String toString() => title; String toString() => title;

View file

@ -1,31 +1,30 @@
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 =
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png'); ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
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

@ -0,0 +1,268 @@
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()
: _lastUsedRateId = '',
super(pairList: _supportedPairs());
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.xhv,
CryptoCurrency.dcr,
CryptoCurrency.oxt,
CryptoCurrency.pivx,
CryptoCurrency.scrt,
CryptoCurrency.stx,
CryptoCurrency.bttc,
CryptoCurrency.zaddr,
CryptoCurrency.usdcpoly,
CryptoCurrency.maticpoly,
];
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 newRatePath = '/api/new_rate';
static const createTradePath = 'api/new_trade';
static const tradePath = 'api/trade';
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': '3',
'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 String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
final uri = Uri.https(apiAuthority, 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;
return Trade(
id: id,
from: request.from,
to: request.to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
state: state,
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 {
//TODO: implement limits from trocador api
return Limits(
min: 0.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 String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
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),
'amount_from': amount.toString(),
'amount_to': amount.toString(),
'payment': isFixedRateMode ? 'True' : 'False',
'min_kycrating': 'C',
'markup': '3',
'best_only': 'True',
};
final uri = Uri.https(apiAuthority, 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 String apiAuthority = shouldUseOnionAddress ? await _getAuthority() : clearNetAuthority;
final uri = Uri.https(apiAuthority, 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);
return Trade(
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
createdAt: date,
amount: fromAmount,
state: state,
payoutAddress: payoutAddress,
);
});
}
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
bool get shouldUseOnionAddress => true;
@override
String get title => 'Trocador';
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.usdt:
return CryptoCurrency.btc.title.toLowerCase();
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'ETH':
return 'ERC20';
default:
return tag.toLowerCase();
}
}
Future<String> _getAuthority() async {
try {
final uri = Uri.https(onionApiAuthority, '/api/trade');
await get(uri);
return onionApiAuthority;
} catch (e) {
return clearNetAuthority;
}
}
}

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;
@ -35,47 +36,40 @@ class TradeRow extends StatelessWidget {
SizedBox(width: 12), SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceBetween, Text('${from.toString()}${to.toString()}',
children: <Widget>[ style: TextStyle(
Text('${from.toString()}${to.toString()}', fontSize: 16,
style: TextStyle( fontWeight: FontWeight.w500,
fontSize: 16, color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!)),
fontWeight: FontWeight.w500, formattedAmount != null
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor! ? Text(formattedAmount! + ' ' + amountCrypto,
)), style: TextStyle(
formattedAmount != null fontSize: 16,
? Text(formattedAmount! + ' ' + amountCrypto, fontWeight: FontWeight.w500,
style: TextStyle( color:
fontSize: 16, Theme.of(context).accentTextTheme!.headline2!.backgroundColor!))
fontWeight: FontWeight.w500, : Container()
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor! ]),
)) SizedBox(height: 5),
: Container() Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
]), if (createdAtFormattedDate != null)
SizedBox(height: 5), Text(createdAtFormattedDate!,
Row( style: TextStyle(
mainAxisAlignment: MainAxisAlignment.spaceBetween, fontSize: 14,
children: <Widget>[ color: Theme.of(context).textTheme!.overline!.backgroundColor!))
if (createdAtFormattedDate != null) ])
Text(createdAtFormattedDate!, ],
style: TextStyle( ))
fontSize: 14,
color: Theme.of(context).textTheme!
.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/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:cake_wallet/view_model/settings/switcher_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,9 +52,14 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
...widget.privacySettingsViewModel.settings.map( ...widget.privacySettingsViewModel.settings.whereType<ChoicesListItem<FiatApiMode>>().map(
(item) => Observer( (item) => Observer(
builder: (_) => SettingsSwitcherCell( builder: (_) => SettingsChoicesCell(item)
),
),
...widget.privacySettingsViewModel.settings.whereType<SwitcherListItem>().map(
(item) => Observer(
builder: (_) => SettingsSwitcherCell(
title: item.title, title: item.title,
value: item.value(), value: item.value(),
onValueChange: item.onValueChange, onValueChange: item.onValueChange,

View file

@ -1,6 +1,9 @@
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';
@ -21,18 +24,22 @@ class PrivacyPage extends BasePage {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SettingsSwitcherCell( SettingsChoicesCell(
title: S.current.disable_fiat, ChoicesListItem<FiatApiMode>(
value: _privacySettingsViewModel.isFiatDisabled, title: S.current.fiat_api,
onValueChange: (BuildContext context, bool value) { items: FiatApiMode.all,
_privacySettingsViewModel.setFiatMode(value); selectedItem: _privacySettingsViewModel.fiatApi,
}), onItemSelected: (FiatApiMode mode) => _privacySettingsViewModel.setFiatMode(mode),
SettingsSwitcherCell( ),
title: S.current.disable_exchange, ),
value: _privacySettingsViewModel.disableExchange, SettingsChoicesCell(
onValueChange: (BuildContext context, bool value) { ChoicesListItem<FiatApiMode>(
_privacySettingsViewModel.setEnableExchange(value); title: S.current.exchange,
}), items: FiatApiMode.all,
selectedItem: _privacySettingsViewModel.exchangeStatus,
onItemSelected: (FiatApiMode mode) => _privacySettingsViewModel.setEnableExchange(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

@ -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

@ -31,7 +31,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 FiatApiMode initialExchangeStatus,
required ThemeBase initialTheme, required ThemeBase initialTheme,
required int initialPinLength, required int initialPinLength,
required String initialLanguageCode, required String initialLanguageCode,
@ -53,7 +53,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 +153,9 @@ abstract class SettingsStoreBase with Store {
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize())); PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
reaction( reaction(
(_) => disableExchange, (_) => exchangeStatus,
(bool disableExchange) => sharedPreferences.setBool( (FiatApiMode mode) => sharedPreferences.setInt(
PreferencesKey.disableExchangeKey, disableExchange)); PreferencesKey.exchangeStatusKey, mode.serialize()));
this this
.nodes .nodes
@ -192,7 +192,7 @@ abstract class SettingsStoreBase with Store {
bool allowBiometricalAuthentication; bool allowBiometricalAuthentication;
@observable @observable
bool disableExchange; FiatApiMode exchangeStatus;
@observable @observable
ThemeBase currentTheme; ThemeBase currentTheme;
@ -284,8 +284,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 = FiatApiMode.deserialize(
.getBool(PreferencesKey.disableExchangeKey) ?? false; raw: sharedPreferences
.getInt(PreferencesKey.exchangeStatusKey) ?? FiatApiMode.enabled.raw);
final legacyTheme = final legacyTheme =
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false) (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index ? ThemeType.dark.index
@ -354,7 +355,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 +401,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 = FiatApiMode.deserialize(
raw: sharedPreferences
.getInt(PreferencesKey.exchangeStatusKey) ?? FiatApiMode.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,5 +1,7 @@
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/choices_list_item.dart';
import 'package:cake_wallet/view_model/settings/settings_list_item.dart';
import 'package:cake_wallet/view_model/settings/switcher_list_item.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';
@ -14,18 +16,19 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore)
: _addCustomNode = false { : _addCustomNode = false {
settings = [ settings = [
SwitcherListItem( ChoicesListItem<FiatApiMode>(
title: S.current.disable_fiat, title: S.current.fiat_api,
value: () => _settingsStore.fiatApiMode == FiatApiMode.disabled, items: FiatApiMode.all,
onValueChange: (_, bool value) => setFiatMode(value), selectedItem: _settingsStore.fiatApiMode,
), onItemSelected: (FiatApiMode mode) => setFiatMode(mode),
SwitcherListItem( ),
title: S.current.disable_exchange,
value: () => _settingsStore.disableExchange, ChoicesListItem<FiatApiMode>(
onValueChange: (_, bool value) { title: S.current.exchange,
_settingsStore.disableExchange = value; items: FiatApiMode.all,
}, selectedItem: _settingsStore.exchangeStatus,
), onItemSelected: (FiatApiMode mode) => _settingsStore.exchangeStatus = mode,
),
SwitcherListItem( SwitcherListItem(
title: S.current.add_custom_node, title: S.current.add_custom_node,
value: () => _addCustomNode, value: () => _addCustomNode,
@ -34,7 +37,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
]; ];
} }
late List<SwitcherListItem> settings; late List<SettingsListItem> settings;
@observable @observable
bool _addCustomNode = false; bool _addCustomNode = false;
@ -46,11 +49,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
bool get addCustomNode => _addCustomNode; bool get addCustomNode => _addCustomNode;
@action @action
void setFiatMode(bool value) { void setFiatMode(FiatApiMode value) {
if (value) { _settingsStore.fiatApiMode = value;
_settingsStore.fiatApiMode = FiatApiMode.disabled;
return;
}
_settingsStore.fiatApiMode = FiatApiMode.enabled;
} }
} }

View file

@ -1,3 +1,4 @@
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 +97,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 +274,7 @@ abstract class DashboardViewModelBase with Store {
settingsStore.shouldShowYatPopup = shouldShow; settingsStore.shouldShowYatPopup = shouldShow;
@computed @computed
bool get isEnabledExchangeAction => !settingsStore.disableExchange; bool get isEnabledExchangeAction => settingsStore.exchangeStatus != FiatApiMode.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

@ -7,6 +7,8 @@ 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';
@ -60,7 +62,7 @@ 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 = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider()],
selectedProviders = ObservableList<ExchangeProvider>() { selectedProviders = ObservableList<ExchangeProvider>() {
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano]; const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp,
@ -449,6 +451,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) {

View file

@ -12,27 +12,23 @@ abstract class PrivacySettingsViewModelBase with Store {
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
@computed @computed
bool get disableExchange => _settingsStore.disableExchange; FiatApiMode get exchangeStatus => _settingsStore.exchangeStatus;
@computed @computed
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress; bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
@computed @computed
bool get isFiatDisabled => _settingsStore.fiatApiMode == FiatApiMode.disabled; FiatApiMode get fiatApi => _settingsStore.fiatApiMode;
@action @action
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
@action @action
void setEnableExchange(bool value) => _settingsStore.disableExchange = value; void setEnableExchange(FiatApiMode value) => _settingsStore.exchangeStatus = value;
@action @action
void setFiatMode(bool value) { void setFiatMode(FiatApiMode value) {
if (value) { _settingsStore.fiatApiMode = value;
_settingsStore.fiatApiMode = FiatApiMode.disabled;
return;
}
_settingsStore.fiatApiMode = FiatApiMode.enabled;
} }
} }

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';
@ -48,6 +49,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>();

View file

@ -29,6 +29,7 @@ class SecretKey {
SecretKey('anypayToken', () => ''), SecretKey('anypayToken', () => ''),
SecretKey('onramperApiKey', () => ''), SecretKey('onramperApiKey', () => ''),
SecretKey('ioniaClientId', () => ''), SecretKey('ioniaClientId', () => ''),
SecretKey('trocadorApiKey', () => ''),
]; ];
final String name; final String name;