mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
* Add Chainflip exchange provider (#1807) * feat: add Chainflip exchange provider feat: add chainflip provider (fetchLimits and fetchRate) feat: add createTrade feat: add icon feat: add swap status feat: add FLIP to list feat: add to transaction list, with target amount feat: update receivedAmount with real values style: dart formatting feat: update received amount chore: cleanup space and typo * fix: use correct retryDurationInBlocks * feat: use secrets for api key --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Add secrets to workflow * revert boostfee change * minor ui fix [skip ci] --------- Co-authored-by: David Cumps <david@cumps.be>
This commit is contained in:
parent
199ada3fa9
commit
9cda2c99e7
21 changed files with 413 additions and 21 deletions
4
.github/workflows/pr_test_build_android.yml
vendored
4
.github/workflows/pr_test_build_android.yml
vendored
|
@ -160,6 +160,10 @@ jobs:
|
||||||
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||||
echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||||
echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
|
echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
|
||||||
|
# end of test secrets
|
||||||
|
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
|
||||||
|
|
||||||
- name: prepare monero_c and cache
|
- name: prepare monero_c and cache
|
||||||
run: |
|
run: |
|
||||||
export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }')
|
export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }')
|
||||||
|
|
4
.github/workflows/pr_test_build_linux.yml
vendored
4
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -156,6 +156,10 @@ jobs:
|
||||||
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||||
echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||||
echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
|
echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
|
||||||
|
# end of test secrets
|
||||||
|
echo "const chainflipApiKey = '${{ secrets.CHAINFLIP_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const chainflipAffiliateFee = '${{ secrets.CHAINFLIP_AFFILIATE_FEE }}';" >> lib/.secrets.g.dart
|
||||||
|
|
||||||
- name: prepare monero_c and cache
|
- name: prepare monero_c and cache
|
||||||
run: |
|
run: |
|
||||||
export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }')
|
export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }')
|
||||||
|
|
BIN
assets/images/chainflip.png
Normal file
BIN
assets/images/chainflip.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
BIN
assets/images/flip_icon.png
Normal file
BIN
assets/images/flip_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
|
@ -107,6 +107,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
CryptoCurrency.tbtc,
|
CryptoCurrency.tbtc,
|
||||||
CryptoCurrency.wow,
|
CryptoCurrency.wow,
|
||||||
CryptoCurrency.ton,
|
CryptoCurrency.ton,
|
||||||
|
CryptoCurrency.flip
|
||||||
];
|
];
|
||||||
|
|
||||||
static const havenCurrencies = [
|
static const havenCurrencies = [
|
||||||
|
@ -226,6 +227,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
|
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 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<int, CryptoCurrency> _rawCurrencyMap =
|
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||||
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
|
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
|
||||||
|
|
|
@ -290,6 +290,13 @@ class DefaultEthereumErc20Tokens {
|
||||||
decimal: 6,
|
decimal: 6,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
),
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Chainflip",
|
||||||
|
symbol: "FLIP",
|
||||||
|
contractAddress: "0x826180541412D574cf1336d22c0C0a287822678A",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
List<Erc20Token> get initialErc20Tokens => _defaultTokens.map((token) {
|
List<Erc20Token> get initialErc20Tokens => _defaultTokens.map((token) {
|
||||||
|
|
|
@ -16,13 +16,14 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
|
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
|
||||||
static const sideShift =
|
static const sideShift =
|
||||||
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
|
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
|
||||||
static const simpleSwap = ExchangeProviderDescription(
|
static const simpleSwap =
|
||||||
title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
|
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
|
||||||
static const trocador =
|
static const trocador =
|
||||||
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
|
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
|
||||||
static const exolix =
|
static const exolix =
|
||||||
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
|
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 =
|
static const thorChain =
|
||||||
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
|
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
|
||||||
static const quantex =
|
static const quantex =
|
||||||
|
@ -31,6 +32,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||||
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 =
|
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}) {
|
static ExchangeProviderDescription deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
|
@ -58,6 +61,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||||
return letsExchange;
|
return letsExchange;
|
||||||
case 11:
|
case 11:
|
||||||
return stealthEx;
|
return stealthEx;
|
||||||
|
case 12:
|
||||||
|
return chainflip;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
|
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
|
||||||
}
|
}
|
||||||
|
|
315
lib/exchange/provider/chainflip_exchange_provider.dart
Normal file
315
lib/exchange/provider/chainflip_exchange_provider.dart
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
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';
|
||||||
|
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:cw_core/utils/print_verbose.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<CryptoCurrency> _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 = secrets.chainflipAffiliateFee;
|
||||||
|
static const _affiliateKey = secrets.chainflipApiKey;
|
||||||
|
|
||||||
|
final Box<Trade> 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<bool> checkIsAvailable() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Limits> 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<dynamic>;
|
||||||
|
|
||||||
|
final minAmount = assets.firstWhere(
|
||||||
|
(asset) => asset['id'] == assetId,
|
||||||
|
orElse: () => null)?['minimalAmountNative'] ?? '0';
|
||||||
|
|
||||||
|
return Limits(min: _amountFromNative(minAmount.toString(), from));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<double> 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) {
|
||||||
|
printV(e.toString());
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Trade> 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,
|
||||||
|
'boostFee': '6',
|
||||||
|
'retryDurationInBlocks': '150'
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
printV(e.toString());
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Trade> 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) {
|
||||||
|
printV(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<Map<String, dynamic>> _getAssets() async =>
|
||||||
|
_getRequest(_assetsPath, {});
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async =>
|
||||||
|
_getRequest(_quotePath, params);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _openDepositChannel(Map<String, String> params) async =>
|
||||||
|
_getRequest(_swapPath, params);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _getRequest(String path, Map<String, String> 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<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>?> _getStatus(Map<String, String> 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<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -203,7 +203,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||||
extraId: extraId,
|
extraId: extraId,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
amount: amount,
|
amount: amount,
|
||||||
receiveAmount:receiveAmount ?? request.toAmount,
|
receiveAmount: receiveAmount ?? request.toAmount,
|
||||||
state: TradeState.created,
|
state: TradeState.created,
|
||||||
payoutAddress: payoutAddress,
|
payoutAddress: payoutAddress,
|
||||||
isSendAll: isSendAll,
|
isSendAll: isSendAll,
|
||||||
|
|
|
@ -168,6 +168,7 @@ class Trade extends HiveObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
String amountFormatted() => formatAmount(amount);
|
String amountFormatted() => formatAmount(amount);
|
||||||
|
String receiveAmountFormatted() => formatAmount(receiveAmount ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
class TradeAdapter extends TypeAdapter<Trade> {
|
class TradeAdapter extends TypeAdapter<Trade> {
|
||||||
|
|
|
@ -161,6 +161,7 @@ class TransactionsPage extends StatelessWidget {
|
||||||
? DateFormat('HH:mm').format(trade.createdAt!)
|
? DateFormat('HH:mm').format(trade.createdAt!)
|
||||||
: null,
|
: null,
|
||||||
formattedAmount: item.tradeFormattedAmount,
|
formattedAmount: item.tradeFormattedAmount,
|
||||||
|
formattedReceiveAmount: item.tradeFormattedReceiveAmount
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ class TradeRow extends StatelessWidget {
|
||||||
required this.createdAtFormattedDate,
|
required this.createdAtFormattedDate,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.formattedAmount,
|
this.formattedAmount,
|
||||||
|
this.formattedReceiveAmount,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,10 +23,12 @@ class TradeRow extends StatelessWidget {
|
||||||
final CryptoCurrency to;
|
final CryptoCurrency to;
|
||||||
final String? createdAtFormattedDate;
|
final String? createdAtFormattedDate;
|
||||||
final String? formattedAmount;
|
final String? formattedAmount;
|
||||||
|
final String? formattedReceiveAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final amountCrypto = from.toString();
|
final amountCrypto = from.toString();
|
||||||
|
final receiveAmountCrypto = to.toString();
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
@ -61,12 +64,21 @@ class TradeRow extends StatelessWidget {
|
||||||
: Container()
|
: Container()
|
||||||
]),
|
]),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
|
Row(
|
||||||
if (createdAtFormattedDate != null)
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Text(createdAtFormattedDate!,
|
children: <Widget>[
|
||||||
|
createdAtFormattedDate != null
|
||||||
|
? Text(createdAtFormattedDate!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
|
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
|
||||||
|
: Container(),
|
||||||
|
formattedReceiveAmount != null
|
||||||
|
? Text(formattedReceiveAmount! + ' ' + receiveAmountCrypto,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
|
||||||
|
: Container(),
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
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/exchange/provider/thorchain_exchange.provider.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
|
@ -444,7 +445,8 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
if (state is TradeIsCreatedSuccessfully) {
|
if (state is TradeIsCreatedSuccessfully) {
|
||||||
exchangeViewModel.reset();
|
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.exchangeTrade)
|
||||||
: Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
|
: Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
|
||||||
}
|
}
|
||||||
|
@ -495,8 +497,10 @@ class ExchangePage extends BasePage {
|
||||||
exchangeViewModel.isSendAllEnabled = false;
|
exchangeViewModel.isSendAllEnabled = false;
|
||||||
final isThorChain = exchangeViewModel.selectedProviders
|
final isThorChain = exchangeViewModel.selectedProviders
|
||||||
.any((provider) => provider is ThorChainExchangeProvider);
|
.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: 1000))
|
||||||
: Debounce(Duration(milliseconds: 500));
|
: Debounce(Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ class TransactionSuccessPage extends InfoPage {
|
||||||
Key? get buttonKey => ValueKey('transaction_success_info_page_button_key');
|
Key? get buttonKey => ValueKey('transaction_success_info_page_button_key');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void Function(BuildContext) get onPressed =>
|
void Function(BuildContext) get onPressed => (BuildContext context) {
|
||||||
(BuildContext context) => Navigator.of(context).pop();
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displaySimpleSwap = true,
|
displaySimpleSwap = true,
|
||||||
displayTrocador = true,
|
displayTrocador = true,
|
||||||
displayExolix = true,
|
displayExolix = true,
|
||||||
|
displayChainflip = true,
|
||||||
displayThorChain = true,
|
displayThorChain = true,
|
||||||
displayLetsExchange = true,
|
displayLetsExchange = true,
|
||||||
displayStealthEx = true;
|
displayStealthEx = true;
|
||||||
|
@ -41,6 +42,9 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool displayExolix;
|
bool displayExolix;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayChainflip;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool displayThorChain;
|
bool displayThorChain;
|
||||||
|
|
||||||
|
@ -57,6 +61,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displaySimpleSwap &&
|
displaySimpleSwap &&
|
||||||
displayTrocador &&
|
displayTrocador &&
|
||||||
displayExolix &&
|
displayExolix &&
|
||||||
|
displayChainflip &&
|
||||||
displayThorChain &&
|
displayThorChain &&
|
||||||
displayLetsExchange &&
|
displayLetsExchange &&
|
||||||
displayStealthEx;
|
displayStealthEx;
|
||||||
|
@ -85,11 +90,15 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
case ExchangeProviderDescription.exolix:
|
case ExchangeProviderDescription.exolix:
|
||||||
displayExolix = !displayExolix;
|
displayExolix = !displayExolix;
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.chainflip:
|
||||||
|
displayChainflip = !displayChainflip;
|
||||||
|
break;
|
||||||
case ExchangeProviderDescription.thorChain:
|
case ExchangeProviderDescription.thorChain:
|
||||||
displayThorChain = !displayThorChain;
|
displayThorChain = !displayThorChain;
|
||||||
break;
|
break;
|
||||||
case ExchangeProviderDescription.letsExchange:
|
case ExchangeProviderDescription.letsExchange:
|
||||||
displayLetsExchange = !displayLetsExchange;
|
displayLetsExchange = !displayLetsExchange;
|
||||||
|
break;
|
||||||
case ExchangeProviderDescription.stealthEx:
|
case ExchangeProviderDescription.stealthEx:
|
||||||
displayStealthEx = !displayStealthEx;
|
displayStealthEx = !displayStealthEx;
|
||||||
break;
|
break;
|
||||||
|
@ -102,6 +111,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displaySimpleSwap = false;
|
displaySimpleSwap = false;
|
||||||
displayTrocador = false;
|
displayTrocador = false;
|
||||||
displayExolix = false;
|
displayExolix = false;
|
||||||
|
displayChainflip = false;
|
||||||
displayThorChain = false;
|
displayThorChain = false;
|
||||||
displayLetsExchange = false;
|
displayLetsExchange = false;
|
||||||
displayStealthEx = false;
|
displayStealthEx = false;
|
||||||
|
@ -113,6 +123,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displaySimpleSwap = true;
|
displaySimpleSwap = true;
|
||||||
displayTrocador = true;
|
displayTrocador = true;
|
||||||
displayExolix = true;
|
displayExolix = true;
|
||||||
|
displayChainflip = true;
|
||||||
displayThorChain = true;
|
displayThorChain = true;
|
||||||
displayLetsExchange = true;
|
displayLetsExchange = true;
|
||||||
displayStealthEx = true;
|
displayStealthEx = true;
|
||||||
|
@ -141,6 +152,8 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
||||||
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
|
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
|
||||||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
|
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
|
||||||
|
(displayChainflip &&
|
||||||
|
item.trade.provider == ExchangeProviderDescription.chainflip) ||
|
||||||
(displayThorChain &&
|
(displayThorChain &&
|
||||||
item.trade.provider == ExchangeProviderDescription.thorChain) ||
|
item.trade.provider == ExchangeProviderDescription.thorChain) ||
|
||||||
(displayLetsExchange &&
|
(displayLetsExchange &&
|
||||||
|
|
|
@ -130,6 +130,11 @@ abstract class DashboardViewModelBase with Store {
|
||||||
caption: ExchangeProviderDescription.exolix.title,
|
caption: ExchangeProviderDescription.exolix.title,
|
||||||
onChanged: () =>
|
onChanged: () =>
|
||||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||||
|
FilterItem(
|
||||||
|
value: () => tradeFilterStore.displayChainflip,
|
||||||
|
caption: ExchangeProviderDescription.chainflip.title,
|
||||||
|
onChanged: () =>
|
||||||
|
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.chainflip)),
|
||||||
FilterItem(
|
FilterItem(
|
||||||
value: () => tradeFilterStore.displayThorChain,
|
value: () => tradeFilterStore.displayThorChain,
|
||||||
caption: ExchangeProviderDescription.thorChain.title,
|
caption: ExchangeProviderDescription.thorChain.title,
|
||||||
|
|
|
@ -18,6 +18,9 @@ class TradeListItem extends ActionListItem {
|
||||||
String get tradeFormattedAmount =>
|
String get tradeFormattedAmount =>
|
||||||
displayMode == BalanceDisplayMode.hiddenBalance ? '---' : trade.amountFormatted();
|
displayMode == BalanceDisplayMode.hiddenBalance ? '---' : trade.amountFormatted();
|
||||||
|
|
||||||
|
String get tradeFormattedReceiveAmount =>
|
||||||
|
displayMode == BalanceDisplayMode.hiddenBalance ? '---' : trade.receiveAmountFormatted();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DateTime get date => trade.createdAt!;
|
DateTime get date => trade.createdAt!;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
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/changenow_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
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/exolix_exchange_provider.dart';
|
||||||
|
@ -56,9 +57,13 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||||
break;
|
break;
|
||||||
case ExchangeProviderDescription.stealthEx:
|
case ExchangeProviderDescription.stealthEx:
|
||||||
_provider = StealthExExchangeProvider();
|
_provider = StealthExExchangeProvider();
|
||||||
|
break;
|
||||||
case ExchangeProviderDescription.thorChain:
|
case ExchangeProviderDescription.thorChain:
|
||||||
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.chainflip:
|
||||||
|
_provider = ChainflipExchangeProvider(tradesStore: trades);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateItems();
|
_updateItems();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cake_wallet/core/create_trade_result.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/letsexchange_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -159,7 +160,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
wallet.type == WalletType.bitcoinCash;
|
wallet.type == WalletType.bitcoinCash;
|
||||||
|
|
||||||
bool get hideAddressAfterExchange =>
|
bool get hideAddressAfterExchange =>
|
||||||
wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
|
wallet.type == WalletType.monero ||
|
||||||
|
wallet.type == WalletType.wownero;
|
||||||
|
|
||||||
bool _useTorOnly;
|
bool _useTorOnly;
|
||||||
final Box<Trade> trades;
|
final Box<Trade> trades;
|
||||||
|
@ -172,6 +174,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
SideShiftExchangeProvider(),
|
SideShiftExchangeProvider(),
|
||||||
SimpleSwapExchangeProvider(),
|
SimpleSwapExchangeProvider(),
|
||||||
ThorChainExchangeProvider(tradesStore: trades),
|
ThorChainExchangeProvider(tradesStore: trades),
|
||||||
|
ChainflipExchangeProvider(tradesStore: trades),
|
||||||
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||||
QuantexExchangeProvider(),
|
QuantexExchangeProvider(),
|
||||||
LetsExchangeExchangeProvider(),
|
LetsExchangeExchangeProvider(),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
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/changenow_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
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/exolix_exchange_provider.dart';
|
||||||
|
@ -68,6 +69,9 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||||
case ExchangeProviderDescription.stealthEx:
|
case ExchangeProviderDescription.stealthEx:
|
||||||
_provider = StealthExExchangeProvider();
|
_provider = StealthExExchangeProvider();
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.chainflip:
|
||||||
|
_provider = ChainflipExchangeProvider(tradesStore: trades);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateItems();
|
_updateItems();
|
||||||
|
@ -98,6 +102,8 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||||
return 'https://letsexchange.io/?transactionId=${trade.id}';
|
return 'https://letsexchange.io/?transactionId=${trade.id}';
|
||||||
case ExchangeProviderDescription.stealthEx:
|
case ExchangeProviderDescription.stealthEx:
|
||||||
return 'https://stealthex.io/exchange/?id=${trade.id}';
|
return 'https://stealthex.io/exchange/?id=${trade.id}';
|
||||||
|
case ExchangeProviderDescription.chainflip:
|
||||||
|
return 'https://scan.chainflip.io/channels/${trade.id}';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ class SecretKey {
|
||||||
SecretKey('stealthExBearerToken', () => ''),
|
SecretKey('stealthExBearerToken', () => ''),
|
||||||
SecretKey('stealthExAdditionalFeePercent', () => ''),
|
SecretKey('stealthExAdditionalFeePercent', () => ''),
|
||||||
SecretKey('moneroTestWalletBlockHeight', () => ''),
|
SecretKey('moneroTestWalletBlockHeight', () => ''),
|
||||||
|
SecretKey('chainflipApiKey', () => ''),
|
||||||
];
|
];
|
||||||
|
|
||||||
static final evmChainsSecrets = [
|
static final evmChainsSecrets = [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue