mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Cw 537 integrate thor chain swaps (#1280)
* thorChain btc to eth swap * eth to btc swap * update the UI * update localization * Update thorchain_exchange.provider.dart * minor fixes * minor fix * fix min amount bug * revert amount_converter changes * fetching thorChain traid info * resolve evm related merge conflicts * minor fix * Fix eth transaction hash for Thorchain Integration * add new status endpoint and refund address for eth * Adjust affiliate fee * Fix conflicts with main * review comments + transaction filter item * taproot addresses check * added 10 outputs check * Update thorchain_exchange.provider.dart * minor fixes * update thorchain title * fix fetching rate for thorchain * Revert "fix fetching rate for thorchain" This reverts commit 3aa1386ecfbca14271bf01a73b424de19c4fd484. * fix thorchain exchange rate --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
@@ -85,7 +85,8 @@ class CWBitcoin extends Bitcoin {
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: feeRate);
|
||||
|
||||
@@ -76,7 +76,8 @@ class CWEthereum extends Ethereum {
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as EVMChainTransactionPriority,
|
||||
currency: currency,
|
||||
|
||||
@@ -22,6 +22,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
|
||||
static const exolix =
|
||||
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
|
||||
static const thorChain =
|
||||
ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png');
|
||||
|
||||
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
|
||||
|
||||
@@ -41,6 +43,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||
return trocador;
|
||||
case 6:
|
||||
return exolix;
|
||||
case 8:
|
||||
return thorChain;
|
||||
case 7:
|
||||
return all;
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_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:hive/hive.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
ThorChainExchangeProvider({required this.tradesStore})
|
||||
: super(pairList: supportedPairs(_notSupported));
|
||||
|
||||
static final List<CryptoCurrency> _notSupported = [
|
||||
...(CryptoCurrency.all
|
||||
.where((element) => ![
|
||||
CryptoCurrency.btc,
|
||||
CryptoCurrency.eth,
|
||||
CryptoCurrency.ltc,
|
||||
CryptoCurrency.bch,
|
||||
CryptoCurrency.aave,
|
||||
CryptoCurrency.dai,
|
||||
CryptoCurrency.gusd,
|
||||
CryptoCurrency.usdc,
|
||||
CryptoCurrency.usdterc20,
|
||||
CryptoCurrency.wbtc,
|
||||
].contains(element))
|
||||
.toList())
|
||||
];
|
||||
|
||||
static final isRefundAddressSupported = [CryptoCurrency.eth];
|
||||
|
||||
static const _baseURL = 'thornode.ninerealms.com';
|
||||
static const _quotePath = '/thorchain/quote/swap';
|
||||
static const _txInfoPath = '/thorchain/tx/status/';
|
||||
static const _affiliateName = 'cakewallet';
|
||||
static const _affiliateBps = '175';
|
||||
|
||||
final Box<Trade> tradesStore;
|
||||
|
||||
@override
|
||||
String get title => 'THORChain';
|
||||
|
||||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
bool get supportsFixedRate => false;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description => ExchangeProviderDescription.thorChain;
|
||||
|
||||
@override
|
||||
Future<bool> checkIsAvailable() async => true;
|
||||
|
||||
@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 = {
|
||||
'from_asset': _normalizeCurrency(from),
|
||||
'to_asset': _normalizeCurrency(to),
|
||||
'amount': _doubleToThorChainString(amount),
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
|
||||
final expectedAmountOut = responseJSON['expected_amount_out'] as String? ?? '0.0';
|
||||
|
||||
return _thorChainAmountToDouble(expectedAmountOut) / amount;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Limits> fetchLimits(
|
||||
{required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required bool isFixedRateMode}) async {
|
||||
final params = {
|
||||
'from_asset': _normalizeCurrency(from),
|
||||
'to_asset': _normalizeCurrency(to),
|
||||
'amount': _doubleToThorChainString(1),
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
final minAmountIn = responseJSON['recommended_min_amount_in'] as String? ?? '0.0';
|
||||
|
||||
return Limits(min: _thorChainAmountToDouble(minAmountIn));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
|
||||
? request.toAddress.replaceFirst('bitcoincash:', '')
|
||||
: request.toAddress;
|
||||
|
||||
final formattedFromAmount = double.parse(request.fromAmount);
|
||||
|
||||
final params = {
|
||||
'from_asset': _normalizeCurrency(request.fromCurrency),
|
||||
'to_asset': _normalizeCurrency(request.toCurrency),
|
||||
'amount': _doubleToThorChainString(formattedFromAmount),
|
||||
'destination': formattedToAddress,
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps,
|
||||
'refund_address':
|
||||
isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '',
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
|
||||
final inputAddress = responseJSON['inbound_address'] as String?;
|
||||
final memo = responseJSON['memo'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: '',
|
||||
from: request.fromCurrency,
|
||||
to: request.toCurrency,
|
||||
provider: description,
|
||||
inputAddress: inputAddress,
|
||||
createdAt: DateTime.now(),
|
||||
amount: request.fromAmount,
|
||||
state: TradeState.notFound,
|
||||
payoutAddress: request.toAddress,
|
||||
memo: memo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> findTradeById({required String id}) async {
|
||||
if (id.isEmpty) throw Exception('Trade id is empty');
|
||||
final formattedId = id.startsWith('0x') ? id.substring(2) : id;
|
||||
final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId');
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 404) {
|
||||
throw Exception('Trade not found for id: $formattedId');
|
||||
} else if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body);
|
||||
final Map<String, dynamic> stagesJson = responseJSON['stages'] as Map<String, dynamic>;
|
||||
|
||||
final inboundObservedStarted = stagesJson['inbound_observed']?['started'] as bool? ?? true;
|
||||
if (!inboundObservedStarted) {
|
||||
throw Exception('Trade has not started for id: $formattedId');
|
||||
}
|
||||
|
||||
final currentState = _updateStateBasedOnStages(stagesJson) ?? TradeState.notFound;
|
||||
|
||||
final tx = responseJSON['tx'];
|
||||
final String fromAddress = tx['from_address'] as String? ?? '';
|
||||
final String toAddress = tx['to_address'] as String? ?? '';
|
||||
final List<dynamic> coins = tx['coins'] as List<dynamic>;
|
||||
final String? memo = tx['memo'] as String?;
|
||||
|
||||
final parts = memo?.split(':') ?? [];
|
||||
|
||||
final String toChain = parts.length > 1 ? parts[1].split('.')[0] : '';
|
||||
final String toAsset = parts.length > 1 && parts[1].split('.').length > 1 ? parts[1].split('.')[1].split('-')[0] : '';
|
||||
|
||||
final formattedToChain = CryptoCurrency.fromString(toChain);
|
||||
final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency:formattedToChain);
|
||||
|
||||
final plannedOutTxs = responseJSON['planned_out_txs'] as List<dynamic>?;
|
||||
final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
from: CryptoCurrency.fromString(tx['chain'] as String? ?? ''),
|
||||
to: toAssetWithChain,
|
||||
provider: description,
|
||||
inputAddress: fromAddress,
|
||||
payoutAddress: toAddress,
|
||||
amount: coins.first['amount'] as String? ?? '0.0',
|
||||
state: currentState,
|
||||
memo: memo,
|
||||
isRefund: isRefund,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
|
||||
Uri uri = Uri.https(_baseURL, _quotePath, params);
|
||||
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
if (response.body.contains('error')) {
|
||||
throw Exception('Unexpected response: ${response.body}');
|
||||
}
|
||||
|
||||
return json.decode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
final networkTitle = currency.tag == 'ETH' ? 'ETH' : currency.title;
|
||||
return '$networkTitle.${currency.title}';
|
||||
}
|
||||
|
||||
String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString();
|
||||
|
||||
double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8;
|
||||
|
||||
TradeState? _updateStateBasedOnStages(Map<String, dynamic> stages) {
|
||||
TradeState? currentState;
|
||||
|
||||
if (stages['inbound_observed']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.confirmation;
|
||||
}
|
||||
if (stages['inbound_confirmation_counted']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.confirmed;
|
||||
}
|
||||
if (stages['inbound_finalised']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.processing;
|
||||
}
|
||||
if (stages['swap_finalised']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.traded;
|
||||
}
|
||||
if (stages['outbound_signed']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.success;
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -27,7 +27,10 @@ class Trade extends HiveObject {
|
||||
this.password,
|
||||
this.providerId,
|
||||
this.providerName,
|
||||
this.fromWalletAddress
|
||||
this.fromWalletAddress,
|
||||
this.memo,
|
||||
this.txId,
|
||||
this.isRefund,
|
||||
}) {
|
||||
if (provider != null) providerRaw = provider.raw;
|
||||
|
||||
@@ -105,6 +108,15 @@ class Trade extends HiveObject {
|
||||
@HiveField(17)
|
||||
String? fromWalletAddress;
|
||||
|
||||
@HiveField(18)
|
||||
String? memo;
|
||||
|
||||
@HiveField(19)
|
||||
String? txId;
|
||||
|
||||
@HiveField(20)
|
||||
bool? isRefund;
|
||||
|
||||
static Trade fromMap(Map<String, Object?> map) {
|
||||
return Trade(
|
||||
id: map['id'] as String,
|
||||
@@ -115,7 +127,10 @@ class Trade extends HiveObject {
|
||||
map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
|
||||
amount: map['amount'] as String,
|
||||
walletId: map['wallet_id'] as String,
|
||||
fromWalletAddress: map['from_wallet_address'] as String?
|
||||
fromWalletAddress: map['from_wallet_address'] as String?,
|
||||
memo: map['memo'] as String?,
|
||||
txId: map['tx_id'] as String?,
|
||||
isRefund: map['isRefund'] as bool?
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,7 +143,10 @@ class Trade extends HiveObject {
|
||||
'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null,
|
||||
'amount': amount,
|
||||
'wallet_id': walletId,
|
||||
'from_wallet_address': fromWalletAddress
|
||||
'from_wallet_address': fromWalletAddress,
|
||||
'memo': memo,
|
||||
'tx_id': txId,
|
||||
'isRefund': isRefund
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||
static const success = TradeState(raw: 'success', title: 'Success');
|
||||
static TradeState deserialize({required String raw}) {
|
||||
switch (raw) {
|
||||
case 'NOT_FOUND':
|
||||
return notFound;
|
||||
case 'pending':
|
||||
return pending;
|
||||
case 'confirming':
|
||||
@@ -98,6 +100,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||
case 'sending':
|
||||
return sending;
|
||||
case 'success':
|
||||
case 'done':
|
||||
return success;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw in TradeState deserialize');
|
||||
|
||||
@@ -9,7 +9,7 @@ class FilterTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 24.0),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget {
|
||||
static const String created = 'created';
|
||||
static const String fetching = 'fetching';
|
||||
static const String finished = 'finished';
|
||||
static const String success = 'success';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget {
|
||||
indicatorColor = Colors.red;
|
||||
break;
|
||||
case finished:
|
||||
case success:
|
||||
indicatorColor = PaletteDark.brightGreen;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -34,7 +34,9 @@ class TradeRow extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_getPoweredImage(provider)!,
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset(provider.image, width: 36, height: 36)),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -69,38 +71,4 @@ class TradeRow extends StatelessWidget {
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget? _getPoweredImage(ExchangeProviderDescription provider) {
|
||||
Widget? image;
|
||||
|
||||
switch (provider) {
|
||||
case ExchangeProviderDescription.xmrto:
|
||||
image = Image.asset('assets/images/xmrto.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.changeNow:
|
||||
image = Image.asset('assets/images/changenow.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.morphToken:
|
||||
image = Image.asset('assets/images/morph.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.sideShift:
|
||||
image = Image.asset('assets/images/sideshift.png', width: 36, height: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.simpleSwap:
|
||||
image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.trocador:
|
||||
image = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
|
||||
break;
|
||||
default:
|
||||
image = null;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
@@ -60,7 +62,7 @@ class ExchangePage extends BasePage {
|
||||
final _receiveAmountFocus = FocusNode();
|
||||
final _receiveAddressFocus = FocusNode();
|
||||
final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
var _isReactionsSet = false;
|
||||
|
||||
final arrowBottomPurple = Image.asset(
|
||||
@@ -431,7 +433,9 @@ class ExchangePage extends BasePage {
|
||||
}
|
||||
if (state is TradeIsCreatedSuccessfully) {
|
||||
exchangeViewModel.reset();
|
||||
Navigator.of(context).pushNamed(Routes.exchangeConfirm);
|
||||
(exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain)
|
||||
? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade)
|
||||
: Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -470,6 +474,13 @@ class ExchangePage extends BasePage {
|
||||
if (depositAmountController.text != exchangeViewModel.depositAmount &&
|
||||
depositAmountController.text != S.of(context).all) {
|
||||
exchangeViewModel.isSendAllEnabled = false;
|
||||
final isThorChain = exchangeViewModel.selectedProviders
|
||||
.any((provider) => provider is ThorChainExchangeProvider);
|
||||
|
||||
_depositAmountDebounce = isThorChain
|
||||
? Debounce(Duration(milliseconds: 1000))
|
||||
: Debounce(Duration(milliseconds: 500));
|
||||
|
||||
_depositAmountDebounce.run(() {
|
||||
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
||||
exchangeViewModel.isReceiveAmountEntered = false;
|
||||
|
||||
@@ -3,18 +3,20 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part'trade_filter_store.g.dart';
|
||||
part 'trade_filter_store.g.dart';
|
||||
|
||||
class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore;
|
||||
|
||||
abstract class TradeFilterStoreBase with Store {
|
||||
TradeFilterStoreBase() : displayXMRTO = true,
|
||||
TradeFilterStoreBase()
|
||||
: displayXMRTO = true,
|
||||
displayChangeNow = true,
|
||||
displaySideShift = true,
|
||||
displayMorphToken = true,
|
||||
displaySimpleSwap = true,
|
||||
displayTrocador = true,
|
||||
displayExolix = true;
|
||||
displayExolix = true,
|
||||
displayThorChain = true;
|
||||
|
||||
@observable
|
||||
bool displayXMRTO;
|
||||
@@ -37,8 +39,17 @@ abstract class TradeFilterStoreBase with Store {
|
||||
@observable
|
||||
bool displayExolix;
|
||||
|
||||
@observable
|
||||
bool displayThorChain;
|
||||
|
||||
@computed
|
||||
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
|
||||
bool get displayAllTrades =>
|
||||
displayChangeNow &&
|
||||
displaySideShift &&
|
||||
displaySimpleSwap &&
|
||||
displayTrocador &&
|
||||
displayExolix &&
|
||||
displayThorChain;
|
||||
|
||||
@action
|
||||
void toggleDisplayExchange(ExchangeProviderDescription provider) {
|
||||
@@ -64,6 +75,9 @@ abstract class TradeFilterStoreBase with Store {
|
||||
case ExchangeProviderDescription.exolix:
|
||||
displayExolix = !displayExolix;
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
displayThorChain = !displayThorChain;
|
||||
break;
|
||||
case ExchangeProviderDescription.all:
|
||||
if (displayAllTrades) {
|
||||
displayChangeNow = false;
|
||||
@@ -73,6 +87,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||
displaySimpleSwap = false;
|
||||
displayTrocador = false;
|
||||
displayExolix = false;
|
||||
displayThorChain = false;
|
||||
} else {
|
||||
displayChangeNow = true;
|
||||
displaySideShift = true;
|
||||
@@ -81,6 +96,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||
displaySimpleSwap = true;
|
||||
displayTrocador = true;
|
||||
displayExolix = true;
|
||||
displayThorChain = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -96,16 +112,13 @@ abstract class TradeFilterStoreBase with Store {
|
||||
? _trades
|
||||
.where((item) =>
|
||||
(displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
|
||||
(displaySideShift &&
|
||||
item.trade.provider == ExchangeProviderDescription.sideShift) ||
|
||||
(displayChangeNow &&
|
||||
item.trade.provider == ExchangeProviderDescription.changeNow) ||
|
||||
(displayMorphToken &&
|
||||
item.trade.provider == ExchangeProviderDescription.morphToken) ||
|
||||
(displaySimpleSwap &&
|
||||
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
||||
(displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) ||
|
||||
(displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) ||
|
||||
(displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) ||
|
||||
(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
||||
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
|
||||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
|
||||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
|
||||
(displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain))
|
||||
.toList()
|
||||
: _trades;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ abstract class AnonpayDetailsViewModelBase with Store {
|
||||
]);
|
||||
|
||||
items.add(TrackTradeListItem(
|
||||
title: 'Track',
|
||||
title: S.current.track,
|
||||
value: invoiceDetail.clearnetStatusUrl,
|
||||
onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl)));
|
||||
}
|
||||
|
||||
@@ -120,6 +120,11 @@ abstract class DashboardViewModelBase with Store {
|
||||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayThorChain,
|
||||
caption: ExchangeProviderDescription.thorChain.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
|
||||
]
|
||||
},
|
||||
subname = '',
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -47,6 +48,9 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
||||
break;
|
||||
}
|
||||
|
||||
_updateItems();
|
||||
@@ -100,8 +104,13 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
final output = sendViewModel.outputs.first;
|
||||
output.address = trade.inputAddress ?? '';
|
||||
output.setCryptoAmount(trade.amount);
|
||||
if (_provider is ThorChainExchangeProvider) output.memo = trade.memo;
|
||||
sendViewModel.selectedCryptoCurrency = trade.from;
|
||||
await sendViewModel.createTransaction();
|
||||
final pendingTransaction = await sendViewModel.createTransaction(provider: _provider);
|
||||
if (_provider is ThorChainExchangeProvider) {
|
||||
trade.id = pendingTransaction?.id ?? '';
|
||||
trades.add(trade);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -127,6 +136,8 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
|
||||
final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
|
||||
items.clear();
|
||||
|
||||
if(trade.provider != ExchangeProviderDescription.thorChain)
|
||||
items.add(ExchangeTradeItem(
|
||||
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
@@ -9,6 +10,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
@@ -18,6 +20,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_request.dart';
|
||||
@@ -96,7 +99,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
|
||||
/// if the provider is not in the user settings (user's first time or newly added provider)
|
||||
/// then use its default value decided by us
|
||||
selectedProviders = ObservableList.of(providersForCurrentPair()
|
||||
selectedProviders = ObservableList.of(providerList
|
||||
.where((element) => exchangeProvidersSelection[element.title] == null
|
||||
? element.isEnabled
|
||||
: (exchangeProvidersSelection[element.title] as bool))
|
||||
@@ -148,6 +151,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
SimpleSwapExchangeProvider(),
|
||||
TrocadorExchangeProvider(
|
||||
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
|
||||
ThorChainExchangeProvider(tradesStore: trades),
|
||||
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||
];
|
||||
|
||||
@@ -496,8 +500,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode);
|
||||
trade.walletId = wallet.id;
|
||||
trade.fromWalletAddress = wallet.walletAddresses.address;
|
||||
|
||||
if (!isCanCreateTrade(trade)) {
|
||||
tradeState = TradeIsCreatedFailure(
|
||||
title: S.current.trade_not_created,
|
||||
error: S.current.thorchain_taproot_address_not_supported);
|
||||
return;
|
||||
}
|
||||
|
||||
tradesStore.setTrade(trade);
|
||||
await trades.add(trade);
|
||||
if (trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade);
|
||||
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
||||
|
||||
/// return after the first successful trade
|
||||
@@ -749,4 +761,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
int get depositMaxDigits => depositCurrency.decimals;
|
||||
|
||||
int get receiveMaxDigits => receiveCurrency.decimals;
|
||||
|
||||
bool isCanCreateTrade(Trade trade) {
|
||||
if (trade.provider == ExchangeProviderDescription.thorChain) {
|
||||
final payoutAddress = trade.payoutAddress ?? '';
|
||||
final fromWalletAddress = trade.fromWalletAddress ?? '';
|
||||
final tapRootPattern = RegExp(P2trAddress.regex.pattern);
|
||||
|
||||
if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
final buildURL = trackUrl + '${order.transferId}';
|
||||
items.add(
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
title: S.current.track,
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
try {
|
||||
|
||||
@@ -66,6 +66,8 @@ abstract class OutputBase with Store {
|
||||
@observable
|
||||
String extractedAddress;
|
||||
|
||||
String? memo;
|
||||
|
||||
@computed
|
||||
bool get isParsedAddress =>
|
||||
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
||||
@@ -175,6 +177,7 @@ abstract class OutputBase with Store {
|
||||
fiatAmount = '';
|
||||
address = '';
|
||||
note = '';
|
||||
memo = null;
|
||||
resetParsedAddress();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
@@ -296,14 +298,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> createTransaction() async {
|
||||
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||
if (provider is ThorChainExchangeProvider) {
|
||||
final outputCount = pendingTransaction?.outputCount ?? 0;
|
||||
if (outputCount > 10) throw Exception("ThorChain does not support more than 10 outputs");
|
||||
}
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
print('Failed with ${e.toString()}');
|
||||
state = FailureState(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
@@ -52,6 +53,9 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
||||
break;
|
||||
}
|
||||
|
||||
_updateItems();
|
||||
@@ -62,6 +66,24 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
static String? getTrackUrl(ExchangeProviderDescription provider, Trade trade) {
|
||||
switch (provider) {
|
||||
case ExchangeProviderDescription.changeNow:
|
||||
return 'https://changenow.io/exchange/txs/${trade.id}';
|
||||
case ExchangeProviderDescription.sideShift:
|
||||
return 'https://sideshift.ai/orders/${trade.id}';
|
||||
case ExchangeProviderDescription.simpleSwap:
|
||||
return 'https://simpleswap.io/exchange?id=${trade.id}';
|
||||
case ExchangeProviderDescription.trocador:
|
||||
return 'https://trocador.app/en/checkout/${trade.id}';
|
||||
case ExchangeProviderDescription.exolix:
|
||||
return 'https://exolix.com/transaction/${trade.id}';
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
return 'https://track.ninerealms.com/${trade.id}';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final Box<Trade> trades;
|
||||
|
||||
@observable
|
||||
@@ -125,46 +147,26 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
items.add(StandartListItem(
|
||||
title: S.current.trade_details_provider, value: trade.provider.toString()));
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.changeNow) {
|
||||
final buildURL = 'https://changenow.io/exchange/txs/${trade.id.toString()}';
|
||||
final trackUrl = TradeDetailsViewModelBase.getTrackUrl(trade.provider, trade);
|
||||
if (trackUrl != null) {
|
||||
items.add(TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
_launchUrl(buildURL);
|
||||
}));
|
||||
title: S.current.track, value: trackUrl, onTap: () => _launchUrl(trackUrl)));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.sideShift) {
|
||||
final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.simpleSwap) {
|
||||
final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
if (trade.isRefund == true) {
|
||||
items.add(StandartListItem(
|
||||
title: 'Refund', value: trade.refundAddress ?? ''));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.trocador) {
|
||||
final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
|
||||
items.add(StandartListItem(
|
||||
title: '${trade.providerName} ${S.current.id.toUpperCase()}',
|
||||
value: trade.providerId ?? ''));
|
||||
|
||||
if (trade.password != null && trade.password!.isNotEmpty)
|
||||
if (trade.password != null && trade.password!.isNotEmpty) {
|
||||
items.add(StandartListItem(
|
||||
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.exolix) {
|
||||
final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user