Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-480-Haven-removal

This commit is contained in:
Blazebrain 2023-10-03 14:06:58 +01:00
commit dc15fbcc74
57 changed files with 575 additions and 166 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

@ -37,10 +37,10 @@ const moneroBlockSize = 1000;
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
MoneroWalletBase({required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo})
abstract class MoneroWalletBase
extends WalletBase<MoneroBalance, MoneroTransactionHistory, MoneroTransactionInfo> with Store {
MoneroWalletBase(
{required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo})
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
CryptoCurrency.xmr: MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
@ -112,11 +112,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void> init() async {
await walletAddresses.init();
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(
<CryptoCurrency, MoneroBalance>{
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))
unlockedBalance:
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
});
_setListeners();
await updateTransactions();
@ -125,15 +125,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;
@ -153,7 +152,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);
@ -208,56 +208,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(',', '.'));
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)) {
@ -268,8 +254,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);
}
@ -327,10 +314,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 --
@ -340,8 +325,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);
@ -359,8 +343,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()) {
@ -426,8 +409,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;
@ -455,8 +438,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
address: coin.address,
value: coin.value,
vout: 0,
keyImage: coin.keyImage
);
keyImage: coin.keyImage);
await unspentCoinsInfo.add(newInfo);
}
@ -464,8 +446,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) {
@ -486,15 +468,13 @@ 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>{},
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
.fold<Map<String, MoneroTransactionInfo>>(<String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
acc[tx.id] = tx;
return acc;
@ -523,8 +503,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
return monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
}
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
transaction_history
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
.getAllTransactions()
.map((row) => MoneroTransactionInfo.fromRow(row))
.where((element) => element.accountIndex == (accountIndex ?? 0))
@ -550,8 +529,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;
@ -582,11 +560,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);
@ -595,8 +571,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;

View file

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

