mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-477-add-ens
This commit is contained in:
commit
953cae3e48
24 changed files with 512 additions and 142 deletions
1
.github/workflows/pr_test_build.yml
vendored
1
.github/workflows/pr_test_build.yml
vendored
|
@ -128,6 +128,7 @@ jobs:
|
||||||
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
|
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
|
||||||
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||||
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||||
|
|
||||||
|
|
BIN
assets/images/exolix.png
Normal file
BIN
assets/images/exolix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -66,13 +66,11 @@ class EthereumClient {
|
||||||
|
|
||||||
bool _isEthereum = currency == CryptoCurrency.eth;
|
bool _isEthereum = currency == CryptoCurrency.eth;
|
||||||
|
|
||||||
final price = await _client!.getGasPrice();
|
final price = _client!.getGasPrice();
|
||||||
|
|
||||||
final Transaction transaction = Transaction(
|
final Transaction transaction = Transaction(
|
||||||
from: privateKey.address,
|
from: privateKey.address,
|
||||||
to: EthereumAddress.fromHex(toAddress),
|
to: EthereumAddress.fromHex(toAddress),
|
||||||
maxGas: gas,
|
|
||||||
gasPrice: price,
|
|
||||||
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||||
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||||
);
|
);
|
||||||
|
@ -102,7 +100,7 @@ class EthereumClient {
|
||||||
return PendingEthereumTransaction(
|
return PendingEthereumTransaction(
|
||||||
signedTransaction: signedTransaction,
|
signedTransaction: signedTransaction,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
fee: BigInt.from(gas) * price.getInWei,
|
fee: BigInt.from(gas) * (await price).getInWei,
|
||||||
sendTransaction: _sendTransaction,
|
sendTransaction: _sendTransaction,
|
||||||
exponent: exponent,
|
exponent: exponent,
|
||||||
);
|
);
|
||||||
|
|
|
@ -37,10 +37,10 @@ const moneroBlockSize = 1000;
|
||||||
|
|
||||||
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||||
|
|
||||||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
abstract class MoneroWalletBase
|
||||||
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
extends WalletBase<MoneroBalance, MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
||||||
MoneroWalletBase({required WalletInfo walletInfo,
|
MoneroWalletBase(
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo})
|
{required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo})
|
||||||
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
|
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
|
||||||
CryptoCurrency.xmr: MoneroBalance(
|
CryptoCurrency.xmr: MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||||
|
@ -112,12 +112,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await walletAddresses.init();
|
await walletAddresses.init();
|
||||||
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(
|
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(<CryptoCurrency, MoneroBalance>{
|
||||||
<CryptoCurrency, MoneroBalance>{
|
currency: MoneroBalance(
|
||||||
currency: MoneroBalance(
|
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
|
unlockedBalance:
|
||||||
unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
|
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
|
||||||
});
|
});
|
||||||
_setListeners();
|
_setListeners();
|
||||||
await updateTransactions();
|
await updateTransactions();
|
||||||
|
|
||||||
|
@ -125,15 +125,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||||
|
|
||||||
if (monero_wallet.getCurrentHeight() <= 1) {
|
if (monero_wallet.getCurrentHeight() <= 1) {
|
||||||
monero_wallet.setRefreshFromBlockHeight(
|
monero_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
|
||||||
height: walletInfo.restoreHeight);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_autoSaveTimer = Timer.periodic(
|
_autoSaveTimer =
|
||||||
Duration(seconds: _autoSaveInterval),
|
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||||
(_) async => await save());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void>? updateBalance() => null;
|
Future<void>? updateBalance() => null;
|
||||||
|
|
||||||
|
@ -153,7 +152,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
login: node.login,
|
login: node.login,
|
||||||
password: node.password,
|
password: node.password,
|
||||||
useSSL: node.isSSL,
|
useSSL: node.isSSL,
|
||||||
isLightWallet: false, // FIXME: hardcoded value
|
isLightWallet: false,
|
||||||
|
// FIXME: hardcoded value
|
||||||
socksProxyAddress: node.socksProxyAddress);
|
socksProxyAddress: node.socksProxyAddress);
|
||||||
|
|
||||||
monero_wallet.setTrustedDaemon(node.trusted);
|
monero_wallet.setTrustedDaemon(node.trusted);
|
||||||
|
@ -189,7 +189,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
final outputs = _credentials.outputs;
|
final outputs = _credentials.outputs;
|
||||||
final hasMultiDestination = outputs.length > 1;
|
final hasMultiDestination = outputs.length > 1;
|
||||||
final unlockedBalance =
|
final unlockedBalance =
|
||||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||||
var allInputsAmount = 0;
|
var allInputsAmount = 0;
|
||||||
|
|
||||||
PendingTransactionDescription pendingTransactionDescription;
|
PendingTransactionDescription pendingTransactionDescription;
|
||||||
|
@ -208,56 +208,42 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
inputs.add(utx.keyImage);
|
inputs.add(utx.keyImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final spendAllCoins = inputs.length == unspentCoins.length;
|
||||||
if (inputs.isEmpty) {
|
|
||||||
throw MoneroTransactionNoInputsException(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
if (hasMultiDestination) {
|
||||||
if (outputs.any((item) => item.sendAll
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|| (item.formattedCryptoAmount ?? 0) <= 0)) {
|
|
||||||
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final int totalAmount = outputs.fold(0, (acc, value) =>
|
final int totalAmount =
|
||||||
acc + (value.formattedCryptoAmount ?? 0));
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||||
|
|
||||||
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
|
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
|
||||||
if (unlockedBalance < totalAmount) {
|
if (unlockedBalance < totalAmount) {
|
||||||
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allInputsAmount < totalAmount + estimatedFee) {
|
if (!spendAllCoins && (allInputsAmount < totalAmount + estimatedFee)) {
|
||||||
throw MoneroTransactionNoInputsException(inputs.length);
|
throw MoneroTransactionNoInputsException(inputs.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
final moneroOutputs = outputs.map((output) {
|
final moneroOutputs = outputs.map((output) {
|
||||||
final outputAddress = output.isParsedAddress
|
final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||||
? output.extractedAddress
|
|
||||||
: output.address;
|
|
||||||
|
|
||||||
return MoneroOutput(
|
return MoneroOutput(
|
||||||
address: outputAddress!,
|
address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.'));
|
||||||
amount: output.cryptoAmount!.replaceAll(',', '.'));
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
pendingTransactionDescription =
|
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
|
||||||
await transaction_history.createTransactionMultDest(
|
|
||||||
outputs: moneroOutputs,
|
outputs: moneroOutputs,
|
||||||
priorityRaw: _credentials.priority.serialize(),
|
priorityRaw: _credentials.priority.serialize(),
|
||||||
accountIndex: walletAddresses.account!.id,
|
accountIndex: walletAddresses.account!.id,
|
||||||
preferredInputs: inputs);
|
preferredInputs: inputs);
|
||||||
} else {
|
} else {
|
||||||
final output = outputs.first;
|
final output = outputs.first;
|
||||||
final address = output.isParsedAddress
|
final address = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||||
? output.extractedAddress
|
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
|
||||||
: output.address;
|
final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
|
||||||
final amount = output.sendAll
|
|
||||||
? null
|
|
||||||
: output.cryptoAmount!.replaceAll(',', '.');
|
|
||||||
final formattedAmount = output.sendAll
|
|
||||||
? null
|
|
||||||
: output.formattedCryptoAmount;
|
|
||||||
|
|
||||||
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
||||||
(formattedAmount == null && unlockedBalance <= 0)) {
|
(formattedAmount == null && unlockedBalance <= 0)) {
|
||||||
|
@ -268,8 +254,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
|
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
|
||||||
if ((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
|
if (!spendAllCoins &&
|
||||||
(formattedAmount == null && allInputsAmount != unlockedBalance)) {
|
((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
|
||||||
|
formattedAmount == null)) {
|
||||||
throw MoneroTransactionNoInputsException(inputs.length);
|
throw MoneroTransactionNoInputsException(inputs.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,10 +314,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// -- rename the waller folder --
|
// -- rename the waller folder --
|
||||||
final currentWalletDir =
|
final currentWalletDir = Directory(await pathForWalletDir(name: name, type: type));
|
||||||
Directory(await pathForWalletDir(name: name, type: type));
|
final newWalletDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||||
final newWalletDirPath =
|
|
||||||
await pathForWalletDir(name: newWalletName, type: type);
|
|
||||||
await currentWalletDir.rename(newWalletDirPath);
|
await currentWalletDir.rename(newWalletDirPath);
|
||||||
|
|
||||||
// -- use new waller folder to rename files with old names still --
|
// -- use new waller folder to rename files with old names still --
|
||||||
|
@ -340,8 +325,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
final currentKeysFile = File('$renamedWalletPath.keys');
|
final currentKeysFile = File('$renamedWalletPath.keys');
|
||||||
final currentAddressListFile = File('$renamedWalletPath.address.txt');
|
final currentAddressListFile = File('$renamedWalletPath.address.txt');
|
||||||
|
|
||||||
final newWalletPath =
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
await pathForWallet(name: newWalletName, type: type);
|
|
||||||
|
|
||||||
if (currentCacheFile.existsSync()) {
|
if (currentCacheFile.existsSync()) {
|
||||||
await currentCacheFile.rename(newWalletPath);
|
await currentCacheFile.rename(newWalletPath);
|
||||||
|
@ -359,8 +343,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
final currentKeysFile = File('$currentWalletPath.keys');
|
final currentKeysFile = File('$currentWalletPath.keys');
|
||||||
final currentAddressListFile = File('$currentWalletPath.address.txt');
|
final currentAddressListFile = File('$currentWalletPath.address.txt');
|
||||||
|
|
||||||
final newWalletPath =
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
await pathForWallet(name: newWalletName, type: type);
|
|
||||||
|
|
||||||
// Copies current wallet files into new wallet name's dir and files
|
// Copies current wallet files into new wallet name's dir and files
|
||||||
if (currentCacheFile.existsSync()) {
|
if (currentCacheFile.existsSync()) {
|
||||||
|
@ -426,8 +409,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
if (unspentCoins.isNotEmpty) {
|
if (unspentCoins.isNotEmpty) {
|
||||||
unspentCoins.forEach((coin) {
|
unspentCoins.forEach((coin) {
|
||||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
final coinInfoList = unspentCoinsInfo.values
|
||||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
if (coinInfoList.isNotEmpty) {
|
||||||
final coinInfo = coinInfoList.first;
|
final coinInfo = coinInfoList.first;
|
||||||
|
@ -447,16 +430,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
Future<void> _addCoinInfo(MoneroUnspent coin) async {
|
Future<void> _addCoinInfo(MoneroUnspent coin) async {
|
||||||
final newInfo = UnspentCoinsInfo(
|
final newInfo = UnspentCoinsInfo(
|
||||||
walletId: id,
|
walletId: id,
|
||||||
hash: coin.hash,
|
hash: coin.hash,
|
||||||
isFrozen: coin.isFrozen,
|
isFrozen: coin.isFrozen,
|
||||||
isSending: coin.isSending,
|
isSending: coin.isSending,
|
||||||
noteRaw: coin.note,
|
noteRaw: coin.note,
|
||||||
address: coin.address,
|
address: coin.address,
|
||||||
value: coin.value,
|
value: coin.value,
|
||||||
vout: 0,
|
vout: 0,
|
||||||
keyImage: coin.keyImage
|
keyImage: coin.keyImage);
|
||||||
);
|
|
||||||
|
|
||||||
await unspentCoinsInfo.add(newInfo);
|
await unspentCoinsInfo.add(newInfo);
|
||||||
}
|
}
|
||||||
|
@ -464,8 +446,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
Future<void> _refreshUnspentCoinsInfo() async {
|
Future<void> _refreshUnspentCoinsInfo() async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> keys = <dynamic>[];
|
final List<dynamic> keys = <dynamic>[];
|
||||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
final currentWalletUnspentCoins =
|
||||||
.where((element) => element.walletId.contains(id));
|
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
|
||||||
|
|
||||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||||
currentWalletUnspentCoins.forEach((element) {
|
currentWalletUnspentCoins.forEach((element) {
|
||||||
|
@ -486,16 +468,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||||
monero_wallet.getAddress(
|
monero_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
accountIndex: accountIndex,
|
|
||||||
addressIndex: addressIndex);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||||
transaction_history.refreshTransactions();
|
transaction_history.refreshTransactions();
|
||||||
return _getAllTransactionsOfAccount(walletAddresses.account?.id).fold<Map<String, MoneroTransactionInfo>>(
|
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
|
||||||
<String, MoneroTransactionInfo>{},
|
.fold<Map<String, MoneroTransactionInfo>>(<String, MoneroTransactionInfo>{},
|
||||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||||
acc[tx.id] = tx;
|
acc[tx.id] = tx;
|
||||||
return acc;
|
return acc;
|
||||||
});
|
});
|
||||||
|
@ -523,12 +503,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
return monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
|
return monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
|
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
|
||||||
transaction_history
|
.getAllTransactions()
|
||||||
.getAllTransactions()
|
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||||
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
.toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
void _setListeners() {
|
void _setListeners() {
|
||||||
_listener?.stop();
|
_listener?.stop();
|
||||||
|
@ -550,8 +529,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
int _getHeightDistance(DateTime date) {
|
int _getHeightDistance(DateTime date) {
|
||||||
final distance =
|
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||||
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
|
||||||
final daysTmp = (distance / 86400).round();
|
final daysTmp = (distance / 86400).round();
|
||||||
final days = daysTmp < 1 ? 1 : daysTmp;
|
final days = daysTmp < 1 ? 1 : daysTmp;
|
||||||
|
|
||||||
|
@ -582,11 +560,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _askForUpdateTransactionHistory() async =>
|
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||||
await updateTransactions();
|
|
||||||
|
|
||||||
int _getFullBalance() =>
|
int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
|
||||||
monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
|
|
||||||
|
|
||||||
int _getUnlockedBalance() =>
|
int _getUnlockedBalance() =>
|
||||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||||
|
@ -595,8 +571,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
var frozenBalance = 0;
|
var frozenBalance = 0;
|
||||||
|
|
||||||
for (var coin in unspentCoinsInfo.values) {
|
for (var coin in unspentCoinsInfo.values) {
|
||||||
if (coin.isFrozen)
|
if (coin.isFrozen) frozenBalance += coin.value;
|
||||||
frozenBalance += coin.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frozenBalance;
|
return frozenBalance;
|
||||||
|
@ -617,9 +592,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
if (!_hasSyncAfterStartup) {
|
if (!_hasSyncAfterStartup) {
|
||||||
_hasSyncAfterStartup = true;
|
_hasSyncAfterStartup = true;
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (walletInfo.isRecovery) {
|
if (walletInfo.isRecovery) {
|
||||||
await setAsRecovered();
|
await setAsRecovered();
|
||||||
|
@ -644,12 +619,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
|
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
|
||||||
if (enableAutoGenerate) {
|
if (enableAutoGenerate) {
|
||||||
walletAddresses.updateUnusedSubaddress(
|
walletAddresses.updateUnusedSubaddress(
|
||||||
accountIndex: account?.id ?? 0,
|
accountIndex: account?.id ?? 0,
|
||||||
defaultLabel: account?.label ?? '',
|
defaultLabel: account?.label ?? '',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@ Future<void> setup({
|
||||||
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
|
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
|
||||||
onAuthenticationFinished: onAuthFinished, closable: closable));
|
onAuthenticationFinished: onAuthFinished, closable: closable));
|
||||||
|
|
||||||
getIt.registerFactory<Setup2FAViewModel>(
|
getIt.registerLazySingleton<Setup2FAViewModel>(
|
||||||
() => Setup2FAViewModel(
|
() => Setup2FAViewModel(
|
||||||
getIt.get<SettingsStore>(),
|
getIt.get<SettingsStore>(),
|
||||||
getIt.get<SharedPreferences>(),
|
getIt.get<SharedPreferences>(),
|
||||||
|
|
|
@ -19,7 +19,6 @@ class PreferencesKey {
|
||||||
'allow_biometrical_authentication';
|
'allow_biometrical_authentication';
|
||||||
static const useTOTP2FA = 'use_totp_2fa';
|
static const useTOTP2FA = 'use_totp_2fa';
|
||||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||||
static const totpSecretKey = 'totp_qr_secret_key';
|
|
||||||
static const disableExchangeKey = 'disable_exchange';
|
static const disableExchangeKey = 'disable_exchange';
|
||||||
static const exchangeStatusKey = 'exchange_status';
|
static const exchangeStatusKey = 'exchange_status';
|
||||||
static const currentTheme = 'current_theme';
|
static const currentTheme = 'current_theme';
|
||||||
|
@ -75,4 +74,5 @@ class PreferencesKey {
|
||||||
static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||||
'should_require_totp_2fa_for_all_security_and_backup_settings';
|
'should_require_totp_2fa_for_all_security_and_backup_settings';
|
||||||
static const selectedCake2FAPreset = 'selected_cake_2fa_preset';
|
static const selectedCake2FAPreset = 'selected_cake_2fa_preset';
|
||||||
|
static const totpSecretKey = 'totp_secret_key';
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||||
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 all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: '');
|
static const exolix =
|
||||||
|
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
|
||||||
|
|
||||||
|
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
|
||||||
|
|
||||||
static ExchangeProviderDescription deserialize({required int raw}) {
|
static ExchangeProviderDescription deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
|
@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
||||||
case 5:
|
case 5:
|
||||||
return trocador;
|
return trocador;
|
||||||
case 6:
|
case 6:
|
||||||
|
return exolix;
|
||||||
|
case 7:
|
||||||
return all;
|
return all;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
|
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
|
||||||
|
|
294
lib/exchange/exolix/exolix_exchange_provider.dart
Normal file
294
lib/exchange/exolix/exolix_exchange_provider.dart
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exchange_pair.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
||||||
|
import 'package:cake_wallet/exchange/limits.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/exolix/exolix_request.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
|
|
||||||
|
class ExolixExchangeProvider extends ExchangeProvider {
|
||||||
|
ExolixExchangeProvider() : super(pairList: _supportedPairs());
|
||||||
|
|
||||||
|
static final apiKey = secrets.exolixApiKey;
|
||||||
|
static const apiBaseUrl = 'exolix.com';
|
||||||
|
static const transactionsPath = '/api/v2/transactions';
|
||||||
|
static const ratePath = '/api/v2/rate';
|
||||||
|
|
||||||
|
static const List<CryptoCurrency> _notSupported = [
|
||||||
|
CryptoCurrency.usdt,
|
||||||
|
CryptoCurrency.xhv,
|
||||||
|
CryptoCurrency.btt,
|
||||||
|
CryptoCurrency.firo,
|
||||||
|
CryptoCurrency.zaddr,
|
||||||
|
CryptoCurrency.xvg,
|
||||||
|
CryptoCurrency.kmd,
|
||||||
|
CryptoCurrency.paxg,
|
||||||
|
CryptoCurrency.rune,
|
||||||
|
CryptoCurrency.scrt,
|
||||||
|
CryptoCurrency.btcln,
|
||||||
|
CryptoCurrency.cro,
|
||||||
|
CryptoCurrency.ftm,
|
||||||
|
CryptoCurrency.frax,
|
||||||
|
CryptoCurrency.gusd,
|
||||||
|
CryptoCurrency.gtc,
|
||||||
|
CryptoCurrency.weth,
|
||||||
|
];
|
||||||
|
|
||||||
|
static List<ExchangePair> _supportedPairs() {
|
||||||
|
final supportedCurrencies =
|
||||||
|
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
|
||||||
|
|
||||||
|
return supportedCurrencies
|
||||||
|
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
|
||||||
|
.expand((i) => i)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Exolix';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isAvailable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get supportsFixedRate => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ExchangeProviderDescription get description => ExchangeProviderDescription.exolix;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> checkIsAvailable() async => true;
|
||||||
|
|
||||||
|
static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Limits> fetchLimits(
|
||||||
|
{required CryptoCurrency from,
|
||||||
|
required CryptoCurrency to,
|
||||||
|
required bool isFixedRateMode}) async {
|
||||||
|
final params = <String, String>{
|
||||||
|
'rateType': getRateType(isFixedRateMode),
|
||||||
|
'amount': '1',
|
||||||
|
};
|
||||||
|
if (isFixedRateMode) {
|
||||||
|
params['coinFrom'] = _normalizeCurrency(to);
|
||||||
|
params['coinTo'] = _normalizeCurrency(from);
|
||||||
|
params['networkFrom'] = _networkFor(to);
|
||||||
|
params['networkTo'] = _networkFor(from);
|
||||||
|
} else {
|
||||||
|
params['coinFrom'] = _normalizeCurrency(from);
|
||||||
|
params['coinTo'] = _normalizeCurrency(to);
|
||||||
|
params['networkFrom'] = _networkFor(from);
|
||||||
|
params['networkTo'] = _networkFor(to);
|
||||||
|
}
|
||||||
|
final uri = Uri.https(apiBaseUrl, ratePath, params);
|
||||||
|
final response = await get(uri);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
return Limits(min: responseJSON['minAmount'] as double?);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||||
|
final _request = request as ExolixRequest;
|
||||||
|
|
||||||
|
final headers = {'Content-Type': 'application/json'};
|
||||||
|
final body = <String, dynamic>{
|
||||||
|
'coinFrom': _normalizeCurrency(_request.from),
|
||||||
|
'coinTo': _normalizeCurrency(_request.to),
|
||||||
|
'networkFrom': _networkFor(_request.from),
|
||||||
|
'networkTo': _networkFor(_request.to),
|
||||||
|
'withdrawalAddress': _request.address,
|
||||||
|
'refundAddress': _request.refundAddress,
|
||||||
|
'rateType': getRateType(isFixedRateMode),
|
||||||
|
'apiToken': apiKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFixedRateMode) {
|
||||||
|
body['withdrawalAmount'] = _request.toAmount;
|
||||||
|
} else {
|
||||||
|
body['amount'] = _request.fromAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.https(apiBaseUrl, transactionsPath);
|
||||||
|
final response = await post(uri, headers: headers, body: json.encode(body));
|
||||||
|
|
||||||
|
if (response.statusCode == 400) {
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
final errors = responseJSON['errors'] as Map<String, String>;
|
||||||
|
final errorMessage = errors.values.join(', ');
|
||||||
|
throw Exception(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
final id = responseJSON['id'] as String;
|
||||||
|
final inputAddress = responseJSON['depositAddress'] as String;
|
||||||
|
final refundAddress = responseJSON['refundAddress'] as String?;
|
||||||
|
final extraId = responseJSON['depositExtraId'] as String?;
|
||||||
|
final payoutAddress = responseJSON['withdrawalAddress'] as String;
|
||||||
|
final amount = responseJSON['amount'].toString();
|
||||||
|
|
||||||
|
return Trade(
|
||||||
|
id: id,
|
||||||
|
from: _request.from,
|
||||||
|
to: _request.to,
|
||||||
|
provider: description,
|
||||||
|
inputAddress: inputAddress,
|
||||||
|
refundAddress: refundAddress,
|
||||||
|
extraId: extraId,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
amount: amount,
|
||||||
|
state: TradeState.created,
|
||||||
|
payoutAddress: payoutAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Trade> findTradeById({required String id}) async {
|
||||||
|
final findTradeByIdPath = transactionsPath + '/$id';
|
||||||
|
final uri = Uri.https(apiBaseUrl, findTradeByIdPath);
|
||||||
|
final response = await get(uri);
|
||||||
|
|
||||||
|
if (response.statusCode == 404) {
|
||||||
|
throw TradeNotFoundException(id, provider: description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode == 400) {
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
final errors = responseJSON['errors'] as Map<String, String>;
|
||||||
|
final errorMessage = errors.values.join(', ');
|
||||||
|
|
||||||
|
throw TradeNotFoundException(id, provider: description, description: errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
|
||||||
|
final from = CryptoCurrency.fromString(coinFrom);
|
||||||
|
final coinTo = responseJSON['coinTo']['coinCode'] as String;
|
||||||
|
final to = CryptoCurrency.fromString(coinTo);
|
||||||
|
final inputAddress = responseJSON['depositAddress'] as String;
|
||||||
|
final amount = responseJSON['amount'].toString();
|
||||||
|
final status = responseJSON['status'] as String;
|
||||||
|
final state = TradeState.deserialize(raw: _prepareStatus(status));
|
||||||
|
final extraId = responseJSON['depositExtraId'] as String?;
|
||||||
|
final outputTransaction = responseJSON['hashOut']['hash'] as String?;
|
||||||
|
final payoutAddress = responseJSON['withdrawalAddress'] as String;
|
||||||
|
|
||||||
|
return Trade(
|
||||||
|
id: id,
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
provider: description,
|
||||||
|
inputAddress: inputAddress,
|
||||||
|
amount: amount,
|
||||||
|
state: state,
|
||||||
|
extraId: extraId,
|
||||||
|
outputTransaction: outputTransaction,
|
||||||
|
payoutAddress: payoutAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = <String, String>{
|
||||||
|
'coinFrom': _normalizeCurrency(from),
|
||||||
|
'coinTo': _normalizeCurrency(to),
|
||||||
|
'networkFrom': _networkFor(from),
|
||||||
|
'networkTo': _networkFor(to),
|
||||||
|
'rateType': getRateType(isFixedRateMode),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isReceiveAmount) {
|
||||||
|
params['withdrawalAmount'] = amount.toString();
|
||||||
|
} else {
|
||||||
|
params['amount'] = amount.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.https(apiBaseUrl, ratePath, params);
|
||||||
|
final response = await get(uri);
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
final message = responseJSON['message'] as String?;
|
||||||
|
throw Exception(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final rate = responseJSON['rate'] as double;
|
||||||
|
|
||||||
|
return rate;
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _prepareStatus(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'deleted':
|
||||||
|
case 'error':
|
||||||
|
return 'overdue';
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _networkFor(CryptoCurrency currency) {
|
||||||
|
switch (currency) {
|
||||||
|
case CryptoCurrency.arb:
|
||||||
|
return 'ARBITRUM';
|
||||||
|
default:
|
||||||
|
return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _normalizeCurrency(CryptoCurrency currency) {
|
||||||
|
switch (currency) {
|
||||||
|
case CryptoCurrency.nano:
|
||||||
|
return 'XNO';
|
||||||
|
case CryptoCurrency.bttc:
|
||||||
|
return 'BTT';
|
||||||
|
case CryptoCurrency.zec:
|
||||||
|
return 'ZEC';
|
||||||
|
default:
|
||||||
|
return currency.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _normalizeTag(String tag) {
|
||||||
|
switch (tag) {
|
||||||
|
case 'POLY':
|
||||||
|
return 'Polygon';
|
||||||
|
default:
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
lib/exchange/exolix/exolix_request.dart
Normal file
20
lib/exchange/exolix/exolix_request.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cake_wallet/exchange/trade_request.dart';
|
||||||
|
|
||||||
|
class ExolixRequest extends TradeRequest {
|
||||||
|
ExolixRequest(
|
||||||
|
{required this.from,
|
||||||
|
required this.to,
|
||||||
|
required this.address,
|
||||||
|
required this.fromAmount,
|
||||||
|
required this.toAmount,
|
||||||
|
required this.refundAddress});
|
||||||
|
|
||||||
|
CryptoCurrency from;
|
||||||
|
CryptoCurrency to;
|
||||||
|
String address;
|
||||||
|
String fromAmount;
|
||||||
|
String toAmount;
|
||||||
|
String refundAddress;
|
||||||
|
}
|
|
@ -35,6 +35,15 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||||
static const completed = TradeState(raw: 'completed', title: 'Completed');
|
static const completed = TradeState(raw: 'completed', title: 'Completed');
|
||||||
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
|
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
|
||||||
static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
|
static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
|
||||||
|
static const wait = TradeState(raw: 'wait', title: 'Waiting');
|
||||||
|
static const overdue = TradeState(raw: 'overdue', title: 'Overdue');
|
||||||
|
static const refund = TradeState(raw: 'refund', title: 'Refund');
|
||||||
|
static const refunded = TradeState(raw: 'refunded', title: 'Refunded');
|
||||||
|
static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation');
|
||||||
|
static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed');
|
||||||
|
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
|
||||||
|
static const sending = TradeState(raw: 'sending', title: 'Sending');
|
||||||
|
static const success = TradeState(raw: 'success', title: 'Success');
|
||||||
static TradeState deserialize({required String raw}) {
|
static TradeState deserialize({required String raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
|
@ -77,6 +86,24 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||||
return failed;
|
return failed;
|
||||||
case 'completed':
|
case 'completed':
|
||||||
return completed;
|
return completed;
|
||||||
|
case 'wait':
|
||||||
|
return wait;
|
||||||
|
case 'overdue':
|
||||||
|
return overdue;
|
||||||
|
case 'refund':
|
||||||
|
return refund;
|
||||||
|
case 'refunded':
|
||||||
|
return refunded;
|
||||||
|
case 'confirmation':
|
||||||
|
return confirmation;
|
||||||
|
case 'confirmed':
|
||||||
|
return confirmed;
|
||||||
|
case 'exchanging':
|
||||||
|
return exchanging;
|
||||||
|
case 'sending':
|
||||||
|
return sending;
|
||||||
|
case 'success':
|
||||||
|
return success;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw in TradeState deserialize');
|
throw Exception('Unexpected token: $raw in TradeState deserialize');
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
|
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.exolix:
|
||||||
|
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
image = null;
|
image = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,10 @@ class Setup2FAPage extends BasePage {
|
||||||
SizedBox(height: 86),
|
SizedBox(height: 86),
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.setup_totp_recommended,
|
title: S.current.setup_totp_recommended,
|
||||||
handler: (_) => Navigator.of(context)
|
handler: (_) {
|
||||||
.pushReplacementNamed(Routes.setup_2faQRPage),
|
setup2FAViewModel.generateSecretKey();
|
||||||
|
return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
],
|
],
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Setup2FAQRPage extends BasePage {
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'${setup2FAViewModel.secretKey}',
|
'${setup2FAViewModel.totpSecretKey}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
|
@ -108,7 +108,7 @@ class Setup2FAQRPage extends BasePage {
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ClipboardUtil.setSensitiveDataToClipboard(
|
ClipboardUtil.setSensitiveDataToClipboard(
|
||||||
ClipboardData(text: '${setup2FAViewModel.secretKey}'));
|
ClipboardData(text: '${setup2FAViewModel.totpSecretKey}'));
|
||||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
|
@ -51,10 +51,11 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Observer(builder: (_) {
|
return Observer(builder: (_) {
|
||||||
// FIX-ME: Added `context` it was not used here before, maby bug ?
|
int itemsCount = tradeDetailsViewModel.items.length;
|
||||||
|
|
||||||
return SectionStandardList(
|
return SectionStandardList(
|
||||||
sectionCount: 1,
|
sectionCount: 1,
|
||||||
itemCounter: (int _) => tradeDetailsViewModel.items.length,
|
itemCounter: (int _) => itemsCount,
|
||||||
itemBuilder: (__, index) {
|
itemBuilder: (__, index) {
|
||||||
final item = tradeDetailsViewModel.items[index];
|
final item = tradeDetailsViewModel.items[index];
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displaySideShift = true,
|
displaySideShift = true,
|
||||||
displayMorphToken = true,
|
displayMorphToken = true,
|
||||||
displaySimpleSwap = true,
|
displaySimpleSwap = true,
|
||||||
displayTrocador = true;
|
displayTrocador = true,
|
||||||
|
displayExolix = true;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool displayXMRTO;
|
bool displayXMRTO;
|
||||||
|
@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool displayTrocador;
|
bool displayTrocador;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool displayExolix;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador;
|
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void toggleDisplayExchange(ExchangeProviderDescription provider) {
|
void toggleDisplayExchange(ExchangeProviderDescription provider) {
|
||||||
|
@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
break;
|
break;
|
||||||
case ExchangeProviderDescription.trocador:
|
case ExchangeProviderDescription.trocador:
|
||||||
displayTrocador = !displayTrocador;
|
displayTrocador = !displayTrocador;
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.exolix:
|
||||||
|
displayExolix = !displayExolix;
|
||||||
|
break;
|
||||||
case ExchangeProviderDescription.all:
|
case ExchangeProviderDescription.all:
|
||||||
if (displayAllTrades) {
|
if (displayAllTrades) {
|
||||||
displayChangeNow = false;
|
displayChangeNow = false;
|
||||||
|
@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displayMorphToken = false;
|
displayMorphToken = false;
|
||||||
displaySimpleSwap = false;
|
displaySimpleSwap = false;
|
||||||
displayTrocador = false;
|
displayTrocador = false;
|
||||||
|
displayExolix = false;
|
||||||
} else {
|
} else {
|
||||||
displayChangeNow = true;
|
displayChangeNow = true;
|
||||||
displaySideShift = true;
|
displaySideShift = true;
|
||||||
|
@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
displayMorphToken = true;
|
displayMorphToken = true;
|
||||||
displaySimpleSwap = true;
|
displaySimpleSwap = true;
|
||||||
displayTrocador = true;
|
displayTrocador = true;
|
||||||
|
displayExolix = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store {
|
||||||
||(displaySimpleSwap &&
|
||(displaySimpleSwap &&
|
||||||
item.trade.provider ==
|
item.trade.provider ==
|
||||||
ExchangeProviderDescription.simpleSwap)
|
ExchangeProviderDescription.simpleSwap)
|
||||||
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador))
|
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)
|
||||||
|
||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
|
||||||
.toList()
|
.toList()
|
||||||
: _trades;
|
: _trades;
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,14 +280,13 @@ abstract class SettingsStoreBase with Store {
|
||||||
reaction(
|
reaction(
|
||||||
(_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
|
(_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
|
||||||
|
|
||||||
|
reaction((_) => totpSecretKey,
|
||||||
|
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
|
||||||
reaction(
|
reaction(
|
||||||
(_) => numberOfFailedTokenTrials,
|
(_) => numberOfFailedTokenTrials,
|
||||||
(int failedTokenTrail) =>
|
(int failedTokenTrail) =>
|
||||||
sharedPreferences.setInt(PreferencesKey.failedTotpTokenTrials, failedTokenTrail));
|
sharedPreferences.setInt(PreferencesKey.failedTotpTokenTrials, failedTokenTrail));
|
||||||
|
|
||||||
reaction((_) => totpSecretKey,
|
|
||||||
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
|
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => shouldShowMarketPlaceInDashboard,
|
(_) => shouldShowMarketPlaceInDashboard,
|
||||||
(bool value) =>
|
(bool value) =>
|
||||||
|
@ -422,15 +421,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
bool shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
bool shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String totpSecretKey;
|
bool useTOTP2FA;
|
||||||
|
|
||||||
@computed
|
|
||||||
String get totpVersionOneLink {
|
|
||||||
return 'otpauth://totp/Cake%20Wallet:$deviceName?secret=$totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30';
|
|
||||||
}
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool useTOTP2FA;
|
String totpSecretKey;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
int numberOfFailedTokenTrials;
|
int numberOfFailedTokenTrials;
|
||||||
|
@ -575,8 +569,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
|
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
|
||||||
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
|
||||||
false;
|
false;
|
||||||
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
|
||||||
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
|
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
|
||||||
|
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
||||||
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
||||||
final shouldShowMarketPlaceInDashboard =
|
final shouldShowMarketPlaceInDashboard =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
|
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
|
||||||
|
@ -677,8 +671,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialFiatMode: currentFiatApiMode,
|
initialFiatMode: currentFiatApiMode,
|
||||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||||
initialTotpSecretKey: totpSecretKey,
|
|
||||||
initialUseTOTP2FA: useTOTP2FA,
|
initialUseTOTP2FA: useTOTP2FA,
|
||||||
|
initialTotpSecretKey: totpSecretKey,
|
||||||
initialFailedTokenTrial: tokenTrialNumber,
|
initialFailedTokenTrial: tokenTrialNumber,
|
||||||
initialExchangeStatus: exchangeStatus,
|
initialExchangeStatus: exchangeStatus,
|
||||||
initialTheme: savedTheme,
|
initialTheme: savedTheme,
|
||||||
|
@ -752,9 +746,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
shouldSaveRecipientAddress =
|
shouldSaveRecipientAddress =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||||
shouldSaveRecipientAddress;
|
shouldSaveRecipientAddress;
|
||||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
|
||||||
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
||||||
|
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||||
numberOfFailedTokenTrials =
|
numberOfFailedTokenTrials =
|
||||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||||
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
||||||
|
|
|
@ -161,6 +161,7 @@ class ExceptionHandler {
|
||||||
"Handshake error in client",
|
"Handshake error in client",
|
||||||
"Error while launching http",
|
"Error while launching http",
|
||||||
"OS Error: Network is unreachable",
|
"OS Error: Network is unreachable",
|
||||||
|
"ClientException: Write failed, uri=https:",
|
||||||
];
|
];
|
||||||
|
|
||||||
static Future<void> _addDeviceInfo(File file) async {
|
static Future<void> _addDeviceInfo(File file) async {
|
||||||
|
|
|
@ -99,6 +99,11 @@ abstract class DashboardViewModelBase with Store {
|
||||||
caption: ExchangeProviderDescription.trocador.title,
|
caption: ExchangeProviderDescription.trocador.title,
|
||||||
onChanged: () => tradeFilterStore
|
onChanged: () => tradeFilterStore
|
||||||
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||||
|
FilterItem(
|
||||||
|
value: () => tradeFilterStore.displayExolix,
|
||||||
|
caption: ExchangeProviderDescription.exolix.title,
|
||||||
|
onChanged: () => tradeFilterStore
|
||||||
|
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
subname = '',
|
subname = '',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
|
||||||
|
@ -53,6 +54,9 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||||
case ExchangeProviderDescription.trocador:
|
case ExchangeProviderDescription.trocador:
|
||||||
_provider = TrocadorExchangeProvider();
|
_provider = TrocadorExchangeProvider();
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.exolix:
|
||||||
|
_provider = ExolixExchangeProvider();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateItems();
|
_updateItems();
|
||||||
|
|
|
@ -6,6 +6,8 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||||
|
@ -151,6 +153,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
SideShiftExchangeProvider(),
|
SideShiftExchangeProvider(),
|
||||||
SimpleSwapExchangeProvider(),
|
SimpleSwapExchangeProvider(),
|
||||||
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
|
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
|
||||||
|
ExolixExchangeProvider(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -547,6 +550,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
amount = isFixedRateMode ? receiveAmount : depositAmount;
|
amount = isFixedRateMode ? receiveAmount : depositAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider is ExolixExchangeProvider) {
|
||||||
|
request = ExolixRequest(
|
||||||
|
from: depositCurrency,
|
||||||
|
to: receiveCurrency,
|
||||||
|
fromAmount: depositAmount.replaceAll(',', '.'),
|
||||||
|
toAmount: receiveAmount.replaceAll(',', '.'),
|
||||||
|
refundAddress: depositAddress,
|
||||||
|
address: receiveAddress);
|
||||||
|
amount = isFixedRateMode ? receiveAmount : depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
amount = amount.replaceAll(',', '.');
|
amount = amount.replaceAll(',', '.');
|
||||||
|
|
||||||
if (limitsState is LimitsLoadedSuccessfully) {
|
if (limitsState is LimitsLoadedSuccessfully) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
unhighlightTabs = false,
|
unhighlightTabs = false,
|
||||||
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
||||||
state = InitialExecutionState() {
|
state = InitialExecutionState() {
|
||||||
_getRandomBase32SecretKey();
|
|
||||||
selectCakePreset(selectedCake2FAPreset);
|
selectCakePreset(selectedCake2FAPreset);
|
||||||
reaction((_) => state, _saveLastAuthTime);
|
reaction((_) => state, _saveLastAuthTime);
|
||||||
}
|
}
|
||||||
|
@ -36,9 +35,12 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
static const banTimeout = 180; // 3 minutes
|
static const banTimeout = 180; // 3 minutes
|
||||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||||
|
|
||||||
String get secretKey => _settingsStore.totpSecretKey;
|
|
||||||
String get deviceName => _settingsStore.deviceName;
|
String get deviceName => _settingsStore.deviceName;
|
||||||
String get totpVersionOneLink => _settingsStore.totpVersionOneLink;
|
|
||||||
|
@computed
|
||||||
|
String get totpSecretKey => _settingsStore.totpSecretKey;
|
||||||
|
|
||||||
|
String totpVersionOneLink = '';
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ExecutionState state;
|
ExecutionState state;
|
||||||
|
@ -84,9 +86,14 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings =>
|
bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings =>
|
||||||
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||||
|
|
||||||
void _getRandomBase32SecretKey() {
|
@action
|
||||||
final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
|
void generateSecretKey() {
|
||||||
_setBase32SecretKey(randomBase32Key);
|
final _totpSecretKey = Utils.generateRandomBase32SecretKey(16);
|
||||||
|
|
||||||
|
totpVersionOneLink =
|
||||||
|
'otpauth://totp/Cake%20Wallet:$deviceName?secret=$_totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30';
|
||||||
|
|
||||||
|
setTOTPSecretKey(_totpSecretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -95,15 +102,10 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _setBase32SecretKey(String value) {
|
void setTOTPSecretKey(String value) {
|
||||||
_settingsStore.totpSecretKey = value;
|
_settingsStore.totpSecretKey = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
void clearBase32SecretKey() {
|
|
||||||
_settingsStore.totpSecretKey = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration? banDuration() {
|
Duration? banDuration() {
|
||||||
final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
|
final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
|
||||||
|
|
||||||
|
@ -145,7 +147,7 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = Utils.verify(
|
final result = Utils.verify(
|
||||||
secretKey: secretKey,
|
secretKey: totpSecretKey,
|
||||||
otp: otpText,
|
otp: otpText,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -156,7 +158,6 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
} else {
|
} else {
|
||||||
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
||||||
adjustTokenTrialNumber(value);
|
adjustTokenTrialNumber(value);
|
||||||
print(value);
|
|
||||||
if (_failureCounter >= maxFailedTrials) {
|
if (_failureCounter >= maxFailedTrials) {
|
||||||
final banDuration = await ban();
|
final banDuration = await ban();
|
||||||
state = AuthenticationBanned(
|
state = AuthenticationBanned(
|
||||||
|
|
|
@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store {
|
||||||
icon: 'assets/images/simpleSwap.png',
|
icon: 'assets/images/simpleSwap.png',
|
||||||
linkTitle: 'support@simpleswap.io',
|
linkTitle: 'support@simpleswap.io',
|
||||||
link: 'mailto:support@simpleswap.io'),
|
link: 'mailto:support@simpleswap.io'),
|
||||||
|
LinkListItem(
|
||||||
|
title: 'Exolix',
|
||||||
|
icon: 'assets/images/exolix.png',
|
||||||
|
linkTitle: 'support@exolix.com',
|
||||||
|
link: 'mailto:support@exolix.com'),
|
||||||
if (!isMoneroOnly) ... [
|
if (!isMoneroOnly) ... [
|
||||||
LinkListItem(
|
LinkListItem(
|
||||||
title: 'Wyre',
|
title: 'Wyre',
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
|
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||||
|
@ -54,6 +55,9 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||||
case ExchangeProviderDescription.trocador:
|
case ExchangeProviderDescription.trocador:
|
||||||
_provider = TrocadorExchangeProvider();
|
_provider = TrocadorExchangeProvider();
|
||||||
break;
|
break;
|
||||||
|
case ExchangeProviderDescription.exolix:
|
||||||
|
_provider = ExolixExchangeProvider();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateItems();
|
_updateItems();
|
||||||
|
@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||||
items.add(StandartListItem(
|
items.add(StandartListItem(
|
||||||
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
|
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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _launchUrl(String url) {
|
void _launchUrl(String url) {
|
||||||
|
|
|
@ -32,6 +32,7 @@ class SecretKey {
|
||||||
SecretKey('fiatApiKey', () => ''),
|
SecretKey('fiatApiKey', () => ''),
|
||||||
SecretKey('payfuraApiKey', () => ''),
|
SecretKey('payfuraApiKey', () => ''),
|
||||||
SecretKey('chatwootWebsiteToken', () => ''),
|
SecretKey('chatwootWebsiteToken', () => ''),
|
||||||
|
SecretKey('exolixApiKey', () => ''),
|
||||||
SecretKey('robinhoodApplicationId', () => ''),
|
SecretKey('robinhoodApplicationId', () => ''),
|
||||||
SecretKey('robinhoodCIdApiSecret', () => ''),
|
SecretKey('robinhoodCIdApiSecret', () => ''),
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue