Merge branch 'main' into cw_linux_direct_input_password

This commit is contained in:
Omar Hatem 2023-09-29 20:29:12 +03:00 committed by GitHub
commit 021e88e667
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 700 additions and 214 deletions

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,3 +1,3 @@
Enhance Monero coin control
Add Filipino localization
Bug Fixes
Fix 2FA code issue
Bug fixes
Minor enhancements

View file

@ -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

View file

@ -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,
);

View file

@ -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);
}
}
}

View file

@ -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>(),

View file

@ -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';
}

View file

@ -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');

View 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;
}
}
}

View 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;
}

View file

@ -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');
}

View file

@ -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;
}

View file

@ -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)),
],

View file

@ -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,

View file

@ -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];

View file

@ -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;
}

View file

@ -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;

View file

@ -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 {

View file

@ -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 = '',

View file

@ -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();

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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(

View file

@ -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',

View file

@ -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) {

View file

@ -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 ﻥﺍﻮﻨﻋ"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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 प्रामाणिक यूआरएल"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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-АВТОРИЗАЦИИ"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -32,6 +32,7 @@ class SecretKey {
SecretKey('fiatApiKey', () => ''),
SecretKey('payfuraApiKey', () => ''),
SecretKey('chatwootWebsiteToken', () => ''),
SecretKey('exolixApiKey', () => ''),
SecretKey('robinhoodApplicationId', () => ''),
SecretKey('robinhoodCIdApiSecret', () => ''),
];