@ -89,7 +89,7 @@ class Setup2FAQRPage extends BasePage {
),
SizedBox(height: 8),
Text(
'${setup2FAViewModel.secretKey}',
'${setup2FAViewModel.totpSecretKey}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
@ -108,7 +108,7 @@ 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(

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) {
@ -57,6 +61,9 @@ abstract class TradeFilterStoreBase with Store {
case ExchangeProviderDescription.trocador:
displayTrocador = !displayTrocador;
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

@ -280,14 +280,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) =>
@ -422,15 +421,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;
@ -575,8 +569,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;
@ -677,8 +671,8 @@ abstract class SettingsStoreBase with Store {
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialTotpSecretKey: totpSecretKey,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
@ -752,9 +746,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

@ -161,6 +161,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

@ -691,5 +691,7 @@
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
"havenSupportNotice": " ﻯﺮﺧﺃ ﺔﻈﻔﺤﻣ ﻲﻓ ﺭﻭﺬﺒﻟﺍ ﺓﺩﺎﻌﺘﺳﺍ ﻚﻨﻜﻤﻳ ﻦﻜﻟﻭ ،ﺔﻜﻌﻜﻟﺍ ﻲﻓ ﺎﻣًﻮﻋﺪﻣ ﺫﻼﻤﻟﺍ ﺪﻌﻳ ﻢﻟ",
"havenSupportSeedsNotice": ".ﻯﺮﺧﺃ ﺔﻈﻔﺤﻣ ﻲﻓ ﺭﻭﺬﺒﻟﺍ ﺓﺩﺎﻌﺘﺳﺍ ﻚﻟﺫ ﺪﻌﺑ ﻚﻨﻜﻤﻳ .ﻦﻣﺁ ﻥﺎﻜﻣ ﻲﻓ ﺎﻬﻈﻔﺣﺍﻭ ﺎﻬﺒﺘﻛﺍ .ﻚﺑ ﺔﺻ",
"totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ"
"do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.",
"totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ"
}

View file

@ -687,5 +687,6 @@
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
"havenSupportNotice": "Haven вече не се поддържа в Cake, но можете да възстановите семената в друг портфейл",
"havenSupportSeedsNotice": "На следващата страница ще видите уникалните семена към вашия портфейл Haven. Запишете го и го съхранявайте на сигурно място. След това можете да възстановите семената в друг портфейл.",
"do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.",
"totp_auth_url": "TOTP AUTH URL"
}

View file

@ -687,5 +687,6 @@
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
"havenSupportNotice": "Haven již není v Cake podporován, ale semena můžete obnovit v jiné peněžence",
"havenSupportSeedsNotice": "Na další stránce uvidíte jedinečná semínka do vaší peněženky Haven. Zapište si to a uložte na bezpečném místě. Semena pak můžete obnovit v jiné peněžence.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
"havenSupportNotice": "Haven wird in Cake nicht mehr unterstützt, aber Sie können die Seeds in einer anderen Wallet wiederherstellen",
"havenSupportSeedsNotice": "Auf der nächsten Seite sehen Sie die einzigartigen Samen für Ihr Haven Wallet. Schreiben Sie es auf und bewahren Sie es an einem sicheren Ort auf. Anschließend können Sie die Seeds in einem anderen Wallet wiederherstellen.",
"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

@ -696,5 +696,6 @@
"buy_provider_unavailable": "Provider currently unavailable.",
"havenSupportNotice": "Haven is no longer supported in Cake, but you can restore the seeds in another wallet",
"havenSupportSeedsNotice": "On the next page, you will see the unique seeds to your Haven Wallet. Write it down and store it in a safe place. You can then restore the seeds in another wallet.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
"havenSupportNotice": "Haven ya no es compatible con Cake, pero puedes restaurar las semillas en otra billetera",
"havenSupportSeedsNotice": "En la página siguiente, verá las semillas únicas de su Haven Wallet. Anótelo y guárdelo en un lugar seguro. Luego puedes restaurar las semillas en otra billetera.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
"havenSupportNotice": "Haven n'est plus pris en charge dans Cake, mais vous pouvez restaurer les graines dans un autre portefeuille",
"havenSupportSeedsNotice": "Sur la page suivante, vous verrez les graines uniques de votre Haven Wallet. Notez-le et conservez-le dans un endroit sûr. Vous pouvez ensuite restaurer les graines dans un autre portefeuille.",
"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

@ -673,5 +673,6 @@
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
"havenSupportNotice": "Haven baya tallafawa a cikin Cake, amma zaku iya dawo da tsaba a cikin wani walat ɗin",
"havenSupportSeedsNotice": "A shafi na gaba, zaku ga keɓaɓɓen iri zuwa Haven Wallet ɗinku. Rubuta shi kuma adana shi a wuri mai aminci. Kuna iya dawo da tsaba a cikin wani walat ɗin.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।",
"havenSupportNotice": "हेवन अब केक में समर्थित नहीं है, लेकिन आप बीज को दूसरे वॉलेट में पुनर्स्थापित कर सकते हैं",
"havenSupportSeedsNotice": "अगले पृष्ठ पर, आप अपने हेवन वॉलेट के अनूठे बीज देखेंगे। इसे लिख लें और किसी सुरक्षित स्थान पर रख दें। फिर आप बीज को दूसरे बटुए में पुनर्स्थापित कर सकते हैं।",
"do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।",
"totp_auth_url": "TOTP प्रामाणिक यूआरएल"
}

View file

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Davatelj trenutno nije dostupan.",
"havenSupportNotice": "Haven više nije podržan u Cakeu, ali možete obnoviti sjemenke u drugom novčaniku",
"havenSupportSeedsNotice": "Na sljedećoj stranici vidjet ćete jedinstveno sjeme za svoj novčanik Haven. Zapišite ga i pohranite na sigurno mjesto. Zatim možete vratiti sjeme u drugi novčanik.",
"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

