mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' into cw_linux_direct_input_password
This commit is contained in:
commit
021e88e667
57 changed files with 700 additions and 214 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 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 exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> 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
|
||||
|
||||
|
|
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 |
|
@ -1,3 +1,3 @@
|
|||
Enhance Monero coin control
|
||||
Add Filipino localization
|
||||
Bug Fixes
|
||||
Fix 2FA code issue
|
||||
Bug fixes
|
||||
Minor enhancements
|
|
@ -1,5 +1,4 @@
|
|||
New Buy Provider Robinhood
|
||||
Fix sending Ethereum issue
|
||||
Enhance Monero coin control
|
||||
Add Filipino localization
|
||||
Bug Fixes
|
||||
Ethereum enhancements and bug fixes
|
||||
Fix 2FA code issue
|
||||
Bug fixes
|
||||
Minor enhancements
|
|
@ -65,13 +65,11 @@ class EthereumClient {
|
|||
|
||||
bool _isEthereum = currency == CryptoCurrency.eth;
|
||||
|
||||
final price = await _client!.getGasPrice();
|
||||
final price = _client!.getGasPrice();
|
||||
|
||||
final Transaction transaction = Transaction(
|
||||
from: privateKey.address,
|
||||
to: EthereumAddress.fromHex(toAddress),
|
||||
maxGas: gas,
|
||||
gasPrice: price,
|
||||
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||
);
|
||||
|
@ -101,7 +99,7 @@ class EthereumClient {
|
|||
return PendingEthereumTransaction(
|
||||
signedTransaction: signedTransaction,
|
||||
amount: amount,
|
||||
fee: BigInt.from(gas) * price.getInWei,
|
||||
fee: BigInt.from(gas) * (await price).getInWei,
|
||||
sendTransaction: _sendTransaction,
|
||||
exponent: exponent,
|
||||
);
|
||||
|
|
|
@ -121,12 +121,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
Future<void> init() async {
|
||||
await walletAddresses.init();
|
||||
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(
|
||||
<CryptoCurrency, MoneroBalance>{
|
||||
currency: MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
|
||||
unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
|
||||
});
|
||||
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(<CryptoCurrency, MoneroBalance>{
|
||||
currency: MoneroBalance(
|
||||
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
|
||||
unlockedBalance:
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
|
||||
});
|
||||
_setListeners();
|
||||
await updateTransactions();
|
||||
|
||||
|
@ -134,15 +134,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||
|
||||
if (monero_wallet.getCurrentHeight() <= 1) {
|
||||
monero_wallet.setRefreshFromBlockHeight(
|
||||
height: walletInfo.restoreHeight);
|
||||
monero_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
|
||||
}
|
||||
}
|
||||
|
||||
_autoSaveTimer = Timer.periodic(
|
||||
Duration(seconds: _autoSaveInterval),
|
||||
(_) async => await save());
|
||||
_autoSaveTimer =
|
||||
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() => null;
|
||||
|
||||
|
@ -162,7 +161,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
login: node.login,
|
||||
password: node.password,
|
||||
useSSL: node.isSSL,
|
||||
isLightWallet: false, // FIXME: hardcoded value
|
||||
isLightWallet: false,
|
||||
// FIXME: hardcoded value
|
||||
socksProxyAddress: node.socksProxyAddress);
|
||||
|
||||
monero_wallet.setTrustedDaemon(node.trusted);
|
||||
|
@ -198,7 +198,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
final outputs = _credentials.outputs;
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
final unlockedBalance =
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||
var allInputsAmount = 0;
|
||||
|
||||
PendingTransactionDescription pendingTransactionDescription;
|
||||
|
@ -217,56 +217,42 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
inputs.add(utx.keyImage);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) {
|
||||
throw MoneroTransactionNoInputsException(0);
|
||||
}
|
||||
final spendAllCoins = inputs.length == unspentCoins.length;
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll
|
||||
|| (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
||||
}
|
||||
|
||||
final int totalAmount = outputs.fold(0, (acc, value) =>
|
||||
acc + (value.formattedCryptoAmount ?? 0));
|
||||
final int totalAmount =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
|
||||
if (unlockedBalance < totalAmount) {
|
||||
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);
|
||||
}
|
||||
|
||||
final moneroOutputs = outputs.map((output) {
|
||||
final outputAddress = output.isParsedAddress
|
||||
? output.extractedAddress
|
||||
: output.address;
|
||||
final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||
|
||||
return MoneroOutput(
|
||||
address: outputAddress!,
|
||||
amount: output.cryptoAmount!.replaceAll(',', '.'));
|
||||
return MoneroOutput(
|
||||
address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.'));
|
||||
}).toList();
|
||||
|
||||
pendingTransactionDescription =
|
||||
await transaction_history.createTransactionMultDest(
|
||||
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
|
||||
outputs: moneroOutputs,
|
||||
priorityRaw: _credentials.priority.serialize(),
|
||||
accountIndex: walletAddresses.account!.id,
|
||||
preferredInputs: inputs);
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
final address = output.isParsedAddress
|
||||
? output.extractedAddress
|
||||
: output.address;
|
||||
final amount = output.sendAll
|
||||
? null
|
||||
: output.cryptoAmount!.replaceAll(',', '.');
|
||||
final formattedAmount = output.sendAll
|
||||
? null
|
||||
: output.formattedCryptoAmount;
|
||||
final address = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
|
||||
final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
|
||||
|
||||
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
||||
(formattedAmount == null && unlockedBalance <= 0)) {
|
||||
|
@ -277,8 +263,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
|
||||
if ((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
|
||||
(formattedAmount == null && allInputsAmount != unlockedBalance)) {
|
||||
if (!spendAllCoins &&
|
||||
((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
|
||||
formattedAmount == null)) {
|
||||
throw MoneroTransactionNoInputsException(inputs.length);
|
||||
}
|
||||
|
||||
|
@ -336,10 +323,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
try {
|
||||
// -- rename the waller folder --
|
||||
final currentWalletDir =
|
||||
Directory(await pathForWalletDir(name: name, type: type));
|
||||
final newWalletDirPath =
|
||||
await pathForWalletDir(name: newWalletName, type: type);
|
||||
final currentWalletDir = Directory(await pathForWalletDir(name: name, type: type));
|
||||
final newWalletDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentWalletDir.rename(newWalletDirPath);
|
||||
|
||||
// -- use new waller folder to rename files with old names still --
|
||||
|
@ -349,8 +334,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
final currentKeysFile = File('$renamedWalletPath.keys');
|
||||
final currentAddressListFile = File('$renamedWalletPath.address.txt');
|
||||
|
||||
final newWalletPath =
|
||||
await pathForWallet(name: newWalletName, type: type);
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
|
||||
if (currentCacheFile.existsSync()) {
|
||||
await currentCacheFile.rename(newWalletPath);
|
||||
|
@ -368,8 +352,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
final currentKeysFile = File('$currentWalletPath.keys');
|
||||
final currentAddressListFile = File('$currentWalletPath.address.txt');
|
||||
|
||||
final newWalletPath =
|
||||
await pathForWallet(name: newWalletName, type: type);
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentCacheFile.existsSync()) {
|
||||
|
@ -436,8 +419,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
||||
final coinInfoList = unspentCoinsInfo.values
|
||||
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
@ -457,16 +440,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
Future<void> _addCoinInfo(MoneroUnspent coin) async {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: id,
|
||||
hash: coin.hash,
|
||||
isFrozen: coin.isFrozen,
|
||||
isSending: coin.isSending,
|
||||
noteRaw: coin.note,
|
||||
address: coin.address,
|
||||
value: coin.value,
|
||||
vout: 0,
|
||||
keyImage: coin.keyImage
|
||||
);
|
||||
walletId: id,
|
||||
hash: coin.hash,
|
||||
isFrozen: coin.isFrozen,
|
||||
isSending: coin.isSending,
|
||||
noteRaw: coin.note,
|
||||
address: coin.address,
|
||||
value: coin.value,
|
||||
vout: 0,
|
||||
keyImage: coin.keyImage);
|
||||
|
||||
await unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
@ -474,8 +456,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
Future<void> _refreshUnspentCoinsInfo() async {
|
||||
try {
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
||||
.where((element) => element.walletId.contains(id));
|
||||
final currentWalletUnspentCoins =
|
||||
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
|
||||
|
||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||
currentWalletUnspentCoins.forEach((element) {
|
||||
|
@ -496,16 +478,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||
monero_wallet.getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex);
|
||||
monero_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
|
||||
@override
|
||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||
transaction_history.refreshTransactions();
|
||||
return _getAllTransactionsOfAccount(walletAddresses.account?.id).fold<Map<String, MoneroTransactionInfo>>(
|
||||
<String, MoneroTransactionInfo>{},
|
||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
|
||||
.fold<Map<String, MoneroTransactionInfo>>(<String, MoneroTransactionInfo>{},
|
||||
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
|
||||
acc[tx.id] = tx;
|
||||
return acc;
|
||||
});
|
||||
|
@ -533,12 +513,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
return monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
|
||||
transaction_history
|
||||
.getAllTransactions()
|
||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||
.toList();
|
||||
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
|
||||
.getAllTransactions()
|
||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||
.toList();
|
||||
|
||||
void _setListeners() {
|
||||
_listener?.stop();
|
||||
|
@ -560,8 +539,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
int _getHeightDistance(DateTime date) {
|
||||
final distance =
|
||||
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||
final daysTmp = (distance / 86400).round();
|
||||
final days = daysTmp < 1 ? 1 : daysTmp;
|
||||
|
||||
|
@ -592,11 +570,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _askForUpdateTransactionHistory() async =>
|
||||
await updateTransactions();
|
||||
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||
|
||||
int _getFullBalance() =>
|
||||
monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
|
||||
int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
|
||||
|
||||
int _getUnlockedBalance() =>
|
||||
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||
|
@ -605,8 +581,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
var frozenBalance = 0;
|
||||
|
||||
for (var coin in unspentCoinsInfo.values) {
|
||||
if (coin.isFrozen)
|
||||
frozenBalance += coin.value;
|
||||
if (coin.isFrozen) frozenBalance += coin.value;
|
||||
}
|
||||
|
||||
return frozenBalance;
|
||||
|
@ -627,9 +602,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
syncStatus = SyncedSyncStatus();
|
||||
|
||||
if (!_hasSyncAfterStartup) {
|
||||
_hasSyncAfterStartup = true;
|
||||
await save();
|
||||
}
|
||||
_hasSyncAfterStartup = true;
|
||||
await save();
|
||||
}
|
||||
|
||||
if (walletInfo.isRecovery) {
|
||||
await setAsRecovered();
|
||||
|
@ -654,12 +629,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
|
||||
if (enableAutoGenerate) {
|
||||
walletAddresses.updateUnusedSubaddress(
|
||||
accountIndex: account?.id ?? 0,
|
||||
defaultLabel: account?.label ?? '',
|
||||
);
|
||||
} else {
|
||||
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
}
|
||||
walletAddresses.updateUnusedSubaddress(
|
||||
accountIndex: account?.id ?? 0,
|
||||
defaultLabel: account?.label ?? '',
|
||||
);
|
||||
} else {
|
||||
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -377,7 +377,7 @@ Future<void> setup({
|
|||
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
|
||||
onAuthenticationFinished: onAuthFinished, closable: closable));
|
||||
|
||||
getIt.registerFactory<Setup2FAViewModel>(
|
||||
getIt.registerLazySingleton<Setup2FAViewModel>(
|
||||
() => Setup2FAViewModel(
|
||||
getIt.get<SettingsStore>(),
|
||||
getIt.get<SharedPreferences>(),
|
||||
|
|
|
@ -19,7 +19,6 @@ class PreferencesKey {
|
|||
'allow_biometrical_authentication';
|
||||
static const useTOTP2FA = 'use_totp_2fa';
|
||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||
static const totpSecretKey = 'totp_qr_secret_key';
|
||||
static const disableExchangeKey = 'disable_exchange';
|
||||
static const exchangeStatusKey = 'exchange_status';
|
||||
static const currentTheme = 'current_theme';
|
||||
|
@ -75,4 +74,5 @@ class PreferencesKey {
|
|||
static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||
'should_require_totp_2fa_for_all_security_and_backup_settings';
|
||||
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 =
|
||||
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}) {
|
||||
switch (raw) {
|
||||
|
@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
|||
case 5:
|
||||
return trocador;
|
||||
case 6:
|
||||
return exolix;
|
||||
case 7:
|
||||
return all;
|
||||
default:
|
||||
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 settling = TradeState(raw: 'settling', title: 'Settlement in progress');
|
||||
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}) {
|
||||
switch (raw) {
|
||||
case 'pending':
|
||||
|
@ -77,6 +86,24 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
|||
return failed;
|
||||
case '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:
|
||||
throw Exception('Unexpected token: $raw in TradeState deserialize');
|
||||
}
|
||||
|
|
|
@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
|
||||
break;
|
||||
default:
|
||||
image = null;
|
||||
}
|
||||
|
|
|
@ -53,8 +53,10 @@ class Setup2FAPage extends BasePage {
|
|||
SizedBox(height: 86),
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.setup_totp_recommended,
|
||||
handler: (_) => Navigator.of(context)
|
||||
.pushReplacementNamed(Routes.setup_2faQRPage),
|
||||
handler: (_) {
|
||||
setup2FAViewModel.generateSecretKey();
|
||||
return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage);
|
||||
},
|
||||
),
|
||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
],
|
||||
|
|
|
@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart' as qr;
|
||||
|
||||
|
||||
class Setup2FAQRPage extends BasePage {
|
||||
Setup2FAQRPage({required this.setup2FAViewModel});
|
||||
|
||||
|
@ -25,7 +24,6 @@ class Setup2FAQRPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
||||
final copyImage = Image.asset(
|
||||
'assets/images/copy_content.png',
|
||||
height: 12,
|
||||
|
@ -91,7 +89,7 @@ class Setup2FAQRPage extends BasePage {
|
|||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'${setup2FAViewModel.secretKey}',
|
||||
'${setup2FAViewModel.totpSecretKey}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
@ -110,7 +108,63 @@ class Setup2FAQRPage extends BasePage {
|
|||
child: InkWell(
|
||||
onTap: () {
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: '${setup2FAViewModel.secretKey}'));
|
||||
ClipboardData(text: '${setup2FAViewModel.totpSecretKey}'));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: Color(0xFFF2F0FA),
|
||||
),
|
||||
child: copyImage,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
StandardListSeparator(),
|
||||
SizedBox(height: 13),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
S.current.totp_auth_url,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Palette.darkGray,
|
||||
height: 1.8333,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'${setup2FAViewModel.totpVersionOneLink}',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.375,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: '${setup2FAViewModel.totpVersionOneLink}'));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: Container(
|
||||
|
@ -129,13 +183,10 @@ class Setup2FAQRPage extends BasePage {
|
|||
Spacer(),
|
||||
PrimaryButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
Routes.totpAuthCodePage,
|
||||
Navigator.of(context).pushReplacementNamed(Routes.totpAuthCodePage,
|
||||
arguments: TotpAuthArgumentsModel(
|
||||
isForSetup: true,
|
||||
)
|
||||
|
||||
);
|
||||
));
|
||||
},
|
||||
text: S.current.continue_text,
|
||||
color: Theme.of(context).primaryColor,
|
||||
|
|
|
@ -51,10 +51,11 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
// FIX-ME: Added `context` it was not used here before, maby bug ?
|
||||
int itemsCount = tradeDetailsViewModel.items.length;
|
||||
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => tradeDetailsViewModel.items.length,
|
||||
itemCounter: (int _) => itemsCount,
|
||||
itemBuilder: (__, index) {
|
||||
final item = tradeDetailsViewModel.items[index];
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store {
|
|||
displaySideShift = true,
|
||||
displayMorphToken = true,
|
||||
displaySimpleSwap = true,
|
||||
displayTrocador = true;
|
||||
displayTrocador = true,
|
||||
displayExolix = true;
|
||||
|
||||
@observable
|
||||
bool displayXMRTO;
|
||||
|
@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store {
|
|||
@observable
|
||||
bool displayTrocador;
|
||||
|
||||
@observable
|
||||
bool displayExolix;
|
||||
|
||||
@computed
|
||||
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador;
|
||||
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
|
||||
|
||||
@action
|
||||
void toggleDisplayExchange(ExchangeProviderDescription provider) {
|
||||
|
@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store {
|
|||
break;
|
||||
case ExchangeProviderDescription.trocador:
|
||||
displayTrocador = !displayTrocador;
|
||||
break;
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
displayExolix = !displayExolix;
|
||||
break;
|
||||
case ExchangeProviderDescription.all:
|
||||
if (displayAllTrades) {
|
||||
displayChangeNow = false;
|
||||
|
@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store {
|
|||
displayMorphToken = false;
|
||||
displaySimpleSwap = false;
|
||||
displayTrocador = false;
|
||||
displayExolix = false;
|
||||
} else {
|
||||
displayChangeNow = true;
|
||||
displaySideShift = true;
|
||||
|
@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store {
|
|||
displayMorphToken = true;
|
||||
displaySimpleSwap = true;
|
||||
displayTrocador = true;
|
||||
displayExolix = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store {
|
|||
||(displaySimpleSwap &&
|
||||
item.trade.provider ==
|
||||
ExchangeProviderDescription.simpleSwap)
|
||||
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador))
|
||||
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)
|
||||
||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
|
||||
.toList()
|
||||
: _trades;
|
||||
}
|
||||
|
|
|
@ -281,14 +281,13 @@ abstract class SettingsStoreBase with Store {
|
|||
reaction(
|
||||
(_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
|
||||
|
||||
reaction((_) => totpSecretKey,
|
||||
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
|
||||
reaction(
|
||||
(_) => numberOfFailedTokenTrials,
|
||||
(int failedTokenTrail) =>
|
||||
sharedPreferences.setInt(PreferencesKey.failedTotpTokenTrials, failedTokenTrail));
|
||||
|
||||
reaction((_) => totpSecretKey,
|
||||
(String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey));
|
||||
|
||||
reaction(
|
||||
(_) => shouldShowMarketPlaceInDashboard,
|
||||
(bool value) =>
|
||||
|
@ -424,15 +423,10 @@ abstract class SettingsStoreBase with Store {
|
|||
bool shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||
|
||||
@observable
|
||||
String totpSecretKey;
|
||||
|
||||
@computed
|
||||
String get totpVersionOneLink {
|
||||
return 'otpauth://totp/Cake%20Wallet:$deviceName?secret=$totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30';
|
||||
}
|
||||
bool useTOTP2FA;
|
||||
|
||||
@observable
|
||||
bool useTOTP2FA;
|
||||
String totpSecretKey;
|
||||
|
||||
@observable
|
||||
int numberOfFailedTokenTrials;
|
||||
|
@ -577,8 +571,8 @@ abstract class SettingsStoreBase with Store {
|
|||
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
|
||||
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
|
||||
false;
|
||||
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
||||
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
|
||||
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
||||
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
||||
final shouldShowMarketPlaceInDashboard =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
|
||||
|
@ -680,8 +674,8 @@ abstract class SettingsStoreBase with Store {
|
|||
initialFiatMode: currentFiatApiMode,
|
||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||
initialTotpSecretKey: totpSecretKey,
|
||||
initialUseTOTP2FA: useTOTP2FA,
|
||||
initialTotpSecretKey: totpSecretKey,
|
||||
initialFailedTokenTrial: tokenTrialNumber,
|
||||
initialExchangeStatus: exchangeStatus,
|
||||
initialTheme: savedTheme,
|
||||
|
@ -755,9 +749,8 @@ abstract class SettingsStoreBase with Store {
|
|||
shouldSaveRecipientAddress =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||
shouldSaveRecipientAddress;
|
||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
||||
|
||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||
numberOfFailedTokenTrials =
|
||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
||||
|
|
|
@ -162,6 +162,7 @@ class ExceptionHandler {
|
|||
"Handshake error in client",
|
||||
"Error while launching http",
|
||||
"OS Error: Network is unreachable",
|
||||
"ClientException: Write failed, uri=https:",
|
||||
];
|
||||
|
||||
static Future<void> _addDeviceInfo(File file) async {
|
||||
|
|
|
@ -99,6 +99,11 @@ abstract class DashboardViewModelBase with Store {
|
|||
caption: ExchangeProviderDescription.trocador.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayExolix,
|
||||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
]
|
||||
},
|
||||
subname = '',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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/simpleswap/simpleswap_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:
|
||||
_provider = TrocadorExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
}
|
||||
|
||||
_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/preferences_key.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_request.dart';
|
||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||
|
@ -151,6 +153,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
SideShiftExchangeProvider(),
|
||||
SimpleSwapExchangeProvider(),
|
||||
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
|
||||
ExolixExchangeProvider(),
|
||||
];
|
||||
|
||||
@observable
|
||||
|
@ -547,6 +550,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
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(',', '.');
|
||||
|
||||
if (limitsState is LimitsLoadedSuccessfully) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
|||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
part 'send_view_model.g.dart';
|
||||
|
||||
|
@ -325,7 +326,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
|
||||
state = TransactionCommitted();
|
||||
} catch (e) {
|
||||
state = FailureState(e.toString());
|
||||
String translatedError = translateErrorMessage(e.toString(), wallet.type, wallet.currency);
|
||||
state = FailureState(translatedError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,4 +414,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
selectedCryptoCurrency = wallet.currency;
|
||||
}
|
||||
}
|
||||
|
||||
String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) {
|
||||
if (walletType == WalletType.ethereum || walletType == WalletType.haven) {
|
||||
if (error.contains('gas required exceeds allowance (0)') || error.contains('insufficient funds for gas')) {
|
||||
return S.current.do_not_have_enough_gas_asset(currency.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
unhighlightTabs = false,
|
||||
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
||||
state = InitialExecutionState() {
|
||||
_getRandomBase32SecretKey();
|
||||
selectCakePreset(selectedCake2FAPreset);
|
||||
reaction((_) => state, _saveLastAuthTime);
|
||||
}
|
||||
|
@ -36,9 +35,12 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
static const banTimeout = 180; // 3 minutes
|
||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||
|
||||
String get secretKey => _settingsStore.totpSecretKey;
|
||||
String get deviceName => _settingsStore.deviceName;
|
||||
String get totpVersionOneLink => _settingsStore.totpVersionOneLink;
|
||||
|
||||
@computed
|
||||
String get totpSecretKey => _settingsStore.totpSecretKey;
|
||||
|
||||
String totpVersionOneLink = '';
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
@ -84,9 +86,14 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings =>
|
||||
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||
|
||||
void _getRandomBase32SecretKey() {
|
||||
final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
|
||||
_setBase32SecretKey(randomBase32Key);
|
||||
@action
|
||||
void generateSecretKey() {
|
||||
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
|
||||
|
@ -95,15 +102,10 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
void _setBase32SecretKey(String value) {
|
||||
void setTOTPSecretKey(String value) {
|
||||
_settingsStore.totpSecretKey = value;
|
||||
}
|
||||
|
||||
@action
|
||||
void clearBase32SecretKey() {
|
||||
_settingsStore.totpSecretKey = '';
|
||||
}
|
||||
|
||||
Duration? banDuration() {
|
||||
final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
|
||||
|
||||
|
@ -145,7 +147,7 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
}
|
||||
|
||||
final result = Utils.verify(
|
||||
secretKey: secretKey,
|
||||
secretKey: totpSecretKey,
|
||||
otp: otpText,
|
||||
);
|
||||
|
||||
|
@ -156,7 +158,6 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
} else {
|
||||
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
||||
adjustTokenTrialNumber(value);
|
||||
print(value);
|
||||
if (_failureCounter >= maxFailedTrials) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
|
|
|
@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store {
|
|||
icon: 'assets/images/simpleSwap.png',
|
||||
linkTitle: '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) ... [
|
||||
LinkListItem(
|
||||
title: 'Wyre',
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
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_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/sideshift/sideshift_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:
|
||||
_provider = TrocadorExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
}
|
||||
|
||||
_updateItems();
|
||||
|
@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store {
|
|||
items.add(StandartListItem(
|
||||
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.exolix) {
|
||||
final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
}
|
||||
}
|
||||
|
||||
void _launchUrl(String url) {
|
||||
|
|
|
@ -694,5 +694,9 @@
|
|||
"onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.",
|
||||
"default_buy_provider": "مزود شراء الافتراضي",
|
||||
"ask_each_time": "اسأل في كل مرة",
|
||||
"buy_provider_unavailable": "مزود حاليا غير متوفر."
|
||||
}
|
||||
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
||||
|
||||
"do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.",
|
||||
"totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ"
|
||||
}
|
||||
|
||||
|
|
|
@ -690,5 +690,7 @@
|
|||
"onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.",
|
||||
"default_buy_provider": "Доставчик по подразбиране купува",
|
||||
"ask_each_time": "Питайте всеки път",
|
||||
"buy_provider_unavailable": "Понастоящем доставчик не е наличен."
|
||||
}
|
||||
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
||||
"do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -690,5 +690,7 @@
|
|||
"onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.",
|
||||
"default_buy_provider": "Výchozí poskytovatel nákupu",
|
||||
"ask_each_time": "Zeptejte se pokaždé",
|
||||
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný."
|
||||
}
|
||||
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
||||
"do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.",
|
||||
"totp_auth_url": "URL AUTH TOTP"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.",
|
||||
"default_buy_provider": "Standard-Kaufanbieter",
|
||||
"ask_each_time": "Jedes Mal fragen",
|
||||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar."
|
||||
}
|
||||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
||||
"do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.",
|
||||
"totp_auth_url": "TOTP-Auth-URL"
|
||||
}
|
||||
|
|
|
@ -699,5 +699,7 @@
|
|||
"default_buy_provider": "Default Buy Provider",
|
||||
"ask_each_time": "Ask each time",
|
||||
"robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.",
|
||||
"buy_provider_unavailable": "Provider currently unavailable."
|
||||
}
|
||||
"buy_provider_unavailable": "Provider currently unavailable.",
|
||||
"do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.",
|
||||
"default_buy_provider": "Proveedor de compra predeterminado",
|
||||
"ask_each_time": "Pregunta cada vez",
|
||||
"buy_provider_unavailable": "Proveedor actualmente no disponible."
|
||||
}
|
||||
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
||||
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
||||
"totp_auth_url": "URL de autenticación TOTP"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.",
|
||||
"default_buy_provider": "Fournisseur d'achat par défaut",
|
||||
"ask_each_time": "Demandez à chaque fois",
|
||||
"buy_provider_unavailable": "Fournisseur actuellement indisponible."
|
||||
}
|
||||
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
||||
"do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.",
|
||||
"totp_auth_url": "URL D'AUTORISATION TOTP"
|
||||
}
|
||||
|
|
|
@ -676,5 +676,7 @@
|
|||
"onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.",
|
||||
"default_buy_provider": "Tsohuwar Siyarwa",
|
||||
"ask_each_time": "Tambaya kowane lokaci",
|
||||
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu."
|
||||
}
|
||||
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
||||
"do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।",
|
||||
"default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता",
|
||||
"ask_each_time": "हर बार पूछें",
|
||||
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।"
|
||||
}
|
||||
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।",
|
||||
"do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।",
|
||||
"totp_auth_url": "TOTP प्रामाणिक यूआरएल"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.",
|
||||
"default_buy_provider": "Zadani davatelj kupnje",
|
||||
"ask_each_time": "Pitajte svaki put",
|
||||
"buy_provider_unavailable": "Davatelj trenutno nije dostupan."
|
||||
"buy_provider_unavailable": "Davatelj trenutno nije dostupan.",
|
||||
"do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
|
@ -686,5 +686,7 @@
|
|||
"onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.",
|
||||
"default_buy_provider": "Penyedia beli default",
|
||||
"ask_each_time": "Tanyakan setiap kali",
|
||||
"buy_provider_unavailable": "Penyedia saat ini tidak tersedia."
|
||||
"buy_provider_unavailable": "Penyedia saat ini tidak tersedia.",
|
||||
"do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.",
|
||||
"totp_auth_url": "URL Otentikasi TOTP"
|
||||
}
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.",
|
||||
"default_buy_provider": "Provider di acquisto predefinito",
|
||||
"ask_each_time": "Chiedi ogni volta",
|
||||
"buy_provider_unavailable": "Provider attualmente non disponibile."
|
||||
}
|
||||
"buy_provider_unavailable": "Provider attualmente non disponibile.",
|
||||
"do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.",
|
||||
"totp_auth_url": "URL DI AUT. TOTP"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。",
|
||||
"default_buy_provider": "デフォルトの購入プロバイダー",
|
||||
"ask_each_time": "毎回尋ねてください",
|
||||
"buy_provider_unavailable": "現在、プロバイダーは利用できません。"
|
||||
}
|
||||
"buy_provider_unavailable": "現在、プロバイダーは利用できません。",
|
||||
"do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。",
|
||||
"totp_auth_url": "TOTP認証URL"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.",
|
||||
"default_buy_provider": "기본 구매 제공자",
|
||||
"ask_each_time": "매번 물어보십시오",
|
||||
"buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다."
|
||||
}
|
||||
"buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.",
|
||||
"do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.",
|
||||
"totp_auth_url": "TOTP 인증 URL"
|
||||
}
|
||||
|
|
|
@ -696,5 +696,7 @@
|
|||
"onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။",
|
||||
"default_buy_provider": "Default Provider ကိုဝယ်ပါ",
|
||||
"ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
|
||||
"buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။"
|
||||
}
|
||||
"buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။",
|
||||
"do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.",
|
||||
"default_buy_provider": "Standaard Koopprovider",
|
||||
"ask_each_time": "Vraag het elke keer",
|
||||
"buy_provider_unavailable": "Provider momenteel niet beschikbaar."
|
||||
}
|
||||
"buy_provider_unavailable": "Provider momenteel niet beschikbaar.",
|
||||
"do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.",
|
||||
"totp_auth_url": "TOTP AUTH-URL"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.",
|
||||
"default_buy_provider": "Domyślny dostawca zakupu",
|
||||
"ask_each_time": "Zapytaj za każdym razem",
|
||||
"buy_provider_unavailable": "Dostawca obecnie niedostępny."
|
||||
}
|
||||
"buy_provider_unavailable": "Dostawca obecnie niedostępny.",
|
||||
"do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.",
|
||||
"totp_auth_url": "Adres URL TOTP AUTH"
|
||||
}
|
||||
|
|
|
@ -697,5 +697,7 @@
|
|||
"onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.",
|
||||
"default_buy_provider": "Provedor de compra padrão",
|
||||
"ask_each_time": "Pergunte cada vez",
|
||||
"buy_provider_unavailable": "Provedor atualmente indisponível."
|
||||
}
|
||||
"buy_provider_unavailable": "Provedor atualmente indisponível.",
|
||||
"do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.",
|
||||
"totp_auth_url": "URL de autenticação TOTP"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.",
|
||||
"default_buy_provider": "По умолчанию поставщик покупки",
|
||||
"ask_each_time": "Спросите каждый раз",
|
||||
"buy_provider_unavailable": "Поставщик в настоящее время недоступен."
|
||||
}
|
||||
"buy_provider_unavailable": "Поставщик в настоящее время недоступен.",
|
||||
"do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.",
|
||||
"totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ"
|
||||
}
|
||||
|
|
|
@ -696,5 +696,7 @@
|
|||
"onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป",
|
||||
"default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น",
|
||||
"ask_each_time": "ถามทุกครั้ง",
|
||||
"buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน"
|
||||
}
|
||||
"buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน",
|
||||
"do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม",
|
||||
"totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP"
|
||||
}
|
||||
|
|
|
@ -687,5 +687,7 @@
|
|||
"support_title_other_links": "Iba pang mga link sa suporta",
|
||||
"support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan",
|
||||
"select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.",
|
||||
"save_to_downloads": "I -save sa mga pag -download"
|
||||
"save_to_downloads": "I -save sa mga pag -download",
|
||||
"do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
|
@ -696,5 +696,7 @@
|
|||
"onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.",
|
||||
"default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı",
|
||||
"ask_each_time": "Her seferinde sor",
|
||||
"buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor."
|
||||
}
|
||||
"buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.",
|
||||
"do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.",
|
||||
"totp_auth_url": "TOTP YETKİ URL'si"
|
||||
}
|
||||
|
|
|
@ -698,5 +698,7 @@
|
|||
"onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.",
|
||||
"default_buy_provider": "Постачальник покупки за замовчуванням",
|
||||
"ask_each_time": "Запитайте кожен раз",
|
||||
"buy_provider_unavailable": "В даний час постачальник недоступний."
|
||||
}
|
||||
"buy_provider_unavailable": "В даний час постачальник недоступний.",
|
||||
"do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -690,5 +690,7 @@
|
|||
"onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔",
|
||||
"default_buy_provider": "پہلے سے طے شدہ خریدنے والا",
|
||||
"ask_each_time": "ہر بار پوچھیں",
|
||||
"buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔"
|
||||
}
|
||||
"buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔",
|
||||
"do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -692,5 +692,7 @@
|
|||
"onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.",
|
||||
"default_buy_provider": "Aiyipada Ra Olupese",
|
||||
"ask_each_time": "Beere lọwọ kọọkan",
|
||||
"buy_provider_unavailable": "Olupese lọwọlọwọ ko si."
|
||||
}
|
||||
"buy_provider_unavailable": "Olupese lọwọlọwọ ko si.",
|
||||
"do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
}
|
||||
|
|
|
@ -697,5 +697,7 @@
|
|||
"onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。",
|
||||
"default_buy_provider": "默认购买提供商",
|
||||
"ask_each_time": "每次问",
|
||||
"buy_provider_unavailable": "提供者目前不可用。"
|
||||
}
|
||||
"buy_provider_unavailable": "提供者目前不可用。",
|
||||
"do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。",
|
||||
"totp_auth_url": "TOTP 授权 URL"
|
||||
}
|
||||
|
|
|
@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
|||
APP_ANDROID_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="1.6.1"
|
||||
MONERO_COM_BUILD_NUMBER=57
|
||||
MONERO_COM_VERSION="1.6.2"
|
||||
MONERO_COM_BUILD_NUMBER=58
|
||||
MONERO_COM_BUNDLE_ID="com.monero.app"
|
||||
MONERO_COM_PACKAGE="com.monero.app"
|
||||
MONERO_COM_SCHEME="monero.com"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="4.9.1"
|
||||
CAKEWALLET_BUILD_NUMBER=170
|
||||
CAKEWALLET_VERSION="4.9.2"
|
||||
CAKEWALLET_BUILD_NUMBER=171
|
||||
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_SCHEME="cakewallet"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# if you get an error `command not found`
|
||||
# give the correct permissions to this file using `chmod 777 append_translation.sh`
|
||||
|
||||
langs=("ar" "bg" "cs" "de" "en" "es" "fr" "ha" "hi" "hr" "id" "it" "ja" "ko" "my" "nl" "pl" "pt" "ru" "th" "tr" "uk" "ur" "yo" "zh")
|
||||
langs=("ar" "bg" "cs" "de" "en" "es" "fr" "ha" "hi" "hr" "id" "it" "ja" "ko" "my" "nl" "pl" "pt" "ru" "th" "tl" "tr" "uk" "ur" "yo" "zh")
|
||||
|
||||
name=$1
|
||||
text=$2
|
||||
|
|
|
@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
|||
APP_IOS_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="1.6.1"
|
||||
MONERO_COM_BUILD_NUMBER=55
|
||||
MONERO_COM_VERSION="1.6.2"
|
||||
MONERO_COM_BUILD_NUMBER=56
|
||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="4.9.1"
|
||||
CAKEWALLET_BUILD_NUMBER=183
|
||||
CAKEWALLET_VERSION="4.9.2"
|
||||
CAKEWALLET_BUILD_NUMBER=185
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
HAVEN_NAME="Haven"
|
||||
|
|
|
@ -15,8 +15,8 @@ if [ -n "$1" ]; then
|
|||
fi
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="1.2.1"
|
||||
CAKEWALLET_BUILD_NUMBER=32
|
||||
CAKEWALLET_VERSION="1.2.2"
|
||||
CAKEWALLET_BUILD_NUMBER=33
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
|
||||
|
|
|
@ -32,6 +32,7 @@ class SecretKey {
|
|||
SecretKey('fiatApiKey', () => ''),
|
||||
SecretKey('payfuraApiKey', () => ''),
|
||||
SecretKey('chatwootWebsiteToken', () => ''),
|
||||
SecretKey('exolixApiKey', () => ''),
|
||||
SecretKey('robinhoodApplicationId', () => ''),
|
||||
SecretKey('robinhoodCIdApiSecret', () => ''),
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue