diff --git a/assets/images/chainflip.png b/assets/images/chainflip.png new file mode 100644 index 0000000000..e588e63618 Binary files /dev/null and b/assets/images/chainflip.png differ diff --git a/assets/images/flip_icon.png b/assets/images/flip_icon.png new file mode 100644 index 0000000000..e588e63618 Binary files /dev/null and b/assets/images/flip_icon.png differ diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 0280bb45af..d00e364ced 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -107,6 +107,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.tbtc, CryptoCurrency.wow, CryptoCurrency.ton, + CryptoCurrency.flip ]; static const havenCurrencies = [ @@ -226,6 +227,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11); static const ton = CryptoCurrency(title: 'TON', fullName: 'Toncoin', raw: 95, name: 'ton', iconPath: 'assets/images/ton_icon.png', decimals: 8); + static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 96, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18); static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/cw_ethereum/lib/default_ethereum_erc20_tokens.dart b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart index c26ee1efc6..ee60a3d6c5 100644 --- a/cw_ethereum/lib/default_ethereum_erc20_tokens.dart +++ b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart @@ -290,6 +290,13 @@ class DefaultEthereumErc20Tokens { decimal: 6, enabled: false, ), + Erc20Token( + name: "Chainflip", + symbol: "FLIP", + contractAddress: "0x826180541412D574cf1336d22c0C0a287822678A", + decimal: 18, + enabled: false, + ), ]; List get initialErc20Tokens => _defaultTokens.map((token) { diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index 9f37233564..249ae61bf4 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -16,22 +16,25 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png'); static const sideShift = ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png'); - static const simpleSwap = ExchangeProviderDescription( - title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png'); + static const simpleSwap = + ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png'); static const trocador = 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 all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); + static const all = + ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); static const thorChain = 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'); + ExchangeProviderDescription(title: 'LetsExchange', raw: 10, image: 'assets/images/letsexchange_icon.svg'); static const stealthEx = - ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/images/stealthex.png'); - + ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/images/stealthex.png'); + static const chainflip = + ExchangeProviderDescription(title: 'Chainflip', raw: 12, image: 'assets/images/chainflip.png'); + static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { case 0: @@ -58,6 +61,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< return letsExchange; case 11: return stealthEx; + case 12: + return chainflip; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); } diff --git a/lib/exchange/provider/chainflip_exchange_provider.dart b/lib/exchange/provider/chainflip_exchange_provider.dart new file mode 100644 index 0000000000..5d06d61836 --- /dev/null +++ b/lib/exchange/provider/chainflip_exchange_provider.dart @@ -0,0 +1,313 @@ +import 'dart:convert'; +import 'dart:math'; + +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 ChainflipExchangeProvider extends ExchangeProvider { + ChainflipExchangeProvider({required this.tradesStore}) + : super(pairList: supportedPairs(_notSupported)); + + static final List _notSupported = [ + ...(CryptoCurrency.all + .where((element) => ![ + CryptoCurrency.btc, + CryptoCurrency.eth, + CryptoCurrency.usdc, + CryptoCurrency.usdterc20, + CryptoCurrency.flip, + CryptoCurrency.sol, + CryptoCurrency.usdcsol, + // TODO: Add CryptoCurrency.etharb + // TODO: Add CryptoCurrency.usdcarb + // TODO: Add CryptoCurrency.dot + ].contains(element)) + .toList()) + ]; + + static const _baseURL = 'chainflip-broker.io'; + static const _assetsPath = '/assets'; + static const _quotePath = '/quote-native'; + static const _swapPath = '/swap'; + static const _txInfoPath = '/status-by-deposit-channel'; + static const _affiliateBps = '170'; + static const _affiliateKey = '6ba154d4-e219-472a-9674-5fa5b1300ccf'; + // TODO: Example key, replace with CW key + + final Box tradesStore; + + @override + String get title => 'Chainflip'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => false; + + @override + ExchangeProviderDescription get description => + ExchangeProviderDescription.chainflip; + + @override + Future checkIsAvailable() async => true; + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final assetId = _normalizeCurrency(from); + + final assetsResponse = await _getAssets(); + final assets = assetsResponse['assets'] as List; + + final minAmount = assets.firstWhere( + (asset) => asset['id'] == assetId, + orElse: () => null)?['minimalAmountNative'] ?? '0'; + + return Limits(min: _amountFromNative(minAmount.toString(), from)); + } + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + // TODO: It seems this rate is getting cached, and re-used for different amounts, can we not do this? + + try { + if (amount == 0) return 0.0; + + final quoteParams = { + 'apiKey': _affiliateKey, + 'sourceAsset': _normalizeCurrency(from), + 'destinationAsset': _normalizeCurrency(to), + 'amount': _amountToNative(amount, from), + 'commissionBps': _affiliateBps + }; + + final quoteResponse = await _getSwapQuote(quoteParams); + + final expectedAmountOut = + quoteResponse['egressAmountNative'] as String? ?? '0'; + + return _amountFromNative(expectedAmountOut, to) / amount; + } catch (e) { + print(e.toString()); + return 0.0; + } + } + + @override + Future createTrade( + {required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll}) async { + try { + final maxSlippage = 2; + + final quoteParams = { + 'apiKey': _affiliateKey, + 'sourceAsset': _normalizeCurrency(request.fromCurrency), + 'destinationAsset': _normalizeCurrency(request.toCurrency), + 'amount': _amountToNative(double.parse(request.fromAmount), request.fromCurrency), + 'commissionBps': _affiliateBps + }; + + final quoteResponse = await _getSwapQuote(quoteParams); + final estimatedPrice = quoteResponse['estimatedPrice'] as double; + final minimumPrice = estimatedPrice * (100 - maxSlippage) / 100; + + final swapParams = { + 'apiKey': _affiliateKey, + 'sourceAsset': _normalizeCurrency(request.fromCurrency), + 'destinationAsset': _normalizeCurrency(request.toCurrency), + 'destinationAddress': request.toAddress, + 'commissionBps': _affiliateBps, + 'minimumPrice': minimumPrice.toString(), + 'refundAddress': request.refundAddress, + 'retryDurationInBlocks': '10' + }; + + final swapResponse = await _openDepositChannel(swapParams); + + final id = '${swapResponse['issuedBlock']}-${swapResponse['network'].toString().toUpperCase()}-${swapResponse['channelId']}'; + + return Trade( + id: id, + from: request.fromCurrency, + to: request.toCurrency, + provider: description, + inputAddress: swapResponse['address'].toString(), + createdAt: DateTime.now(), + amount: request.fromAmount, + receiveAmount: request.toAmount, + state: TradeState.waiting, + payoutAddress: request.toAddress, + isSendAll: isSendAll); + } catch (e) { + print(e.toString()); + rethrow; + } + } + + @override + Future findTradeById({required String id}) async { + try { + final channelParts = id.split('-'); + + final statusParams = { + 'apiKey': _affiliateKey, + 'issuedBlock': channelParts[0], + 'network': channelParts[1], + 'channelId': channelParts[2] + }; + + final statusResponse = await _getStatus(statusParams); + + if (statusResponse == null) + throw Exception('Trade not found for id: $id'); + + final status = statusResponse['status']; + final currentState = _determineState(status['state'].toString()); + + final depositAmount = status['deposit']?['amount']?.toString() ?? '0.0'; + final receiveAmount = status['swapEgress']?['amount']?.toString() ?? '0.0'; + final refundAmount = status['refundEgress']?['amount']?.toString() ?? '0.0'; + final isRefund = status['refundEgress'] != null; + final amount = isRefund ? refundAmount : receiveAmount; + + final newTrade = Trade( + id: id, + from: _toCurrency(status['sourceAsset'].toString()), + to: _toCurrency(status['destinationAsset'].toString()), + provider: description, + amount: depositAmount, + receiveAmount: amount, + state: currentState, + payoutAddress: status['destinationAddress'].toString(), + outputTransaction: status['swapEgress']?['transactionReference']?.toString(), + isRefund: isRefund); + + // Find trade and update receiveAmount with the real value received + final storedTrade = _getStoredTrade(id); + + if (storedTrade != null) { + storedTrade.$2.receiveAmount = newTrade.receiveAmount; + storedTrade.$2.outputTransaction = newTrade.outputTransaction; + tradesStore.put(storedTrade.$1, storedTrade.$2); + } + + return newTrade; + } catch (e) { + print(e.toString()); + rethrow; + } + } + + String _normalizeCurrency(CryptoCurrency currency) { + final network = switch (currency.tag) { + 'ETH' => 'eth', + 'SOL' => 'sol', + _ => currency.title.toLowerCase() + }; + + return '${currency.title.toLowerCase()}.$network'; + } + + CryptoCurrency? _toCurrency(String name) { + final currency = switch (name) { + 'btc.btc' => CryptoCurrency.btc, + 'eth.eth' => CryptoCurrency.eth, + 'usdc.eth' => CryptoCurrency.usdc, + 'usdt.eth' => CryptoCurrency.usdterc20, + 'flip.eth' => CryptoCurrency.flip, + 'sol.sol' => CryptoCurrency.sol, + 'usdc.sol' => CryptoCurrency.usdcsol, + _ => null + }; + + return currency; + } + + (dynamic, Trade)? _getStoredTrade(String id) { + for (var i = tradesStore.length -1; i >= 0; i--) { + Trade? t = tradesStore.getAt(i); + + if (t != null && t.id == id) + return (i, t); + } + + return null; + } + + String _amountToNative(double amount, CryptoCurrency currency) => + (amount * pow(10, currency.decimals)).toInt().toString(); + + double _amountFromNative(String amount, CryptoCurrency currency) => + double.parse(amount) / pow(10, currency.decimals); + + Future> _getAssets() async => + _getRequest(_assetsPath, {}); + + Future> _getSwapQuote(Map params) async => + _getRequest(_quotePath, params); + + Future> _openDepositChannel(Map params) async => + _getRequest(_swapPath, params); + + Future> _getRequest(String path, Map params) async { + final uri = Uri.https(_baseURL, path, params); + + final response = await http.get(uri); + + if ((response.statusCode != 200) || (response.body.contains('error'))) { + throw Exception('Unexpected response: ${response.statusCode} / ${uri.toString()} / ${response.body}'); + } + + return json.decode(response.body) as Map; + } + + Future?> _getStatus(Map params) async { + final uri = Uri.https(_baseURL, _txInfoPath, params); + + final response = await http.get(uri); + + if (response.statusCode == 404) return null; + + if ((response.statusCode != 200) || (response.body.contains('error'))) { + throw Exception('Unexpected response: ${response.statusCode} / ${uri.toString()} / ${response.body}'); + } + + return json.decode(response.body) as Map; + } + + TradeState _determineState(String state) { + final swapState = switch (state) { + 'waiting' => TradeState.waiting, + 'receiving' => TradeState.processing, + 'swapping' => TradeState.processing, + 'sending' => TradeState.processing, + 'sent' => TradeState.processing, + 'completed' => TradeState.success, + 'failed' => TradeState.failed, + _ => TradeState.notFound + }; + + return swapState; + } +} diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 5eeb6f9cf4..3bf783af83 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -184,7 +184,7 @@ class ExolixExchangeProvider extends ExchangeProvider { extraId: extraId, createdAt: DateTime.now(), amount: amount, - receiveAmount:receiveAmount ?? request.toAmount, + receiveAmount: receiveAmount ?? request.toAmount, state: TradeState.created, payoutAddress: payoutAddress, isSendAll: isSendAll, diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index a0c08aac7b..77cc0d567e 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -166,6 +166,7 @@ class Trade extends HiveObject { } String amountFormatted() => formatAmount(amount); + String receiveAmountFormatted() => formatAmount(receiveAmount ?? ''); } class TradeAdapter extends TypeAdapter { diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 0db9ac35b6..2eae0add7e 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -160,7 +160,8 @@ class TransactionsPage extends StatelessWidget { createdAtFormattedDate: trade.createdAt != null ? DateFormat('HH:mm').format(trade.createdAt!) : null, - formattedAmount: item.tradeFormattedAmount, + formattedAmount: item.tradeFormattedAmount, + formattedReceiveAmount: item.tradeFormattedReceiveAmount ), ); } diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 84a5d2beba..8eea77b192 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -13,6 +13,7 @@ class TradeRow extends StatelessWidget { required this.createdAtFormattedDate, this.onTap, this.formattedAmount, + this.formattedReceiveAmount, super.key, }); @@ -22,10 +23,12 @@ class TradeRow extends StatelessWidget { final CryptoCurrency to; final String? createdAtFormattedDate; final String? formattedAmount; + final String? formattedReceiveAmount; @override Widget build(BuildContext context) { final amountCrypto = from.toString(); + final receiveAmountCrypto = to.toString(); return InkWell( onTap: onTap, @@ -61,12 +64,21 @@ class TradeRow extends StatelessWidget { : Container() ]), SizedBox(height: 5), - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (createdAtFormattedDate != null) - Text(createdAtFormattedDate!, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).extension()!.dateSectionRowColor)) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + createdAtFormattedDate != null + ? Text(createdAtFormattedDate!, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).extension()!.dateSectionRowColor)) + : Container(), + formattedReceiveAmount != null + ? Text(formattedReceiveAmount! + ' ' + receiveAmountCrypto, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).extension()!.dateSectionRowColor)) + : Container(), ]) ], )) diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 2f8e3eb5ce..9bdf57f3dc 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.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'; @@ -442,7 +443,8 @@ class ExchangePage extends BasePage { } if (state is TradeIsCreatedSuccessfully) { exchangeViewModel.reset(); - (exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain) + (exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain || + exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.chainflip) ? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade) : Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm); } @@ -485,8 +487,10 @@ class ExchangePage extends BasePage { exchangeViewModel.isSendAllEnabled = false; final isThorChain = exchangeViewModel.selectedProviders .any((provider) => provider is ThorChainExchangeProvider); + final isChainflip = exchangeViewModel.selectedProviders + .any((provider) => provider is ChainflipExchangeProvider); - _depositAmountDebounce = isThorChain + _depositAmountDebounce = isThorChain || isChainflip ? Debounce(Duration(milliseconds: 1000)) : Debounce(Duration(milliseconds: 500)); diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index c1e462cd6c..a2c6e36460 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -16,6 +16,7 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = true, displayTrocador = true, displayExolix = true, + displayChainflip = true, displayThorChain = true, displayLetsExchange = true, displayStealthEx = true; @@ -41,6 +42,9 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayExolix; + @observable + bool displayChainflip; + @observable bool displayThorChain; @@ -56,7 +60,8 @@ abstract class TradeFilterStoreBase with Store { displaySideShift && displaySimpleSwap && displayTrocador && - displayExolix && + displayExolix && + displayChainflip && displayThorChain && displayLetsExchange && displayStealthEx; @@ -85,11 +90,15 @@ abstract class TradeFilterStoreBase with Store { case ExchangeProviderDescription.exolix: displayExolix = !displayExolix; break; + case ExchangeProviderDescription.chainflip: + displayChainflip = !displayChainflip; + break; case ExchangeProviderDescription.thorChain: displayThorChain = !displayThorChain; break; case ExchangeProviderDescription.letsExchange: displayLetsExchange = !displayLetsExchange; + break; case ExchangeProviderDescription.stealthEx: displayStealthEx = !displayStealthEx; break; @@ -102,6 +111,7 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = false; displayTrocador = false; displayExolix = false; + displayChainflip = false; displayThorChain = false; displayLetsExchange = false; displayStealthEx = false; @@ -113,6 +123,7 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = true; displayTrocador = true; displayExolix = true; + displayChainflip = true; displayThorChain = true; displayLetsExchange = true; displayStealthEx = true; @@ -141,6 +152,8 @@ abstract class TradeFilterStoreBase with Store { item.trade.provider == ExchangeProviderDescription.simpleSwap) || (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) || (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) || + (displayChainflip && + item.trade.provider == ExchangeProviderDescription.chainflip) || (displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain) || (displayLetsExchange && diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 808657f66c..3dcd24efb3 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -132,6 +132,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.exolix.title, onChanged: () => tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)), + FilterItem( + value: () => tradeFilterStore.displayChainflip, + caption: ExchangeProviderDescription.chainflip.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.chainflip)), FilterItem( value: () => tradeFilterStore.displayThorChain, caption: ExchangeProviderDescription.thorChain.title, diff --git a/lib/view_model/dashboard/trade_list_item.dart b/lib/view_model/dashboard/trade_list_item.dart index 55ae4e99f3..973f5b76f0 100644 --- a/lib/view_model/dashboard/trade_list_item.dart +++ b/lib/view_model/dashboard/trade_list_item.dart @@ -18,6 +18,9 @@ class TradeListItem extends ActionListItem { String get tradeFormattedAmount => displayMode == BalanceDisplayMode.hiddenBalance ? '---' : trade.amountFormatted(); + String get tradeFormattedReceiveAmount => + displayMode == BalanceDisplayMode.hiddenBalance ? '---' : trade.receiveAmountFormatted(); + @override DateTime get date => trade.createdAt!; } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 4cb7e4cadc..5b01b946bb 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; @@ -55,9 +56,13 @@ abstract class ExchangeTradeViewModelBase with Store { break; case ExchangeProviderDescription.stealthEx: _provider = StealthExExchangeProvider(); + break; case ExchangeProviderDescription.thorChain: _provider = ThorChainExchangeProvider(tradesStore: trades); break; + case ExchangeProviderDescription.chainflip: + _provider = ChainflipExchangeProvider(tradesStore: trades); + break; } _updateItems(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index d29b7df6b6..47a706fc84 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cake_wallet/core/create_trade_result.dart'; +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -170,6 +171,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), ThorChainExchangeProvider(tradesStore: trades), + ChainflipExchangeProvider(tradesStore: trades), if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), QuantexExchangeProvider(), LetsExchangeExchangeProvider(), diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 19315f40d5..63a4f05904 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/provider/chainflip_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; @@ -67,6 +68,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.stealthEx: _provider = StealthExExchangeProvider(); break; + case ExchangeProviderDescription.chainflip: + _provider = ChainflipExchangeProvider(tradesStore: trades); + break; } _updateItems(); @@ -97,6 +101,8 @@ abstract class TradeDetailsViewModelBase with Store { return 'https://letsexchange.io/?transactionId=${trade.id}'; case ExchangeProviderDescription.stealthEx: return 'https://stealthex.io/exchange/?id=${trade.id}'; + case ExchangeProviderDescription.chainflip: + return 'https://scan.chainflip.io/channels/${trade.id}'; } return null; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index affe4017c3..d67ab7605a 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -62,7 +62,7 @@ class SecretKey { SecretKey('bitcoinTestWalletReceiveAddress', () => ''), SecretKey('ethereumTestWalletReceiveAddress', () => ''), SecretKey('litecoinTestWalletReceiveAddress', () => ''), - SecretKey('bitco inCashTestWalletReceiveAddress', () => ''), + SecretKey('bitcoinCashTestWalletReceiveAddress', () => ''), SecretKey('polygonTestWalletReceiveAddress', () => ''), SecretKey('solanaTestWalletReceiveAddress', () => ''), SecretKey('tronTestWalletReceiveAddress', () => ''),