@ -683,5 +683,6 @@
"buy_provider_unavailable": "Penyedia saat ini tidak tersedia.",
"havenSupportNotice": "Haven tidak lagi didukung di Cake, namun Anda dapat memulihkan benih di dompet lain",
"havenSupportSeedsNotice": "Di halaman berikutnya, Anda akan melihat benih unik untuk Dompet Haven Anda. Catat dan simpan di tempat yang aman. Anda kemudian dapat mengembalikan benih di dompet lain.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Provider attualmente non disponibile.",
"havenSupportNotice": "Haven non è più supportato in Cake, ma puoi ripristinare i seed in un altro portafoglio",
"havenSupportSeedsNotice": "Nella pagina successiva vedrai i semi unici del tuo Haven Wallet. Annotarlo e conservarlo in un luogo sicuro. Potrai quindi ripristinare i seed in un altro portafoglio.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "現在、プロバイダーは利用できません。",
"havenSupportNotice": "Haven は Cake ではサポートされなくなりましたが、シードを別のウォレットに復元することができます",
"havenSupportSeedsNotice": "次のページでは、Haven ウォレットの固有のシードが表示されます。書き留めて安全な場所に保管してください。その後、シードを別のウォレットに復元できます。",
"do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。",
"totp_auth_url": "TOTP認証URL"
}

View file

@ -695,5 +695,6 @@
"buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.",
"havenSupportNotice": "Haven은 더 이상 Cake에서 지원되지 않지만, 다른 지갑에서 씨앗을 복원할 수 있습니다.",
"havenSupportSeedsNotice": "다음 페이지에서는 Haven Wallet의 고유한 시드를 볼 수 있습니다. 적어서 안전한 곳에 보관하세요. 그런 다음 다른 지갑에 씨앗을 복원할 수 있습니다.",
"do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.",
"totp_auth_url": "TOTP 인증 URL"
}

View file

@ -693,5 +693,6 @@
"buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။",
"havenSupportNotice": "Haven ကို ကိတ်မုန့်တွင် ပံ့ပိုးမပေးတော့သော်လည်း အစေ့များကို အခြားပိုက်ဆံအိတ်တွင် ပြန်ယူနိုင်သည်။",
"havenSupportSeedsNotice": "နောက်စာမျက်နှာတွင်၊ သင်၏ Haven Wallet ၏ထူးခြားသောမျိုးစေ့များကို သင်တွေ့လိမ့်မည်။ အဲဒါကို ချရေးပြီး လုံခြုံတဲ့နေရာမှာ သိမ်းထားပါ။ ထို့နောက် အခြားပိုက်ဆံအိတ်တွင် မျိုးစေ့များကို ပြန်ယူနိုင်သည်။",
"do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။",
"totp_auth_url": "TOTP AUTH URL"
}

View file

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Provider momenteel niet beschikbaar.",
"havenSupportNotice": "Haven wordt niet langer ondersteund in Cake, maar je kunt de zaden in een andere portemonnee herstellen",
"havenSupportSeedsNotice": "Op de volgende pagina ziet u de unieke zaden voor uw Haven Wallet. Schrijf het op en bewaar het op een veilige plaats. Je kunt de zaden vervolgens in een andere portemonnee herstellen.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Dostawca obecnie niedostępny.",
"havenSupportNotice": "Haven nie jest już obsługiwany w Cake, ale możesz przywrócić nasiona w innym portfelu",
"havenSupportSeedsNotice": "Na następnej stronie zobaczysz unikalne nasiona w swoim portfelu Haven. Zapisz go i przechowuj w bezpiecznym miejscu. Następnie możesz przywrócić nasiona w innym portfelu.",
"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

@ -694,5 +694,6 @@
"buy_provider_unavailable": "Provedor atualmente indisponível.",
"havenSupportNotice": "Haven não é mais suportado no Cake, mas você pode restaurar as sementes em outra carteira",
"havenSupportSeedsNotice": "Na próxima página, você verá as sementes exclusivas da sua Carteira Haven. Anote e guarde em local seguro. Você pode então restaurar as sementes em outra carteira.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "Поставщик в настоящее время недоступен.",
"havenSupportNotice": "Haven больше не поддерживается в Cake, но вы можете восстановить сиды в другом кошельке.",
"havenSupportSeedsNotice": "На следующей странице вы увидите уникальные семена для вашего кошелька Haven. Запишите его и сохраните в надежном месте. Затем вы сможете восстановить семена в другом кошельке.",
"do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.",
"totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ"
}

