2023-02-06 21:20:43 +02:00
|
|
|
import 'dart:convert';
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
|
|
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
|
|
|
import 'package:cake_wallet/exchange/limits.dart';
|
|
|
|
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
|
|
|
import 'package:cake_wallet/exchange/trade.dart';
|
|
|
|
import 'package:cake_wallet/exchange/trade_request.dart';
|
2023-02-06 21:20:43 +02:00
|
|
|
import 'package:cake_wallet/exchange/trade_state.dart';
|
2023-10-25 22:58:25 +02:00
|
|
|
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
|
2025-06-20 21:56:18 +02:00
|
|
|
import 'package:cw_core/utils/proxy_wrapper.dart';
|
2023-02-06 21:20:43 +02:00
|
|
|
import 'package:cw_core/crypto_currency.dart';
|
2024-12-09 12:23:59 -06:00
|
|
|
import 'package:cw_core/utils/print_verbose.dart';
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
class TrocadorExchangeProvider extends ExchangeProvider {
|
2023-11-17 01:23:34 +02:00
|
|
|
TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}})
|
2024-03-29 15:51:34 -03:00
|
|
|
: _lastUsedRateId = '',
|
|
|
|
_provider = [],
|
2023-10-25 22:58:25 +02:00
|
|
|
super(pairList: supportedPairs(_notSupported));
|
2023-03-02 19:24:52 +02:00
|
|
|
|
2023-03-01 23:44:15 +02:00
|
|
|
bool useTorOnly;
|
2025-03-11 21:51:52 +02:00
|
|
|
Map<String, bool> providerStates;
|
2023-11-17 01:23:34 +02:00
|
|
|
|
|
|
|
static const List<String> availableProviders = [
|
|
|
|
'Swapter',
|
|
|
|
'StealthEx',
|
|
|
|
'Simpleswap',
|
2024-03-29 15:51:34 -03:00
|
|
|
'Swapuz',
|
2023-11-17 01:23:34 +02:00
|
|
|
'ChangeNow',
|
|
|
|
'Changehero',
|
|
|
|
'FixedFloat',
|
|
|
|
'LetsExchange',
|
|
|
|
'Exolix',
|
|
|
|
'Godex',
|
|
|
|
'Exch',
|
2024-04-12 16:01:21 +02:00
|
|
|
'CoinCraddle',
|
|
|
|
'Alfacash',
|
|
|
|
'LocalMonero',
|
|
|
|
'XChange',
|
|
|
|
'NeroSwap',
|
|
|
|
'Changee',
|
|
|
|
'BitcoinVN',
|
|
|
|
'EasyBit',
|
|
|
|
'WizardSwap',
|
|
|
|
'Quantex',
|
|
|
|
'SwapSpace',
|
2023-11-17 01:23:34 +02:00
|
|
|
];
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
static const List<CryptoCurrency> _notSupported = [
|
|
|
|
CryptoCurrency.stx,
|
|
|
|
CryptoCurrency.zaddr,
|
|
|
|
];
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
static const apiKey = secrets.trocadorApiKey;
|
2025-02-17 15:23:28 +02:00
|
|
|
static const clearNetAuthority = 'api.trocador.app';
|
2025-06-20 21:56:18 +02:00
|
|
|
static const onionApiAuthority = clearNetAuthority;
|
|
|
|
// static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
|
2023-02-06 22:20:46 +02:00
|
|
|
static const markup = secrets.trocadorExchangeMarkup;
|
2025-02-17 15:23:28 +02:00
|
|
|
static const newRatePath = '/new_rate';
|
|
|
|
static const createTradePath = '/new_trade';
|
|
|
|
static const tradePath = '/trade';
|
|
|
|
static const coinPath = '/coin';
|
2025-03-11 21:51:52 +02:00
|
|
|
static const providersListPath = '/exchanges';
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
String _lastUsedRateId;
|
2023-11-17 01:23:34 +02:00
|
|
|
List<dynamic> _provider;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
@override
|
2023-10-25 22:58:25 +02:00
|
|
|
String get title => 'Trocador';
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
@override
|
2023-10-25 22:58:25 +02:00
|
|
|
bool get isAvailable => true;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
@override
|
|
|
|
bool get isEnabled => true;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
@override
|
|
|
|
bool get supportsFixedRate => true;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
@override
|
|
|
|
bool get supportsOnionAddress => true;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
@override
|
|
|
|
ExchangeProviderDescription get description => ExchangeProviderDescription.trocador;
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
@override
|
|
|
|
Future<bool> checkIsAvailable() async => true;
|
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
@override
|
|
|
|
Future<Limits> fetchLimits(
|
|
|
|
{required CryptoCurrency from,
|
|
|
|
required CryptoCurrency to,
|
|
|
|
required bool isFixedRateMode}) async {
|
2023-10-25 22:58:25 +02:00
|
|
|
final params = {
|
2023-08-24 16:30:53 -05:00
|
|
|
'ticker': _normalizeCurrency(from),
|
2023-03-02 19:24:52 +02:00
|
|
|
'name': from.name,
|
|
|
|
};
|
2023-02-28 18:12:24 +02:00
|
|
|
|
2023-03-02 19:24:52 +02:00
|
|
|
final uri = await _getUri(coinPath, params);
|
2025-06-20 21:56:18 +02:00
|
|
|
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
|
|
|
|
|
2023-03-02 19:24:52 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
if (response.statusCode != 200)
|
2023-03-02 19:24:52 +02:00
|
|
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
|
|
|
|
|
|
|
final responseJSON = json.decode(response.body) as List<dynamic>;
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
if (responseJSON.isEmpty) throw Exception('No data');
|
2023-03-02 19:24:52 +02:00
|
|
|
|
|
|
|
final coinJson = responseJSON.first as Map<String, dynamic>;
|
2023-02-28 18:12:24 +02:00
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
return Limits(
|
2025-01-28 23:53:43 +02:00
|
|
|
min: coinJson['minimum'] as double?,
|
|
|
|
// TODO: remove hardcoded value and call `api/new_rate` when Trocador adds min and max to it
|
|
|
|
max: from == CryptoCurrency.zano ? 2600 : coinJson['maximum'] as double?,
|
2023-02-06 21:20:43 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<double> fetchRate(
|
|
|
|
{required CryptoCurrency from,
|
|
|
|
required CryptoCurrency to,
|
|
|
|
required double amount,
|
|
|
|
required bool isFixedRateMode,
|
|
|
|
required bool isReceiveAmount}) async {
|
|
|
|
try {
|
2023-10-25 22:58:25 +02:00
|
|
|
if (amount == 0) return 0.0;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
final params = <String, String>{
|
2023-08-24 16:30:53 -05:00
|
|
|
'ticker_from': _normalizeCurrency(from),
|
|
|
|
'ticker_to': _normalizeCurrency(to),
|
2023-02-06 21:20:43 +02:00
|
|
|
'network_from': _networkFor(from),
|
|
|
|
'network_to': _networkFor(to),
|
2023-02-06 21:42:17 +02:00
|
|
|
if (!isFixedRateMode) 'amount_from': amount.toString(),
|
|
|
|
if (isFixedRateMode) 'amount_to': amount.toString(),
|
2023-02-06 21:20:43 +02:00
|
|
|
'payment': isFixedRateMode ? 'True' : 'False',
|
|
|
|
'min_kycrating': 'C',
|
2023-02-06 22:20:46 +02:00
|
|
|
'markup': markup,
|
2023-02-06 21:20:43 +02:00
|
|
|
};
|
|
|
|
|
2023-03-02 19:24:52 +02:00
|
|
|
final uri = await _getUri(newRatePath, params);
|
2025-06-20 21:56:18 +02:00
|
|
|
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
|
|
|
|
|
2024-11-16 11:06:49 -06:00
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
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? ?? '';
|
|
|
|
|
2023-11-17 01:23:34 +02:00
|
|
|
var quotes = responseJSON['quotes']['quotes'] as List;
|
2025-03-11 21:51:52 +02:00
|
|
|
_provider = quotes
|
|
|
|
.where((quote) => providerStates[quote['provider']] != false)
|
|
|
|
.map((quote) => quote['provider'])
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
if (_provider.isEmpty) {
|
|
|
|
throw Exception('No enabled providers found for the selected trade.');
|
|
|
|
}
|
2023-11-17 01:23:34 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
if (rateId.isNotEmpty) _lastUsedRateId = rateId;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
return isReceiveAmount ? (amount / fromAmount) : (toAmount / amount);
|
|
|
|
} catch (e) {
|
2024-12-09 12:23:59 -06:00
|
|
|
printV(e.toString());
|
2023-02-06 21:20:43 +02:00
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
@override
|
2024-03-29 15:51:34 -03:00
|
|
|
Future<Trade> createTrade({
|
|
|
|
required TradeRequest request,
|
|
|
|
required bool isFixedRateMode,
|
|
|
|
required bool isSendAll,
|
|
|
|
}) async {
|
2023-10-25 22:58:25 +02:00
|
|
|
final params = {
|
|
|
|
'ticker_from': _normalizeCurrency(request.fromCurrency),
|
|
|
|
'ticker_to': _normalizeCurrency(request.toCurrency),
|
|
|
|
'network_from': _networkFor(request.fromCurrency),
|
|
|
|
'network_to': _networkFor(request.toCurrency),
|
|
|
|
'payment': isFixedRateMode ? 'True' : 'False',
|
|
|
|
'min_kycrating': 'C',
|
|
|
|
'markup': markup,
|
|
|
|
if (!isFixedRateMode) 'amount_from': request.fromAmount,
|
|
|
|
if (isFixedRateMode) 'amount_to': request.toAmount,
|
|
|
|
'address': request.toAddress,
|
2024-12-13 01:24:37 +02:00
|
|
|
'refund': request.refundAddress,
|
|
|
|
'refund_memo' : '0',
|
2023-10-25 22:58:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if (isFixedRateMode) {
|
|
|
|
await fetchRate(
|
|
|
|
from: request.fromCurrency,
|
|
|
|
to: request.toCurrency,
|
|
|
|
amount: double.tryParse(request.toAmount) ?? 0,
|
|
|
|
isFixedRateMode: true,
|
|
|
|
isReceiveAmount: true,
|
|
|
|
);
|
|
|
|
params['id'] = _lastUsedRateId;
|
|
|
|
}
|
|
|
|
|
2025-03-11 21:51:52 +02:00
|
|
|
if (_provider.isEmpty) {
|
2023-11-17 01:23:34 +02:00
|
|
|
throw Exception('No available provider is enabled');
|
|
|
|
}
|
|
|
|
|
2025-03-11 21:51:52 +02:00
|
|
|
params['provider'] = _provider.first as String;
|
2023-11-17 01:23:34 +02:00
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
final uri = await _getUri(createTradePath, params);
|
2025-06-20 21:56:18 +02:00
|
|
|
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
|
|
|
|
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
if (response.statusCode == 400) {
|
|
|
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
|
|
|
final error = responseJSON['error'] as String;
|
|
|
|
final message = responseJSON['message'] as String;
|
|
|
|
throw Exception('${error}\n$message');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.statusCode != 200)
|
|
|
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
|
|
|
|
|
|
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
|
|
|
final id = responseJSON['trade_id'] as String;
|
|
|
|
final inputAddress = responseJSON['address_provider'] as String;
|
|
|
|
final refundAddress = responseJSON['refund_address'] as String;
|
|
|
|
final status = responseJSON['status'] as String;
|
|
|
|
final payoutAddress = responseJSON['address_user'] as String;
|
|
|
|
final date = responseJSON['date'] as String;
|
|
|
|
final password = responseJSON['password'] as String;
|
|
|
|
final providerId = responseJSON['id_provider'] as String;
|
|
|
|
final providerName = responseJSON['provider'] as String;
|
2024-07-23 01:20:55 +01:00
|
|
|
final amount = responseJSON['amount_from']?.toString();
|
|
|
|
final receiveAmount = responseJSON['amount_to']?.toString();
|
2025-06-16 17:52:15 +03:00
|
|
|
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
|
2023-10-25 22:58:25 +02:00
|
|
|
|
|
|
|
return Trade(
|
2024-07-23 01:20:55 +01:00
|
|
|
id: id,
|
|
|
|
from: request.fromCurrency,
|
|
|
|
to: request.toCurrency,
|
|
|
|
provider: description,
|
|
|
|
inputAddress: inputAddress,
|
|
|
|
refundAddress: refundAddress,
|
|
|
|
state: TradeState.deserialize(raw: status),
|
|
|
|
password: password,
|
|
|
|
providerId: providerId,
|
|
|
|
providerName: providerName,
|
|
|
|
createdAt: DateTime.tryParse(date)?.toLocal(),
|
|
|
|
amount: amount ?? request.fromAmount,
|
|
|
|
receiveAmount: receiveAmount ?? request.toAmount,
|
|
|
|
payoutAddress: payoutAddress,
|
|
|
|
isSendAll: isSendAll,
|
2025-06-16 17:52:15 +03:00
|
|
|
extraId: addressProviderMemo,
|
2024-07-23 01:20:55 +01:00
|
|
|
);
|
2023-10-25 22:58:25 +02:00
|
|
|
}
|
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
@override
|
|
|
|
Future<Trade> findTradeById({required String id}) async {
|
2024-11-16 11:06:49 -06:00
|
|
|
final uri = await _getUri(tradePath, {'id': id});
|
2025-06-20 21:56:18 +02:00
|
|
|
return ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async {
|
2023-10-25 22:58:25 +02:00
|
|
|
if (response.statusCode != 200)
|
2023-02-06 21:20:43 +02:00
|
|
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
2025-06-20 21:56:18 +02:00
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
final responseListJson = json.decode(response.body) as List;
|
|
|
|
final responseJSON = responseListJson.first;
|
|
|
|
final id = responseJSON['trade_id'] as String;
|
2023-03-11 00:21:25 +02:00
|
|
|
final payoutAddress = responseJSON['address_user'] as String;
|
2023-02-06 21:20:43 +02:00
|
|
|
final refundAddress = responseJSON['refund_address'] as String;
|
2023-03-11 00:21:25 +02:00
|
|
|
final inputAddress = responseJSON['address_provider'] as String;
|
2023-02-06 21:20:43 +02:00
|
|
|
final fromAmount = responseJSON['amount_from']?.toString() ?? '0';
|
2023-02-07 19:39:39 +02:00
|
|
|
final password = responseJSON['password'] as String;
|
|
|
|
final providerId = responseJSON['id_provider'] as String;
|
|
|
|
final providerName = responseJSON['provider'] as String;
|
2024-12-13 01:24:37 +02:00
|
|
|
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
|
2023-02-06 21:20:43 +02:00
|
|
|
|
|
|
|
return Trade(
|
|
|
|
id: id,
|
2023-10-25 22:58:25 +02:00
|
|
|
from: CryptoCurrency.fromString(responseJSON['ticker_from'] as String),
|
|
|
|
to: CryptoCurrency.fromString(responseJSON['ticker_to'] as String),
|
2023-02-06 21:20:43 +02:00
|
|
|
provider: description,
|
|
|
|
inputAddress: inputAddress,
|
|
|
|
refundAddress: refundAddress,
|
2023-10-25 22:58:25 +02:00
|
|
|
createdAt: DateTime.parse(responseJSON['date'] as String),
|
2023-02-06 21:20:43 +02:00
|
|
|
amount: fromAmount,
|
2023-10-25 22:58:25 +02:00
|
|
|
state: TradeState.deserialize(raw: responseJSON['status'] as String),
|
2023-02-06 21:20:43 +02:00
|
|
|
payoutAddress: payoutAddress,
|
2023-02-07 19:39:39 +02:00
|
|
|
password: password,
|
|
|
|
providerId: providerId,
|
|
|
|
providerName: providerName,
|
2024-12-13 01:24:37 +02:00
|
|
|
extraId: addressProviderMemo,
|
2023-02-06 21:20:43 +02:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-03-11 21:51:52 +02:00
|
|
|
Future<List<TrocadorPartners>> fetchProviders() async {
|
|
|
|
final uri = await _getUri(providersListPath, {'api_key': apiKey});
|
2025-06-20 21:56:18 +02:00
|
|
|
final response = await ProxyWrapper().get(clearnetUri: uri);
|
|
|
|
|
2025-03-11 21:51:52 +02:00
|
|
|
|
|
|
|
if (response.statusCode != 200)
|
|
|
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
|
|
|
|
|
|
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
|
|
|
|
|
|
|
final providersJsonList = responseJSON['list'] as List<dynamic>;
|
|
|
|
final filteredProvidersList = providersJsonList
|
|
|
|
.map((providerJson) => TrocadorPartners.fromJson(providerJson as Map<String, dynamic>))
|
|
|
|
.where((provider) => provider.rating != 'D')
|
|
|
|
.toList();
|
|
|
|
filteredProvidersList.sort((a, b) => a.rating.compareTo(b.rating));
|
|
|
|
return filteredProvidersList;
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
String _networkFor(CryptoCurrency currency) {
|
2023-02-09 10:52:24 -06:00
|
|
|
switch (currency) {
|
|
|
|
case CryptoCurrency.eth:
|
|
|
|
return 'ERC20';
|
|
|
|
case CryptoCurrency.maticpoly:
|
|
|
|
return 'Mainnet';
|
|
|
|
case CryptoCurrency.usdcpoly:
|
2023-12-13 15:03:07 +01:00
|
|
|
case CryptoCurrency.usdtPoly:
|
|
|
|
case CryptoCurrency.usdcEPoly:
|
2023-02-09 10:52:24 -06:00
|
|
|
return 'MATIC';
|
|
|
|
case CryptoCurrency.zec:
|
|
|
|
return 'Mainnet';
|
|
|
|
default:
|
|
|
|
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
|
|
|
|
}
|
2023-02-06 21:20:43 +02:00
|
|
|
}
|
|
|
|
|
2023-08-24 16:30:53 -05:00
|
|
|
String _normalizeCurrency(CryptoCurrency currency) {
|
|
|
|
switch (currency) {
|
|
|
|
case CryptoCurrency.zec:
|
|
|
|
return 'zec';
|
2023-12-13 15:03:07 +01:00
|
|
|
case CryptoCurrency.usdcEPoly:
|
|
|
|
return 'usdce';
|
2023-08-24 16:30:53 -05:00
|
|
|
default:
|
|
|
|
return currency.title.toLowerCase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
String _normalizeTag(String tag) {
|
|
|
|
switch (tag) {
|
|
|
|
case 'ETH':
|
|
|
|
return 'ERC20';
|
2023-02-13 08:51:26 -06:00
|
|
|
case 'TRX':
|
|
|
|
return 'TRC20';
|
2023-04-20 13:55:44 -05:00
|
|
|
case 'LN':
|
|
|
|
return 'Lightning';
|
2023-02-06 21:20:43 +02:00
|
|
|
default:
|
|
|
|
return tag.toLowerCase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-02 21:13:19 +02:00
|
|
|
Future<Uri> _getUri(String path, Map<String, String> queryParams) async {
|
|
|
|
final uri = Uri.http(onionApiAuthority, path, queryParams);
|
|
|
|
|
2023-10-25 22:58:25 +02:00
|
|
|
if (useTorOnly) return uri;
|
2023-03-01 23:44:15 +02:00
|
|
|
|
2023-02-06 21:20:43 +02:00
|
|
|
try {
|
2025-06-20 21:56:18 +02:00
|
|
|
await ProxyWrapper().get(clearnetUri: uri);
|
2023-03-01 23:44:15 +02:00
|
|
|
|
2023-03-02 21:13:19 +02:00
|
|
|
return uri;
|
2023-02-06 21:20:43 +02:00
|
|
|
} catch (e) {
|
2023-03-02 21:13:19 +02:00
|
|
|
return Uri.https(clearNetAuthority, path, queryParams);
|
2023-02-06 21:20:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-11 21:51:52 +02:00
|
|
|
|
|
|
|
class TrocadorPartners {
|
|
|
|
final String name;
|
|
|
|
final String rating;
|
|
|
|
final double? insurance;
|
|
|
|
final bool? enabledMarkup;
|
|
|
|
final double? eta;
|
|
|
|
|
|
|
|
TrocadorPartners({
|
|
|
|
required this.name,
|
|
|
|
required this.rating,
|
|
|
|
required this.insurance,
|
|
|
|
required this.enabledMarkup,
|
|
|
|
required this.eta,
|
|
|
|
});
|
|
|
|
|
|
|
|
factory TrocadorPartners.fromJson(Map<String, dynamic> json) {
|
|
|
|
return TrocadorPartners(
|
|
|
|
name: json['name'] as String? ?? '',
|
|
|
|
rating: json['rating'] as String? ?? 'N/A',
|
|
|
|
insurance: json['insurance'] as double?,
|
|
|
|
enabledMarkup: json['enabledmarkup'] as bool?,
|
|
|
|
eta: json['eta'] as double?,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|