diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index 3cc67e53a..e9665e179 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -187,6 +187,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
+ echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index f00584345..9632786a5 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -173,6 +173,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
+ echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
diff --git a/assets/images/letsexchange_icon.svg b/assets/images/letsexchange_icon.svg
new file mode 100644
index 000000000..104b43a6b
--- /dev/null
+++ b/assets/images/letsexchange_icon.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 7733562fb..b812907ad 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -2395,8 +2395,6 @@ class PublicKeyWithDerivationPath {
}
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
- // print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
- // print(network);
if (network is BitcoinCashNetwork) {
if (!address.startsWith("bitcoincash:") &&
(address.startsWith("q") || address.startsWith("p"))) {
diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart
index 0653200ee..314b8768a 100644
--- a/cw_bitcoin/lib/electrum_wallet_addresses.dart
+++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart
@@ -223,8 +223,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (walletInfo.type == WalletType.bitcoinCash) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
- // await _generateInitialAddresses();
- // await _generateInitialAddresses(type: SegwitAddresType.mweb);
+ await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
+ await _generateInitialAddresses(type: SegwitAddresType.mweb);
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index c4f3b862e..159a6150e 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -250,6 +250,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return;
}
+ final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
+ while (mwebAddrs.length < 1000) {
+ print("waiting for mweb addresses to finish generating...");
+ await Future.delayed(const Duration(milliseconds: 1000));
+ }
+
await getStub();
await updateUnspent();
await updateBalance();
@@ -403,8 +409,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
);
}
- bool isNew = transactionHistory.transactions[tx.id] == null;
-
// don't update the confirmations if the tx is updated by electrum:
if (tx.confirmations == 0 || utxo.height != 0) {
tx.height = utxo.height;
@@ -412,6 +416,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.confirmations = confirmations;
}
+ bool isNew = transactionHistory.transactions[tx.id] == null;
+
if (!(tx.outputAddresses?.contains(utxo.address) ?? false)) {
tx.outputAddresses?.add(utxo.address);
isNew = true;
@@ -454,22 +460,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
print("SCANNING FROM HEIGHT: $restoreHeight");
final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight);
- // process old utxos:
- // for (final utxo in mwebUtxosBox.values) {
- // if (utxo.address.isEmpty) {
- // continue;
- // }
-
- // // if (walletInfo.restoreHeight > utxo.height) {
- // // continue;
- // // }
- // // await handleIncoming(utxo, _stub);
-
- // if (utxo.height > walletInfo.restoreHeight) {
- // await walletInfo.updateRestoreHeight(utxo.height);
- // }
- // }
-
// process new utxos as they come in:
_utxoStream?.cancel();
_utxoStream = _stub.utxos(req).listen((Utxo sUtxo) async {
@@ -486,12 +476,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// return;
// }
- // if (utxo.address.isEmpty) {
- // await updateUnspent();
- // await updateBalance();
- // initDone = true;
- // }
-
await updateUnspent();
await updateBalance();
@@ -517,6 +501,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
.where((tx) => tx.direction == TransactionDirection.outgoing && tx.isPending)
.map(checkPendingTransaction)))
.any((x) => x));
+
final outputIds =
mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList();
@@ -527,10 +512,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final height = await electrumClient.getCurrentBlockChainTip();
if (height == null || status.blockHeaderHeight != height) return;
if (status.mwebUtxosHeight != height) return;
+
int amount = 0;
Set inputAddresses = {};
var output = convert.AccumulatorSink();
var input = sha256.startChunkedConversion(output);
+
for (final outputId in spent) {
final utxo = mwebUtxosBox.get(outputId);
await mwebUtxosBox.delete(outputId);
@@ -545,6 +532,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
inputAddresses.add(utxo.address);
input.add(hex.decode(outputId));
}
+
if (inputAddresses.isEmpty) return;
input.close();
var digest = output.events.single;
@@ -561,7 +549,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
inputAddresses: inputAddresses.toList(),
outputAddresses: [],
);
- print("BEING ADDED HERE@@@@@@@@@@@@@@@@@@@@@@@2");
transactionHistory.addOne(tx);
await transactionHistory.save();
@@ -670,14 +657,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} catch (_) {}
// update unspent balances:
-
- // reset coin balances and txCount to 0:
- // unspentCoins.forEach((coin) {
- // if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
- // coin.bitcoinAddressRecord.balance = 0;
- // coin.bitcoinAddressRecord.txCount = 0;
- // });
-
await updateUnspent();
for (var addressRecord in walletAddresses.allAddresses) {
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index 0e7434029..04a7ab5cc 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -1,9 +1,9 @@
import 'dart:async';
import 'dart:typed_data';
-import 'package:bech32/bech32.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@@ -14,10 +14,6 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
-String encodeMwebAddress(List scriptPubKey) {
- return bech32.encode(Bech32("ltcmweb1", scriptPubKey), 250);
-}
-
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
@@ -31,10 +27,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
- }) : super(walletInfo) {
- // start generating mweb addresses in the background:
- initMwebAddresses();
- }
+ }) : super(walletInfo) {}
final Bip32Slip10Secp256k1 mwebHd;
bool mwebEnabled;
@@ -46,6 +39,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
List get spendPubkey =>
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
+ @override
+ Future init() async {
+ await super.init();
+ initMwebAddresses();
+ }
+
Future ensureMwebAddressUpToIndexExists(int index) async {
Uint8List scan = Uint8List.fromList(scanSecret);
Uint8List spend = Uint8List.fromList(spendPubkey);
@@ -66,10 +65,35 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
}
Future initMwebAddresses() async {
- for (int i = 0; i < 4; i++) {
+ print("Initializing MWEB address timer!");
+ Timer.periodic(const Duration(seconds: 2), (timer) async {
+ if (super.allAddresses.length > 1000) {
+ timer.cancel();
+ return;
+ }
+ print("Generating MWEB addresses...");
await generateNumAddresses(250);
await Future.delayed(const Duration(milliseconds: 1500));
- }
+
+ if (mwebAddrs.length > 1000) {
+ // convert mwebAddrs to BitcoinAddressRecords:
+ List mwebAddresses = mwebAddrs
+ .asMap()
+ .entries
+ .map((e) => BitcoinAddressRecord(
+ e.value,
+ index: e.key,
+ type: SegwitAddresType.mweb,
+ network: network,
+ ))
+ .toList();
+ // add them to the list of all addresses:
+ addAddresses(mwebAddresses);
+ print("MWEB addresses initialized ${mwebAddrs.length}");
+ timer.cancel();
+ return;
+ }
+ });
}
@override
@@ -126,6 +150,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
}
if (mwebEnabled) {
+ await ensureMwebAddressUpToIndexExists(1);
return mwebAddrs[0];
}
diff --git a/lib/entities/language_service.dart b/lib/entities/language_service.dart
index 4a6b358e1..78437fdce 100644
--- a/lib/entities/language_service.dart
+++ b/lib/entities/language_service.dart
@@ -31,6 +31,7 @@ class LanguageService {
'ha': 'Hausa Najeriya (Nigeria)',
'tl': 'Filipino (Tagalog)',
'hy': 'Հայերեն (Armenian)',
+ 'vi': 'Tiếng Việt (Vietnamese)',
};
static const Map localeCountryCode = {
@@ -61,6 +62,7 @@ class LanguageService {
'ha': 'hau',
'tl': 'phl',
'hy': 'arm',
+ 'vi': 'vnm',
};
static final list = {};
diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart
index a91288024..9f3723356 100644
--- a/lib/exchange/exchange_provider_description.dart
+++ b/lib/exchange/exchange_provider_description.dart
@@ -27,8 +27,10 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable<
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
static const quantex =
ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png');
+ static const letsExchange =
+ ExchangeProviderDescription(title: 'LetsExchange', raw: 10, image: 'assets/images/letsexchange_icon.svg');
static const stealthEx =
- ExchangeProviderDescription(title: 'StealthEx', raw: 10, image: 'assets/images/stealthex.png');
+ ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/images/stealthex.png');
static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) {
@@ -53,6 +55,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable<
case 9:
return quantex;
case 10:
+ return letsExchange;
+ case 11:
return stealthEx;
default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart
new file mode 100644
index 000000000..a11e69796
--- /dev/null
+++ b/lib/exchange/provider/letsexchange_exchange_provider.dart
@@ -0,0 +1,292 @@
+import 'dart:convert';
+import 'dart:developer';
+
+import 'package:cake_wallet/.secrets.g.dart' as secrets;
+import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
+import 'package:cake_wallet/exchange/exchange_provider_description.dart';
+import 'package:cake_wallet/exchange/limits.dart';
+import 'package:cake_wallet/exchange/trade.dart';
+import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
+import 'package:cake_wallet/exchange/trade_request.dart';
+import 'package:cake_wallet/exchange/trade_state.dart';
+import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:http/http.dart' as http;
+
+class LetsExchangeExchangeProvider extends ExchangeProvider {
+ LetsExchangeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
+
+ static const List _notSupported = [];
+
+ static const apiKey = secrets.letsExchangeBearerToken;
+ static const _baseUrl = 'api.letsexchange.io';
+ static const _infoPath = '/api/v1/info';
+ static const _infoRevertPath = '/api/v1/info-revert';
+ static const _createTransactionPath = '/api/v1/transaction';
+ static const _createTransactionRevertPath = '/api/v1/transaction-revert';
+ static const _getTransactionPath = '/api/v1/transaction';
+
+ static const _affiliateId = secrets.letsExchangeAffiliateId;
+
+ @override
+ String get title => 'LetsExchange';
+
+ @override
+ bool get isAvailable => true;
+
+ @override
+ bool get isEnabled => true;
+
+ @override
+ bool get supportsFixedRate => true;
+
+ @override
+ ExchangeProviderDescription get description => ExchangeProviderDescription.letsExchange;
+
+ @override
+ Future checkIsAvailable() async => true;
+
+ @override
+ Future fetchLimits(
+ {required CryptoCurrency from,
+ required CryptoCurrency to,
+ required bool isFixedRateMode}) async {
+ final networkFrom = _getNetworkType(from);
+ final networkTo = _getNetworkType(to);
+
+ try {
+ final params = {
+ 'from': from.title,
+ 'to': to.title,
+ if (networkFrom != null) 'network_from': networkFrom,
+ if (networkTo != null) 'network_to': networkTo,
+ 'amount': '1',
+ 'affiliate_id': _affiliateId
+ };
+
+ final responseJSON = await _getInfo(params, isFixedRateMode);
+ final min = double.tryParse(responseJSON['min_amount'] as String);
+ final max = double.tryParse(responseJSON['max_amount'] as String);
+ return Limits(min: min, max: max);
+ } catch (e) {
+ log(e.toString());
+ throw Exception('Failed to fetch limits');
+ }
+ }
+
+ @override
+ Future fetchRate(
+ {required CryptoCurrency from,
+ required CryptoCurrency to,
+ required double amount,
+ required bool isFixedRateMode,
+ required bool isReceiveAmount}) async {
+ final networkFrom = _getNetworkType(from);
+ final networkTo = _getNetworkType(to);
+ try {
+ final params = {
+ 'from': from.title,
+ 'to': to.title,
+ if (networkFrom != null) 'network_from': networkFrom,
+ if (networkTo != null) 'network_to': networkTo,
+ 'amount': amount.toString(),
+ 'affiliate_id': _affiliateId
+ };
+
+ final responseJSON = await _getInfo(params, isFixedRateMode);
+
+ final amountToGet = double.tryParse(responseJSON['amount'] as String) ?? 0.0;
+
+ return isFixedRateMode ? amount / amountToGet : amountToGet / amount;
+ } catch (e) {
+ log(e.toString());
+ return 0.0;
+ }
+ }
+
+ @override
+ Future createTrade(
+ {required TradeRequest request,
+ required bool isFixedRateMode,
+ required bool isSendAll}) async {
+ final networkFrom = _getNetworkType(request.fromCurrency);
+ final networkTo = _getNetworkType(request.toCurrency);
+ try {
+ final params = {
+ 'from': request.fromCurrency.title,
+ 'to': request.toCurrency.title,
+ if (networkFrom != null) 'network_from': networkFrom,
+ if (networkTo != null) 'network_to': networkTo,
+ 'amount': isFixedRateMode ? request.toAmount.toString() : request.fromAmount.toString(),
+ 'affiliate_id': _affiliateId
+ };
+
+ final responseInfoJSON = await _getInfo(params, isFixedRateMode);
+ final rateId = responseInfoJSON['rate_id'] as String;
+
+ final withdrawalAddress = _normalizeBchAddress(request.toAddress);
+ final returnAddress = _normalizeBchAddress(request.refundAddress);
+
+ final tradeParams = {
+ 'coin_from': request.fromCurrency.title,
+ 'coin_to': request.toCurrency.title,
+ if (!isFixedRateMode) 'deposit_amount': request.fromAmount.toString(),
+ 'withdrawal': withdrawalAddress,
+ if (isFixedRateMode) 'withdrawal_amount': request.toAmount.toString(),
+ 'withdrawal_extra_id': '',
+ 'return': returnAddress,
+ 'rate_id': rateId,
+ if (networkFrom != null) 'network_from': networkFrom,
+ if (networkTo != null) 'network_to': networkTo,
+ 'affiliate_id': _affiliateId
+ };
+
+ final headers = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': apiKey
+ };
+
+ final uri = Uri.https(_baseUrl,
+ isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath, tradeParams);
+ final response = await http.post(uri, headers: headers);
+
+ if (response.statusCode != 200) {
+ throw Exception('LetsExchange create trade failed: ${response.body}');
+ }
+ final responseJSON = json.decode(response.body) as Map;
+ final id = responseJSON['transaction_id'] as String;
+ final from = responseJSON['coin_from'] as String;
+ final to = responseJSON['coin_to'] as String;
+ final payoutAddress = responseJSON['withdrawal'] as String;
+ final depositAddress = responseJSON['deposit'] as String;
+ final refundAddress = responseJSON['return'] as String;
+ final depositAmount = responseJSON['deposit_amount'] as String;
+ final receiveAmount = responseJSON['withdrawal_amount'] as String;
+ final status = responseJSON['status'] as String;
+ final createdAtString = responseJSON['created_at'] as String;
+ final expiredAtTimestamp = responseJSON['expired_at'] as int;
+
+ final createdAt = DateTime.parse(createdAtString);
+ final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
+
+ CryptoCurrency fromCurrency;
+ if (request.fromCurrency.tag != null && request.fromCurrency.title == from) {
+ fromCurrency = request.fromCurrency;
+ } else {
+ fromCurrency = CryptoCurrency.fromString(from);
+ }
+
+ CryptoCurrency toCurrency;
+ if (request.toCurrency.tag != null && request.toCurrency.title == to) {
+ toCurrency = request.toCurrency;
+ } else {
+ toCurrency = CryptoCurrency.fromString(to);
+ }
+
+ return Trade(
+ id: id,
+ from: fromCurrency,
+ to: toCurrency,
+ provider: description,
+ inputAddress: depositAddress,
+ payoutAddress: payoutAddress,
+ refundAddress: refundAddress,
+ amount: depositAmount,
+ receiveAmount: receiveAmount,
+ state: TradeState.deserialize(raw: status),
+ createdAt: createdAt,
+ expiredAt: expiredAt,
+ );
+ } catch (e) {
+ log(e.toString());
+ throw TradeNotCreatedException(description);
+ }
+ }
+
+ @override
+ Future findTradeById({required String id}) async {
+ final headers = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': apiKey
+ };
+
+ final url = Uri.https(_baseUrl, '$_getTransactionPath/$id');
+ final response = await http.get(url, headers: headers);
+
+ if (response.statusCode != 200) {
+ throw Exception('LetsExchange fetch trade failed: ${response.body}');
+ }
+ final responseJSON = json.decode(response.body) as Map;
+ final from = responseJSON['coin_from'] as String;
+ final to = responseJSON['coin_to'] as String;
+ final payoutAddress = responseJSON['withdrawal'] as String;
+ final depositAddress = responseJSON['deposit'] as String;
+ final refundAddress = responseJSON['return'] as String;
+ final depositAmount = responseJSON['deposit_amount'] as String;
+ final receiveAmount = responseJSON['withdrawal_amount'] as String;
+ final status = responseJSON['status'] as String;
+ final createdAtString = responseJSON['created_at'] as String;
+ final expiredAtTimestamp = responseJSON['expired_at'] as int;
+
+ final createdAt = DateTime.parse(createdAtString);
+ final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
+
+ return Trade(
+ id: id,
+ from: CryptoCurrency.fromString(from),
+ to: CryptoCurrency.fromString(to),
+ provider: description,
+ inputAddress: depositAddress,
+ payoutAddress: payoutAddress,
+ refundAddress: refundAddress,
+ amount: depositAmount,
+ receiveAmount: receiveAmount,
+ state: TradeState.deserialize(raw: status),
+ createdAt: createdAt,
+ expiredAt: expiredAt,
+ isRefund: status == 'refund',
+ );
+ }
+
+ Future