View file

@ -693,5 +693,6 @@
"buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน",
"havenSupportNotice": "เค้กไม่รองรับ Haven อีกต่อไป แต่คุณสามารถกู้คืนเมล็ดในกระเป๋าเงินอื่นได้",
"havenSupportSeedsNotice": "ในหน้าถัดไป คุณจะเห็นเมล็ดพันธุ์เฉพาะในกระเป๋าสตางค์ Haven Wallet ของคุณ จดบันทึกและเก็บไว้ในที่ปลอดภัย จากนั้นคุณสามารถกู้คืนเมล็ดในกระเป๋าเงินอื่นได้",
"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

@ -693,5 +693,6 @@
"buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.",
"havenSupportNotice": "Haven artık Cake'de desteklenmiyor ancak tohumları başka bir cüzdana geri yükleyebilirsiniz.",
"havenSupportSeedsNotice": "Bir sonraki sayfada Haven Cüzdanınızın benzersiz tohumlarını göreceksiniz. Bunu bir yere yazın ve güvenli bir yerde saklayın. Daha sonra tohumları başka bir cüzdana geri yükleyebilirsiniz.",
"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

@ -695,5 +695,6 @@
"buy_provider_unavailable": "В даний час постачальник недоступний.",
"havenSupportNotice": "Haven більше не підтримується в Cake, але ви можете відновити насіння в іншому гаманці",
"havenSupportSeedsNotice": "На наступній сторінці ви побачите унікальні насіння для вашого гаманця Haven. Запишіть і збережіть у надійному місці. Потім ви можете відновити насіння в іншому гаманці.",
"do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.",
"totp_auth_url": "TOTP AUTH URL"
}

View file

@ -687,5 +687,6 @@
"buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔",
"havenSupportNotice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﺮﮐ ﻝﺎﺤﺑ ﮟﯿﻣ ﮮﻮﭩﺑ ﺭﻭﺍ ﯽﺴﮐ ﻮﮐ ﮞﻮﺠﯿﺑ ﭖﺁ ﻦﮑﯿﻟ ،ﮯﮨ ﮟﯿﮩﻧ ﺖﻟﻮﮩﺳ ﯽﮐ ﻥﻮﯿﮨ ﺏﺍ ",
"havenSupportSeedsNotice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﺮﮐ ﻝﺎﺤﺑ ﮟﯿﻣ ﮮﻮﭩﺑ ﮮﺮﺳﻭﺩ ﻮﮐ ﮞﻮﺠﯿﺑ ﭖﺁ ﺪﻌﺑ ﮯﮐ ﺱﺍ ۔ﮟﯿﻟ ﺮﮐ ﻅﻮﻔﺤﻣ ﺮﭘ ﮧﮕﺟ ﻅﻮ",
"do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔",
"totp_auth_url": "TOTP AUTH URL"
}

View file

@ -689,5 +689,6 @@
"buy_provider_unavailable": "Olupese lọwọlọwọ ko si.",
"havenSupportNotice": "Haven ko ni atilẹyin mọ ni Akara oyinbo, ṣugbọn o le mu awọn irugbin pada ninu apamọwọ miiran",
"havenSupportSeedsNotice": "Ni oju-iwe atẹle, iwọ yoo rii awọn irugbin alailẹgbẹ si apamọwọ Haven rẹ. Kọ silẹ ki o si fi pamọ si aaye ailewu. Lẹhinna o le mu awọn irugbin pada ninu apamọwọ miiran.",
"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

@ -694,5 +694,6 @@
"buy_provider_unavailable": "提供者目前不可用。",
"havenSupportNotice": "Cake 不再支持 Haven但您可以在另一个钱包中恢复种子",
"havenSupportSeedsNotice": "在下一页上,您将看到您的 Haven 钱包中的独特种子。将其写下来并将其存放在安全的地方。然后您可以将种子恢复到另一个钱包中。",
"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', () => ''),
];